Wiktionary viwiktionary https://vi.wiktionary.org/wiki/Wiktionary:Trang_Ch%C3%ADnh MediaWiki 1.47.0-wmf.15 case-sensitive Phương tiện Đặc biệt Thảo luận Thành viên Thảo luận Thành viên Wiktionary Thảo luận Wiktionary Tập tin Thảo luận Tập tin MediaWiki Thảo luận MediaWiki Bản mẫu Thảo luận Bản mẫu Trợ giúp Thảo luận Trợ giúp Thể loại Thảo luận Thể loại Phụ lục Thảo luận Phụ lục TimedText TimedText talk Mô đun Thảo luận Mô đun Event Event talk vốn 0 93465 2513878 2419253 2026-08-14T02:15:29Z ~2026-44581-94 61744 xóa thông tin người không liên quan 2513878 wikitext text/x-wiki {{-vie-}} {{-etym-}} {{vi-etym-sino|hv=n|本||[[bản]]/[[bổn]]}}. {{doublet|vi|bổn|bản}}. {{-pron-}} {{vi-pron}} {{-nôm-}} {{đầu}} *[[呠]]: [[nhổn]], [[bõm]], [[bỏm]], [[bọn]], [[vốn]] *[[本]]: [[bổn]], [[thao]], [[bỏn]], [[bản]], [[bốn]], [[bủn]], [[vỏn]], [[bộn]], [[vốn]] *[[󰆳]]: [[vốn]] {{mid}} *[[夲]]: [[bổn]], [[thao]], [[bản]], [[vốn]] *[[板]]: [[bẩng]], [[phản]], [[bổn]], [[bửng]], [[ván]], [[bản]], [[vốn]] *[[𢏿]]: [[vốn]] {{cuối}} {{-paro-}} :* [[vờn]] {{-noun-}} {{vie-noun}} # [[tiền|Tiền]] [[gốc]] [[bỏ]] vào một cuộc [[kinh doanh]]. #: ''Các doanh nghiệp có '''vốn''' đầu tư nước ngoài.'' #: ''Chị ta đi buôn mà mất cả '''vốn''' lẫn lãi.'' # [[cái|Cái]] [[do]] [[trí tuệ]] [[tích lũy]]. #: ''Phải phục hồi, khai hoá, phát huy '''vốn''' cổ ([[w:Phạm Văn Đồng|Phạm Văn Đồng]])'' #: '''''Vốn''' văn chương dân gian.'' #: '''''Vốn''' ngoại ngữ của anh ấy cũng khá.'' # [[trgt|Trgt]] [[Nguyên]] là. #: ''Nguyên người quanh quất đâu xa, họ.'' #: ''Kim, tên.'' #: ''Trọng, '''vốn''' nhà trâm anh (<u>[[w:Truyện Kiều|Truyện Kiều]]</u>)'' #: ''Vấn đề giải quyết việc làm '''vốn''' đã bức xúc càng thêm gay gắt ([[w:Phan Văn Khải|Phan Văn Khải]])'' {{-ref-}} {{R:FVDP}} {{R:WinVNKey:Lê Sơn Thanh}} 5x1nzklfw0eijujqtg0okivof37li44 Bản mẫu:gl 10 107778 2513979 2388463 2026-08-14T05:27:54Z TheHighFighter2 42988 /* */ 2513979 wikitext text/x-wiki #redirect [[Bản mẫu:gloss]] 1qjtaxe76s6aamzn7gr9hzhpi9o5uti assignment operator 0 177652 2513870 2452054 2026-08-13T15:59:00Z Hiyuune 50834 2513870 wikitext text/x-wiki =={{langname|en}}== ==={{section|n}}=== {{en-noun}} # {{lb|en|computing}} [[toán tử|Toán tử]] [[gán]], [[toán tử]] [[chỉ định]]. ===={{section|hyper}}==== * {{l|en|operator}} ===={{sectio|hypo}}==== {{col2 |en|augmented assignment operator |compound assignment operator }} ==={{section|ref}}=== * {{R:FVDP|db=ev}} aeopzjlt0o98bsn932b4kpae7efsc83 2513871 2513870 2026-08-13T15:59:13Z Hiyuune 50834 2513871 wikitext text/x-wiki =={{langname|en}}== ==={{section|n}}=== {{en-noun}} # {{lb|en|computing}} [[toán tử|Toán tử]] [[gán]], [[toán tử]] [[chỉ định]]. ===={{section|hyper}}==== * {{l|en|operator}} ===={{section|hypo}}==== {{col2 |en|augmented assignment operator |compound assignment operator }} ==={{section|ref}}=== * {{R:FVDP|db=ev}} 1y01ue7ol22ft8rurtmluit7qhi7u5r 阿尔法粒子 0 213996 2513823 1988759 2026-08-13T13:08:40Z Kelly zhrm 58416 2513823 wikitext text/x-wiki =={{langname|zh}}== {{zh-see|阿爾法粒子}} 5be31zx6s4g06arheseej3l7j8k15a2 禁用 0 215678 2513853 1987903 2026-08-13T15:35:05Z Hiyuune 50834 2513853 wikitext text/x-wiki =={{langname|zh}}== ==={{section|etym}} 1=== {{zh-forms|bị cấm}} ===={{section|pron}}==== {{zh-pron |m=jìnyòng |c=gam3 jung6 |mn=tw:kìm-iōng |cat=v }} ===={{section|v}}==== {{head|zh|Động từ}} # [[cấm|Cấm]] [[sử dụng]]. #: {{zh-x|禁用 手機。|Cấm sử dụng điện thoại.}} # {{lb|zh|computing}} [[vô hiệu hóa|Vô hiệu hóa]], [[tắt]]. ====={{section|ant}}===== * {{as|vô hiệu hóa}} {{zh-l|啟用}} ==={{section|etym}} 2=== {{zh-forms|chịu đựng, chống chịu|alt=襟用-Quảng Đông}} ===={{section|pron}}==== {{zh-pron |c=kam1 jung6 |cat=a }} ===={{section|adj}}==== {{head|zh|Tính từ}} # {{lb|zh|Cantonese}} [[bền|Bền]]; [[bền]] [[chắc]], chịu [[ma sát]] [[tốt]]. ====={{section|syn}}===== * {{zh-l|耐用}} ebue9xci82bka7zy9inty7qrse5s9fq 2513854 2513853 2026-08-13T15:35:52Z Hiyuune 50834 /* Tiếng Trung Quốc */ (sử dụng [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]) 2513854 wikitext text/x-wiki =={{langname|zh}}== ==={{section|etym}} 1=== {{zh-forms|bị cấm|dùng, sử dụng}} ===={{section|pron}}==== {{zh-pron |m=jìnyòng |c=gam3 jung6 |mn=tw:kìm-iōng |cat=v }} ===={{section|v}}==== {{head|zh|Động từ}} # [[cấm|Cấm]] [[sử dụng]]. #: {{zh-x|禁用 手機。|Cấm sử dụng điện thoại.}} # {{lb|zh|computing}} [[vô hiệu hóa|Vô hiệu hóa]], [[tắt]]. ====={{section|ant}}===== * {{as|vô hiệu hóa}} {{zh-l|啟用}} ==={{section|etym}} 2=== {{zh-forms|chịu đựng, chống chịu|alt=襟用-Quảng Đông}} ===={{section|pron}}==== {{zh-pron |c=kam1 jung6 |cat=a }} ===={{section|adj}}==== {{head|zh|Tính từ}} # {{lb|zh|Cantonese}} [[bền|Bền]]; [[bền]] [[chắc]], chịu [[ma sát]] [[tốt]]. ====={{section|syn}}===== * {{zh-l|耐用}} agc875uew5w50fdgy3znbwe91qzbko9 琉璃瓦 0 215736 2513855 1987707 2026-08-13T15:37:01Z Hiyuune 50834 2513855 wikitext text/x-wiki =={{langname|zh}}== {{zh-forms|type=21}} ==={{section|pron}}=== {{zh-pron |m=liúlíwǎ |c=lau4 lei1 ngaa5 |cat=n }} ==={{section|n}}=== {{head|zh|Danh từ}} # {{w|Ngói lưu ly}}. qkiesgq1bojk76xcu52x44gdul7xeyv 2513856 2513855 2026-08-13T15:37:27Z Hiyuune 50834 /* {{langname|zh}} */ 2513856 wikitext text/x-wiki =={{langname|zh}}== {{zh-forms|type=21||gạch, ngói}} ==={{section|pron}}=== {{zh-pron |m=liúlíwǎ |c=lau4 lei1 ngaa5 |cat=n }} ==={{section|n}}=== {{head|zh|Danh từ}} # {{w|Ngói lưu ly}}. f7w7twq4n5tuiaytqhhgo93n9bhdkkg 固体 0 215760 2513862 1986737 2026-08-13T15:48:46Z Hiyuune 50834 2513862 wikitext text/x-wiki {{also|固體}} =={{langname|zh}}== {{zh-see|固體}} g7l7htbnkbgbve8mofuzhin7mlscgd5 2513864 2513862 2026-08-13T15:51:39Z Hiyuune 50834 /* Tiếng Trung Quốc */ (sử dụng [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]) 2513864 wikitext text/x-wiki {{also|固體}} =={{langname|ja}}== {{ja-kanjitab|こ|たい|yomi=o}} ==={{section|pron}}=== * {{ja-pron|こたい|acc=0|acc_ref=NHK}} ==={{section|n}}=== {{ja-noun|こたい}} # Thể [[rắn]]. ===={{section|rel}}==== * {{ja-r|液体|えきたい|gloss=[[chất lỏng]]}} * {{ja-r|気体|きたい|gloss=[[chất khí]]}} ==={{section|ref}}=== <references/> :* {{R:Kanjipedia Kotoba|0002034600}} {{C|ja|Vật lý học|sort=こたい}} =={{langname|zh}}== {{zh-see|固體}} 9q3c3ksapf7qtwuo5sr7wbf025o1uvz 阿拉斯加州 0 233584 2513790 1988768 2026-08-13T12:01:30Z Hiyuune 50834 2513790 wikitext text/x-wiki =={{langname|zh}}== {{zh-forms|type=41}} ==={{section|pron}}=== {{zh-pron |m=Ālāsījiā zhōu |c=aa3 laai1 si1 gaa1 zau1 |cat=pn }} ==={{section|pr-noun}}=== {{head|zh|Danh từ riêng}} # [[Alaska]]. r7x4q2irpw90ho0afte9pyzk532zhox 2513791 2513790 2026-08-13T12:02:17Z Hiyuune 50834 /* {{langname|zh}} */ 2513791 wikitext text/x-wiki =={{langname|zh}}== {{zh-forms|type=41}} {{wp|zh:}} ==={{section|pron}}=== {{zh-pron |m=Ālāsījiā zhōu |c=aa3 laai1 si1 gaa1 zau1 |cat=pn }} ==={{section|pr-noun}}=== {{head|zh|Danh từ riêng}} # [[Alaska]]. 1m3ts8ui6qdk3za8tse7ofu8iutefkk Thể loại:Tiếng Việt Nam Việt Nam 14 240326 2513844 2462632 2026-08-13T15:00:00Z Hiyuune 50834 2513844 wikitext text/x-wiki {{auto cat|lect=1|lang=vi|cat=Tiếng Việt Việt Nam|miền Nam Việt Nam}} atgdg1b7kgifh55qn3k1llzoka1jwt3 Nhật Bản 0 246116 2513828 2320273 2026-08-13T13:21:35Z Hiyuune 50834 /* Dịch */ 2513828 wikitext text/x-wiki =={{langname|vi}}== [[File:Flag of Japan.svg|thumb|240px|Quốc kỳ '''Nhật Bản''']] [[Image:Japan satellite.jpg|thumb|240px|Ảnh chụp vệ tinh '''Nhật Bản''']] {{wp}} ==={{section|alt}}=== *{{alt|vi|Nhật-Bản|Nhật-bản|Nhật Bổn|Nhật-Bổn|Nhật-bổn|Nhựt Bản|Nhựt-Bản|Nhựt-bản|Nhựt Bổn|Nhựt-Bổn|Nhựt-bổn}} ==={{section|etym}}=== {{vi-etym-sino|日本}}, từ {{bor|vi|ja|日本|tr=Nippon, Nihon}}. ==={{section|pron}}=== {{vi-pron}} ==={{section|pr-noun}}=== {{vi-pr-noun|head=[[Nhật]] [[bản|Bản]]}} # {{senseid|vi|Q17}}{{place|vi|quốc gia|r/Đông Á|caplc=Tokyo}}. #: {{ux|vi|Quốc hội '''Nhật Bản'''.}} #: {{ux|vi|Biển '''Nhật Bản'''.}} #: {{ux|vi|Rất nhiều thực tập sinh Việt Nam bị trục xuất khỏi '''Nhật Bản''', nguyên nhân chủ yếu là do vi phạm pháp luật như trộm cắp, cư trú và làm việc bất hợp pháp hoặc không hợp tác với cảnh sát địa phương.}} #* {{w|Phan Chu Trinh|Phan Châu Trinh}} ''{{lang|vi|Đạo đức và luân lí Đông Tây}}'': #*: {{uxi|vi|Người nước ta thường tự xưng là đồng loại, đồng đạo, đồng văn với '''Nhật Bản'''; thấy họ tiến thì nức nở khen chớ không khi nào chịu xét vì sao mà họ tiến tới như thế? Họ chỉ đóng tàu súng mà được giàu mạnh hay là họ còn trao dồi đạo đức, sửa đổi luân lý mới được như ngày nay? Ai có đọc đến lịch sử Nhật Bản mới biết '''Nhật Bản''' họ cũng bồi đắp nền đạo đức của họ lắm. Từ lúc Minh Trị duy tân cho đến 24 năm sau hạ chiếu lập hiến trong nước Nhật biết bao nhiêu người lo khuynh Mạc Phủ lo lập hiến pháp, biết bao nhiêu kẻ đổ máu rát cổ mới gây dựng nên một nước tân tiến rất giàu mạnh như bây giờ.}} ===={{section|trans}}==== {{trans-top|một quốc gia ở Đông Á}} * {{langname|ar}}: {{t+|ar|الْيَابَان|f}} * {{langname|ab}}: {{t+|ab|Иапониа}} * {{langname|ace}}: {{t+|ace|Jeupun}} * {{langname|ady}}: {{t+|ady|Японие}} * {{langname|af}}: {{t+|af|Japan}} * {{langname|en}}: {{t+|en|Japan}} * {{langname|et}}: {{t+|et|Jaapan}} * {{langname|ln}}: {{t|ln|Zapɔ́}} * {{langname|lt}}: {{t+|lt|Japonija|f}} * {{langname|nah}}: {{t|nah|Xapōn}} * {{langname|na}}: {{t|na|Djapan}} * {{langname|nv}}: {{t|nv|Binááʼádaałtsʼózí dineʼé bikéyah}} * {{langname|fr}}: le {{t+|fr|Japon|m}} * {{langname|cs}}: {{t+|cs|Japonsko|n}} * {{langname|zh}}: *: {{langname|nan-hbl}}: {{t+|nan-hbl|日本|tr=Ji̍t-pún, Li̍t-pún}} *: {{langname|cmn}}: {{t+|cmn|日本}} *: {{langname|cjy}}: {{t+|cjy|日本}} * {{langname|zu}}: {{t+|zu|IJapani}} {{trans-bottom}} ==={{section|adj}}=== {{vi-adj|head=[[Nhật]] [[bản|Bản]]}} # {{lb|vi|uncommon}} Thuộc về Nhật Bản. ==={{section|syn}}=== * {{vi-l|Nhật}} * {{vi-l|Phù Tang}} ==={{section|see}}=== {{danh sách:quốc gia châu Á/vi}} lg21g100bxkmlx2ghjvboeje3hx5afx 2513831 2513828 2026-08-13T13:25:20Z Hiyuune 50834 /* Danh từ riêng */ 2513831 wikitext text/x-wiki =={{langname|vi}}== [[File:Flag of Japan.svg|thumb|240px|Quốc kỳ '''Nhật Bản''']] [[Image:Japan satellite.jpg|thumb|240px|Ảnh chụp vệ tinh '''Nhật Bản''']] {{wp}} ==={{section|alt}}=== *{{alt|vi|Nhật-Bản|Nhật-bản|Nhật Bổn|Nhật-Bổn|Nhật-bổn|Nhựt Bản|Nhựt-Bản|Nhựt-bản|Nhựt Bổn|Nhựt-Bổn|Nhựt-bổn}} ==={{section|etym}}=== {{vi-etym-sino|日本}}, từ {{bor|vi|ja|日本|tr=Nippon, Nihon}}. ==={{section|pron}}=== {{vi-pron}} ==={{section|pr-noun}}=== {{vi-pr-noun|head=[[Nhật]] [[bản|Bản]]}} # {{senseid|vi|Q17}}{{place|vi|quốc gia|r/Đông Á|caplc=Tokyo}}. #: {{ux|vi|Quốc hội '''Nhật Bản'''.}} #: {{ux|vi|Biển '''Nhật Bản'''.}} #: {{ux|vi|Rất nhiều thực tập sinh Việt Nam bị trục xuất khỏi '''Nhật Bản''', nguyên nhân chủ yếu là do vi phạm pháp luật như trộm cắp, cư trú và làm việc bất hợp pháp hoặc không hợp tác với cảnh sát địa phương.}} #: {{ux|vi|Không ít người '''Nhật Bản''' có góc nhìn rất tiêu cực về Việt Nam vì những hành vi làm ngược lại tôn chỉ nhập gia tùy tục, và khả năng thay đổi hay loại bỏ định kiến này là khá khó.}} #* {{w|Phan Chu Trinh|Phan Châu Trinh}} ''{{lang|vi|Đạo đức và luân lí Đông Tây}}'': #*: {{uxi|vi|Người nước ta thường tự xưng là đồng loại, đồng đạo, đồng văn với '''Nhật Bản'''; thấy họ tiến thì nức nở khen chớ không khi nào chịu xét vì sao mà họ tiến tới như thế? Họ chỉ đóng tàu súng mà được giàu mạnh hay là họ còn trao dồi đạo đức, sửa đổi luân lý mới được như ngày nay? Ai có đọc đến lịch sử Nhật Bản mới biết '''Nhật Bản''' họ cũng bồi đắp nền đạo đức của họ lắm. Từ lúc Minh Trị duy tân cho đến 24 năm sau hạ chiếu lập hiến trong nước Nhật biết bao nhiêu người lo khuynh Mạc Phủ lo lập hiến pháp, biết bao nhiêu kẻ đổ máu rát cổ mới gây dựng nên một nước tân tiến rất giàu mạnh như bây giờ.}} ===={{section|trans}}==== {{trans-top|một quốc gia ở Đông Á}} * {{langname|ar}}: {{t+|ar|الْيَابَان|f}} * {{langname|ab}}: {{t+|ab|Иапониа}} * {{langname|ace}}: {{t+|ace|Jeupun}} * {{langname|ady}}: {{t+|ady|Японие}} * {{langname|af}}: {{t+|af|Japan}} * {{langname|en}}: {{t+|en|Japan}} * {{langname|et}}: {{t+|et|Jaapan}} * {{langname|ln}}: {{t|ln|Zapɔ́}} * {{langname|lt}}: {{t+|lt|Japonija|f}} * {{langname|nah}}: {{t|nah|Xapōn}} * {{langname|na}}: {{t|na|Djapan}} * {{langname|nv}}: {{t|nv|Binááʼádaałtsʼózí dineʼé bikéyah}} * {{langname|fr}}: le {{t+|fr|Japon|m}} * {{langname|cs}}: {{t+|cs|Japonsko|n}} * {{langname|zh}}: *: {{langname|nan-hbl}}: {{t+|nan-hbl|日本|tr=Ji̍t-pún, Li̍t-pún}} *: {{langname|cmn}}: {{t+|cmn|日本}} *: {{langname|cjy}}: {{t+|cjy|日本}} * {{langname|zu}}: {{t+|zu|IJapani}} {{trans-bottom}} ==={{section|adj}}=== {{vi-adj|head=[[Nhật]] [[bản|Bản]]}} # {{lb|vi|uncommon}} Thuộc về Nhật Bản. ==={{section|syn}}=== * {{vi-l|Nhật}} * {{vi-l|Phù Tang}} ==={{section|see}}=== {{danh sách:quốc gia châu Á/vi}} aukk78lricqu86tmqvpknurbnjj4rgu 2513868 2513831 2026-08-13T15:56:53Z Hiyuune 50834 /* {{section|trans}} */ 2513868 wikitext text/x-wiki =={{langname|vi}}== [[File:Flag of Japan.svg|thumb|240px|Quốc kỳ '''Nhật Bản''']] [[Image:Japan satellite.jpg|thumb|240px|Ảnh chụp vệ tinh '''Nhật Bản''']] {{wp}} ==={{section|alt}}=== *{{alt|vi|Nhật-Bản|Nhật-bản|Nhật Bổn|Nhật-Bổn|Nhật-bổn|Nhựt Bản|Nhựt-Bản|Nhựt-bản|Nhựt Bổn|Nhựt-Bổn|Nhựt-bổn}} ==={{section|etym}}=== {{vi-etym-sino|日本}}, từ {{bor|vi|ja|日本|tr=Nippon, Nihon}}. ==={{section|pron}}=== {{vi-pron}} ==={{section|pr-noun}}=== {{vi-pr-noun|head=[[Nhật]] [[bản|Bản]]}} # {{senseid|vi|Q17}}{{place|vi|quốc gia|r/Đông Á|caplc=Tokyo}}. #: {{ux|vi|Quốc hội '''Nhật Bản'''.}} #: {{ux|vi|Biển '''Nhật Bản'''.}} #: {{ux|vi|Rất nhiều thực tập sinh Việt Nam bị trục xuất khỏi '''Nhật Bản''', nguyên nhân chủ yếu là do vi phạm pháp luật như trộm cắp, cư trú và làm việc bất hợp pháp hoặc không hợp tác với cảnh sát địa phương.}} #: {{ux|vi|Không ít người '''Nhật Bản''' có góc nhìn rất tiêu cực về Việt Nam vì những hành vi làm ngược lại tôn chỉ nhập gia tùy tục, và khả năng thay đổi hay loại bỏ định kiến này là khá khó.}} #* {{w|Phan Chu Trinh|Phan Châu Trinh}} ''{{lang|vi|Đạo đức và luân lí Đông Tây}}'': #*: {{uxi|vi|Người nước ta thường tự xưng là đồng loại, đồng đạo, đồng văn với '''Nhật Bản'''; thấy họ tiến thì nức nở khen chớ không khi nào chịu xét vì sao mà họ tiến tới như thế? Họ chỉ đóng tàu súng mà được giàu mạnh hay là họ còn trao dồi đạo đức, sửa đổi luân lý mới được như ngày nay? Ai có đọc đến lịch sử Nhật Bản mới biết '''Nhật Bản''' họ cũng bồi đắp nền đạo đức của họ lắm. Từ lúc Minh Trị duy tân cho đến 24 năm sau hạ chiếu lập hiến trong nước Nhật biết bao nhiêu người lo khuynh Mạc Phủ lo lập hiến pháp, biết bao nhiêu kẻ đổ máu rát cổ mới gây dựng nên một nước tân tiến rất giàu mạnh như bây giờ.}} ===={{section|trans}}==== {{trans-top|một quốc gia ở Đông Á}} * {{langname|ar}}: {{t+|ar|الْيَابَان|f}} * {{langname|ab}}: {{t+|ab|Иапониа}} * {{langname|ace}}: {{t+|ace|Jeupun}} * {{langname|ady}}: {{t+|ady|Японие}} * {{langname|af}}: {{t+|af|Japan}} * {{langname|ain}}: {{t|ain|シサㇺ|tr=Sisam}} * {{langname|ak}}: {{t|ak|Yapan}}, {{t|ak|Gyapan}} * {{langname|en}}: {{t+|en|Japan}} * {{langname|he}}: {{t+|he|יַפָּן|f|tr=yapán}} * {{langname|et}}: {{t+|et|Jaapan}} * {{langname|hi}}: {{t+|hi|जापान|m}} * {{langname|hu}}: {{t+|hu|Japán}} * {{langname|hrx}}: {{t|hrx|Schapong|n}} * {{langname|is}}: {{t+|is|Japan|n}} * {{langname|io}}: {{t+|io|Japonia}} * {{langname|ilo}}: {{t|ilo|Japon}} * {{langname|ln}}: {{t|ln|Zapɔ́}} * {{langname|lt}}: {{t+|lt|Japonija|f}} * {{langname|nah}}: {{t|nah|Xapōn}} * {{langname|na}}: {{t|na|Djapan}} * {{langname|nv}}: {{t|nv|Binááʼádaałtsʼózí dineʼé bikéyah}} * {{langname|fr}}: le {{t+|fr|Japon|m}} * {{langname|cs}}: {{t+|cs|Japonsko|n}} * {{langname|zh}}: *: {{langname|nan-hbl}}: {{t+|nan-hbl|日本|tr=Ji̍t-pún, Li̍t-pún}} *: {{langname|cmn}}: {{t+|cmn|日本}} *: {{langname|cjy}}: {{t+|cjy|日本}} * {{langname|zu}}: {{t+|zu|IJapani}} {{trans-bottom}} ==={{section|adj}}=== {{vi-adj|head=[[Nhật]] [[bản|Bản]]}} # {{lb|vi|uncommon}} Thuộc về Nhật Bản. ==={{section|syn}}=== * {{vi-l|Nhật}} * {{vi-l|Phù Tang}} ==={{section|see}}=== {{danh sách:quốc gia châu Á/vi}} aj9hr9w8s9hc7uulejjl48kv9g766pr Thể loại:Emoji 14 248471 2513818 2364384 2026-08-13T13:00:47Z TheHighFighter2 42988 Đã lùi lại sửa đổi [[Special:Diff/2364384|2364384]] của [[Special:Contributions/Kwamikagami|Kwamikagami]] ([[User talk:Kwamikagami|thảo luận]]) 2513818 wikitext text/x-wiki [[Thể loại:Mục từ đa ngữ]] [[Thể loại:Ký hiệu]] 6picgcu8u0naxokw9dtd80pdc1z8es6 gelaten 0 251651 2513830 2001302 2026-08-13T13:24:36Z Kelly zhrm 58416 2513830 wikitext text/x-wiki =={{langname|nl}}== ==={{ĐM|pron}}=== * {{audio|nl|Nl-gelaten.ogg}} * {{rhymes|nl|aːtən|s=3}} ==={{ĐM|n}}=== {{head|nl|Biến thể hình thái danh từ}} # {{plural of|nl|gelaat}} ==={{ĐM|participle}}=== {{nl-past-ptc}} # {{past participle of|nl|laten}} ===={{ĐM|decl}}==== {{nl-decl-past-ptc}} ==={{ĐM|ana}}=== * {{l|nl|elegant}} c3sa41rtxi2dy3bqbf8oo7468cabbhi gedane 0 252969 2513832 2001594 2026-08-13T13:26:28Z Kelly zhrm 58416 2513832 wikitext text/x-wiki =={{langname|nl}}== ==={{ĐM|pron}}=== * {{audio|nl|Nl-gedane.ogg}} ==={{ĐM|participle}}=== {{head|nl|Biến thể hình thái phân từ}} # {{infl of|nl|gedaan||infl}} ==={{ĐM|ana}}=== * {{l|nl|genade}} pmg7eerzh1rf8ac9tj0fdmiyiwquq19 gekeerd 0 253063 2513829 2001651 2026-08-13T13:22:47Z Kelly zhrm 58416 2513829 wikitext text/x-wiki =={{langname|nl}}== ==={{ĐM|pron}}=== * {{audio|nl|Nl-gekeerd.ogg}} * {{rhymes|nl|eːrt|s=2}} ==={{ĐM|participle}}=== {{nl-past-ptc}} # {{past participle of|nl|keren}} ===={{ĐM|decl}}==== {{nl-decl-past-ptc}} nyl497cxgsvc53vu7w9azwenrjip8ql Thể loại:Ký hiệu 14 260791 2513821 2361800 2026-08-13T13:02:55Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự]] thành [[Thể loại:Ký hiệu]] (đã tắt đổi hướng) 2361800 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Mô đun:labels/data/lang/vi 828 280313 2513840 2355058 2026-08-13T14:55:22Z Hiyuune 50834 2513840 Scribunto text/plain local labels = {} labels["xứ Nẫu"] = { aliases = {"xứ Nẩu", "Nẫu", "Nẩu", "Nau", "Nẫu dialects", "Nẫu dialect", "Nẩu dialects", "Nẩu dialect"}, plain_categories = "Nẫu", } labels["Bình Định"] = { aliases = {"Binh Dinh", "Bình Định dialect", "Binh Dinh dialect"}, Wikipedia = "Bình Định province", plain_categories = "Nẫu", } labels["Trung Bộ"] = { aliases = {"central Vietnam", "Central Vietnamese", "central Vietnamese"}, Wikipedia = true, plain_categories = "Tiếng Việt Trung Bộ", } labels["Fangcheng"] = { aliases = {"Fangcheng dialect"}, Wikipedia = "Gin people", plain_categories = true, } labels["Hải Phòng"] = { aliases = {"Hai Phong", "Haiphong", "Hải Phòng dialect", "Hai Phong dialect", "Haiphong dialect"}, Wikipedia = true, plain_categories = "Northern", } labels["Hà Nội"] = { aliases = {"Hanoi"}, Wikipedia = {"Hanoi"}, plain_categories = "Northern", } labels["Hà Tĩnh"] = { aliases = {"Ha Tinh", "Hà Tĩnh dialect", "Ha Tinh dialect"}, Wikipedia = "Hà Tĩnh province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Huế"] = { aliases = {"Hue", "Huế dialect", "Hue dialect"}, Wikipedia = true, plain_categories = "Tiếng Việt Huế", } labels["Louisiana"] = { aliases = {"New Orleans"}, Wikipedia = "Vietnamese in New Orleans", plain_categories = true, } labels["Mekong Delta"] = { aliases = {"Mekong Delta dialect", "Southwestern Vietnamese", "South Western Vietnamese", "Southwest Vietnamese", "southwestern Vietnamese", "south western Vietnamese", "southwest Vietnamese"}, Wikipedia = true, plain_categories = true, } labels["Nghệ An"] = { aliases = {"Nghe An", "Nghệ An dialect", "Nghe An dialect"}, Wikipedia = "Nghệ An province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Nghệ Tĩnh"] = { aliases = {"Nghe Tinh", "Nghệ Tinh dialect", "Nghe Tinh dialect"}, Wikipedia = true, plain_categories = "Tiếng Việt Trung Bộ", } labels["North Central Vietnam"] = { aliases = {"North-Central Vietnam", "Northern Central Vietnam", "north central Vietnam", "north-central Vietnam", "northern central Vietnam", "North Central Vietnamese", "North-Central Vietnamese", "Northern Central Vietnamese", "north central Vietnamese", "north-central Vietnamese", "northern central Vietnamese"}, Wikipedia = "North Central Coast", plain_categories = "Tiếng Việt Trung Bộ", } labels["Northern Vietnam"] = { aliases = {"North Vietnam", "northern Vietnam", "north Vietnam", "Northern Vietnamese", "North Vietnamese", "northern Vietnamese", "north Vietnamese"}, Wikipedia = true, plain_categories = "Northern", } labels["South Central Vietnam"] = { aliases = {"Southcentral Vietnam", "South central Vietnam", "South-Central Vietnam", "South Central Coast", "South central coast"}, Wikipedia = "South Central Coast", plain_categories = "South Central", } labels["Northeastern Vietnam"] = { aliases = {"Northeast Vietnam", "northeastern Vietnam", "northeast Vietnam", "Northeastern Vietnamese", "Northeast Vietnamese", "northeastern Vietnamese", "northeast Vietnamese"}, Wikipedia = true, plain_categories = "Northern", } labels["Overseas Vietnamese"] = { aliases = {"overseas Vietnamese"}, Wikipedia = true, plain_categories = true, } labels["Phú Yên"] = { aliases = {"Phu Yen", "Phú Yên dialect", "Phu Yen dialect"}, Wikipedia = "Phú Yên province", plain_categories = "Nẫu", } labels["Quảng Bình"] = { aliases = {"Quang Binh", "Quảng Bình dialect", "Quang Binh dialect"}, Wikipedia = "Quảng Bình province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Đà Nẵng"] = { aliases = {"Danang", "Da Nang", "Đà Nẵng dialect", "Danang dialect"}, Wikipedia = true, plain_categories = "South Central", } labels["Quảng Nam"] = { aliases = {"Quang Nam", "Quảng Nam dialect", "Quang Nam dialect"}, Wikipedia = "Quảng Nam province", plain_categories = "South Central", } labels["Quảng Ngãi"] = { aliases = {"Quang Ngai", "Quảng Ngãi dialect", "Quang Ngai dialect"}, Wikipedia = "Quảng Ngãi province", plain_categories = "South Central", } labels["Quảng Trị"] = { aliases = {"Quang Tri", "Quảng Trị dialect", "Quang Tri dialect"}, Wikipedia = "Quảng Trị province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Saigon"] = { aliases = {"Saigonese", "Ho Chi Minh City", "Hồ Chí Minh City"}, Wikipedia = true, plain_categories = "Southern", } labels["Southern Vietnam"] = { aliases = {"South Vietnam", "southern Vietnam", "south Vietnam", "Southern Vietnamese", "South Vietnamese", "southern Vietnamese", "south Vietnamese"}, Wikipedia = true, plain_categories = "Southern", } labels["Thanh Hoá"] = { aliases = {"Thanh Hoa", "Thanh Hoá dialect", "Thanh Hoa dialect"}, Wikipedia = "Thanh Hóa province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Việt Nam"] = { aliases = {"Vietnamese", "Vietnam"}, Wikipedia = true, plain_categories = "Tiếng Việt Việt Nam", } ---------------- non-regional ---------------- labels["tulay"] = { display = "[[từ láy]]", } return require("Module:labels").finalize_data(labels) fbxdaftipbfww34c6gzqmsgviegwxmu 2513845 2513840 2026-08-13T15:02:43Z Hiyuune 50834 2513845 Scribunto text/plain local labels = {} labels["xứ Nẫu"] = { aliases = {"xứ Nẩu", "Nẫu", "Nẩu", "Nau", "Nẫu dialects", "Nẫu dialect", "Nẩu dialects", "Nẩu dialect"}, plain_categories = "Nẫu", } labels["Bình Định"] = { aliases = {"Binh Dinh", "Bình Định dialect", "Binh Dinh dialect"}, Wikipedia = "Bình Định province", plain_categories = "Nẫu", } labels["Trung Bộ"] = { aliases = {"central Vietnam", "Central Vietnamese", "central Vietnamese"}, Wikipedia = true, plain_categories = "Tiếng Việt Trung Bộ", } labels["Fangcheng"] = { aliases = {"Fangcheng dialect"}, Wikipedia = "Gin people", plain_categories = true, } labels["Hải Phòng"] = { aliases = {"Hai Phong", "Haiphong", "Hải Phòng dialect", "Hai Phong dialect", "Haiphong dialect"}, Wikipedia = true, plain_categories = "Northern", } labels["Hà Nội"] = { aliases = {"Hanoi"}, Wikipedia = {"Hanoi"}, plain_categories = "Northern", } labels["Hà Tĩnh"] = { aliases = {"Ha Tinh", "Hà Tĩnh dialect", "Ha Tinh dialect"}, Wikipedia = "Hà Tĩnh province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Huế"] = { aliases = {"Hue", "Huế dialect", "Hue dialect"}, Wikipedia = true, plain_categories = "Tiếng Việt Huế", } labels["Louisiana"] = { aliases = {"New Orleans"}, Wikipedia = "Vietnamese in New Orleans", plain_categories = true, } labels["Đồng bằng Sông Cửu Long"] = { aliases = {"Mekong Delta dialect", "Southwestern Vietnamese", "South Western Vietnamese", "Southwest Vietnamese", "southwestern Vietnamese", "south western Vietnamese", "southwest Vietnamese"}, Wikipedia = true, plain_categories = "Tiếng Việt Nam Việt Nam", } labels["Nghệ An"] = { aliases = {"Nghe An", "Nghệ An dialect", "Nghe An dialect"}, Wikipedia = "Nghệ An province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Nghệ Tĩnh"] = { aliases = {"Nghe Tinh", "Nghệ Tinh dialect", "Nghe Tinh dialect"}, Wikipedia = true, plain_categories = "Tiếng Việt Trung Bộ", } labels["North Central Vietnam"] = { aliases = {"North-Central Vietnam", "Northern Central Vietnam", "north central Vietnam", "north-central Vietnam", "northern central Vietnam", "North Central Vietnamese", "North-Central Vietnamese", "Northern Central Vietnamese", "north central Vietnamese", "north-central Vietnamese", "northern central Vietnamese"}, Wikipedia = "North Central Coast", plain_categories = "Tiếng Việt Trung Bộ", } labels["Northern Vietnam"] = { aliases = {"North Vietnam", "northern Vietnam", "north Vietnam", "Northern Vietnamese", "North Vietnamese", "northern Vietnamese", "north Vietnamese"}, Wikipedia = true, plain_categories = "Northern", } labels["South Central Vietnam"] = { aliases = {"Southcentral Vietnam", "South central Vietnam", "South-Central Vietnam", "South Central Coast", "South central coast"}, Wikipedia = "South Central Coast", plain_categories = "South Central", } labels["Northeastern Vietnam"] = { aliases = {"Northeast Vietnam", "northeastern Vietnam", "northeast Vietnam", "Northeastern Vietnamese", "Northeast Vietnamese", "northeastern Vietnamese", "northeast Vietnamese"}, Wikipedia = true, plain_categories = "Northern", } labels["Overseas Vietnamese"] = { aliases = {"overseas Vietnamese"}, Wikipedia = true, plain_categories = true, } labels["Phú Yên"] = { aliases = {"Phu Yen", "Phú Yên dialect", "Phu Yen dialect"}, Wikipedia = "Phú Yên province", plain_categories = "Nẫu", } labels["Quảng Bình"] = { aliases = {"Quang Binh", "Quảng Bình dialect", "Quang Binh dialect"}, Wikipedia = "Quảng Bình province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Đà Nẵng"] = { aliases = {"Danang", "Da Nang", "Đà Nẵng dialect", "Danang dialect"}, Wikipedia = true, plain_categories = "South Central", } labels["Quảng Nam"] = { aliases = {"Quang Nam", "Quảng Nam dialect", "Quang Nam dialect"}, Wikipedia = "Quảng Nam province", plain_categories = "South Central", } labels["Quảng Ngãi"] = { aliases = {"Quang Ngai", "Quảng Ngãi dialect", "Quang Ngai dialect"}, Wikipedia = "Quảng Ngãi province", plain_categories = "South Central", } labels["Quảng Trị"] = { aliases = {"Quang Tri", "Quảng Trị dialect", "Quang Tri dialect"}, Wikipedia = "Quảng Trị province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Sài Gòn"] = { aliases = {"Saigon", "Saigonese", "Ho Chi Minh City", "Hồ Chí Minh City"}, Wikipedia = true, plain_categories = "Tiếng Việt Nam Việt Nam", } labels["Nam Việt Nam"] = { aliases = {"South Vietnam", "southern Vietnam", "south Vietnam", "Southern Vietnamese", "South Vietnamese", "southern Vietnamese", "south Vietnamese"}, Wikipedia = "Nam Bộ", plain_categories = "Tiếng Việt Nam Việt Nam", } labels["Thanh Hoá"] = { aliases = {"Thanh Hoa", "Thanh Hoá dialect", "Thanh Hoa dialect"}, Wikipedia = "Thanh Hóa province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Việt Nam"] = { aliases = {"Vietnamese", "Vietnam"}, Wikipedia = true, plain_categories = "Tiếng Việt Việt Nam", } ---------------- non-regional ---------------- labels["tulay"] = { display = "[[từ láy]]", } return require("Module:labels").finalize_data(labels) 3beguy4khq7lgfeivbah45mqqur926k 2513846 2513845 2026-08-13T15:04:09Z Hiyuune 50834 2513846 Scribunto text/plain local labels = {} labels["xứ Nẫu"] = { aliases = {"xứ Nẩu", "Nẫu", "Nẩu", "Nau", "Nẫu dialects", "Nẫu dialect", "Nẩu dialects", "Nẩu dialect"}, plain_categories = "Nẫu", } labels["Bình Định"] = { aliases = {"Binh Dinh", "Bình Định dialect", "Binh Dinh dialect"}, Wikipedia = "Bình Định province", plain_categories = "Nẫu", } labels["Trung Bộ"] = { aliases = {"central Vietnam", "Central Vietnamese", "central Vietnamese"}, Wikipedia = true, plain_categories = "Tiếng Việt Trung Bộ", } labels["Fangcheng"] = { aliases = {"Fangcheng dialect"}, Wikipedia = "Gin people", plain_categories = true, } labels["Hải Phòng"] = { aliases = {"Hai Phong", "Haiphong", "Hải Phòng dialect", "Hai Phong dialect", "Haiphong dialect"}, Wikipedia = true, plain_categories = "Northern", } labels["Hà Nội"] = { aliases = {"Hanoi"}, Wikipedia = {"Hanoi"}, plain_categories = "Northern", } labels["Hà Tĩnh"] = { aliases = {"Ha Tinh", "Hà Tĩnh dialect", "Ha Tinh dialect"}, Wikipedia = "Hà Tĩnh province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Huế"] = { aliases = {"Hue", "Huế dialect", "Hue dialect"}, Wikipedia = true, plain_categories = "Tiếng Việt Huế", } labels["Louisiana"] = { aliases = {"New Orleans"}, Wikipedia = "Vietnamese in New Orleans", plain_categories = true, } labels["Đồng bằng Sông Cửu Long"] = { aliases = {"Mekong Delta dialect", "Southwestern Vietnamese", "South Western Vietnamese", "Southwest Vietnamese", "southwestern Vietnamese", "south western Vietnamese", "southwest Vietnamese"}, Wikipedia = true, plain_categories = "Tiếng Việt Đồng bằng Sông Cửu Long", } labels["Nghệ An"] = { aliases = {"Nghe An", "Nghệ An dialect", "Nghe An dialect"}, Wikipedia = "Nghệ An province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Nghệ Tĩnh"] = { aliases = {"Nghe Tinh", "Nghệ Tinh dialect", "Nghe Tinh dialect"}, Wikipedia = true, plain_categories = "Tiếng Việt Trung Bộ", } labels["North Central Vietnam"] = { aliases = {"North-Central Vietnam", "Northern Central Vietnam", "north central Vietnam", "north-central Vietnam", "northern central Vietnam", "North Central Vietnamese", "North-Central Vietnamese", "Northern Central Vietnamese", "north central Vietnamese", "north-central Vietnamese", "northern central Vietnamese"}, Wikipedia = "North Central Coast", plain_categories = "Tiếng Việt Trung Bộ", } labels["Northern Vietnam"] = { aliases = {"North Vietnam", "northern Vietnam", "north Vietnam", "Northern Vietnamese", "North Vietnamese", "northern Vietnamese", "north Vietnamese"}, Wikipedia = true, plain_categories = "Northern", } labels["South Central Vietnam"] = { aliases = {"Southcentral Vietnam", "South central Vietnam", "South-Central Vietnam", "South Central Coast", "South central coast"}, Wikipedia = "South Central Coast", plain_categories = "South Central", } labels["Northeastern Vietnam"] = { aliases = {"Northeast Vietnam", "northeastern Vietnam", "northeast Vietnam", "Northeastern Vietnamese", "Northeast Vietnamese", "northeastern Vietnamese", "northeast Vietnamese"}, Wikipedia = true, plain_categories = "Northern", } labels["Overseas Vietnamese"] = { aliases = {"overseas Vietnamese"}, Wikipedia = true, plain_categories = true, } labels["Phú Yên"] = { aliases = {"Phu Yen", "Phú Yên dialect", "Phu Yen dialect"}, Wikipedia = "Phú Yên province", plain_categories = "Nẫu", } labels["Quảng Bình"] = { aliases = {"Quang Binh", "Quảng Bình dialect", "Quang Binh dialect"}, Wikipedia = "Quảng Bình province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Đà Nẵng"] = { aliases = {"Danang", "Da Nang", "Đà Nẵng dialect", "Danang dialect"}, Wikipedia = true, plain_categories = "South Central", } labels["Quảng Nam"] = { aliases = {"Quang Nam", "Quảng Nam dialect", "Quang Nam dialect"}, Wikipedia = "Quảng Nam province", plain_categories = "South Central", } labels["Quảng Ngãi"] = { aliases = {"Quang Ngai", "Quảng Ngãi dialect", "Quang Ngai dialect"}, Wikipedia = "Quảng Ngãi province", plain_categories = "South Central", } labels["Quảng Trị"] = { aliases = {"Quang Tri", "Quảng Trị dialect", "Quang Tri dialect"}, Wikipedia = "Quảng Trị province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Sài Gòn"] = { aliases = {"Saigon", "Saigonese", "Ho Chi Minh City", "Hồ Chí Minh City"}, Wikipedia = true, plain_categories = "Tiếng Việt Nam Việt Nam", } labels["Nam Việt Nam"] = { aliases = {"South Vietnam", "southern Vietnam", "south Vietnam", "Southern Vietnamese", "South Vietnamese", "southern Vietnamese", "south Vietnamese"}, Wikipedia = "Nam Bộ", plain_categories = "Tiếng Việt Nam Việt Nam", } labels["Thanh Hoá"] = { aliases = {"Thanh Hoa", "Thanh Hoá dialect", "Thanh Hoa dialect"}, Wikipedia = "Thanh Hóa province", plain_categories = "Tiếng Việt Trung Bộ", } labels["Việt Nam"] = { aliases = {"Vietnamese", "Vietnam"}, Wikipedia = true, plain_categories = "Tiếng Việt Việt Nam", } ---------------- non-regional ---------------- labels["tulay"] = { display = "[[từ láy]]", } return require("Module:labels").finalize_data(labels) mpoyuep1232mvtfpwcdf237dch3jsk3 áo khươi 0 282889 2513875 2104057 2026-08-13T16:09:45Z Hiyuune 50834 2513875 wikitext text/x-wiki =={{langname|tyz}}== ==={{ĐM|pron}}=== {{tyz-IPA}} ==={{ĐM|n}}=== {{tyz-noun}} # [[chú]] (chồng của cô). ==={{ĐM|ref}}=== * {{R:Lương Bèn}} nr7z4me80d5rqng0i304obdsdbtzuoi lu̱a 0 282909 2514012 2104111 2026-08-14T09:39:37Z Hiyuune 50834 2514012 wikitext text/x-wiki =={{langname|tyz}}== ==={{section|pron}}=== {{tyz-IPA}} ==={{section|n}}=== {{tyz-noun}} # Cái [[máng]] bằng [[gỗ]]. ==={{section|ref}}=== * {{R:Lương Bèn}} 9ao8x7vk4cz1kgxuvhdkh8nwmukmm68 thản 0 282910 2514010 2104112 2026-08-14T09:36:58Z Hiyuune 50834 2514010 wikitext text/x-wiki =={{langname|tyz}}== ==={{ĐM|pron}}=== {{tyz-IPA}} ==={{ĐM|n}}=== {{tyz-noun}} # Cái [[giá]] đựng [[đồ]]. # [[ban thờ|Bàn thờ]]. ==={{ĐM|ref}}=== * {{R:Lương Bèn}} lep8qrbwceaild09h2raak4iysaufyq nào po̱ 0 283039 2514007 2104429 2026-08-14T09:33:51Z Hiyuune 50834 2514007 wikitext text/x-wiki =={{langname|tyz}}== ==={{ĐM|pron}}=== {{tyz-IPA}} ==={{ĐM|adj}}=== {{tyz-adj}} # [[não bạt]] [[to]] ([[nhỏ]] [[núm]]). 6b30tdvklccjv5ungn4zyzlqthxsrqs 2514008 2514007 2026-08-14T09:35:33Z Hiyuune 50834 /* {{langname|tyz}} */ 2514008 wikitext text/x-wiki =={{langname|tyz}}== ==={{ĐM|pron}}=== {{tyz-IPA}} ==={{ĐM|adj}}=== {{tyz-adj}} # [[não bạt]] [[to]] ([[nhỏ]] [[núm]]). ==={{ĐM|ref}}=== *{{R:tyz:Hoàng Văn Ma, Lục Văn Pảo, Hoàng Chí}} *{{R:tyz:Lương Bèn}} *{{R:tyz:tdcnt}} mf0xajnu9fbetrrkld5l9umomxdhblf 2514009 2514008 2026-08-14T09:36:16Z Hiyuune 50834 /* Tiếng Tày */ (sử dụng [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]) 2514009 wikitext text/x-wiki =={{langname|tyz}}== ==={{ĐM|pron}}=== {{tyz-IPA}} ==={{ĐM|n}}=== {{tyz-noun}} # [[não bạt]] [[to]] ([[nhỏ]] [[núm]]). ==={{ĐM|ref}}=== *{{R:tyz:Hoàng Văn Ma, Lục Văn Pảo, Hoàng Chí}} *{{R:tyz:Lương Bèn}} *{{R:tyz:tdcnt}} 36jj9m21kfnt26ygfggl7kywr8xavdv nào me̱ 0 283040 2514011 2104430 2026-08-14T09:38:21Z Hiyuune 50834 2514011 wikitext text/x-wiki =={{langname|tyz}}== ==={{section|pron}}=== {{tyz-IPA}} ==={{section|n}}=== {{tyz-noun|[[鐃]][[⿰女米]]}} # [[não bạt]] [[nhỏ]] ([[to]] [[núm]]). ==={{section|ref}}=== *{{R:tyz:Hoàng Văn Ma, Lục Văn Pảo, Hoàng Chí}} *{{R:tyz:Lương Bèn}} *{{R:tyz:tdcnt}} gm6akfji2mtfph9zt8vgwq9d29qefrz cột kẹo 0 283115 2514005 2104564 2026-08-14T09:31:59Z Hiyuune 50834 2514005 wikitext text/x-wiki =={{langname|tyz}}== ==={{ĐM|pron}}=== {{tyz-IPA}} ==={{ĐM|adj}}=== {{tyz-adj}} # [[cong queo|Cong queo]]. # [[giả dối|Giả dối]]. ==={{ĐM|ref}}=== * {{R:Lương Bèn}} t8mv6uh235ccaelw3unm1bquff36whr kí cáo 0 283127 2514006 2104614 2026-08-14T09:32:26Z Hiyuune 50834 2514006 wikitext text/x-wiki =={{langname|tyz}}== ==={{ĐM|pron}}=== {{tyz-IPA}} ==={{ĐM|adj}}=== {{tyz-adj}} # [[mưu kế|Mưu kế]], [[thủ đoạn]]. ==={{ĐM|ref}}=== * {{R:Lương Bèn}} tc1nhrwav57t8pj4qx2bab88bb2gf11 toòng cháo 0 283137 2513872 2104650 2026-08-13T16:00:20Z Hiyuune 50834 2513872 wikitext text/x-wiki =={{langname|tyz}}== ==={{ĐM|pron}}=== {{tyz-IPA}} ==={{ĐM|adj}}=== {{tyz-adj}} # [[thao láo|Thao láo]]. ==={{ĐM|ref}}=== * {{R:Lương Bèn}} mlezybf8eafcasajiyiie4s99nf10v1 bạch ngạch 0 283138 2513873 2104651 2026-08-13T16:00:46Z Hiyuune 50834 2513873 wikitext text/x-wiki =={{langname|tyz}}== ==={{ĐM|pron}}=== {{tyz-IPA}} ==={{ĐM|adj}}=== {{tyz-adj}} # [[lếu láo|Lếu láo]]. ==={{ĐM|ref}}=== * {{R:Lương Bèn}} pune7xn5en0o06fbkea4tj2uousoft1 quai quẳn 0 283142 2513874 2104658 2026-08-13T16:01:12Z Hiyuune 50834 2513874 wikitext text/x-wiki =={{langname|tyz}}== ==={{ĐM|pron}}=== {{tyz-IPA}} ==={{ĐM|adj}}=== {{tyz-adj}} # [[khôn ngoan|Khôn ngoan]], [[khôn khéo]]. ==={{ĐM|ref}}=== * {{R:Lương Bèn}} 4uqex2hrq829c50snemheafzc6xgzb4 mjàng 0 283143 2513849 2104660 2026-08-13T15:29:25Z Hiyuune 50834 2513849 wikitext text/x-wiki =={{langname|tyz}}== ==={{section|pron}}=== {{tyz-IPA}} ==={{section|adj}}=== {{tyz-adj}} # [[thành thạo|Thành thạo]]. # [[nhanh|Nhanh]]. # [[khéo léo|Khéo léo]]. ==={{section|ref}}=== * {{R:Lương Bèn}} b1pt3ha9r2a4sql7l0zurlq1tmewzdy pác nẩư 0 283149 2513850 2104667 2026-08-13T15:30:33Z Hiyuune 50834 2513850 wikitext text/x-wiki =={{langname|tyz}}== ==={{section|pron}}=== {{tyz-IPA}} ==={{section|adj}}=== {{tyz-adj}} # [[nhanh miệng|Nhanh miệng]]. ==={{section|ref}}=== * {{R:Lương Bèn}} 1by7alsbr2zowgilscslcnkmi7r6fiw híu chảy 0 283152 2513852 2104670 2026-08-13T15:31:31Z Hiyuune 50834 2513852 wikitext text/x-wiki =={{langname|tyz}}== ==={{section|pron}}=== {{tyz-IPA}} ==={{section|adj}}=== {{tyz-adj}} # [[tinh nhanh|Tinh nhanh]], [[cừ khôi]]. ==={{section|ref}}=== * {{R:Lương Bèn}} d9y6ji5b1ghzjqdr4k6nu9rlx0vilio pja coòng 0 283172 2513851 2104705 2026-08-13T15:31:04Z Hiyuune 50834 2513851 wikitext text/x-wiki =={{langname|tyz}}== ==={{section|pron}}=== {{tyz-IPA}} ==={{section|n}}=== {{tyz-noun}} # [[cá|Cá]] [[ngạnh]]. ==={{section|ref}}=== * {{R:Lương Bèn}} tdtsa62uvvihnvq06fsg6nstcjzoq94 Bản mẫu:kk-regional 10 288310 2513795 2314851 2026-08-13T12:09:50Z Hiyuune 50834 2513795 wikitext text/x-wiki {{#invoke:checkparams|error}}<!-- Validate template parameters -->{{kk-alt |Cyrl={{{1|{{PAGENAME}}}}} |Arab={{#if:{{{2|}}}|{{{2|}}}|}} |Latn={{#if:{{{3|}}}|{{{3|}}}|}}}}<noinclude>[[Thể loại:Bản mẫu tiếng Kazakh]]</noinclude> ebxof5hmfah2dhk4wu1kvmq6yb0b457 ᠨᠢᠭᠡᠨ 0 291852 2513986 2285583 2026-08-14T08:26:10Z WhoAlone 40420 2513986 wikitext text/x-wiki =={{langname|cmg}}== ==={{section|num}}=== {{head|cmg|Số từ}} # [[một|Một]]. ===={{section|desc}}==== * {{desc|mn|нэг|ᠨᠢᠭᠡ}} 9a22rd64zlp4e6ns58t7zmjrhnd9ck1 斗比 0 293309 2513975 2133410 2026-08-14T05:09:52Z WhoAlone 40420 2513975 wikitext text/x-wiki {{-tuw-kkl-}} {{-noun-}} {{head|tuw-kkl|Danh từ|tr=do(u)bi}} # [[cáo]]. {{C|tuw-kkl|Họ Chó}} knzinqqajsqc4aw2r8r5vnl33v0fibi 依彻库伦 0 293313 2513967 2133416 2026-08-14T05:07:25Z WhoAlone 40420 2513967 wikitext text/x-wiki {{-tuw-kkl-}} {{-noun-}} {{head|tuw-kkl|Danh từ|tr=(y)iche kulun}} # [[cô dâu]]. {{c|tuw-kkl|Mọi người|Hôn nhân}} ijnh6szn6vm4x7lxl126fz6htg90a75 伊彻额驸 0 293314 2513974 2133417 2026-08-14T05:09:11Z WhoAlone 40420 2513974 wikitext text/x-wiki {{-tuw-kkl-}} {{-noun-}} {{head|tuw-kkl|Danh từ|tr=(y)iche efu}} # [[chú rể]]. {{c|tuw-kkl|Mọi người|Hôn nhân}} aw5f5ihk55joq8p2j150ccsio1m6r6b 朱瓦俐 0 293318 2513956 2133422 2026-08-14T05:03:12Z WhoAlone 40420 2513956 wikitext text/x-wiki {{-tuw-kkl-}} {{-noun-}} {{head|tuw-kkl|Danh từ|tr=zhuwali}} # [[cóc]]. {{c|tuw-kkl|Lớp Lưỡng cư}} f71ug0yf9s6awoj4ugj17hvxoypdnk0 0 293515 2513836 2133789 2026-08-13T14:47:36Z TheHighFighter2 42988 /* */ 2513836 wikitext text/x-wiki {{character image|IPA Unicode 0x0070+0x032A.svg}} =={{langname|mul}}== {{wikipedia|Âm bật môi răng vô thanh}} ==={{ĐM|symbol}}=== {{head|mul|Ký hiệu}} # {{label|mul|[[w:Bảng mẫu tự ngữ âm quốc tế|IPA]]}} [[âm bật|Âm bật]] [[môi]] [[răng]] [[vô thanh]]. [[Thể loại:Ký hiệu IPA|P]] 7gwxbel8gydhzjf7wv50ulgkhljfobt Thể loại:Ký hiệu IPA 14 293516 2513837 2133790 2026-08-13T14:48:55Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự IPA]] thành [[Thể loại:Ký hiệu IPA]] (đã tắt đổi hướng) 2133790 wikitext text/x-wiki [[Thể loại:Ký tự đa ngữ|IPA]] ejey756p3beuzwvt8wmctl7eqr43ya2 2513839 2513837 2026-08-13T14:54:26Z TheHighFighter2 42988 /* */ 2513839 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Bản mẫu:kk-alt 10 293548 2513797 2314855 2026-08-13T12:12:10Z Hiyuune 50834 2513797 wikitext text/x-wiki {{inflection-table-top|title=Các dạng chữ viết thay thế|class=floatright|palette=blue}} ! {{w|Bảng chữ cái Kazakh#Chữ Ả Rập|Ả Rập}} | {{#if:{{{ar|}}}{{{Arab|}}}|{{l-self|kk|{{{ar|}}}{{{Arab|}}}|tr=-}}|{{l-self|kk|{{#invoke:kk-Cyrl-Arab-translit|tr|{{{Cyrl|{{PAGENAME}}}}}}}|tr=-}}}}{{#if:{{{ar2|}}}{{{Arab2|}}}|, {{l-self|kk|{{{ar2|}}}{{{Arab2|}}}|tr=-}}|}} |- {{#if: {{{old|}}}{{{ar-old|}}}{{{old2|}}}{{{ar-old2|}}} | {{!-}} ! {{w|İske imlâ alphabet|İske imlâ|lang=en}} {{!}} {{#if: {{{old|}}}{{{ar-old|}}} | {{l-self|kk|{{{old|}}}{{{ar-old|}}}|tr=-}}}}| }}{{#if:{{{old2|}}}{{{ar-old2|}}}|, {{l-self|kk|{{{old2|}}}{{{ar-old2|}}}|tr=-}}|}} |- ! style="min-width:4em;max-width:7em"| {{w|Bảng chữ cái Kazakh#Chữ Kirin|Kirin}} | {{#if:{{{cy|}}}{{{Cyrl|}}}|{{l-self|kk|{{{cy|}}}{{{Cyrl|}}}|tr=-}}|{{l-self|kk|{{{Cyrl|{{PAGENAME}}}}}|tr=-}}}}{{#if:{{{cy2|}}}{{{Cyrl2|}}}|, {{l-self|kk|{{{cy2|}}}{{{Cyrl2|}}}|tr=-}}|}} |- ! {{w|Bảng chữ cái Kazakh#Chữ Latinh|Latinh}} | {{#if:{{{la|}}}{{{Latn|}}}|{{l-self|kk|{{{la|}}}{{{Latn|}}}|tr=-}}|{{l-self|kk|{{xlit|kk|module=kk-convert|{{{Cyrl|{{PAGENAME}}}}}}}|tr=-}}}}{{#if:{{{la2|}}}{{{Latn2|}}}|, {{l-self|kk|{{{la2|}}}{{{Latn2|}}}|tr=-}}|}} <!-- yañalif bị vô hiệu hóa, cần được xử lý trước khi bật lại |- {{#ifeq:{{xlit|kk|module=kk-translit|{{{Cyrl|{{PAGENAME}}}}}}}|{{#invoke:kk-translit|yanalif|{{{Cyrl|{{PAGENAME}}}}}}}||! {{w|Yañalif}} {{!}} {{#if: {{{yan|}}}{{{ya-Latn|}}} {{!}} {{l-self|kk|{{{yan|}}}{{{ya-Latn|}}}|tr=-}}|{{l-self|kk|{{#invoke:kk-translit|yanalif|{{{Cyrl|{{PAGENAME}}}}}|tr=-}}}}}}}} --> {{inflection-table-bottom}}<noinclude>{{tài liệu}}</noinclude> kp3y6ffdyy3pjz6kb2eekmjhtjsxegp Thành viên:Renamed user 884fe7926b62f9d3ae6cf3bcc87670a6 2 303715 2513876 2169746 2026-08-13T20:56:00Z Deepfriedokra 43439 Deepfriedokra đã đổi [[Thành viên:Trong Dang]] thành [[Thành viên:Renamed user 884fe7926b62f9d3ae6cf3bcc87670a6]] (đã tắt đổi hướng): Tự động di chuyển trang khi đổi tên thành viên “[[Special:CentralAuth/Trong Dang|Trong Dang]]” thành “[[Special:CentralAuth/Renamed user 884fe7926b62f9d3ae6cf3bcc87670a6|Renamed user 884fe7926b62f9d3ae6cf3bcc87670a6]]” 2169746 wikitext text/x-wiki {{User vi}} 6ncsg265a2svz1rafiqtm8cqdcyvzoj Thảo luận Thành viên:Renamed user 884fe7926b62f9d3ae6cf3bcc87670a6 3 303936 2513877 2384132 2026-08-13T20:56:00Z Deepfriedokra 43439 Deepfriedokra đã đổi [[Thảo luận Thành viên:Trong Dang]] thành [[Thảo luận Thành viên:Renamed user 884fe7926b62f9d3ae6cf3bcc87670a6]] (đã tắt đổi hướng): Tự động di chuyển trang khi đổi tên thành viên “[[Special:CentralAuth/Trong Dang|Trong Dang]]” thành “[[Special:CentralAuth/Renamed user 884fe7926b62f9d3ae6cf3bcc87670a6|Renamed user 884fe7926b62f9d3ae6cf3bcc87670a6]]” 2384132 wikitext text/x-wiki <div style="border:1px solid #FDA; vertical-align:top; padding: 0.5em; background: #FFFFE5;"> <div style="margin-left:0.5em; margin-right: 0.5em; margin-bottom:1em; margin-top: 0.1em;"> <span style="font-size: 1.3rem;">'''Xin chào Trong Dang! Chào mừng bạn đến với Wiktionary tiếng Việt!'''</span> <div class="plainlinks"><p style="margin-top:0.1em; margin-bottom: 1em;">Wiktionary là từ điển mở mà ai cũng đều có thể chỉnh sửa. Cách sử dụng và quy định có thể gây khó khăn cho bạn, nhưng hãy dần học hỏi và '''[[Wiktionary:Táo bạo|mạnh dạn đóng góp]]'''. Các thông tin dưới đây sẽ có thể giúp bạn sử dụng và chỉnh sửa Wiktionary.</p></div> </div> <!-- Quan trọng --> <div style="clear: both; margin-left: 1.5em; margin-right 1.5em; font-size: 100%;"> {| class="plainlinks" width="100%" style="margin:auto;" | style="text-align:center; padding:4px" | [[File:OOjs UI icon search-ltr.svg|30px|link=]] | style="width:50%; padding:4px;" | '''[[Trợ giúp:Tìm kiếm|Tìm kiếm Wiktionary]]''' <br />Tìm hiểu cách tìm và tra cứu mục từ trên Wiktionary. | style="text-align:center; padding:4px" | [[File:OOjs UI icon lightbulb.svg|30px|link=]] | style="width:50%; padding:4px;" | '''[[Wiktionary:Yêu cầu mục từ|Yêu cầu mục từ]]'''<br />Nơi để bạn thêm những từ cần viết nhưng chưa có tại Wiktionary. |- | style="text-align:center; padding:4px" | [[File:OOjs UI icon userAvatar.svg|30px|link=|]] | style="width:50%; padding:4px;" | '''[[Wiktionary:Cộng đồng|Cổng Cộng đồng Wiktionary tiếng Việt]]'''<br />Nơi tìm hiểu mọi thông tin về của Wiktionary. | style="text-align:center; padding:4px" | [[File:OOjs UI icon articleCheck-ltr.svg|30px|link=]] | style="width:50%; padding:4px;" | '''[[Wiktionary:Quy định và hướng dẫn|Quy định và hướng dẫn]]''' <br />Chính sách của Wiktionary là gì? Xin vui lòng đọc nó trước khi thực hiện chỉnh sửa! |- | style="text-align:center; padding:4px" | [[File:OOjs UI icon edit-ltr.svg|30px|link=]] | style="width:50%; padding:4px;" | '''[[Trợ giúp:Viết trang mới|Viết trang mới]]'''<br />Tham khảo cách viết trang hay mục từ mới tại Wiktionary. | style="text-align:center; padding:4px" | [[File:OOjs UI icon tray.svg|30px|link=]] | style="width:50%; padding:4px;" | '''[[Wiktionary:Chỗ thử|Chỗ thử]]'''<br />Nơi để bạn viết nháp và thử nghiệm. |- | style="text-align:center; padding:4px" | [[File:OOjs UI icon wikiText.svg|30px|link=]] | style="width:50%; padding:4px;" | '''[[Trợ giúp:Sửa đổi|Thực hiện sửa đổi]]'''<br />Tham khảo hướng dẫn sửa đổi và cùng bắt đầu sửa đổi Wiktionary. | style="text-align:center; padding:4px" | [[File:OOjs UI icon helpNotice-ltr.svg|30px|link=]] | style="width:50%; padding:4px;" | '''[[Wiktionary:Bàn giúp đỡ|Bàn giúp đỡ]]'''<br />Nơi hỏi đáp khi gặp khó khăn trên Wiktionary. |- | style="text-align:center; padding:4px" | [[File:OOjs UI icon book-ltr.svg|30px|link=]] | style="width:50%; padding:4px;" | '''[[Trợ giúp:Mục lục|Mục lục trợ giúp]]''' <br />Mục lục giúp bạn thông tin và hướng dẫn khác khi gặp khó khăn trong những lúc đóng góp! | style="text-align:center; padding:4px" | [[File:OOjs UI icon speechBubbles-ltr.svg|30px|link=]] | style="width:50%; padding:4px;" | '''[[Wiktionary:Thảo luận|Thảo luận chung]]'''<br />Nơi trao đổi ý kiến giữa bạn và các thành viên khác! |}</div> <div style="padding: 0.5em; background: #FEC; border: 1px solid #FDA; margin-top: 1em; font-size: 1em"> [[File:Vector sig vi.png|250px|right]] '''Hãy luôn nhớ rằng, sau khi viết thảo luận xong thì đừng quên bước quan trọng đó là ký tên ở phía sau thảo luận'''. Phương thức ký tên được sử dụng trong Wiktionary không phải là viết tên trực tiếp, nhưng xin hãy ký tên bằng cách sử dụng 4 dấu ngã (<code><nowiki>--~~~~</nowiki></code>) hoặc bằng cách nhấp vào nút chữ ký ([[File:OOUI JS signature icon LTR.svg|20px|border]]) trong cửa sổ chỉnh sửa của phiên bản trình soạn thảo mã nguồn thông thường.<br/> [[Wiktionary:Guestbook_for_non-Vietnamese_speakers|Welcome! If you are not good at Vietnamese or do not speak it, click here.]] </div> </div>&nbsp; – [[Thành viên:Nguyên Hưng Trần|<span style="color:darkgreen;font-family:Georgia, serif;">'''The Emerald of Harmony'''</span>]][[Thảo luận Thành viên:Nguyên Hưng Trần|<sup style="font-family:Georgia;">awaits you in a fantasy world of silence...</sup>]] 01:57, ngày 11 tháng 5 năm 2024 (UTC) == Thể loại == Chào bạn, rất cảm ơn bạn tạo các mục từ cho bách khoa thư. Tuy nhiên, mời bạn thêm thể loại cho các bài viết vừa mới tạo. Tôi nhận thấy có rất nhiều bài viết được bạn tạo nhưng chưa thêm thể loại. Một số trang, tôi có giúp việc này, tuy vậy, hiện tại số lượng mục từ "thiếu" thể loại được bạn tạo thêm liên tục tăng nhanh chóng. Mời bạn vào cải thiện. Trân trọng! [[Thành viên:P. ĐĂNG|Phong Đăng]] ([[Thảo luận Thành viên:P. ĐĂNG|thảo luận]]) 11:45, ngày 11 tháng 2 năm 2025 (UTC) == Về việc xóa thông tin chữ nôm trong mục từ == Đề nghị bạn giải thích tại sao lại xóa hết thông tin chữ nôm, phiên âm hán việt, từ tương tự khỏi mục từ, tôi thấy cần có lý do hợp lý mới xóa các nội dung này.--[[Thành_viên:Kateru Zakuro|<span style="color:#EE1D25;">'''Kateru'''</span> <span style="color:#EE1D25;">'''Zakuro'''</span>]] ([[Thảo luận Thành viên:Kateru Zakuro|thảo luận]]) 22:21, ngày 19 tháng 6 năm 2026 (UTC) :Xin lỗi vì chưa tham khảo ý kiến trước. :Do hiện tại các thông tin xuất hiện thẳng lên trang gây khó đọc, dài dòng (có thể thấy ở mục từ [[tư]]) và tôi nghĩ nên chuyển dần định dạng theo enwikt như vài thành viên khác đã thực hiện. – [[Thành viên:Trong Dang|Trong Dang]] ([[Thảo luận Thành viên:Trong Dang|thảo luận]]) 00:36, ngày 20 tháng 6 năm 2026 (UTC) ::@[[Thành viên:Trong Dang|Trong Dang]]: Cái này thật ra có thể thu gọn bằng cách thay {{tl|đầu}} và {{tl|cuối}} bằng {{tl|col-top}} và {{tl|col-bottom}} là được, em sẽ quay lùi tất cả trang và dùng bot thay thế tự động. – [[Thành_viên:Kateru Zakuro|<span style="color:#EE1D25;">'''Kateru'''</span> <span style="color:#EE1D25;">'''Zakuro'''</span>]] ([[Thảo luận Thành viên:Kateru Zakuro|thảo luận]]) 01:31, ngày 20 tháng 6 năm 2026 (UTC) ::Lưu ý, nên thảo luận tham khảo ý kiến trước khi thực hiện các sửa đổi mang tính hàng loạt. Hy vọng bạn rút kinh nghiệm. [[Thành viên:P. ĐĂNG|Phong Đăng]] ([[Thảo luận Thành viên:P. ĐĂNG|thảo luận]]) 14:27, ngày 20 tháng 6 năm 2026 (UTC) 0ueibkgiiwi6q29x8lk64xfy6vi2zlg Thể loại:Chữ cái tiếng Palaung Shwe 14 304507 2513843 2158564 2026-08-13T14:57:39Z TheHighFighter2 42988 /* */ 2513843 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Từ tái tạo:Tiếng Mông Cổ nguyên thuỷ/naran 0 306090 2513952 2163665 2026-08-14T04:50:41Z WhoAlone 40420 2513952 wikitext text/x-wiki {{reconstructed}} {{-xgn-pro-}} {{-etym-}} So sánh {{cognate|zkt|𘲺}}. {{-noun-}} '''*naran''' # [[mặt trời]]. {{-desc-}} {{top3}} * Trung Mông Cổ: ** {{desc|xng|ᠨᠠᠷᠠᠨ}} *** {{desc|cmg|ᠨᠠᠷᠠᠨ}} **** {{desc|mn|нар}} **** {{desc|bua|наран}} **** {{desc|ykh|нара|нару}} *** {{desc|xwo|}} **** {{desc|xal|нарн}} * Nam Mông Cổ: ** {{desc|yuy|naran}} ** Shirongol: *** Bảo An: **** {{desc|peh|narang|narong}} **** {{desc|sce|naran}} **** {{desc|kxs|naro}} *** Monguor: **** {{desc|xgn-mgr|nara}} **** {{desc|xgn-mgl|nara}} * {{desc|dta|nar}} * {{desc|mhj|ناران|tr=naran}} {{bottom}} [[Thể loại:Mục từ tiếng Mông Cổ nguyên thuỷ|N]] [[Thể loại:Danh từ tiếng Mông Cổ nguyên thuỷ|N]] rmvt6xf91z2ajq4ye48hu81js82jiri muvaffakiyetsizleştiricileştiriveremeyebileceklerimizdenmişsinizcesine 0 308350 2513903 2171638 2026-08-14T02:50:14Z WhoAlone 40420 2513903 wikitext text/x-wiki {{-tur-}} {{-etym-}} {{inh+|tr|ota|موفقیت‌سزلش‌دریجیلش‌دریویره‌میه‌بیله‌جك‌لریمزدن‌مشسڭزجه‌سنه}}. Nghĩa đen: "như thể bạn nằm trong số những người mà chúng tôi không thể dễ dàng biến thành người tạo ra những kẻ không thành công". Với 70 chữ cái, nó được Köksal Karakuş đề xuất là từ dài nhất trong tiếng Thổ Nhĩ Kỳ. {{-phrase-}} {{head|tr|Cụm từ}} # [[như thể|Như thể]] [[bạn]] [[nằm]] [[trong]] [[số]] [[những]] [[người]] [[mà]] [[chúng tôi]] [[không thể]] [[dễ dàng]] [[biến]] [[thành]] [[người]] [[tạo]] [[ra]] [[những]] [[kẻ]] [[không]] [[thành công]]. #:{{ux|tr|Söz konusu öğretmeni makamına çağırıp ona diyor ki: "'''Muvaffakiyetsizleştiricileştiriveremeyebileceklerimizdenmişsinizcesine''' laflar ediyormuşsunuz ha?"|Ông ta mời giáo viên vào phòng và nói "Thầy đang nói '''như thể thầy nằm trong số những người mà chúng tôi không thể dễ dàng biến thành người tạo ra những kẻ không thành công''', phải không?"}} {{-usage-}} Trong tiếng Thổ Nhĩ Kỳ thông thường, cụm từ này sẽ là "Hemencecik başarısızlaştırıcı hâline getiremeyebileceğimiz kişilerden biriymişsiniz gibi". {{-references-}} * "[https://web.archive.org/web/20110718172642/http://www.yenimesaj.com.tr/index.php?haberno=8001752&tarih=2008-03-08 Yeni Mesaj, Turkish newspaper]". Lưu trữ [http://www.yenimesaj.com.tr/index.php?haberno=8001752&tarih=2008-03-08 bản gốc] ngày 18 tháng 7 năm 2011. [[Thể loại:Từ dài tiếng Thổ Nhĩ Kỳ]] ry5smliq5cj2ik6rwv4z88300jf4o0o abortoria 0 311833 2513826 2266182 2026-08-13T13:19:56Z Kelly zhrm 58416 2513826 wikitext text/x-wiki =={{langname|en}}== ==={{ĐM|pron}}=== * {{IPA4|en|/æ.bɔɹˈtɔɹ.i.ə/|a=US}} ==={{ĐM|noun}}=== {{head|en|Biến thể hình thái danh từ}} # {{plural of|en|abortorium}} =={{langname|nl}}== ==={{ĐM|noun}}=== {{head|nl|Biến thể hình thái danh từ}} # {{plural of|nl|abortorium}} ddd7hpcbp7jh10j11wdxm8g9tyc1klx Japonsko 0 313600 2513869 2334423 2026-08-13T15:57:43Z Hiyuune 50834 /* Xem thêm */ (sử dụng [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]) 2513869 wikitext text/x-wiki {{also|Japóńskô}} =={{langname|cs}}== {{wp|cs:}} ==={{section|pron}}=== * {{cs-IPA}} * {{audio|cs|Cs-Japonsko.ogg}} * {{hyph|cs|Ja|pon|sko}} ==={{section|pr-noun}}=== {{cs-proper noun|n|adj=japonský|dem=Japonec|fdem=Japonka}} # {{tcl|cs|Nhật Bản|id=Q17}} ===={{section|decl}}==== {{cs-ndecl|n.sg}} ===={{section|rel}}==== {{col|cs|japonština<g:f>}} ==={{section|further}}=== * {{R:cs:PSJC}} * {{R:cs:SSJC}} * {{R:cs:ASSC}} * {{R:cs:IJP}} {{cln|cs|Từ có hậu tố -sko}} =={{langname|sk}}== {{wp|sk:}} ==={{section|pron}}=== * {{sk-IPA}} ==={{section|pr-noun}}=== {{sk-proper noun|n|adj=japonský|dem=Japonec|fdem=Japonka}} # {{tcl|sk|Nhật Bản|id=Q17}} ===={{section|decl}}==== {{sk-ndecl|n|n=sg}} ===={{section|rel}}==== {{col|sk|japončina<g:f>}} ==={{section|see}}=== {{list:quốc gia của châu Á/sk}} ==={{section|further}}=== * {{R:sk:SDK}} {{cln|sk|Từ có hậu tố -sko}} sr68sezghxhtvhx5brctryxd8uifobx ăn ong 0 314876 2513860 2182837 2026-08-13T15:46:21Z Hiyuune 50834 2513860 wikitext text/x-wiki {{also|Anong|anong}} =={{langname|vi}}== ==={{section|pron}}=== {{vi-pron}} ==={{section|v}}=== {{vi-verb}} # {{lb|vi|Mekong Delta}} Đi lấy [[mật ong]] từ việc gác [[kèo]] trước đó. {{C|vi|Ong}} 9dhrndv2nxil1xb75tuk82bij1busqr 2513861 2513860 2026-08-13T15:46:49Z Hiyuune 50834 /* {{section|v}} */ 2513861 wikitext text/x-wiki {{also|Anong|anong}} =={{langname|vi}}== ==={{section|pron}}=== {{vi-pron}} ==={{section|v}}=== {{vi-verb}} # {{lb|vi|Đồng bằng Sông Cửu Long}} Đi lấy [[mật ong]] từ việc gác [[kèo]] trước đó. {{C|vi|Ong}} ldhd1esevkfw5c7j77snrzdvgtp9xuh Thể loại:Tiếng Việt Huế 14 315016 2513835 2293519 2026-08-13T13:46:16Z Hiyuune 50834 /* */ 2513835 wikitext text/x-wiki {{auto cat|lect=1|lang=vi|cat=Tiếng Việt Trung Bộ|[[Huế]], [[Việt Nam]]}} k3odhaxwxngrpgv7or4tgufzk39mton bibliotheken 0 323594 2513827 2198763 2026-08-13T13:21:28Z Kelly zhrm 58416 2513827 wikitext text/x-wiki {{also|Bibliotheken}} =={{langname|nl}}== ==={{ĐM|pron}}=== * {{audio|nl|Nl-bibliotheken.ogg}} ==={{ĐM|n}}=== {{head|nl|Biến thể hình thái danh từ}} # {{plural of|nl|bibliotheek}} nesw80s876nbqr4tecu36dix0xc6n4j Mô đun:category tree/topic/Communication 828 324839 2513817 2512776 2026-08-13T12:56:14Z TheHighFighter2 42988 2513817 Scribunto text/plain local labels = {} local unpack = unpack or table.unpack -- Lua 5.2 compatibility -- FIXME: Lookup langs in the language list. for _, lang_etc in ipairs { "Ngôn ngữ Ả Rập", {"Ngôn ngữ Trung Quốc"}, "Ngôn ngữ Anh", "Ngôn ngữ Đức", "Ngôn ngữ Nhật", "Ngôn ngữ Latinh", "Ngôn ngữ Triều Tiên ", "Ngôn ngữ Okinawa", "Ngôn ngữ Bồ Đào Nha", "Ngôn ngữ Phạn", "Ngôn ngữ Tây Ban Nha", "Ngôn ngữ Việt" } do if type(lang_etc) ~= "table" then lang_etc = {lang_etc} end local lang, desc = unpack(lang_etc) desc = desc or ("%s"):format(lang, lang) labels[lang] = { type = "chủ đề thuộc nhóm liên quan", description = "=" .. desc, parents = {"Tên ngôn ngữ"}, } end labels["Giao tiếp"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"Tất cả chủ đề"}, wpcat = true, } labels["Thuật ngữ theo lĩnh vực"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"Thuật ngữ"}, wpcat = true, } labels["Thuật ngữ"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"Ngôn ngữ"}, wpcat = true, } labels["Bảng chữ cái"] = { type = "chủ đề thuộc nhóm tên gọi", description = "default", parents = {"Hệ chữ viết"}, } labels["Ngôn ngữ được xây dựng"] = { -- distinguish from "cat:constructed languages" family category type = "chủ đề thuộc nhóm tên gọi", description = "={{w|Ngôn ngữ được xây dựng}}", parents = {"Tên ngôn ngữ"}, } labels["Ngôn ngữ cơ thể"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"Ngôn ngữ", "Giao tiếp phi ngôn ngữ"}, } labels["Phát sóng"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"Truyền thông đại chúng", "Viễn thông"}, } labels["thành phần chữ Hán"] = { type = "chủ đề thuộc nhóm tập hợp", description = "=[[Chinese character]] [[component]]s", parents = {"chữ cái, ký hiệu và dấu câu"}, } labels["diacritical marks"] = { type = "chủ đề thuộc nhóm tập hợp", description = "default", parents = {"chữ cái, ký hiệu và dấu câu"}, } labels["Phương ngữ"] = { type = "chủ đề thuộc nhóm tập hợp", description = "default", parents = {"Ngôn ngữ"}, } labels["Đọc viết chính tả"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"Giao tiếp"}, } labels["extinct languages"] = { type = "chủ đề thuộc nhóm tên gọi", description = "default", parents = {"languages"}, } labels["sign languages"] = { type = "chủ đề thuộc nhóm tên gọi", description = "default", parents = {"languages"}, } labels["facial expressions"] = { type = "chủ đề thuộc nhóm tập hợp", description = "default", parents = {"nonverbal communication", "face"}, } labels["figures of speech"] = { type = "chủ đề thuộc nhóm tập hợp", description = "=[[figure of speech|figures of speech]]", parents = {"rhetoric"}, } labels["flags"] = { type = "chủ đề thuộc nhóm liên quan,chủ đề thuộc nhóm tên gọi,chủ đề thuộc nhóm loại hình", description = "default", parents = {"communication"}, } labels["biệt ngữ"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"ngôn ngữ"}, } labels["Han characters"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"Hệ thống viết"}, } labels["Ngôn ngữ"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"Giao tiếp"}, } labels["language families"] = { type = "chủ đề thuộc nhóm tên gọi", description = "=[[language family|language families]], both accepted and controversial", parents = {"Ngôn ngữ", "Tên"}, } labels["Tên ngôn ngữ"] = { type = "chủ đề thuộc nhóm tên gọi", description = "default", parents = {"Ngôn ngữ"}, } labels["chữ cái, ký hiệu và dấu câu"] = { type = "chủ đề thuộc nhóm tập hợp", description = "=[[letter]]s, [[symbol]]s, and [[punctuation]]", parents = {"orthography"}, } labels["logical fallacies"] = { type = "chủ đề thuộc nhóm tập hợp", description = "=[[logical fallacy|logical fallacies]], clearly defined errors in reasoning used to support or refute an argument", additional = "{{also|Category:{{{langcode}}}:biases}}", parents = {"rhetoric", "logic"}, } labels["Phương tiện truyền thông"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"Giao tiếp"}, } labels["mobile phones"] = { type = "chủ đề thuộc nhóm liên quan,chủ đề thuộc nhóm tập hợp", description = "default", parents = {"telephony"}, } labels["Giao tiếp phi ngôn ngữ"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"Giao tiếp"}, } labels["orthography"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"viết"}, } labels["cổ văn tự học"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"viết"}, } labels["bưu chính"] = { type = "chủ đề thuộc nhóm liên quan", description = "=[[bưu chính]] hoặc [[thư điện tử]]", parents = {"Giao tiếp"}, } labels["quan hệ công chúng"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"Giao tiếp"}, } labels["punctuation marks"] = { type = "chủ đề thuộc nhóm tập hợp", description = "default", parents = {"chữ cái, ký hiệu và dấu câu"}, } labels["Truyền thanh"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"Viễn thông"}, } labels["rhetoric"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"language"}, } labels["signs"] = { type = "chủ đề thuộc nhóm liên quan,chủ đề thuộc nhóm tên gọi,chủ đề thuộc nhóm loại hình", description = "default", parents = {"communication"}, } labels["sociolects"] = { type = "chủ đề thuộc nhóm tên gọi", description = "default", parents = {"language"}, } labels["ký hiệu"] = { type = "chủ đề thuộc nhóm tập hợp", description = "=[[symbol]]s, especially [[mathematical]] and [[scientific]] symbols", additional = "Most symbols have equivalent meanings in many languages and can therefore be found in [[:Category:Translingual symbols]].", parents = {"chữ cái, ký hiệu và dấu câu"}, } labels["Trò chuyện"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"Ngôn ngữ", "Hành vi con người"}, } labels["Viễn thông"] = { type = "chủ đề thuộc nhóm liên quan", description = "default no singularize", parents = {"Giao tiếp", "Công nghệ"}, } labels["telegraphy"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"telecommunications", "electronics"}, wpcat = true, commonscat = true, } labels["telephony"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"telecommunications", "electronics"}, } labels["texting"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"telecommunications"}, } labels["textual division"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"viết"}, } labels["typography"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"writing", "printing"}, } labels["viết"] = { type = "chủ đề thuộc nhóm liên quan", description = "default", parents = {"Ngôn ngữ", "Hành vi con người"}, } labels["Hệ thống viết"] = { type = "chủ đề thuộc nhóm tập hợp", description = "default", parents = {"viết"}, } return labels 2ec6ewn3w3axf7unwafor6iax8d6wfp Thành viên:Hiyuune/common.js 2 329942 2513936 2313010 2026-08-14T04:33:21Z Hiyuune 50834 2513936 javascript text/javascript importScript("en:User:Erutuon/scripts/addAutoCat.js"); mw.loader.load("//vi.wiktionary.org/w/index.php?title=Th%C3%A0nh_vi%C3%AAn:Veritas_Sapientiae/twinkle.js&action=raw&ctype=text/javascript"); importScript("Thành viên:Hiyuune/Gadget-PagePreviews.js"); gxp1otpuju4ns4ojxuxo0o8e0mhms9r 2513938 2513936 2026-08-14T04:34:33Z Hiyuune 50834 2513938 javascript text/javascript importScript("en:User:Erutuon/scripts/addAutoCat.js"); mw.loader.load("//vi.wiktionary.org/w/index.php?title=Th%C3%A0nh_vi%C3%AAn:Veritas_Sapientiae/twinkle.js&action=raw&ctype=text/javascript"); mw.loader.load("//vi.wiktionary.org/w/index.php?title=Th%C3%A0nh_vi%C3%AAn:Hiyuune/Gadget-PagePreviews.js&action=raw&ctype=text/javascript"); 4z6w1teoqcjem8koi0ymuj4p8fapdnm 2513946 2513938 2026-08-14T04:43:23Z Hiyuune 50834 2513946 javascript text/javascript importScript("en:User:Erutuon/scripts/addAutoCat.js"); mw.loader.load("//vi.wiktionary.org/w/index.php?title=Th%C3%A0nh_vi%C3%AAn:Veritas_Sapientiae/twinkle.js&action=raw&ctype=text/javascript"); mw.loader.load("//vi.wiktionary.org/w/index.php?title=Th%C3%A0nh_vi%C3%AAn:Hiyuune/Gadget-popups.js&action=raw&ctype=text/javascript"); e03ub5wozf7dcbaqs6rjfodegwdwkd6 Thể loại:Ký hiệu tiếng Palaung Shwe 14 342916 2513841 2361789 2026-08-13T14:56:47Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Palaung Shwe]] thành [[Thể loại:Ký hiệu tiếng Palaung Shwe]] (đã tắt đổi hướng) 2361789 wikitext text/x-wiki [[Thể loại:Ký tự đa ngữ|P]] [[Thể loại:Mục từ tiếng Palaung Shwe]] 7rz05egyxm1od0zpnzs209xbc3cn81g 2513842 2513841 2026-08-13T14:57:09Z TheHighFighter2 42988 /* */ 2513842 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Mô đun:category tree/symbols 828 357831 2513838 2513192 2026-08-13T14:54:00Z TheHighFighter2 42988 2513838 Scribunto text/plain local labels = {} local raw_categories = {} ----------------------------------------------------------------------------- -- -- -- LABELS -- -- -- ----------------------------------------------------------------------------- labels["Ký hiệu"] = { description = "Các ký hiệu được viết bằng {{{langname}}}.", umbrella_parents = "Tất cả", parents = {{name = "{{{langcat}}}", raw = true}}, } labels["Dấu câu"] = { description = "{{{langname}}} glyphs that are added to characters to modify their sounds or meanings.", parents = {"Ký hiệu"}, } labels["Han characters"] = { description = "{{{langname}}} symbols of the Han logographic script, which can represent sounds or convey meanings directly.", topright = "{{wp|Chinese characters}}", parents = {"logograms"}, } labels["ideograms"] = { description = "{{{langname}}} symbols that represent objects, ideas or concepts directly.", umbrella_parents = "Fundamental", parents = {"symbols"}, } labels["iteration marks"] = { description = "{{{langname}}} glyphs that act as a shorthand for repeated text.", parents = {"abbreviations", "symbols"}, } labels["Chữ cái"] = { description = "{{{langname}}} symbols that represent single sounds. Variations of letters, such as letters with diacritics, should also be categorized here.", parents = {"Ký hiệu"}, } labels["ligatures"] = { description = "{{{langname}}} letters that have been combined into a single graphical unit.", parents = {"symbols"}, } labels["Văn tự ngữ tố"] = { description = "{{{langname}}} symbols that represent single words or morphemes.", parents = {"Ký hiệu"}, } labels["matched pairs"] = { description = "{{{langname}}} pairs of separate characters that are used together. Example: [[( )]].", parents = {"symbols"}, } labels["numeral symbols"] = { description = "{{{langname}}} symbols that represent numerals.", parents = {"symbols"}, } labels["punctuation marks"] = { description = "{{{langname}}} symbols that indicate structure and organization of writing.", parents = {"symbols"}, } labels["syllables"] = { description = "{{{langname}}} symbols that represent groups of sounds. Variations of syllables, such as syllables with diacritics, should also be categorized here.", parents = {"symbols"}, } labels["multigraphs"] = { description = "{{{langname}}} symbols consisting of multiple letters.", parents = {"symbols"}, } local function multigraph(name, n) labels[name] = { description = "{{{langname}}} symbols consisting of " .. n .. " letters.", parents = {"multigraphs"}, } end multigraph("digraphs", "two") multigraph("trigraphs", "three") multigraph("tetragraphs", "four") multigraph("pentagraphs", "five") multigraph("hexagraphs", "six") multigraph("heptagraphs", "seven") multigraph("octagraphs", "eight") -- Add 'umbrella_parents' key if not already present. for key, data in pairs(labels) do if not data.umbrella_parents then data.umbrella_parents = "Thể loại con ký hiệu theo ngôn ngữ" end end ----------------------------------------------------------------------------- -- -- -- RAW CATEGORIES -- -- -- ----------------------------------------------------------------------------- raw_categories["Thể loại con ký hiệu theo ngôn ngữ"] = { description = "Umbrella categories covering topics related to symbols.", additional = "{{{umbrella_meta_msg}}}", parents = { "Siêu thể loại chung", {name = "Ký hiệu", is_label = true, sort = " "}, }, } raw_categories["Ký hiệu IPA"] = { description = "Các ký hiệu trong {{w|bảng phiên âm quốc tế}} (IPA).", additional = "Để biết thêm thông tin, hãy đọc [[Wiktionary:IPA]].", catfix = "mul", parents = { {name = "Ký hiệu", is_label = true, lang = "mul"}, }, } raw_categories["NAPA symbols"] = { description = "Symbols which are part of the {{w|Americanist phonetic notation|North American Phonetic Alphabet}}.", catfix = "mul", parents = { {name = "symbols", is_label = true, lang = "mul"}, }, } raw_categories["UPA symbols"] = { description = "Symbols which are part of the {{w|Uralic Phonetic Alphabet}}.", catfix = "mul", parents = { {name = "symbols", is_label = true, lang = "mul"}, }, } raw_categories["Ký hiệu đa ngữ"] = { description = "Thể loại này liệt kê các ký hiệu đa ngữ (các ký hiệu được sử dụng chung trên toàn thế giới).", catfix = "mul", parents = { "Đa ngữ", "Thể loại:Ký hiệu theo ngôn ngữ", }, } raw_categories["Mã ISO"] = { description = "Thể loại này liệt kê các mã ISO.", catfix = "mul", parents = { "Ký hiệu đa ngữ", }, } return {LABELS = labels, RAW_CATEGORIES = raw_categories} t9e475073zg6u6aceqgudcjir2d9tmu Thể loại:Ký hiệu tiếng Punjab 14 363223 2513816 2282756 2026-08-13T12:43:49Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Punjab]] thành [[Thể loại:Ký hiệu tiếng Punjab]] (đã tắt đổi hướng) 2282756 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Phai 14 365562 2513815 2290075 2026-08-13T12:43:04Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Phai]] thành [[Thể loại:Ký hiệu tiếng Phai]] (đã tắt đổi hướng) 2290075 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Khuen 14 365669 2513802 2290217 2026-08-13T12:35:15Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Khuen]] thành [[Thể loại:Ký hiệu tiếng Khuen]] (đã tắt đổi hướng) 2290217 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Kensiu 14 365770 2513801 2290444 2026-08-13T12:34:44Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Kensiu]] thành [[Thể loại:Ký hiệu tiếng Kensiu]] (đã tắt đổi hướng) 2290444 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Ormur 14 366246 2513814 2291611 2026-08-13T12:42:33Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Ormur]] thành [[Thể loại:Ký hiệu tiếng Ormur]] (đã tắt đổi hướng) 2291611 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Koli Parkar 14 366253 2513807 2291619 2026-08-13T12:38:01Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Koli Parkar]] thành [[Thể loại:Ký hiệu tiếng Koli Parkar]] (đã tắt đổi hướng) 2291619 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Kanuri 14 366255 2513800 2291621 2026-08-13T12:34:14Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Kanuri]] thành [[Thể loại:Ký hiệu tiếng Kanuri]] (đã tắt đổi hướng) 2291621 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Oriya 14 368229 2513812 2296046 2026-08-13T12:41:41Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Oriya]] thành [[Thể loại:Ký hiệu tiếng Oriya]] (đã tắt đổi hướng) 2296046 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Konso 14 369724 2513809 2299190 2026-08-13T12:40:04Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Konso]] thành [[Thể loại:Ký hiệu tiếng Konso]] (đã tắt đổi hướng) 2299190 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Kistane 14 370883 2513803 2302161 2026-08-13T12:35:46Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Kistane]] thành [[Thể loại:Ký hiệu tiếng Kistane]] (đã tắt đổi hướng) 2302161 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Konkan 14 371398 2513808 2303111 2026-08-13T12:38:36Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Konkan]] thành [[Thể loại:Ký hiệu tiếng Konkan]] (đã tắt đổi hướng) 2303111 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Korku 14 374326 2513810 2309594 2026-08-13T12:40:28Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Korku]] thành [[Thể loại:Ký hiệu tiếng Korku]] (đã tắt đổi hướng) 2309594 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Kolhe 14 374339 2513806 2309619 2026-08-13T12:37:27Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Kolhe]] thành [[Thể loại:Ký hiệu tiếng Kolhe]] (đã tắt đổi hướng) 2309619 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Oriya Adiwasi 14 374364 2513813 2309658 2026-08-13T12:42:08Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Oriya Adiwasi]] thành [[Thể loại:Ký hiệu tiếng Oriya Adiwasi]] (đã tắt đổi hướng) 2309658 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Koda 14 375198 2513804 2311176 2026-08-13T12:36:19Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Koda]] thành [[Thể loại:Ký hiệu tiếng Koda]] (đã tắt đổi hướng) 2311176 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Kannada 14 375946 2513799 2312606 2026-08-13T12:33:42Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Kannada]] thành [[Thể loại:Ký hiệu tiếng Kannada]] (đã tắt đổi hướng) 2312606 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Kodava 14 375950 2513805 2312610 2026-08-13T12:36:56Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Kodava]] thành [[Thể loại:Ký hiệu tiếng Kodava]] (đã tắt đổi hướng) 2312610 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Ký hiệu tiếng Kukna 14 375982 2513811 2312642 2026-08-13T12:40:49Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Kukna]] thành [[Thể loại:Ký hiệu tiếng Kukna]] (đã tắt đổi hướng) 2312642 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx ᠬᠣᠶᠠᠷ 0 379212 2513987 2319520 2026-08-14T08:26:24Z WhoAlone 40420 2513987 wikitext text/x-wiki =={{langname|mn}}== {{mn-variant|ᠬᠣᠶᠠᠷ|хоёр}} ==={{ĐM|pron}}=== {{mn-IPA|хоёр}} ==={{ĐM|num}}=== {{head|mn|Số từ}} # [[hai]]. =={{langname|cmg}}== ==={{section|num}}=== {{head|cmg|Số từ}} # [[hai]]. ===={{section|desc}}==== * {{desc|mn|хоёр|ᠬᠣᠶᠠᠷ}} pcjk8zq3rl3zbeu1v7i62hv3x4mbh1e Mô đun:mul-letter 828 400562 2513794 2363904 2026-08-13T12:06:54Z Hiyuune 50834 2513794 Scribunto text/plain local export = {} local PAGENAME = mw.loadData("Module:headword/data").pagename local function getCasing(char, variant) if not variant then variant = "common" end local casing = require("Module:mul-letter/data")[variant][char] if not casing then if mw.ustring.len(char) > 1 then casing = {mw.ustring.lower(char), mw.ustring.upper(mw.ustring.sub(char, 1, 1)) .. mw.ustring.lower(mw.ustring.sub(char, 2, -1)), mw.ustring.upper(char)} else casing = {mw.ustring.lower(char), mw.ustring.upper(char), mw.ustring.upper(char)} end end return casing end function export.show(frame) local args = frame:getParent().args local head = args["head"] or PAGENAME local lang = require("Module:languages").getByCode("mul") local scriptCode = args["sc"] or require("Module:scripts").findBestScriptWithoutLang(head):getCode() local script = scriptCode and require("Module:scripts").getByCode(scriptCode) or lang:findBestScript(head) local variant = args["var"] local data = { lang = lang, sc = script, pos_category = "Chữ cái", categories = {}, heads = {head}, no_redundant_head_cat = not args.head, inflections = {} } local casing = getCasing(head, variant) if casing[1] ~= head then table.insert(data.inflections, {label = "chữ thường", casing[1]}) end if casing[2] ~= head and casing[2] ~= casing[3] then table.insert(data.inflections, {label = "chữ hỗn hợp", casing[2]}) end if casing[3] ~= head then table.insert(data.inflections, {label = "chữ hoa", casing[3]}) end return require("Module:headword").full_headword(data) .. require("Module:utilities").format_categories("Ký tự " .. script:getCategoryName(), lang) end return export nloxburs5t47t6mc2ew513481r3trdn Thể loại:Ký tự Chữ symbolic 14 400901 2513819 2364385 2026-08-13T13:01:27Z TheHighFighter2 42988 Đã lùi lại sửa đổi [[Special:Diff/2364385|2364385]] của [[Special:Contributions/Kwamikagami|Kwamikagami]] ([[User talk:Kwamikagami|thảo luận]]) 2513819 wikitext text/x-wiki [[Thể loại:Ký hiệu]] 1jxsjd6237srnlzgdwqjzrd6if31jm3 Thể loại:Ký tự Chữ unspecified 14 400902 2513820 2364386 2026-08-13T13:02:00Z TheHighFighter2 42988 đã dời [[Thể loại:Ký tự]]; đã thêm [[Thể loại:Ký hiệu]] dùng [[w:WP:HOTCAT|HotCat]] 2513820 wikitext text/x-wiki [[Thể loại:Ký hiệu]] 1jxsjd6237srnlzgdwqjzrd6if31jm3 Thể loại:Ký hiệu tiếng Kafa 14 405539 2513798 2405812 2026-08-13T12:33:09Z TheHighFighter2 42988 TheHighFighter2 đã đổi [[Thể loại:Ký tự tiếng Kafa]] thành [[Thể loại:Ký hiệu tiếng Kafa]] (đã tắt đổi hướng) 2405812 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx 6월 0 410962 2513980 2502615 2026-08-14T05:30:02Z TheHighFighter2 42988 2513980 wikitext text/x-wiki =={{langname|ko}}== ==={{ĐM|etym}}=== {{ko-etym-Sino|六|sáu|月|tháng}}. ==={{ĐM|pron}} 1=== {{ko-IPA|유월}} ===={{ĐM|noun}}==== {{ko-noun|hangeul=유월|hanja=[[六月]]/[[6月]]}} # [[tháng Sáu|Tháng Sáu]] {{gl|Hàn Quốc}}. ==={{ĐM|pron}} 2=== {{ko-IPA|류월}} ===={{ĐM|noun}}==== {{ko-noun|hangeul=류월|hanja=[[六月]]/[[6月]]}} # [[tháng Sáu|Tháng Sáu]] {{gl|Bắc Triều Tiên}}. {{C|ko|Tháng}} 8ns1gw3pzqrxlpswxnjfcgxmtv99mc2 2513981 2513980 2026-08-14T05:33:43Z TheHighFighter2 42988 /* Tiếng Triều Tiên */ 2513981 wikitext text/x-wiki =={{langname|ko}}== ==={{ĐM|etym}}=== {{ko-etym-Sino|六|sáu|月|tháng}}. ==={{ĐM|pron}} 1=== {{ko-IPA|유월}} ===={{ĐM|noun}}==== {{ko-noun|hangeul=유월|hanja=[[六月]]/[[6月]]}} # {{lb|ko|Hàn Quốc}} [[tháng Sáu|Tháng Sáu]]. ==={{ĐM|pron}} 2=== {{ko-IPA|류월}} ===={{ĐM|noun}}==== {{ko-noun|hangeul=류월|hanja=[[六月]]/[[6月]]}} # {{lb|ko|Bắc Triều Tiên}} [[tháng Sáu|Tháng Sáu]]. {{C|ko|Tháng}} bo1zmkjadctbn6oz9viownhceicwo0r Ɛ̃ 0 411302 2513793 2512129 2026-08-13T12:03:58Z Hiyuune 50834 2513793 wikitext text/x-wiki {{minitoc}} =={{langname|mul}}== ==={{ĐM|desction}}=== {{mul-letter}} #[[chữ|Chữ]] '''[[Ɛ]] viết hoa''' với dấu ngã ([[tilde]]). ==={{ĐM|see}}=== {{Latn-script}} =={{langname|ada}}== ==={{ĐM|pron}}=== *{{IPA4|ada|[ɛ̃]}} ==={{ĐM|symbol}}=== {{head|ada|Ký tự|chữ thường|ɛ̃}} #Chữ ''{{l|ada|Ɛ}}'' viết hoa với dấu mũi hóa. #:{{quote|ada|HYƐ MAWU NINE NYA NÍ TSUMI! NƆ́ NƐ MAWU HA NƐ E KP'''Ɛ̃''' Ɔ, MƐNƆ MA NYƐ MAA PEE LƐ NƐ E DA?|HÃY XEM-XÉT CÔNG-VIỆC CỦA ĐỨC CHÚA TRỜI; VÌ VẬT GÌ NGÀI Đà ĐÁNH CONG, AI CÓ THỂ LÀM NGAY LẠI ĐƯỢC? ([[s:Truyền đạo/7#7:13|Truyền đạo 7:13]])}} ===={{ĐM|see}}==== *{{letters|lang=ada|sc=Latn|qual=Bảng chữ cái Latinh tiếng Adangme|Aa|Bb|Dd|Ee|Ɛɛ|Ff|Gg|Hh|Ii|Jj|Kk|Ll|Mm|Nn|Oo|Ɔɔ|Pp|Ss|Tt|Uu|Vv|Ww|Yy|Zz}} ==={{ĐM|ref}}=== *{{Chú thích web|ada|entryurl=https://www.bible.com/bible/2265/ECC.7.DANGME|entry=Fiɛlɔ 7|author=Bible Society of Ghana|work=BAIBLO ALOO NGMAMI KLƆUKLƆU Ɔ|year=2025}} =={{langname|bba}}== ==={{ĐM|pron}}=== *{{IPA4|bba|/ɛ̃/}} ==={{ĐM|symbol}}=== {{head|bba|Ký tự|chữ thường|ɛ̃}} #Chữ ''{{l|bsq|Ɛ}}'' viết hoa với dấu mũi hóa. #:{{ux|bba|GB'''Ɛ̃'''GB'''Ɛ̃'''NƐ|[[âm thanh|ÂM THANH]] [[vang|VANG]]|inline=1}} ==={{ĐM|see}}=== *{{letters|lang=bba|sc=Latn|qual=Bảng chữ cái Latinh tiếng Baatonum|Aa|Bb|Dd|Ee|Ɛɛ|Ff|Gg|Gb/gb|Ii|Kk|Kp/kp|Ll|Mm|Nn|Oo|Ɔɔ|Pp|Rr|Ss|Tt|Uu|Ww|Yy}} ==={{ĐM|ref}}=== *{{Chú thích sách|en|title=[https://www.webonary.org/baatonum/files/Dictionnaire-Baatonum-Fran%C3%A7ais_corrig%C3%A9-_version-du-01-09-2017.pdf Baatonum – French Dictionary]|editor=Léonard Goragui|year=2021|publisher=SIL International|page=57}} =={{langname|bsq}}== ==={{ĐM|pron}}=== *{{IPA4|bsq|/ɛ̃/}} ==={{ĐM|symbol}}=== {{head|bsq|Ký tự|chữ thường|ɛ̃}} #Chữ ''{{l|bsq|Ɛ}}'' viết hoa với dấu mũi hóa. #:{{quote|bsq|'''Ɛ̃̀'''hɛ̃́ɛ̀, kpá nìà kɛ mɔ̀ ní kpá ɓěɔ̀ ɖò, ké fɔ́ nìà kɛ mɔ̀ ní fɔ́ɔ̀ ɖò.|Người nầy là xương bởi xương tôi, thịt bởi thịt tôi mà ra. ([[s:Sáng thế Ký/2#2:23|Sáng thế Ký 2:23]])}} ==={{ĐM|see}}=== *{{letters|lang=bsq|sc=Latn|qual=Bảng chữ cái Latinh tiếng Bassa|Aa|(Áá)|(Àà)|(Ǎǎ)|(Ââ)|(Ãã)|Bb|Ɓɓ|Cc|Dd|Ɖɖ|Dy/dy|Ɛɛ|(Ɛ́/ɛ́)|(Ɛ̀/ɛ̀)|(Ɛ̌/ɛ̌)|(Ɛ̂/ɛ̂)|(Ɛ̃/ɛ̃)|Ee|(Éé)|(Èè)|(Ěě)|(Êê)|Ff|Gg|Gb/gb|Gm/gm|Hh|Hw/hw|Ii|(Íí)|(Ìì)|(Ǐǐ)|(Îî)|(Ĩĩ)|Jj|Kk|Kp/kp|Mm|Nn|Ny/ny|Ɔɔ|(Ɔ́/ɔ́)|(Ɔ̀/ɔ̀)|(Ɔ̌/ɔ̌)|(Ɔ̂/ɔ̂)|(Ɔ̃/ɔ̃)|Oo|(Óó)|(Òò)|(Ǒǒ)|(Ôô)|Pp|Ss|Tt|Uu|(Úú)|(Ùù)|(Ǔǔ)|(Ûû)|(Ũũ)|Vv|Ww|Xw/xw|Zz}} ==={{ĐM|ref}}=== *{{Chú thích web|en|work=Bassa-English Dictionary|author=CEFL|year=2025|entryurl=https://cefliberia.org/bassa/bassa-dictionary/o2/|entry=Ɔ}} *{{Chú thích web|bsq|work=Ɓǎsɔ́ɔ̀ Báɓòɔ̀|author=Bible Society in Liberia|year=2025|entryurl=https://www.bible.com/bible/2826/GEN.2.%25C6%2581B02|entry=Jɛ́nɛ́sì 2}} =={{langname|bqc}}== ==={{ĐM|pron}}=== *{{IPA4|bqc|/ɛ̃/}} ==={{ĐM|symbol}}=== {{head|bqc|Ký tự|chữ thường|ɛ̃}} #Chữ ''{{l|bqc|Ɛ}}'' viết hoa với dấu mũi hóa. #:{{quote|bqc|ASA GB'''Ɛ̃'''PII DAZIƐ.|VÌ LÀ MẸ CỦA CẢ LOÀI NGƯỜI. ([[s:Sáng thế Ký/3#3:20|Sáng thế Ký 3:20]])}} ==={{ĐM|see}}=== *{{letters|lang=bqc|sc=Latn|qual=Bảng chữ cái Latinh tiếng Boko|Aa|Bb|Dd|Ee|Ɛɛ|Ff|Gg|Gb/gb|Ii|Kk|Kp/kp|Ll|Mm|Nn|Oo|Ɔɔ|Pp|Rr|Ss|Tt|Uu|Vv|Ww|Yy|Zz}} ==={{ĐM|ref}}=== *{{Chú thích web|bqc|work=Boko Bible|author=SIM International|year=2024|entryurl=https://www.bible.com/bible/1155/GEN.3.BOO2024|entry=Daa 3}} =={{langname|bus}}== ==={{ĐM|pron}}=== *{{IPA4|bus|[ɛ̃]}} ==={{ĐM|letter}}=== {{letter|bus}} #Chữ cái thứ 13 viết hoa trong bảng chữ cái tiếng Bokobaru. #:{{quote|bus|À EBERU NƐ́GↃ̃GB'''Ɛ̃''' KƐ̀ WÀ Ń ÍNↃ KÁ NILI ÍN Ń PÍNGI, MↃDE À NƐ́NↃGB'''Ɛ̃'''NↃ TÓ.|PHÀM CON TRAI CỦA DÂN HÊ-BƠ-RƠ MỚI SANH, HÃY LIỆNG XUỐNG SÔNG; CÒN CON GÁI, THÌ ĐỂ CHO SỐNG. ([[s:Xuất Ê-díp-tô Ký/1#1:22|Xuất Ê-díp-tô Ký 1:22]])}} ==={{ĐM|see}}=== *{{letters|lang=bus|sc=Latn|qual=Bảng chữ cái Latinh tiếng Bokobaru|Aa|Aa/aa|Ãã|Ãa/ãa|Bb|Dd|Ee|Ee/ee|Ẽẽ|Ẽe/ẽe|Ɛɛ|Ɛɛ/ɛɛ|Ɛ̃/ɛ̃|Ɛ̃ɛ/ɛ̃ɛ|Ff|Gg|Gb/gb|Hh|Ii|Ii/ii|Ĩĩ|Ĩi/ĩi|Kk|Kp/kp|Ll|Mm|Nn|Oo|Oo/oo|Õõ|Õo/õo|Ɔɔ|Ɔɔ/ɔɔ|Ɔ̃/ɔ̃|Ɔ̃ɔ/ɔ̃ɔ|Pp|Rr|Ss|Tt|Uu|Uu/uu|Ũũ|Ũu/ũu|Vv|Ww|Yy|Zz}} ==={{ĐM|ref}}=== *{{Chú thích web|bus|author=SIM International|entryurl=https://www.bible.com/bible/3085/EXO.1.BBB|entry=BƆNA 1|year=2024|work=Bokobaru Bible}} =={{langname|bqp}}== ==={{ĐM|pron}}=== *{{IPA4|bqp|/ɛ̃/}} ==={{ĐM|letter}}=== {{letter|bqp}} #Chữ cái thứ 13 viết hoa trong bảng chữ cái Latinh tiếng Busa. #:{{quote|bqp|MATƐNI ABIRE OÁRƐ, DE GB'''Ɛ̃'''KE SÚN Á SÃTƐ KŨ LƐ́NNA LƐ́KPAKƆ̃ANAAORO YÃI.|TÔI NÓI NHƯ VẬY, HẦU CHO CHẲNG AI LẤY LỜI DỖ-DÀNH MÀ LỪA-DỐI ANH EM. ([[s:Cô-lô-se/2#2:4|Cô-lô-se 2:4]])}} ==={{ĐM|see}}=== *{{letters|lang=bqp|sc=Latn|qual=Bảng chữ cái Latinh tiếng Busa|Aa|Aa/aa|Ãã|Ãa/ãa|Bb|Dd|Ee|Ee/ee|Ẽẽ|Ẽe/ẽe|Ɛɛ|Ɛɛ/ɛɛ|Ɛ̃/ɛ̃|Ɛ̃ɛ/ɛ̃ɛ|Ff|Gg|Gb/gb|Hh|Ii|Ii/ii|Ĩĩ|Ĩi/ĩi|Kk|Kp/kp|Ll|Mm|Nn|Oo|Oo/oo|Õõ|Õo/õo|Ɔɔ|Ɔɔ/ɔɔ|Ɔ̃/ɔ̃|Ɔ̃ɔ/ɔ̃ɔ|Pp|Rr|Ss|Tt|Uu|Uu/uu|Ũũ|Ũu/ũu|Vv|Ww|Yy|Zz}} ==={{ĐM|ref}}=== *{{Chú thích web|bqp|author=SIM International|entryurl=https://www.bible.com/bible/3084/COL.2.BB2005|entry=KƆLƆSEDENƆ 2|year=2005|work=Bisã Bible}} =={{langname|dgi}}== ==={{ĐM|pron}}=== *{{IPA|dgi|/ɛ̃/}} ==={{ĐM|symbol}}=== {{head|dgi|Ký tự|chữ thường|ɛ̃}} #Chữ ''{{l|dgi|Ɛ}}'' viết hoa với dấu mũi hóa. #:{{ux|dgi|KOR'''Ɛ̃'''TƖ|[[s:I Cô-rinh-tô|CÔ-RINH-TÔ]]|inline=1}} ==={{ĐM|see}}=== *{{letters|lang=dgi|sc=Latn|qual=Bảng chữ cái Latinh tiếng Dagara Bắc|Aa|Bb|Ɓɓ|Cc|Dd|Ee|Ɛɛ|Ff|Gg|Gb/gb|Hh|’H/’h|Ii|Ɩɩ|Jj|Kk|Kp/kp|Ll|’L/’l|Mm|Nn|Ny/ny|Ŋŋ|Ŋm/ŋm|Oo|Ɔɔ|Pp|Rr|Ss|Tt|Uu|Ʋʋ|Vv|Ww|’W/’w|Yy|Ƴƴ|Zz|’}} ==={{ĐM|ref}}=== *{{Chú thích web|dgi|author=Bible Society of Burkina Faso|work=A SƐB-SÕW|year=2013|entry=1 Korɛ̃tɩ 1|entryurl=https://www.bible.com/bible/3053/1CO.1.DBDC}} =={{langname|dnj}}== ==={{ĐM|pron}}=== *{{IPA4|dnj|[ɛ̃]}} ==={{ĐM|symbol}}=== {{head|dnj|Ký tự|chữ thường|ɛ̃}} #Chữ cái '''{{l|dnj|Ɛ}} viết hoa''' với dấu mũi hóa. #:{{quote|dnj|GÓ YÀWƐ È GRÚSƎ̀ KƎ KAÁ NƐ̀Z'''Ɛ̃'''à KÁ GRÚSƎ̀ KƎ̀A MƐ WÀ GAƐ WO KÁ, É KÁ KƎ̀A N KÁ PƟ́Ɛ LƟ́.|CẦU ĐỨC GIÊ-HÔ-VA LẤY ƠN ĐÃI HAI CON NHƯ HAI CON Đà ĐÃI CÁC NGƯỜI THÁC CỦA CHÚNG TA, VÀ Đà ĐÃI CHÍNH MÌNH TA! ([[s:Ru-tơ/1#1:8|Ru-tơ 1:8]])}} ==={{ĐM|see}}=== *{{letters|lang=dnj|sc=Latn|qual=Bảng chữ cái Latinh tiếng Dan ở Liberia|Aa|Bb|Ɓɓ|Dd|Ɗɗ|Ee|Ɛɛ|Ff|Gg|GB/gb|Hh|Ii|Kk|KP/kp|KW/kw|Ll|Mm|Nn|NW/nw|NY/ny|Ŋŋ|Oo|Ɔɔ|Əə|Ɵɵ|Pp|Rr|Ss|Tt|Uu|Ɥɥ|Vv|Ww|Xx|Yy|Zz}} *{{letters|lang=dnj|sc=Latn|qual=Bảng chữ cái Latinh tiếng Dan Tây ở Bờ Biển Ngà|Aa|AƆ/aɔ|Bb|BH/bh|Dd|DH/dh|Ee|Ɛɛ|Ëë|ƐA/ɛa|Ff|Gg|GB/gb|GW/gw|Ii|Kk|KP/kp|KW/kw|Ll|Mm|Nn|NG/ng|Oo|Ɔɔ|Öö|Pp|Rr|Ss|Tt|Uu|Üü|Vv|Ww|Yy|Zz}} *{{letters|lang=dnj|sc=Latn|qual=Bảng chữ cái Latinh tiếng Dan Đông ở Bờ Biển Ngà|Aa|Œœ|Bb|BH/bh|Dd|DH/dh|Ee|Ʌʌ|Ɛɛ|Ææ|Ff|Gg|GB/gb|GW/gw|Hh|Ii|Ɩɩ|Kk|KP/kp|KW/kw|Ll|Mm|Nn|Oo|ɤ|Ɔɔ|Pp|Rr|Ss|Tt|Uu|Ɯɯ|Ʋʋ|Vv|Ww|Yy|Zz}} ==={{ĐM|ref}}=== *{{Chú thích web|dnj|author=Bible Society in Liberia|entryurl=https://www.bible.com/bible/2828/RUT.1.DHB|entry=Wrúfè 1|year=2025|work=Aaɓi Kwì}} *{{Chú thích web|dnj|author=Wycliffe Bible Translators|entryurl=https://www.bible.com/bible/1831/EPH.1.DNJBL|entry=Efɛzö 1|year=1991|work=Dan Blowo (Yacouba) Nouveau Testament}} =={{langname|gaa}}== ==={{ĐM|pron}}=== *{{IPA4|gaa|/ɛ̃/}} ==={{ĐM|letter}}=== {{letter|gaa}} #Chữ cái thứ 7 viết hoa trong bảng chữ cái Latinh tiếng Ga. #:{{quote|gaa|MIBI SOLOMON BAAYE MAŊTSƐ YƐ MISƐƐ, NI LƐ EBAATA MIS'''Ɛ̃'''I LƐ NƆ.|SA-LÔ-MÔN CON TRAI NGƯƠI SẼ TRỊ-VÌ KẾ TA, VÀ NÓ SẼ NGỒI TRÊN NGÔI TA. ([[s:I Các Vua/1#1:17|I Các Vua 1:17]])}} ==={{ĐM|see}}=== *{{letters|lang=gaa|sc=Latn|qual=Bảng chữ cái Latinh tiếng Ga|Aa|(Ãã)|Bb|Dd|Ee|Ẽẽ|Ɛɛ|Ɛ̃/ɛ̃|Ff|Gg|(Gb/gb)|(Gw/gw)|Hh|(Hw/hw)|Ii|(Ĩĩ)|Jj|(Jw/jw)|Kk|(Kp/kp)|(Kw/kw)|Ll|Mm|Nn|(Ny/ny)|Ŋŋ|(Ŋm/ŋm)|(Ŋw/ŋw)|Oo|(Õõ)|Ɔɔ|(Ɔ̃/ɔ̃)|Pp|Rr|Ss|(Sh/sh)|(Shw/shw)|Tt|(Ts/ts)|(Tsw/tsw)|Uu|(Ũũ)|Vv|Ww|Yy|Zz}} ==={{ĐM|ref}}=== #{{Chú thích web|gaa|author=Bible Society of Ghana|entryurl=https://www.bible.com/bible/2708/1KI.1.NEGAB|entry=1 Maŋtsɛmɛi 1|year=2024|work=ŊMALƐ KRƆŊKRƆŊ LƐ}} =={{langname|gej}}== ==={{ĐM|pron}}=== *{{IPA4|gej|[ɛ̃]}} ==={{ĐM|symbol}}=== {{head|gej|Ký tự|chữ thường|ɛ̃}} #Chữ cái '''{{l|gej|Ɛ}} viết hoa''' với dấu mũi hóa. #:{{ux|gej|ÈSEA GBÌGBƆ̀ HL'''Ɛ̃'''|[[s:Phục truyền Luật lệ Ký|PHỤC TRUYỀN LUẬT LỆ KÝ]]|inline=1}} ==={{ĐM|see}}=== *{{letters|lang=gej|sc=Latn|qual=Bảng chữ cái Latinh tiếng Gen|Aa|Bb|Cc|Dd|Ɖɖ|Ee|Ɛɛ|Ff|Gg|GB/gb|Ɣɣ|Hh|Xx|Ii|Jj|Kk|KP/kp|Ll|Mm|Nn|NY/ny|Ŋŋ|Ɔɔ|Pp|Ss|Tt|Uu|Ʋʋ|Vv|Ww|Yy|Zz}} ==={{ĐM|ref}}=== *{{Chú thích web|gej|author=Bible Society of Togo|work=GƐ̃GBE BIBLIA 2014|entry=Èsea gbìgbɔ̀ hlɛ̃ 1|entryurl=https://www.bible.com/bible/2236/DEU.1.GEN|year=2014}} =={{langname|ife}}== ==={{ĐM|pron}}=== *{{IPA4|ife|/ɛ̃/}} ==={{ĐM|letter}}=== {{letter|ife}} #Chữ cái thứ 8 viết hoa trong bảng chữ cái Latinh tiếng Ifè. #:{{ux|kbp|Ɔ̀G'''Ɛ̃'''|[[tính|TÍNH]] [[lười|LƯỜI]]|inline=1}} ==={{ĐM|see}}=== *{{letters|lang=ife|sc=Latn|qual=Bảng chữ cái Latinh tiếng Ifè|Aa|Ãã|Bb|Ɖɖ|Dz/dz|Ee|Ɛɛ|Ɛ̃/ɛ̃|Ff|Gg|Gb/gb|Hh|Ii|Ĩĩ|Kk|Kp/kp|Ll|Mm|Nn|Ny/ny|Ŋŋ|Oo|Ɔɔ|Ɔ̃/ɔ̃|Rr|Ss|Tt|Ts/ts|Uu|Ũũ|Ww|Yy}} ==={{ĐM|ref}}=== *{{Chú thích web|editor=Mary Gardner & Elizabeth Graveling|entryurl=https://www.webonary.org/ife/gc787e220-e01b-4451-ab30-46a76ff3063e/?lang=en|entry=ɔ̀gɛ̃|year=2016|work=Ife–French-English Dictionary|publisher=SIL International}} =={{langname|gkp}}== ==={{ĐM|pron}}=== *{{IPA4|gkp|[ɛ̃]}} ==={{ĐM|symbol}}=== {{head|gkp|Ký tự}} #Chữ cái '''{{l|gkp|Ɛ}} viết hoa''' với dấu mũi hóa. #:{{quote|gkp|Ŋ̀ÓLII-SÃAI KÉLEE KÁ GÙLA MEDIƐŊ KƆ-KULUƂA-ŊAI DA V'''Ɛ̃'''Ɛ-KPÂLAŊ SU-ƂELAI WÔLII KA DÍTƐƐ ḾBƆ.|TA CHỈ XIN CÁC NGƯƠI MỘT ĐỀU NẦY, LÀ MỖI NGƯỜI TRONG CÁC NGƯƠI PHẢI GIAO CHO TA NHỮNG VÒNG MÌNH Đà ĐOẠT LẤY. ([[s:Các Quan Xét/8#8:24|Các Quan Xét 8:24]])}} ==={{ĐM|see}}=== *{{letters|lang=gkp|sc=Latn|qual=Bảng chữ cái năm 1989|a|(á)|(à)|(à)|(â)|(ã)|b|ɓ|d|e|(é)|(è)|(ê)|ə|ɛ|(ɛ́)|(ɛ̀)|(ɛ̂)|(ɛ̃)|f|g|ɠ|gb|gw|h|hw|i|(í)|(ì)|(î)|(ĩ)|j|k|kp|kw|l|m|n|nw|ɲ|ŋ|o|(ó)|(ò)|(ô)|ɔ|(ɔ́)|(ɔ̀)|(ɔ̂)|(ɔ̃)|p|r|s|t|u|(ú)|(ù)|(û)|(ũ)|v|w|y}} ==={{ĐM|ref}}=== *{{Chú thích web|gkp|author=Bible Society in Liberia|work=Bible|year=2012|entry=M̀ɛni-sâa Ƃó-ƃelai 8|entryurl=https://www.bible.com/bible/500/JDG.8.KWB}} =={{langname|lob}}== ==={{ĐM|pron}}=== *{{IPA4|lob|/ɛ̃/}} ==={{ĐM|symbol}}=== {{head|lob|Ký tự|chữ thường|ɛ̃}} #Chữ ''{{l|lob|Ɛ}}'' viết hoa với dấu mũi hóa. #:{{ux|lob|KOR'''Ɛ̃'''TƖDARA|[[s:I Cô-rinh-tô|CÔ-RINH-TÔ]]|inline=1}} ==={{ĐM|see}}=== *{{letters|lang=lob|sc=Latn|qual=Bảng chữ cái Latinh tiếng Lobi|Aa|Bb|Ɓɓ|Cc|Dd|Ee|Ɛɛ|Ǝǝ|Ff|Gg|Gb/gb|Hh|Ii|Ɩɩ|Jj|Kk|Kh/kh|Kp/kp|Ll|’L/’l|Mm|Nn|Ny/ny|Oo|Ɔɔ|Pp|Ph/ph|Rr|Ss|Tt|Th/th|Uu|Ʋʋ|Vv|Ww|’W/’w|(Ⱳⱳ)|Yy|Ƴƴ}} ==={{ĐM|ref}}=== *{{Chú thích web|lob|author=British & Foreign Bible Society|entryurl=https://www.bible.com/bible/3594/1CO.1.LOBIRI|entry=1 Korɛ̃tɩdara 1|year=1985|work=Thãgbaa Ʋlfɛ Pha 1985}} =={{langname|lom}}== ==={{ĐM|pron}}=== *{{IPA4|lom|[ɛ̃]}} ==={{ĐM|symbol}}=== {{head|lom|Ký tự|chữ thường|ɛ̃}} #Chữ ''{{l|lom|Ɛ}}'' viết hoa với dấu mũi hóa. #:{{quote|lom|TƐ DƐ TÙO VELEI W'''Ɛ̃'''ƐNI.|CHẲNG TỪNG BIẾT ĐƯỜNG-LỐI TA. ([[s:Hê-bơ-rơ/3#3:10|Hê-bơ-rơ 3:10]])}} ==={{ĐM|ref}}=== *{{Chú thích web|lom|author=Bible Society in Liberia|work=DEƲE NIINƐI|year=1971|entry=HIIƂULUITIƐ 3|entryurl=https://www.bible.com/bible/1456/HEB.3.DN1971}} =={{langname|nga}}== ==={{ĐM|pron}}=== *{{IPA4|nga|[ɛ̃]}} ==={{ĐM|symbol}}=== {{head|nga|Ký tự|chữ thường|ɛ̃}} #Chữ ''{{l|nga|Ɛ}} viết hoa'' với dấu mũi hóa. #:{{quote|nga|'''Ɛ̃'''fɛ̃ lɛnggɛ kɛ ma usu 'dã hã ni, ma ɔ nɛ fɛ̃a mɔ bolo kɛ wa nãlã ma dɛ 'do gili iko ni.|Môi-miệng sốt-sắng và lòng độc-ác,<br>Khác nào bình-gốm bọc vàng bạc pha. ([[s:Châm ngôn/26#26:23|Châm ngôn 26:23]])}} ===={{ĐM|see}}==== *{{letters|lang=nga|sc=Latn|qual=Bảng chữ cái Latinh tiếng Ngbaka|Aa|Bb|ʼB/ʼb|Dd|ʼD/ʼd|Ee|Ɛɛ|Ff|Gg|Gb/gb|Hh|Ii|Kk|Kp/kp|Ll|Mm|Nn|Ng/ng|Ngg/ngg|Oo|Ɔɔ|Ss|Tt|Uu|Vv|Ww|Zz}} ==={{ĐM|adj}}=== {{head|nga|Tính từ}} #{{see-entry|ɛ̃}} ==={{ĐM|ref}}=== *{{Chú thích web|fr|editor=Marcel Henrix|work=Ngbaka – French Dictionary|year=2020|entry=ɛ|entryurl=https://www.webonary.org/ngbaka/browse/browse-vernacular-french/?key=nga&letter=%C9%9B&lang=en}} *{{Chú thích web|nga|author=Bible Society of the Democratic Republic of Congo|work=WE NU GALE|year=1995|entry=GBA WENƆ 26|entryurl=https://www.bible.com/bible/2920/PRO.26.NGB}} =={{langname|sbd}}== ==={{ĐM|pron}}=== *{{IPA4|sbd|/ɛ̃/}} ==={{ĐM|symbol}}=== {{head|sbd|Ký tự|chữ thường|ɛ̃}} #Ký tự ''{{l|sbd|Ɛ}}'' viết hoa với dấu mũi hóa. #:{{ux|sbd|PA'''Ɛ̃'''PA'''Ɛ̃'''|[[trắng tinh|TRẮNG TINH]]|inline=1}} ===={{ĐM|see}}==== *{{letters|lang=sbd|sc=Latn|qual=Bảng chữ cái Latinh tiếng Samo Nam|Aa|Bb|Dd|Ff|Gg|Hh|Ii|Kk|Ll|Mm|Nn|Pp|Rr|Ss|Tt|Ww|Yy|Zz}} ==={{ĐM|ref}}=== *{{Chú thích web|entry=paɛ̃paɛ̃|entryurl=https://www.webonary.org/san-sud/gd83828bc-b5c8-4a68-9cb7-0de3e73bda12/?lang=en|author=Urs Niggli|work=Southern San - French - English Dictionary|year=2021|publisher=SIL International}} *{{Chú thích web|sbd|entryurl=https://www.bible.com/bible/3059/GEN.1.SBPE2011|entry=SGK 1|work=LAWA A BOO SONBORE|year=2011|author=Bible Society of Burkina Faso}} =={{langname|sld}}== ==={{ĐM|pron}}=== *{{IPA4|sld|/ɛ̃/}} ==={{ĐM|letter}}=== {{letter|sld}} #Chữ cái thứ 10 viết hoa trong bảng chữ cái Latinh tiếng Sissala. #:{{quote|sld|BEE WƖ́Ɛ́ NƖƐ NÁ HÁ WƖ WƖƖSƐ '''Ɛ̃''', ƁA NƖ́Ɩ́ JA ONNI NYÁŊ WUU.|VÌ MỌI ĐỀU ĐÓ, CÁC DÂN NGOẠI VẪN THƯỜNG TÌM, ([[s:Ma-thi-ơ/6#6:32|Ma-thi-ơ 6:32]])}} ==={{ĐM|see}}=== *{{letters|lang=sld|sc=Latn|qual=Bảng chữ cái Latinh tiếng Sissala|Aa|Ãã|Bb|Ɓɓ|Cc|Dd|Ee|Ẽẽ|Ɛɛ|Ɛ̃/ɛ̃|Ff|Gg|Gb/gb|Hh|Ii|Ĩĩ|Ɩɩ|Ɩ̃/ɩ̃|Jj|Kk|Ll|Mm|Nn|Ny/ny|Ŋŋ|Ŋm/ŋm|Oo|Õõ|Ɔɔ|Ɔ̃/ɔ̃|Pp|Rr|Ss|Tt|Uu|Ʋʋ|Ʋ̃/ʋ̃|Vv|Ww|Yy|Zz}} ==={{ĐM|ref}}=== *{{Chú thích web|sld|author=The Bible League|entryurl=https://live.bible.is/bible/sldtbl/mat/6|entry=Matie 6|year=1999|work=Bible.is}} =={{langname|akp}}== ==={{ĐM|pron}}=== *{{IPA4|akp|/ɛ̃/}} ==={{ĐM|symbol}}=== {{head|akp|Ký tự|chữ thường|ɛ̃}} #Chữ ''{{l|akp|Ɛ}}'' viết hoa với dấu mũi hóa. #:{{quote|akp|LOTà WŨ ƆWI SƆ ƆFINIKI, ƔƐƐ ƆS'''Ɛ̃''' SƆ Ɔ̃IBANYUA ASƆRƆ̃ IƉE.|TA Đà CHO NÓ THÌ-GIỜ ĐỂ ĂN-NĂN, MÀ NÓ CHẲNG MUỐN ĂN-NĂN ĐỀU TÀ-DÂM NÓ! ([[s:Khải huyền/1#1:21|Khải huyền 1:21]])}} ==={{ĐM|see}}=== *{{letters|lang=akp|sc=Latn|qual=Bảng chữ cái Latinh tiếng Siwu|Aa|Bb|Dd|Dz/dz|Ɖɖ|Ee|Ɛɛ|Ff|Gg|Gb/gb|Ɣɣ|Hh|Ii|Kk|Kp/kp|Ll|Mm|Nn|Ny/ny|Oo|Ɔɔ|Pp|Rr|Ss|Tt|Ts/ts|Uu|Vv|Ww|Yy}} ==={{ĐM|ref}}=== *{{Chú thích web|akp|author=Wycliffe Bible Translators, Inc.|entryurl=https://www.bible.com/bible/1738/REV.1.AKP|entry=KAƉIKATE ƆKO 1|year=2008|work=Siwu}} =={{langname|tcd}}== ==={{ĐM|pron}}=== *{{IPA4|tcd|[ɛ̃]}} ==={{ĐM|letter}}=== {{letter|tcd}} #Chữ cái thứ 11 viết hoa trong bảng chữ cái Latinh tiếng Tafi. #:{{ux|tcd|RASH'''Ɛ̃'''L|[[w:Rachel|RACHEL]]|inline=1}} ==={{ĐM|see}}=== *{{letters|lang=tcd|sc=Latn|qual=Bảng chữ cái Latinh tiếng Tafi|Aa|Ãã|Bb|Bh/bh|Dd|Dz/dz|Dzy/dzy|Ɖɖ|Ee|Ɛɛ|Ɛ̃/ɛ̃|Ff|Fw/fw|Gg|Gb/gb|Hh|Hw/hw|Ii|Ĩĩ|Ɩɩ|Ɩ̃/ɩ̃|Kk|Kp/kp|Ll|Mm|Nn|Ny/ny|Ŋŋ|Ŋw/ŋw|Oo|Ɔɔ|Ɔ̃/ɔ̃|Pp|Rr|Ss|Sh/sh|Tt|Ts/ts|Tsy/tsy|Tsyw/tsyw|Uu|Ũũ|Ʊʊ|Ʊ̃/ʊ̃|Vv|Ww|W̃/w̃|Xx|Xw/xw|Yy|Zz|Zh/zh}} ==={{ĐM|ref}}=== *{{Chú thích web|tcd|work=ONUGBÙ NYƐNYà VUVƆ|author=Wycliffe Bible Translators, Inc.|year=2025|entry=Mateo 2|entryurl=https://www.bible.com/bible/3070/MAT.2.TCD}} =={{langname|tbz}}== ==={{ĐM|pron}}=== *{{IPA4|tbz|/ɛ̃/}} ==={{ĐM|symbol}}=== {{head|tbz|Ký tự|chữ thường|ɛ̃}} #Chữ ''{{l|tbz|Ɛ}} viết hoa'' với dấu mũi hóa. #:{{quote|tbz|KƐ̀ KUYIE NDO BO YIE NKƐ́ N Y'''Ɛ̃'''TƐ|CHỚ CHI ĐỨC CHÚA TRỜI ĐẸP LÒNG CÀ-TÁN TÔI, ([[s:Gióp/6#6:9|Gióp 6:9]])}} ==={{ĐM|see}}=== *{{letters|lang=tbz|sc=Latn|qual=Bảng chữ cái Latinh tiếng Tammari|Aa|(Àà)|(Áá)|(Ãã)|(Ã̀/ã̀)|(Ã́/ã́)|Bb|Cc|Dd|Ee|(Èè)|(Éé)|Ɛɛ|(Ɛ̀/ɛ̀)|(Ɛ́/ɛ́)|(Ɛ̃/ɛ̃)|(Ɛ̃̀/ɛ̃̀)|(Ɛ̃́/ɛ̃́)|Ff|Hh|Ii|(Ìì)|(Íí)|(Ĩĩ)|(Ĩ̀/ĩ̀)|(Ĩ́/ĩ́)|Kk|Kp/kp|Mm|(Ḿḿ)|(M̀/m̀)|Nn|(Ńń)|(Ǹǹ)|Oo|(Òò)|(Óó)|Ɔɔ|(Ɔ̀/ɔ̀)|(Ɔ́/ɔ́)|(Ɔ̃/ɔ̃)|(Ɔ̃̀/ɔ̃̀)|(Ɔ̃́/ɔ̃́)|Pp|Rr|Ss|Tt|Uu|(Ùù)|(Úú)|(Ũũ)|(Ũ̀/ũ̀)|(Ṹ/ṹ)|Ww|Yy}} ==={{ĐM|ref}}=== *{{Chú thích sách|fr|author=Annelore Zürcher|title=[https://www.sil.org/system/files/reapdata/18/46/48/18464836488843238420469350815761768632/2725_Zurcher_Phonemes_ditammari_1983.pdf Les phonèmes segmentaux du ditammari: Rapport de recherches]|year=1983|publisher=Société Internationale de Linguistique|location=Lomé}} *{{Chú thích web|tbz|author= L’Alliance Biblique du Bénin|entryurl=https://live.bible.is/bible/TBZBSB/JOB/6|entry=ISƆBU 6|year=2001|work=Bibiri - Kuyie Nnaanti Patiri}} =={{langname|bov}}== ==={{ĐM|pron}}=== *{{IPA4|bov|[ɛ̃]}} ==={{ĐM|symbol}}=== {{head|bov|Ký tự|chữ thường|ɛ̃}} #Chữ ''{{l|bov|Ɛ}} viết hoa'' với dấu mũi hóa. #:{{quote|bov|NWA'''Ɛ̃''' AMƐ|HỠI ANH EM TA|inline=1}} ==={{ĐM|see}}=== *{{letters|lang=bov|sc=Latn|qual=Bảng chữ cái Latinh tiếng Tuwuli|Aa|Bb|Dd|Ee|Ɛɛ|Ff|Gg|Hh|Ii|Kk|Ll|Mm|Nn|Oo|Ɔɔ|Pp|Rr|Ss|Tt|Uu|Vv|Ww|Yy|Zz|Dz/dz|Gb/gb|Kp/kp|Ny/ny|Ts/ts}} ==={{ĐM|ref}}=== *{{Chú thích web|bov|work=Tuwuli|author=Wycliffe Bible Translators, Inc.|year=2008|entry=ADZUMA 1|entryurl=https://www.bible.com/bible/1752/ACT.1.BOV}} =={{langname|wwa}}== ==={{ĐM|pron}}=== *{{IPA4|wwa|/ɛ̃/}} ==={{ĐM|symbol}}=== {{head|wwa|Ký tự|chữ thường|ɛ̃}} #Chữ ''{{l|wwa|Ɛ}}'' viết hoa với dấu mũi hóa. #:{{ux|wwa|'''Ɛ̃'''ɛ|[[dạ|Dạ]]|inline=1}} ===={{ĐM|see}}==== *{{letters|lang=wwa|sc=Latn|qual=Bảng chữ cái Latinh tiếng Wama|Aa|Bb|Cc|Dd|Ee|Ɛɛ|Ff|Ii|Kk|Kp/kp|Ll|Mm|Nn|Ŋŋ|Oo|Ɔɔ|Pp|Rr|Ss|Tt|Uu|Ww|Yy}} ==={{ĐM|ref}}=== *{{Chú thích web|entry=ɛ|entryurl=https://www.webonary.org/waama/browse/browse-vernacular-english/?key=wwa&letter=%C9%9B&lang=en|editor=Katharina Pope|work=Waama-French Dictionary|year=2014|publisher=SIL International|edition=Preliminary Edition}} *{{Chú thích web|wwa|entryurl=https://live.bible.is/bible/WWAWYI/MAT/1|entry=Yeesu Krisi yaadarima sa maa Matie ta wari 1|work=Bible.is|year=1994|author=Wycliffe Bible Translators, Inc.}} 4p45nqbp587uxp370zftnmrx4o8yn9j ɛ̍ 0 411904 2513792 2513495 2026-08-13T12:03:35Z Hiyuune 50834 2513792 wikitext text/x-wiki =={{langname|mul}}== ==={{ĐM|desction}}=== {{mul-letter}} #[[chữ|Chữ]] '''[[ɛ]] viết thường''' với dấu thẳng đứng bên trên. ==={{ĐM|see}}=== {{Latn-script}} =={{langname|kss}}== ==={{ĐM|letter}}=== {{letter|kss}} #Chữ cái '''{{l|kss|ɛ}} viết thường''' với dấu thanh trung. #:{{ux|kss|Chɛ̄n'''ɛ̍'''se̍ē|[[s:Sáng thế Ký|Sáng thế Ký]]|inline=1}} ==={{ĐM|see}}=== *{{letters|lang=kss|sc=Latn|qual=Bảng chữ cái Latinh tiếng Kissi Nam|a|b|c|d|e|ɛ|f|gb|h|i|j|k|kp|l|m|mb|n|nd|ŋ|ŋg|ɲ|o|ɔ|p|s|t|u|w|y}} ==={{ĐM|ref}}=== *{{Chú thích web|kss|work=Yāu̍ Mɛ̄lɛ̄ka̍a̍|author=Bible Society in Liberia|year=2015|entryurl=https://www.bible.com/bible/2241/GEN.1.YM|entry=Chɛ̄nɛ̍se̍ē 1}} =={{langname|nga}}== ==={{ĐM|letter}}=== {{letter|nga}} #Chữ '''{{l|nga|ɛ}} viết thường''' với dấu thanh trung. #:{{ux|nga|bàg'''ɛ̍'''l'''ɛ̍'''|[[bẫy]]|inline=1}} ===={{ĐM|see}}==== *{{letters|lang=nga|sc=Latn|qual=Bảng chữ cái Latinh tiếng Ngbaka|Aa|Bb|ʼB/ʼb|Dd|ʼD/ʼd|Ee|Ɛɛ|Ff|Gg|Gb/gb|Hh|Ii|Kk|Kp/kp|Ll|Mm|Nn|Ng/ng|Ngg/ngg|Oo|Ɔɔ|Ss|Tt|Uu|Vv|Ww|Zz}} ==={{ĐM|pronoun}}=== {{head|nga|Đại từ}} #[[tôi|Tôi]], [[tao]],... ==={{ĐM|ref}}=== *{{Chú thích web|fr|editor=Marcel Henrix|work=Ngbaka – French Dictionary|year=2020|entry=ɛ|entryurl=https://www.webonary.org/ngbaka/browse/browse-vernacular-french/?key=nga&letter=%C9%9B&lang=en}} *{{Chú thích web|nga|author=Bible Society of the Democratic Republic of Congo|work=WE NU GALE|year=1995|entry=GBA WENƆ 26|entryurl=https://www.bible.com/bible/2920/PRO.26.NGB}} =={{langname|yba}}== ==={{ĐM|letter}}=== {{letter|yba}} #Chữ cái '''{{l|yba|ɛ}} viết thường''' với dấu thanh cao. #:{{quote|yba|Iju̍da ma̍ Ip'''ɛ̍'''rɛ̄ɛsi ba̍la Ij'''ɛ̍'''ra; '''ɛ̍'''n'''ɛ̍''' yī wā re̍ka Ita̍ma.|Giu-đa bởi Tha-ma sanh Pha-rê và Xa-ra. ([[s:Ma-thi-ơ/1#1:3|Ma-thi-ơ 1:3]])}} ==={{ĐM|see}}=== *{{letters|lang=yba|sc=Latn|qual=Bảng chữ cái Latinh tiếng Yala|a|b|ch|d|e|ɛ|f|g|gb|h|i|j|k|kp|kw|l|m|n|ng|ngm|ny|o|ɔ|p|r|sh|t|u|w|y}} ==={{ĐM|ref}}=== *{{Chú thích web|yba|work=Ye̳ka Onīpīpē̳ Yī O̳wo̳|author=Bible Society of Nigeria|year=2020|entryurl=https://www.bible.com/bible/3079/MAT.1.YBASR|entry=Ima̍tiyu 1}} gn58axefdsv96slza6ah48lisyje0cj Mô đun:kk-convert 828 412041 2513796 2026-08-13T12:12:02Z Hiyuune 50834 Trang mới: “local export = {} local vowels = "үҮиИеЕәӘөӨӯӮАаоОЫыІіЯяЮюЁё" local rsubn = mw.ustring.gsub local tt = { ["ү"]="ü",["Ү"]="Ü",["т"]="t",["Т"]="T",["һ"]="h",["Һ"]="H",["р"]="r",["Р"]="R" ,["ф"]="f",["Ф"]="F",["ш"]="ş",["Ш"]="Ş",["н"]="n",["Н"]="N",["п"]="p",["П"]="P" ,["й"]="i",["Й"]="İ",["л"]="l",["Л"]="L",["з"]="z",["З"]="Z",["е"]="e",["Е"]="E" ,["г"]="g",["Г"]="G",["б"]="b",["Б"]="B",["у"]="u",["У"]="U",["с"…” 2513796 Scribunto text/plain local export = {} local vowels = "үҮиИеЕәӘөӨӯӮАаоОЫыІіЯяЮюЁё" local rsubn = mw.ustring.gsub local tt = { ["ү"]="ü",["Ү"]="Ü",["т"]="t",["Т"]="T",["һ"]="h",["Һ"]="H",["р"]="r",["Р"]="R" ,["ф"]="f",["Ф"]="F",["ш"]="ş",["Ш"]="Ş",["н"]="n",["Н"]="N",["п"]="p",["П"]="P" ,["й"]="i",["Й"]="İ",["л"]="l",["Л"]="L",["з"]="z",["З"]="Z",["е"]="e",["Е"]="E" ,["г"]="g",["Г"]="G",["б"]="b",["Б"]="B",["у"]="u",["У"]="U",["с"]="s",["С"]="S" ,["х"]="x",["Х"]="X",["ы"]="y",["Ы"]="Y",["м"]="m",["М"]="M",["о"]="o",["О"]="O" ,["и"]="i",["И"]="I",["ж"]="j",["Ж"]="J",["к"]="k",["К"]="K",["д"]="d",["Д"]="D" ,["в"]="v",["В"]="V",["а"]="a",["А"]="A",["ң"]="ñ",["Ң"]="Ñ",["ә"]="ä",["Ә"]="Ä" ,["э"]="e",["Э"]="E",["ұ"]="ū",["Ұ"]="Ū",["қ"]="q",["Қ"]="Q",["ғ"]="ğ",["Ғ"]="Ğ" ,["і"]="ı",["І"]="ı",["ө"]="ö",["Ө"]="Ö" --mostly in loanwords from Russian ,["ё"]="io",["Ё"]="İo",["ц"]="ts",["Ц"]="TS",["ч"]="tş",["Ч"]="Tş",["щ"]="ştş",["Щ"]="Ştş" ,["ъ"]="",["Ъ"]="",["э"]="e",["Э"]="E",["ю"]="iu",["Ю"]="İu" ,["я"]="ia",["Я"]="İa",["ӯ"]="u",["Ӯ"]="U" }; -- Main translit function export.tr(text, lang, sc) if type(text) == "table" then options = {} text, script = text.args[1], text.args[2] end if not sc then sc = require("Module:languages").getByCode("kk"):findBestScript(text):getCode() end if sc ~= "Cyrl" then return nil end text = rsubn(text, "нг([^" .. vowels .. "])", "ң%1") text = rsubn(text, "Нг([^" .. vowels .. "])", "Ң%1") text = rsubn(text, "нг$", "ң") text = rsubn(text, "([^" .. vowels .. "])я", "%1ә") text = rsubn(text, "([^" .. vowels .. "])Я", "%1Ә") text = rsubn(text, "[иИ]([яЯ])", "%1") --currently it is not clear how these two are romanized text = rsubn(text, "щ", "ш") text = rsubn(text, "Щ", "Ш") text = rsubn(text,"[ьЬ]" , "" ) --remove them if their romanization changes text = rsubn(text, '.', tt) return text end -- NOT MAIN TRANSLIT -- Yañalif 1938 version local yanalif = { ["ү"]="y",["Ү"]="Y",["ш"]="c",["Ш"]="C",["й"]="i",["Й"]="I",["б"]="ʙ" ,["ы"]="ь",["Ы"]="Ь",["и"]="i",["И"]="I",["ж"]="ç",["Ж"]="Ç" ,["ң"]="ꞑ",["Ң"]="Ꞑ",["ә"]="ə",["Ә"]="Ə",["ғ"]="ƣ",["Ғ"]="Ƣ" ,["і"]="j",["І"]="J",["ө"]="ɵ",["Ө"]="Ɵ" --mostly in loanwords from Russian ,["ё"]="io",["Ё"]="Io",["ч"]="c",["Ч"]="Tc",["щ"]="cc",["Щ"]="Cc" ,["ъ"]="ʼ",["Ъ"]="ʼ",["ю"]="iu",["Ю"]="Iu" ,["я"]="ia",["Я"]="Ia"} --Yañalif 1927 version local oldyan = { ["ү"]="y",["Ү"]="Y",["ш"]="c",["Ш"]="C",["й"]="i",["Й"]="I",["б"]="ʙ" ,["ы"]="ь",["Ы"]="Ь",["и"]="j",["И"]="J",["ж"]="ç",["Ж"]="Ç" ,["ң"]="ꞑ",["Ң"]="Ꞑ",["ә"]="ə",["Ә"]="Ə",["ғ"]="ƣ",["Ғ"]="Ƣ" ,["і"]="i",["І"]="I",["ө"]="ɵ",["Ө"]="Ɵ",["в"]="ʙ",["В"]="B" ,["ф"]="p",["Ф"]="P",["х"]="h",["Х"]="H",["у"]="v",["У"]="V" ,["ұ"]="u",["Ұ"]="U" --mostly in loanwords from Russian ,["ё"]="io",["Ё"]="Io",["ч"]="c",["Ч"]="Tc",["щ"]="cc",["Щ"]="Cc" ,["ъ"]="ʼ",["Ъ"]="ʼ",["ю"]="iu",["Ю"]="Iu" ,["я"]="ia",["Я"]="Ia"} --Pinyin local pinyin = { ["ү"]="ü",["Ү"]="Ü",["ш"]="x",["Ш"]="X",["й"]="y",["Й"]="Y" ,["ы"]="e",["Ы"]="E",["и"]="y",["И"]="Y",["у"]="w",["У"]="W" ,["ң"]="ng",["Ң"]="ng",["ә"]="ə",["Ә"]="Ə",["ғ"]="ƣ",["Ғ"]="Ƣ" ,["і"]="i",["І"]="I",["ө"]="ɵ",["Ө"]="Ɵ",["һ"]="ⱨ",["Һ"]="Ⱨ" ,["х"]="h",["Х"]="H",["қ"]="ⱪ",["Қ"]="Ⱪ",["е"]="ê",["Е"]="ê" ,["ұ"]="u",["Ұ"]="U" --mostly in loanwords from Russian ,["ё"]="io",["Ё"]="Io",["ч"]="q",["Ч"]="Q",["щ"]="x",["Щ"]="X" ,["ъ"]="ʼ",["Ъ"]="ʼ",["ю"]="iu",["Ю"]="Iu",["ь"]="",["Ь"]="" ,["я"]="ia",["Я"]="Ia"} -- Yanalif 1938 version function export.yanalif(text, lang, sc) if type(text) == "table" then options = {} text, script = text.args[1], text.args[2] end if not sc then sc = require("Module:languages").getByCode("kk"):findBestScript(text):getCode() end if sc ~= "Cyrl" then return nil end text = rsubn(text, "нг([^" .. vowels .. "])", "ң%1") text = rsubn(text, "Нг([^" .. vowels .. "])", "Ң%1") text = rsubn(text, "нг$", "ң") text = rsubn(text, "([^" .. vowels .. "])я", "%1ә") text = rsubn(text, "([^" .. vowels .. "])Я", "%1Ә") text = rsubn(text, "[иИ]([яЯ])", "%1") text = rsubn(text, '.', yanalif) text = rsubn(text, '.', tt) return text end local frontur = "әӘіІ" local backur = "АаЫы" local frontround = "үҮөӨ" local backround = "ӯӮоОҰұ" local consonant = "[^" .. vowels .. ". -]" -- Yanalif pre 1927 version function export.oldyan(text, lang, sc) if type(text) == "table" then options = {} text, script = text.args[1], text.args[2] end if not sc then sc = require("Module:languages").getByCode("kk"):findBestScript(text):getCode() end if sc ~= "Cyrl" then return nil end text = rsubn(text, "нг([^" .. vowels .. "])", "ң%1") text = rsubn(text, "Нг([^" .. vowels .. "])", "Ң%1") text = rsubn(text, "нг$", "ң") text = rsubn(text, "([^" .. vowels .. "])я", "%1ә") text = rsubn(text, "([^" .. vowels .. "])Я", "%1Ә") -- check preceeding vowel -- for w text = rsubn(text, "([" .. backur .. "])(" .. consonant .. ")у", "%1%2ыу") text = rsubn(text, "([" .. backround .. "])(" .. consonant .. ")у", "%1%2ұу") text = rsubn(text, "([" .. frontur .. "])(" .. consonant .. ")у", "%1%2іу") text = rsubn(text, "([" .. frontround .. "])(" .. consonant .. ")у", "%1%2үу") -- for j text = rsubn(text, "([" .. backur .. backround .. "])(" .. consonant .. "?)и", "%1%2ыи") text = rsubn(text, "([" .. frontur .. frontround .. "])(" .. consonant .. "?)и", "%1%2іи") -- if none, check following vowel -- for w text = rsubn(text, "([^" .. vowels .. "])У(" .. consonant .. "?)([" .. backur .. "])", "%1ЫУ%2%3") text = rsubn(text, "([^" .. vowels .. "])у(" .. consonant .. "?)([" .. backur .. "])", "%1ыу%2%3") text = rsubn(text, "([^" .. vowels .. "])У(" .. consonant .. "?)([" .. backround .. "])", "%1ҰУ%2%3") text = rsubn(text, "([^" .. vowels .. "])у(" .. consonant .. "?)([" .. backround .. "])", "%1ұу%2%3") text = rsubn(text, "([^" .. vowels .. "])У(" .. consonant .. "?)([" .. frontur .. "])", "%1ІУ%2%3") text = rsubn(text, "([^" .. vowels .. "])у(" .. consonant .. "?)([" .. frontur .. "])", "%1іу%2%3") text = rsubn(text, "([^" .. vowels .. "])У(" .. consonant .. "?)([" .. frontround .. "])", "%1ҮУ%2%3") text = rsubn(text, "([^" .. vowels .. "])у(" .. consonant .. "?)([" .. frontround .. "])", "%1үу%2%3") -- for j text = rsubn(text, "([^" .. vowels .. "])и(" .. consonant .. "?)([" .. backur .. backround .. "])", "%1ыи%2%3") text = rsubn(text, "([^" .. vowels .. "])и(" .. consonant .. "?)([" .. frontur .. frontround .. "])", "%1іи%2%3") text = rsubn(text, "([^" .. vowels .. "])И(" .. consonant .. "?)([" .. backur .. backround .. "])", "%1ЫИ%2%3") text = rsubn(text, "([^" .. vowels .. "])И(" .. consonant .. "?)([" .. frontur .. frontround .. "])", "%1ІИ%2%3") text = rsubn(text, "^И(" .. consonant .. "?)([" .. backur .. backround .. "])", "ЫИ%1%2") text = rsubn(text, "^И(" .. consonant .. "?)([" .. frontur .. frontround .. "])", "ІИ%1%2") -- final touches text = rsubn(text, "[иИ]([яЯ])", "%1") text = rsubn(text, '.', oldyan) text = rsubn(text, '.', tt) return text end -- Pinyin function export.pinyin(text, lang, sc) if type(text) == "table" then options = {} text, script = text.args[1], text.args[2] end if not sc then sc = require("Module:languages").getByCode("kk"):findBestScript(text):getCode() end if sc ~= "Cyrl" then return nil end text = rsubn(text, "([^" .. vowels .. "])я", "%1ә") text = rsubn(text, "([^" .. vowels .. "])Я", "%1Ә") text = rsubn(text, "[иИ]([яЯ])", "%1") text = rsubn(text, "([^" .. vowels .. "])[у]", "%1ұу") text = rsubn(text, "([^" .. vowels .. "])[У]", "%1ұУ") text = rsubn(text, "^[у]([^" .. vowels .. "])", "ұу%1") text = rsubn(text, "^[У]([^" .. vowels .. "])", "ұУ%1") text = rsubn(text, '.', pinyin) text = rsubn(text, '.', tt) return text end return export j8c91uxbs7hb9rpdqvnc1g97kj38gl5 شيدې 0 412042 2513822 2026-08-13T13:04:34Z Kelly zhrm 58416 Trang mới: “=={{langname|ps}}== ==={{ĐM|etym}}=== {{inh+|ps|ira-pro|*xšwiptah}}. ==={{ĐM|pron}}=== * {{IPA4|ps|/ʃide/}} ==={{ĐM|n}}=== {{ps-noun|g=f|tr=shide}} # [[sữa|Sữa]].” 2513822 wikitext text/x-wiki =={{langname|ps}}== ==={{ĐM|etym}}=== {{inh+|ps|ira-pro|*xšwiptah}}. ==={{ĐM|pron}}=== * {{IPA4|ps|/ʃide/}} ==={{ĐM|n}}=== {{ps-noun|g=f|tr=shide}} # [[sữa|Sữa]]. nnfzo1vxa44owg33jfspcmu2tuevo6y 阿爾法粒子 0 412043 2513824 2026-08-13T13:10:05Z Kelly zhrm 58416 Trang mới: “=={{langname|zh}}== {{zh-forms|s=阿尔法粒子|type=32}} ==={{ĐM|pron}}=== {{zh-pron |m=ā'ěrfǎ lìzǐ |c=aa3 ji5 faat3 lap1 zi2 |cat=n }} ==={{ĐM|n}}=== {{head|zh|Danh từ}} # [[hạt|Hạt]] [[alpha]]. ===={{ĐM|syn}}==== * {{zh-l|α粒子}} * {{zh-l|阿爾發粒子}} * {{zh-l|阿耳法粒子}} * {{zh-l|阿伐粒子}} * {{zh-l|阿爾伐粒子}} * {{zh-l|阿爾發質點}} * {{zh-l|阿爾伐質點}} * {{zh-l|甲種粒子}}” 2513824 wikitext text/x-wiki =={{langname|zh}}== {{zh-forms|s=阿尔法粒子|type=32}} ==={{ĐM|pron}}=== {{zh-pron |m=ā'ěrfǎ lìzǐ |c=aa3 ji5 faat3 lap1 zi2 |cat=n }} ==={{ĐM|n}}=== {{head|zh|Danh từ}} # [[hạt|Hạt]] [[alpha]]. ===={{ĐM|syn}}==== * {{zh-l|α粒子}} * {{zh-l|阿爾發粒子}} * {{zh-l|阿耳法粒子}} * {{zh-l|阿伐粒子}} * {{zh-l|阿爾伐粒子}} * {{zh-l|阿爾發質點}} * {{zh-l|阿爾伐質點}} * {{zh-l|甲種粒子}} m6os3xq31dzysz4h3lfy89ab4kzgfty Thể loại:Từ đánh vần với 法 tiếng Trung Quốc 14 412044 2513825 2026-08-13T13:11:30Z Kelly zhrm 58416 Trang mới: “{{auto cat}}” 2513825 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Tiếng Việt Việt Nam 14 412045 2513833 2026-08-13T13:33:06Z Hiyuune 50834 /* */ 2513833 wikitext text/x-wiki {{auto cat|lect=1|lang=vi|Việt Nam}} sot9mlry15qlord4psx02694mah7ik6 Thể loại:Tiếng Việt Trung Bộ 14 412046 2513834 2026-08-13T13:39:11Z Hiyuune 50834 /* */ 2513834 wikitext text/x-wiki {{auto cat|lect=1|lang=vi|[[Trung Bộ]], [[Việt Nam]]|wp=Trung Bộ,Nghệ Tĩnh,Thanh Hóa,Nghệ An,Hà Tĩnh,Quảng Bình,Quảng Trị,Huế,Bắc Trung Bộ|cat=Tiếng Việt Việt Nam}} 1xlkbtlosnlivgg424h4uytfbvnnz05 Thể loại:Tiếng Việt Đồng bằng Sông Cửu Long 14 412047 2513847 2026-08-13T15:04:31Z Hiyuune 50834 Trang mới: “{{auto cat|lect=1|lang=vi|Đồng bằng Sông Cửu Long|cat=Tiếng Việt Nam Việt Nam}}” 2513847 wikitext text/x-wiki {{auto cat|lect=1|lang=vi|Đồng bằng Sông Cửu Long|cat=Tiếng Việt Nam Việt Nam}} 033wcaqjcftvh1rv3bwam6zx9vxrd3m karamihan 0 412048 2513848 2026-08-13T15:28:29Z Hiyuune 50834 Trang mới: “=={{langname|tl}}== ==={{section|etym}}=== {{ety|tl|:af|dami|ka- -han|text=+|tree=1}} ==={{section|pron}}=== {{tl-pr}} ==={{section|n}}=== {{tl-noun|b=+}} # [[đa số|Đa số]], [[số đông]]. #: {{syn|tl|nakararami|mayorya}} #: {{ant|tl|minorya}} ===={{section|drv}}==== {{col|tl|sangkaramihan}} ==={{section|further}}=== * {{R:Pambansang Diksiyonaryo}}” 2513848 wikitext text/x-wiki =={{langname|tl}}== ==={{section|etym}}=== {{ety|tl|:af|dami|ka- -han|text=+|tree=1}} ==={{section|pron}}=== {{tl-pr}} ==={{section|n}}=== {{tl-noun|b=+}} # [[đa số|Đa số]], [[số đông]]. #: {{syn|tl|nakararami|mayorya}} #: {{ant|tl|minorya}} ===={{section|drv}}==== {{col|tl|sangkaramihan}} ==={{section|further}}=== * {{R:Pambansang Diksiyonaryo}} g7rby2uuaw4izp7s2b4odpb1bz1t3bw กระเบื้อง 0 412049 2513857 2026-08-13T15:38:39Z Hiyuune 50834 Trang mới: “=={{langname|th}}== ==={{section|etym}}=== Cùng gốc với {{cog|sou|เบื้อง}}, {{cog|lo|ກະເບຶ້ອງ}}, {{cog|shn|မိူင်ႈ}} hoặc {{m|shn|ဝိူင်ႈ}}. ==={{section|pron}}=== {{th-pron|กฺระ-เบื้อง}} ==={{section|n}}=== {{th-noun|แผ่น}} # [[gạch|Gạch]]; [[ngói]].” 2513857 wikitext text/x-wiki =={{langname|th}}== ==={{section|etym}}=== Cùng gốc với {{cog|sou|เบื้อง}}, {{cog|lo|ກະເບຶ້ອງ}}, {{cog|shn|မိူင်ႈ}} hoặc {{m|shn|ဝိူင်ႈ}}. ==={{section|pron}}=== {{th-pron|กฺระ-เบื้อง}} ==={{section|n}}=== {{th-noun|แผ่น}} # [[gạch|Gạch]]; [[ngói]]. du0b0kp9aarcx41mcc9ipesxfj3tp5z မိူင်ႈ 0 412050 2513858 2026-08-13T15:39:56Z Hiyuune 50834 Trang mới: “=={{langname|shn}}== ==={{section|pron}}=== {{shn-pron|မိူင်ႈ}} ==={{section|n}}=== {{shn-noun}} # [[gạch|Gạch]]; [[ngói]].” 2513858 wikitext text/x-wiki =={{langname|shn}}== ==={{section|pron}}=== {{shn-pron|မိူင်ႈ}} ==={{section|n}}=== {{shn-noun}} # [[gạch|Gạch]]; [[ngói]]. 1g7l3mwfoyhcjge1p6epme743nsdb58 Vần:Tiếng Shan/ɤŋ 0 412051 2513859 2026-08-13T15:43:15Z Hiyuune 50834 Trang mới: “{{rhymes nav|shn|ɤŋ}} =={{section|pron}}== * {{IPA|shn|/ɤŋ/}} =={{section|rhymes}}== ===1 âm tiết=== {{col|shn|ၵႃႇလႃႉႁႃႈလႅင်မိူင်း|ၵူၼ်းမိူင်း|ၵၢၼ်မိူင်း|ၶိူင်ႈ|ၶိူင်ႈပိူင်|ငိူင်း|ၸဝ်ႈၵႅမ်မိူင်း|ၸဝ်ႈၼေႃႇမိူင်း|ၸိုင်ႈမိူင်း|ၸိူင်း|ၸိူင်ႉ|…” 2513859 wikitext text/x-wiki {{rhymes nav|shn|ɤŋ}} =={{section|pron}}== * {{IPA|shn|/ɤŋ/}} =={{section|rhymes}}== ===1 âm tiết=== {{col|shn|ၵႃႇလႃႉႁႃႈလႅင်မိူင်း|ၵူၼ်းမိူင်း|ၵၢၼ်မိူင်း|ၶိူင်ႈ|ၶိူင်ႈပိူင်|ငိူင်း|ၸဝ်ႈၵႅမ်မိူင်း|ၸဝ်ႈၼေႃႇမိူင်း|ၸိုင်ႈမိူင်း|ၸိူင်း|ၸိူင်ႉ|တမ်ႇတိူင်း|ထိူင်း|ၼွၵ်ႈမိူင်း|ပၵ်းပိူင်|ပိူင်|ပိူင်ႇ|ပိူင်ႈ|ပႅၵ်ႇပိူင်ႈ|ပၢၵ်ႇပီမိူင်း|ၽွင်းမိူင်း|ၽိတ်းပိူင်ႈ|ၽိူင်း|ၾိူင်း|မိူင်|မိူင်း|မၢၵ်ႇၽိူင်း|မၢၼ်ႈမိူင်း|ယိူင်း|ယိူင်ႈ|လၵ်းမိူင်း|လိၼ်မိူင်း|လိူင်|လိူင်း|လိူင်ႇ|လူတ်ႉၶိူင်ႈ|ဝႂ်ၶၢမ်ႈမိူင်း|ဝိူင်း|ဝၢၼ်ႈမိူင်း|သိူင်ႇ|ႁိူင်း|ႁူဝ်ပၢၵ်ႇပီမိူင်း|ဢိူင်ႇ|ဢုပ်ႉပိူင်ႇ}} [[mnw:ကာရန်:သေံဇၞော်/ɤŋ]] [[shn:တူၼ်းၸၢပ်ႈလႅပ်ႈ:တႆး/ɤŋ]] [[th:สัมผัส:ภาษาไทใหญ่/ɤŋ]] 75gn0z4wm31nba554itglf7giasje2b 固體 0 412052 2513863 2026-08-13T15:50:08Z Hiyuune 50834 Trang mới: “{{also|固体}} =={{langname|zh}}== {{zh-forms|s=固体}} ==={{section|pron}}=== {{zh-pron |m=gùtǐ |ma=Zh-gùti.ogg |c=gu3 tai2 |ca=LL-Q9186 (yue)-Justinrleung-固體.wav |h=pfs=ku-thí |mn=kò͘-thé |mn-t=gu3 ti2 |cat=n }} ==={{section|n}}=== {{head|zh|Danh từ}} # Thể [[rắn]], [[đặc]]. #: {{zh-x|固體 狀態|Trạng thái thể '''rắn'''.}} ===={{section|see}}==== * {{zh-l|流體}} * {{zh-l|氣體}} * {{zh-l|液體}}” 2513863 wikitext text/x-wiki {{also|固体}} =={{langname|zh}}== {{zh-forms|s=固体}} ==={{section|pron}}=== {{zh-pron |m=gùtǐ |ma=Zh-gùti.ogg |c=gu3 tai2 |ca=LL-Q9186 (yue)-Justinrleung-固體.wav |h=pfs=ku-thí |mn=kò͘-thé |mn-t=gu3 ti2 |cat=n }} ==={{section|n}}=== {{head|zh|Danh từ}} # Thể [[rắn]], [[đặc]]. #: {{zh-x|固體 狀態|Trạng thái thể '''rắn'''.}} ===={{section|see}}==== * {{zh-l|流體}} * {{zh-l|氣體}} * {{zh-l|液體}} 2db7eo13n87whnipss81c3aq2xt5wt7 2513865 2513863 2026-08-13T15:52:10Z Hiyuune 50834 /* Tiếng Trung Quốc */ (sử dụng [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]) 2513865 wikitext text/x-wiki {{also|固体}} =={{langname|ja}}== {{ja-kanjitab|こ|たい|yomi=on}} {{ja-see|固体}} {{-}} =={{langname|zh}}== {{zh-forms|s=固体}} ==={{section|pron}}=== {{zh-pron |m=gùtǐ |ma=Zh-gùti.ogg |c=gu3 tai2 |ca=LL-Q9186 (yue)-Justinrleung-固體.wav |h=pfs=ku-thí |mn=kò͘-thé |mn-t=gu3 ti2 |cat=n }} ==={{section|n}}=== {{head|zh|Danh từ}} # Thể [[rắn]], [[đặc]]. #: {{zh-x|固體 狀態|Trạng thái thể '''rắn'''.}} ===={{section|see}}==== * {{zh-l|流體}} * {{zh-l|氣體}} * {{zh-l|液體}} nildjsjvkowjimrsetfscikespkanow 2513866 2513865 2026-08-13T15:52:44Z Hiyuune 50834 /* Tiếng Trung Quốc */ (sử dụng [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]) 2513866 wikitext text/x-wiki {{also|固体}} =={{langname|ja}}== {{ja-kanjitab|こ|たい|yomi=on}} {{ja-see|固体}} {{-}} =={{langname|ko}}== {{ko-hanjatab}} ==={{section|n}}=== {{ko-noun|hj|rv=|hangeul=고체}} # {{hanja form of|고체|thể [[rắn]]}} =={{langname|zh}}== {{zh-forms|s=固体}} ==={{section|pron}}=== {{zh-pron |m=gùtǐ |ma=Zh-gùti.ogg |c=gu3 tai2 |ca=LL-Q9186 (yue)-Justinrleung-固體.wav |h=pfs=ku-thí |mn=kò͘-thé |mn-t=gu3 ti2 |cat=n }} ==={{section|n}}=== {{head|zh|Danh từ}} # Thể [[rắn]], [[đặc]]. #: {{zh-x|固體 狀態|Trạng thái thể '''rắn'''.}} ===={{section|see}}==== * {{zh-l|流體}} * {{zh-l|氣體}} * {{zh-l|液體}} 1occmhcq2x5k5fp6p02h3q0r2yply27 2513867 2513866 2026-08-13T15:53:07Z Hiyuune 50834 /* Tiếng Nhật */ (sử dụng [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]) 2513867 wikitext text/x-wiki {{also|固体}} =={{langname|vi}}== {{vi-hantutab}} ==={{section|adv}}=== {{vi-adv|sc=Hani}} # {{vi-Nom form of|có thể}} =={{langname|ja}}== {{ja-kanjitab|こ|たい|yomi=on}} {{ja-see|固体}} {{-}} =={{langname|ko}}== {{ko-hanjatab}} ==={{section|n}}=== {{ko-noun|hj|rv=|hangeul=고체}} # {{hanja form of|고체|thể [[rắn]]}} =={{langname|zh}}== {{zh-forms|s=固体}} ==={{section|pron}}=== {{zh-pron |m=gùtǐ |ma=Zh-gùti.ogg |c=gu3 tai2 |ca=LL-Q9186 (yue)-Justinrleung-固體.wav |h=pfs=ku-thí |mn=kò͘-thé |mn-t=gu3 ti2 |cat=n }} ==={{section|n}}=== {{head|zh|Danh từ}} # Thể [[rắn]], [[đặc]]. #: {{zh-x|固體 狀態|Trạng thái thể '''rắn'''.}} ===={{section|see}}==== * {{zh-l|流體}} * {{zh-l|氣體}} * {{zh-l|液體}} n1dxoj8d19ve60fpp2em70r5ig8z7qx こたい 0 412053 2513879 2026-08-14T02:23:36Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-see|個体|固体}}” 2513879 wikitext text/x-wiki =={{langname|ja}}== {{ja-see|個体|固体}} h5f7fs3s567cucxs3inla4wjyi55jsm kotai 0 412054 2513880 2026-08-14T02:24:01Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== ==={{ĐM|rom}}=== {{ja-romaji}} # {{ja-romanization of|こたい}}” 2513880 wikitext text/x-wiki =={{langname|ja}}== ==={{ĐM|rom}}=== {{ja-romaji}} # {{ja-romanization of|こたい}} mu14nbvlqmtekfq8g6e23vyboloe4fg gùtǐ 0 412055 2513881 2026-08-14T02:25:16Z WhoAlone 40420 Trang mới: “{{also|guti}} =={{langname|cmn}}== ==={{ĐM|rom}}=== {{cmn-pinyin}} # {{cmn-pinyin of|固體}}” 2513881 wikitext text/x-wiki {{also|guti}} =={{langname|cmn}}== ==={{ĐM|rom}}=== {{cmn-pinyin}} # {{cmn-pinyin of|固體}} 4oj7wlsaows538xl6tdon91t4o55gc8 고체 0 412056 2513882 2026-08-14T02:28:27Z WhoAlone 40420 Trang mới: “=={{langname|ko}}== ==={{ĐM|etym}}=== {{ko-etym-Sino|固體}}. ==={{ĐM|pron}}=== {{ko-IPA}} ==={{ĐM|noun}}=== {{ko-noun|hanja=固體}} # Thể [[rắn]]. #: {{cot|ko|기체(氣體)|액체(液體)}}” 2513882 wikitext text/x-wiki =={{langname|ko}}== ==={{ĐM|etym}}=== {{ko-etym-Sino|固體}}. ==={{ĐM|pron}}=== {{ko-IPA}} ==={{ĐM|noun}}=== {{ko-noun|hanja=固體}} # Thể [[rắn]]. #: {{cot|ko|기체(氣體)|액체(液體)}} bpcs0f359qmib11r291gohk1gswcjbd 亜美利 0 412057 2513883 2026-08-14T02:32:19Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}}” 2513883 wikitext text/x-wiki =={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}} s8uorizb207aw0r4p9a9uh0qd9s6kfj 亜美李 0 412058 2513884 2026-08-14T02:32:22Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}}” 2513884 wikitext text/x-wiki =={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}} s8uorizb207aw0r4p9a9uh0qd9s6kfj 亜美梨 0 412059 2513885 2026-08-14T02:32:24Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}}” 2513885 wikitext text/x-wiki =={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}} s8uorizb207aw0r4p9a9uh0qd9s6kfj 亜美理 0 412060 2513886 2026-08-14T02:32:27Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}}” 2513886 wikitext text/x-wiki =={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}} s8uorizb207aw0r4p9a9uh0qd9s6kfj 亜美里 0 412061 2513887 2026-08-14T02:32:29Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}}” 2513887 wikitext text/x-wiki =={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}} s8uorizb207aw0r4p9a9uh0qd9s6kfj 亜美麗 0 412062 2513888 2026-08-14T02:32:32Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}}” 2513888 wikitext text/x-wiki =={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}} s8uorizb207aw0r4p9a9uh0qd9s6kfj 亜美莉 0 412063 2513889 2026-08-14T02:32:35Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}}” 2513889 wikitext text/x-wiki =={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}} s8uorizb207aw0r4p9a9uh0qd9s6kfj 亜未梨 0 412064 2513890 2026-08-14T02:32:37Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}}” 2513890 wikitext text/x-wiki =={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}} s8uorizb207aw0r4p9a9uh0qd9s6kfj 亜未莉 0 412065 2513891 2026-08-14T02:32:43Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}}” 2513891 wikitext text/x-wiki =={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}} s8uorizb207aw0r4p9a9uh0qd9s6kfj 亜巳利 0 412066 2513892 2026-08-14T02:32:45Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}}” 2513892 wikitext text/x-wiki =={{langname|ja}}== {{ja-kanjitab|あ|み|り}} ==={{ĐM|pr-noun}}=== {{ja-pos|proper|あみり}} # {{given name|ja|nữ|sort=あみり}} s8uorizb207aw0r4p9a9uh0qd9s6kfj 亞美利 0 412067 2513893 2026-08-14T02:33:44Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-see|亜美利}}” 2513893 wikitext text/x-wiki =={{langname|ja}}== {{ja-see|亜美利}} 4v2wnci3uds1rm09un9w143jorqgvb0 亞美李 0 412068 2513894 2026-08-14T02:34:48Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-see|亜美李}}” 2513894 wikitext text/x-wiki =={{langname|ja}}== {{ja-see|亜美李}} c8a0c1owmekbgd24hqr9tq9r4vcdalv 亞美梨 0 412069 2513895 2026-08-14T02:35:55Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-see|亜美梨}}” 2513895 wikitext text/x-wiki =={{langname|ja}}== {{ja-see|亜美梨}} q1la5bk6tob2kex31p03njjzwl0yd30 亞美理 0 412070 2513896 2026-08-14T02:36:20Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-see|亜美理}}” 2513896 wikitext text/x-wiki =={{langname|ja}}== {{ja-see|亜美理}} 8ea1pvr5s3l2j9hxxywnjvsxt5506ra 亞美里 0 412071 2513897 2026-08-14T02:36:39Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-see|亜美里}}” 2513897 wikitext text/x-wiki =={{langname|ja}}== {{ja-see|亜美里}} s8u9922hsvqhcraqb8jp5stu1on1pkb 亞美麗 0 412072 2513898 2026-08-14T02:38:19Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-see|亜美麗}}” 2513898 wikitext text/x-wiki =={{langname|ja}}== {{ja-see|亜美麗}} oj5vtubo8whz83nx8qte7ftos6skhn3 亞美莉 0 412073 2513899 2026-08-14T02:38:33Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-see|亜美莉}}” 2513899 wikitext text/x-wiki =={{langname|ja}}== {{ja-see|亜美莉}} 2jwvxd6kh6yllln2345kihlnmls6y4y 亞未梨 0 412074 2513900 2026-08-14T02:38:48Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-see|亜未梨}}” 2513900 wikitext text/x-wiki =={{langname|ja}}== {{ja-see|亜未梨}} es5i8qey5bp23ptzf432bff8yx07zs3 亞未莉 0 412075 2513901 2026-08-14T02:39:07Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-see|亜未莉}}” 2513901 wikitext text/x-wiki =={{langname|ja}}== {{ja-see|亜未莉}} hsj364g3q0bww3azcl6mn8ldmilizvs 亞巳利 0 412076 2513902 2026-08-14T02:39:23Z WhoAlone 40420 Trang mới: “=={{langname|ja}}== {{ja-see|亜巳利}}” 2513902 wikitext text/x-wiki =={{langname|ja}}== {{ja-see|亜巳利}} bqfzrcu8xvq3tvsp52ujuvrwhm20cjv ᠬᠣᠱᠣᠢ ᠬᠠᠲᠤᠨ 0 412077 2513904 2026-08-14T02:53:45Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== {{der+|cmg|mnc|ᡥᠣᡧᠣᡳ ᡶᡠᠵᡳᠨ}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{lb|cmg|historical}} {{rfdef|cmg}} ===={{ĐM|decl}}==== {{rfinfl|cmg|noun}} ===={{ĐM|desc}}==== * {{desc|mn|ᠬᠣᠱᠣᠢ ᠬᠠᠲᠤᠨ}} *: {{desc|mn|хошой хатан|sclang=1}}” 2513904 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== {{der+|cmg|mnc|ᡥᠣᡧᠣᡳ ᡶᡠᠵᡳᠨ}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{lb|cmg|historical}} {{rfdef|cmg}} ===={{ĐM|decl}}==== {{rfinfl|cmg|noun}} ===={{ĐM|desc}}==== * {{desc|mn|ᠬᠣᠱᠣᠢ ᠬᠠᠲᠤᠨ}} *: {{desc|mn|хошой хатан|sclang=1}} g2hy349qk1khxik9ybb2569wkegg0k4 Thể loại:Yêu cầu định nghĩa mục từ tiếng Mông Cổ cổ điển 14 412078 2513905 2026-08-14T02:57:03Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513905 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:Yêu cầu liên quan đến tiếng Mông Cổ cổ điển 14 412079 2513906 2026-08-14T02:57:14Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513906 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx amarrásseis 0 412080 2513907 2026-08-14T03:00:15Z Hiyuune 50834 + pt, gl 2513907 wikitext text/x-wiki =={{langname|pt}}== ==={{section|v}}=== {{head|pt|Biến thể hình thái động từ}} # {{pt-verb form of|amarrar}} =={{langname|gl}}== ==={{section|v}}=== {{head|gl|Biến thể hình thái động từ}} # {{gl-reinteg-verb form of|amarrar}} k201vmzpx562y1unolpk9hle9qxsf8a mõtūks 0 412081 2513908 2026-08-14T03:03:38Z Hiyuune 50834 Trang mới: “=={{langname|liv}}== ==={{section|etym}}=== Từ {{inh|liv|urj-fin-pro|*mët'os}}. ==={{section|pron}}=== {{liv-p}} ==={{section|n}}=== {{head|liv|Danh từ}} # {{taxfmt|Tetrao urogallus|loài}} ===={{section|decl}}==== {{liv-decl|205}} ==={{section|ref}}=== * {{R:liv:LEL|mõtūks}}” 2513908 wikitext text/x-wiki =={{langname|liv}}== ==={{section|etym}}=== Từ {{inh|liv|urj-fin-pro|*mët'os}}. ==={{section|pron}}=== {{liv-p}} ==={{section|n}}=== {{head|liv|Danh từ}} # {{taxfmt|Tetrao urogallus|loài}} ===={{section|decl}}==== {{liv-decl|205}} ==={{section|ref}}=== * {{R:liv:LEL|mõtūks}} k0iq8y1mk3nhf4mynw4m3icda6daq8a ᠮᠠᠾᠠᠠ 0 412082 2513909 2026-08-14T03:04:53Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|sa|महा}}, {{mention|sa|महत्}}, {{mention|sa|महत्}}, từ {{der|cmg|inc-pro|*maźʰā́}}, từ {{der|cmg|iir-pro|*maȷ́ʰā́}}, từ {{der|cmg|ine-pro|*méǵh₂s}}. ==={{ĐM|pron}}=== * {{IPA4|cmg|/ma.ɦɑː/}} * {{hyph|cmg|ᠮᠠ|ᠾᠠᠠ}} ==={{ĐM|prefix}}=== {{head|cmg|Tiền tố}} # [[vĩ đại]]. # [[to]], [[lớn]]. # [[đại]]-, [[siêu]]-” 2513909 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|sa|महा}}, {{mention|sa|महत्}}, {{mention|sa|महत्}}, từ {{der|cmg|inc-pro|*maźʰā́}}, từ {{der|cmg|iir-pro|*maȷ́ʰā́}}, từ {{der|cmg|ine-pro|*méǵh₂s}}. ==={{ĐM|pron}}=== * {{IPA4|cmg|/ma.ɦɑː/}} * {{hyph|cmg|ᠮᠠ|ᠾᠠᠠ}} ==={{ĐM|prefix}}=== {{head|cmg|Tiền tố}} # [[vĩ đại]]. # [[to]], [[lớn]]. # [[đại]]-, [[siêu]]- 2my9bkrb5cdw2h31m2ea56wqdjwstk2 Thể loại:Tiền tố tiếng Mông Cổ cổ điển 14 412083 2513910 2026-08-14T03:05:07Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513910 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx nominatīv 0 412084 2513911 2026-08-14T03:05:14Z Hiyuune 50834 Trang mới: “{{also|nominativ|Nominativ}} =={{langname|lv}}== ==={{section|n}}=== {{head|lv|Biến thể hình thái danh từ}} # {{inflection of|lv|nominatīvs||voc|s}} =={{langname|liv}}== ==={{section|etym}}=== Cuối cùng từ {{der|liv|la|nōminātīvus}}. {{etystub|liv}} ==={{section|pron}}=== {{liv-p}} ==={{section|n}}=== {{head|liv|Danh từ}} # {{lb|liv|grammatical case}} [[danh cách|Danh cách]]. ===={{section|decl}}==== {{liv-decl|141|n=sg}}” 2513911 wikitext text/x-wiki {{also|nominativ|Nominativ}} =={{langname|lv}}== ==={{section|n}}=== {{head|lv|Biến thể hình thái danh từ}} # {{inflection of|lv|nominatīvs||voc|s}} =={{langname|liv}}== ==={{section|etym}}=== Cuối cùng từ {{der|liv|la|nōminātīvus}}. {{etystub|liv}} ==={{section|pron}}=== {{liv-p}} ==={{section|n}}=== {{head|liv|Danh từ}} # {{lb|liv|grammatical case}} [[danh cách|Danh cách]]. ===={{section|decl}}==== {{liv-decl|141|n=sg}} 6hhl5xrtffxhng2cazg654wvu3obawg jegā 0 412085 2513912 2026-08-14T03:07:47Z Hiyuune 50834 Trang mới: “=={{langname|liv}}== ==={{section|etym}}=== Từ {{inh|liv|urj-fin-pro|*joka|*ekä}}. ==={{section|pron}}=== {{liv-p}} ==={{section|det}}=== {{head|liv|Từ hạn định}} # [[mỗi|Mỗi]], [[mọi]]. ===={{section|decl}}==== {{liv-decl|2}} ==={{section|ref}}=== * {{R:liv:LEL|jegā}}” 2513912 wikitext text/x-wiki =={{langname|liv}}== ==={{section|etym}}=== Từ {{inh|liv|urj-fin-pro|*joka|*ekä}}. ==={{section|pron}}=== {{liv-p}} ==={{section|det}}=== {{head|liv|Từ hạn định}} # [[mỗi|Mỗi]], [[mọi]]. ===={{section|decl}}==== {{liv-decl|2}} ==={{section|ref}}=== * {{R:liv:LEL|jegā}} 53k0m3s0r458nic06korwtcw05tp5ub jõka 0 412086 2513913 2026-08-14T03:11:07Z Hiyuune 50834 Trang mới: “=={{langname|vot}}== ==={{section|etym}}=== Từ {{inh|vot|urj-fin-pro|*joka}}. ==={{section|pron}}=== {{vot-p|J=+|C=+|E=+}} ==={{section|det}}=== {{head|vot|Từ hạn định}} # {{lb|vot|không biến tố}} [[mỗi|Mỗi]]. ==={{section|ref}}=== * {{R:vot:VKS|jõka}}” 2513913 wikitext text/x-wiki =={{langname|vot}}== ==={{section|etym}}=== Từ {{inh|vot|urj-fin-pro|*joka}}. ==={{section|pron}}=== {{vot-p|J=+|C=+|E=+}} ==={{section|det}}=== {{head|vot|Từ hạn định}} # {{lb|vot|không biến tố}} [[mỗi|Mỗi]]. ==={{section|ref}}=== * {{R:vot:VKS|jõka}} hyj0cdnmfamfevoz9s6gvjoizdj5j00 Thể loại:Hình vị tiếng Mông Cổ cổ điển 14 412087 2513914 2026-08-14T03:20:26Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513914 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx ᠹᠦᠷᠪᠦ 0 412088 2513915 2026-08-14T03:22:17Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|xct|ཕུར་བུ|t=Sao Mộc; Thứ Năm}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ|tr=phürbü}} # [[thứ Năm]]. ===={{ĐM|decl}}==== {{rfinfl|cmg|noun}} ===={{ĐM|desc}}==== * {{desc|mn|ᠫᠦᠷᠪᠦ}} *: {{desc|mn|пүрэв|sclang=1}} * {{desc|bua|пүрбэ|bor=1}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ|tr=phürbü}} # {{given name|cmg|male|from=tiếng Tây Tạng…” 2513915 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|xct|ཕུར་བུ|t=Sao Mộc; Thứ Năm}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ|tr=phürbü}} # [[thứ Năm]]. ===={{ĐM|decl}}==== {{rfinfl|cmg|noun}} ===={{ĐM|desc}}==== * {{desc|mn|ᠫᠦᠷᠪᠦ}} *: {{desc|mn|пүрэв|sclang=1}} * {{desc|bua|пүрбэ|bor=1}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ|tr=phürbü}} # {{given name|cmg|male|from=tiếng Tây Tạng cổ điển|meaning=thứ Năm}} ===={{ĐM|decl}}==== {{rfinfl|cmg|noun}} ===={{ĐM|desc}}==== * {{desc|mn|ᠫᠦᠷᠪᠦ}} *: {{desc|mn|Пүрэв|sclang=1}} * {{desc|bua|Пүрбэ|bor=1}} * {{desc|ru|Пурбо́|bor=1}} {{C|cmg|Ngày trong tuần}} ewiuraxkfobta10gp0t5alkc51gpti9 2513916 2513915 2026-08-14T03:23:13Z WhoAlone 40420 /* {{ĐM|pr-noun}} */ 2513916 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|xct|ཕུར་བུ|t=Sao Mộc; Thứ Năm}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ|tr=phürbü}} # [[thứ Năm]]. ===={{ĐM|decl}}==== {{rfinfl|cmg|noun}} ===={{ĐM|desc}}==== * {{desc|mn|ᠫᠦᠷᠪᠦ}} *: {{desc|mn|пүрэв|sclang=1}} * {{desc|bua|пүрбэ|bor=1}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ|tr=phürbü}} # {{given name|cmg|nam|from=tiếng Tây Tạng cổ điển}} ===={{ĐM|decl}}==== {{rfinfl|cmg|noun}} ===={{ĐM|desc}}==== * {{desc|mn|ᠫᠦᠷᠪᠦ}} *: {{desc|mn|Пүрэв|sclang=1}} * {{desc|bua|Пүрбэ|bor=1}} * {{desc|ru|Пурбо́|bor=1}} {{C|cmg|Ngày trong tuần}} 8uqcibrlsaox75ukqwxzqd6nqlzzgx1 joga 0 412089 2513917 2026-08-14T03:24:35Z Hiyuune 50834 Trang mới: “{{also|jóga}} =={{langname|en}}== ==={{section|n}}=== {{en-noun|?}} # {{alt form|en|yoga}} {{gloss|cây, {{taxlink|Elaeocarpus joga|loài}}}} ==={{section|ref}}=== * {{pedia|lang=en|Elaeocarpus joga|i=1}} ==={{section|ana}}=== * {{anagrams|en|a=agjo|ajog|gajo}} =={{langname|pl}}== {{wp|pl:}} ==={{section|etym}}=== {{lbor|pl|sa|योग}}, từ {{der|pl|inc-pro|*yáwgas}}, từ {{der|pl|iir-pro|*yáwgas}}, từ {{der|pl|ine-pro||*yówgos}}, từ {{m|ine-pro|*yewg-}…” 2513917 wikitext text/x-wiki {{also|jóga}} =={{langname|en}}== ==={{section|n}}=== {{en-noun|?}} # {{alt form|en|yoga}} {{gloss|cây, {{taxlink|Elaeocarpus joga|loài}}}} ==={{section|ref}}=== * {{pedia|lang=en|Elaeocarpus joga|i=1}} ==={{section|ana}}=== * {{anagrams|en|a=agjo|ajog|gajo}} =={{langname|pl}}== {{wp|pl:}} ==={{section|etym}}=== {{lbor|pl|sa|योग}}, từ {{der|pl|inc-pro|*yáwgas}}, từ {{der|pl|iir-pro|*yáwgas}}, từ {{der|pl|ine-pro||*yówgos}}, từ {{m|ine-pro|*yewg-}}. ==={{section|pron}}=== {{pl-pr|a=Pl-joga.ogg}} ==={{section|n}}=== {{pl-noun|f}} # [[yoga]] {{gl|Ấn Độ giáo}} ===={{section|decl}}==== {{pl-decl-noun-f|jo|g}} ===={{section|rel}}==== {{col|pl|title=danh từ|jogin|joginka|jogizm}} ==={{section|further}}=== * {{R:pl:WSJP}} * {{R:pl:PWN}} {{C|pl|Yoga}} =={{langname|se}}== ==={{section|pron}}=== * {{se-IPA}} ==={{section|n}}=== {{head|se|Biến thể hình thái danh từ}} # {{inflection of|se|johka||acc//gen|s}} =={{langname|pt}}== ==={{section|v}}=== {{head|pt|Biến thể hình thái động từ}} # {{pt-verb form of|jogar}} =={{langname|hu}}== ==={{section|etym}}=== {{affix|hu|jog|-a|pos2=hậu tố sở hữu|nocat=y}} ==={{section|pron}}=== * {{hu-IPA}} * {{hyphenation|hu|jo|ga}} ==={{section|n}}=== {{head|hu|Biến thể hình thái danh từ}} # {{inflection of|hu|jog||poss|với|spos|ở|3|s}} ===={{section|decl}}==== {{hu-infl-nom|jogá|o|n=sg|esm_sg=1}} =={{langname|krl}}== {{krl-regional|joka|joga}} ==={{section|etym}}=== Từ {{inh|krl|urj-fin-pro|*joka}}. Các từ cùng gốc bao gồm {{cog|fi|joka}} và {{cog|vep|joga}}. ==={{section|pron}}=== * {{IPA|krl|/ˈjoɡɑ/}} * {{hyph|krl|jo|ga}} ==={{section|det}}=== {{head|krl|Từ hạn định}} {{tlb|krl|invariable}} # {{lb|krl|South Karelian|dialectal}} [[mỗi|Mỗi]] ===={{section|syn}}==== * {{q|Bắc Karelia}} {{l|krl|joka}}, {{q|Nam Karelia}} {{l|krl|jogo}} ==={{section|ref}}=== * {{R:krl:KKS|joka}} =={{langname|sh}}== ==={{section|pron}}=== * {{IPA|sh|/jǒːɡa/}} * {{hyphenation|sh|jo|ga}} ==={{section|n}}=== {{sh-noun|jóga|f}} # {{lb|sh|uncountable}} [[yoga]] {{gl|Ấn Độ giáo}} ===={{section|decl}}==== {{sh-decl-noun-unc |joga |joge |jozi |jogu |jogo |jozi |jogom }} =={{langname|sk}}== ==={{section|etym}}=== {{der+|sk|sa|योग}}, từ {{der|sk|ine-pro|*yewg-}}. ==={{section|pron}}=== * {{IPA|sk|/ˈjɔɡa/}} ==={{section|n}}=== {{sk-noun|f|gen=jogy}} # {{lb|sk|uncountable}} [[yoga]] {{gl|Ấn Độ giáo}}. ===={{section|decl}}==== {{sk-decl-noun-sg|joga|jogy|joge|jogu|joge|jogou}} ==={{section|further}}=== * {{R:sk:SDK}} =={{langname|sl}}== ==={{section|pron}}=== * {{sl-IPA|jọ̑ga}} ==={{section|n}}=== {{sl-noun|jọ̑ga|f}} # [[yoga]] {{gl|Ấn Độ giáo}}. ===={{section|decl}}==== {{sl-decl-noun-f|jóg|n=sg}} =={{langname|tl}}== ==={{section|alt}}=== * {{alt|tl|diyoga|dyoga}} ==={{section|n}}=== {{tl-pr|jogà}} ==={{section|n}}=== {{tl-noun|jogà|b=+}} {{tlb|tl|gay slang}} # [[ngực|Ngực]] [[to]]. #: {{syn|tl|suso|dede}} ==={{section|further}}=== * {{R:Tagalog Slang Dictionary|42}} =={{langname|vep}}== ==={{section|etym}}=== Từ {{der|vep|urj-fin-pro|*joka}}. Từ cùng gốc bao gồm {{cog|fi|joka}}. ==={{section|adj}}=== {{head|vep|Tính từ}} # [[mỗi|Mỗi]]. l1wr2ly1d4hwhs4ao8uvtnthblj0zax 2513919 2513917 2026-08-14T03:25:00Z Hiyuune 50834 2513919 wikitext text/x-wiki {{also|jóga}} {{minitoc}} =={{langname|en}}== ==={{section|n}}=== {{en-noun|?}} # {{alt form|en|yoga}} {{gloss|cây, {{taxlink|Elaeocarpus joga|loài}}}} ==={{section|ref}}=== * {{pedia|lang=en|Elaeocarpus joga|i=1}} ==={{section|ana}}=== * {{anagrams|en|a=agjo|ajog|gajo}} =={{langname|pl}}== {{wp|pl:}} ==={{section|etym}}=== {{lbor|pl|sa|योग}}, từ {{der|pl|inc-pro|*yáwgas}}, từ {{der|pl|iir-pro|*yáwgas}}, từ {{der|pl|ine-pro||*yówgos}}, từ {{m|ine-pro|*yewg-}}. ==={{section|pron}}=== {{pl-pr|a=Pl-joga.ogg}} ==={{section|n}}=== {{pl-noun|f}} # [[yoga]] {{gl|Ấn Độ giáo}} ===={{section|decl}}==== {{pl-decl-noun-f|jo|g}} ===={{section|rel}}==== {{col|pl|title=danh từ|jogin|joginka|jogizm}} ==={{section|further}}=== * {{R:pl:WSJP}} * {{R:pl:PWN}} {{C|pl|Yoga}} =={{langname|se}}== ==={{section|pron}}=== * {{se-IPA}} ==={{section|n}}=== {{head|se|Biến thể hình thái danh từ}} # {{inflection of|se|johka||acc//gen|s}} =={{langname|pt}}== ==={{section|v}}=== {{head|pt|Biến thể hình thái động từ}} # {{pt-verb form of|jogar}} =={{langname|hu}}== ==={{section|etym}}=== {{affix|hu|jog|-a|pos2=hậu tố sở hữu|nocat=y}} ==={{section|pron}}=== * {{hu-IPA}} * {{hyphenation|hu|jo|ga}} ==={{section|n}}=== {{head|hu|Biến thể hình thái danh từ}} # {{inflection of|hu|jog||poss|với|spos|ở|3|s}} ===={{section|decl}}==== {{hu-infl-nom|jogá|o|n=sg|esm_sg=1}} =={{langname|krl}}== {{krl-regional|joka|joga}} ==={{section|etym}}=== Từ {{inh|krl|urj-fin-pro|*joka}}. Các từ cùng gốc bao gồm {{cog|fi|joka}} và {{cog|vep|joga}}. ==={{section|pron}}=== * {{IPA|krl|/ˈjoɡɑ/}} * {{hyph|krl|jo|ga}} ==={{section|det}}=== {{head|krl|Từ hạn định}} {{tlb|krl|invariable}} # {{lb|krl|South Karelian|dialectal}} [[mỗi|Mỗi]] ===={{section|syn}}==== * {{q|Bắc Karelia}} {{l|krl|joka}}, {{q|Nam Karelia}} {{l|krl|jogo}} ==={{section|ref}}=== * {{R:krl:KKS|joka}} =={{langname|sh}}== ==={{section|pron}}=== * {{IPA|sh|/jǒːɡa/}} * {{hyphenation|sh|jo|ga}} ==={{section|n}}=== {{sh-noun|jóga|f}} # {{lb|sh|uncountable}} [[yoga]] {{gl|Ấn Độ giáo}} ===={{section|decl}}==== {{sh-decl-noun-unc |joga |joge |jozi |jogu |jogo |jozi |jogom }} =={{langname|sk}}== ==={{section|etym}}=== {{der+|sk|sa|योग}}, từ {{der|sk|ine-pro|*yewg-}}. ==={{section|pron}}=== * {{IPA|sk|/ˈjɔɡa/}} ==={{section|n}}=== {{sk-noun|f|gen=jogy}} # {{lb|sk|uncountable}} [[yoga]] {{gl|Ấn Độ giáo}}. ===={{section|decl}}==== {{sk-decl-noun-sg|joga|jogy|joge|jogu|joge|jogou}} ==={{section|further}}=== * {{R:sk:SDK}} =={{langname|sl}}== ==={{section|pron}}=== * {{sl-IPA|jọ̑ga}} ==={{section|n}}=== {{sl-noun|jọ̑ga|f}} # [[yoga]] {{gl|Ấn Độ giáo}}. ===={{section|decl}}==== {{sl-decl-noun-f|jóg|n=sg}} =={{langname|tl}}== ==={{section|alt}}=== * {{alt|tl|diyoga|dyoga}} ==={{section|n}}=== {{tl-pr|jogà}} ==={{section|n}}=== {{tl-noun|jogà|b=+}} {{tlb|tl|gay slang}} # [[ngực|Ngực]] [[to]]. #: {{syn|tl|suso|dede}} ==={{section|further}}=== * {{R:Tagalog Slang Dictionary|42}} =={{langname|vep}}== ==={{section|etym}}=== Từ {{der|vep|urj-fin-pro|*joka}}. Từ cùng gốc bao gồm {{cog|fi|joka}}. ==={{section|adj}}=== {{head|vep|Tính từ}} # [[mỗi|Mỗi]]. d0qwzk8ynjsaat131ct02gpru86fiey ᠵᠢᠨᠣᠩ 0 412090 2513918 2026-08-14T03:24:50Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== Từ {{der|cmg|cmn-ear|晉王|t=Tấn vương}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{lb|cmg|historical}} [[jinong]] {{gloss|tước hiệu quý tộc Mông Cổ}} ===={{ĐM|decl}}==== {{rfinfl|cmg|noun}} ===={{ĐM|coord}}==== {{col|cmg|ᠬᠠᠭᠠᠨ|ᠬᠠᠨ|ᠨᠣᠶᠠᠨ|ᠲᠠᠢᠵᠢ|ᠲᠠᠪᠤᠨᠠᠩ|ᠵᠠᠢᠰᠠᠩ}} ===={{ĐM|desc}}==== * {{desc|mn|ᠵᠢᠨᠣᠩ}}…” 2513918 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== Từ {{der|cmg|cmn-ear|晉王|t=Tấn vương}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{lb|cmg|historical}} [[jinong]] {{gloss|tước hiệu quý tộc Mông Cổ}} ===={{ĐM|decl}}==== {{rfinfl|cmg|noun}} ===={{ĐM|coord}}==== {{col|cmg|ᠬᠠᠭᠠᠨ|ᠬᠠᠨ|ᠨᠣᠶᠠᠨ|ᠲᠠᠢᠵᠢ|ᠲᠠᠪᠤᠨᠠᠩ|ᠵᠠᠢᠰᠠᠩ}} ===={{ĐM|desc}}==== * {{desc|mn|ᠵᠢᠨᠣᠩ}} *: {{desc|mn|жонон|sclang=1}} * {{desc|mnc|ᠵᡳᠨᠣᠩ|bor=1}} ** {{desc|cmn|濟農|tr=jìnóng|bor=1}} a2a8ccubywz66wn7e8khkqbqbth0c3x ᠵᠠᠮᠴᠤ 0 412091 2513920 2026-08-14T03:40:52Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|xct|རྒྱ་མཚོ|t=biển, đại dương}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{given name|cmg|nam|from=tiếng Tây Tạng cổ điển}}. ===={{ĐM|decl}}==== {{rfinfl|cmg|proper noun}} ===={{ĐM|desc}}==== * {{desc|mn|ᠵᠠᠮᠴᠤ}} *: {{desc|mn|Жамц|sclang=1}}” 2513920 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|xct|རྒྱ་མཚོ|t=biển, đại dương}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{given name|cmg|nam|from=tiếng Tây Tạng cổ điển}}. ===={{ĐM|decl}}==== {{rfinfl|cmg|proper noun}} ===={{ĐM|desc}}==== * {{desc|mn|ᠵᠠᠮᠴᠤ}} *: {{desc|mn|Жамц|sclang=1}} 2d75q4yx1utq17o8nczte27csgl0tck ᠬᠣᠩᠵᠢᠨ 0 412092 2513921 2026-08-14T03:47:15Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== Từ {{der|cmg|cmn-ear|鴻臣}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{lb|cmg|historical}} {{rfdef|cmg}} ===={{ĐM|decl}}==== {{rfinfl|cmg|noun}} ===={{ĐM|desc}}==== * {{desc|mn|ᠬᠣᠩᠵᠢᠨ}} *: {{desc|mn|хонжин|sclang=1}}” 2513921 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== Từ {{der|cmg|cmn-ear|鴻臣}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{lb|cmg|historical}} {{rfdef|cmg}} ===={{ĐM|decl}}==== {{rfinfl|cmg|noun}} ===={{ĐM|desc}}==== * {{desc|mn|ᠬᠣᠩᠵᠢᠨ}} *: {{desc|mn|хонжин|sclang=1}} 0cixqxy1vy0hddjohfdhdilnq4f2s4o ᠯᠣᠪᠰᠠᠩ 0 412093 2513922 2026-08-14T03:51:51Z WhoAlone 40420 Trang mới: “=={{langname|cmn}}== ==={{ĐM|etym}}=== {{bor+|cmg|xct|བློ་བཟང}}. ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{given name|cmg|nam|from=tiếng Tây Tạng cổ điển}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ riêng}} ===={{ĐM|alter form}}==== * {{alt|cmg|ᠯᠣᠪᠽᠠᠩ}} ===={{ĐM|desc}}==== * {{desc|mn|ᠯᠣᠪᠰᠠᠩ}} *: {{desc|mn|Лувсан|sclang=1}} * {{desc|tyv|Лопсаң|bor=1}}” 2513922 wikitext text/x-wiki =={{langname|cmn}}== ==={{ĐM|etym}}=== {{bor+|cmg|xct|བློ་བཟང}}. ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{given name|cmg|nam|from=tiếng Tây Tạng cổ điển}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ riêng}} ===={{ĐM|alter form}}==== * {{alt|cmg|ᠯᠣᠪᠽᠠᠩ}} ===={{ĐM|desc}}==== * {{desc|mn|ᠯᠣᠪᠰᠠᠩ}} *: {{desc|mn|Лувсан|sclang=1}} * {{desc|tyv|Лопсаң|bor=1}} k2cbmf034e3izfcydw6l52trf04vn3e 2513923 2513922 2026-08-14T03:53:06Z WhoAlone 40420 2513923 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|xct|བློ་བཟང}}. ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{given name|cmg|nam|from=tiếng Tây Tạng cổ điển}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ riêng}} ===={{ĐM|alter form}}==== * {{alt|cmg|ᠯᠣᠪᠽᠠᠩ}} ===={{ĐM|desc}}==== * {{desc|mn|ᠯᠣᠪᠰᠠᠩ}} *: {{desc|mn|Лувсан|sclang=1}} * {{desc|tyv|Лопсаң|bor=1}} 4tdzwslgx1bps4r5boiirp19p7k24ef Thể loại:Danh từ riêng tiếng Mông Cổ cổ điển 14 412094 2513924 2026-08-14T03:53:18Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513924 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx ᠰᠣᠨᠣᠮᠹᠢᠯ 0 412095 2513925 2026-08-14T03:55:46Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|xct|[[བསོད་ནམས]]་[[འཕེལ]]}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{given name|cmg|nam|from=tiếng Tây Tạng cổ điển}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ riêng}}” 2513925 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|xct|[[བསོད་ནམས]]་[[འཕེལ]]}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{given name|cmg|nam|from=tiếng Tây Tạng cổ điển}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ riêng}} o1ppmvpgbenfdv9gapt721tr2kzqfyo ᠪᠡᠢᠰᠡ 0 412096 2513926 2026-08-14T04:02:54Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== Gọi tắt từ {{mention|cmg|ᠬᠣᠰᠢᠭᠤᠨ ᠤ ᠪᠡᠢᠰᠡ}}. {{bor+|cmg|mnc|ᠪᡝᡳᠰᡝ}}, dạng số nhiều của {{mention|mnc|ᠪᡝᡳᠯᡝ}}, từ {{der|cmg|juc|}}. Thay thế tước hiệu Mông Cổ bản địa {{mention|cmg| ᠬᠣᠰᠢᠭᠤᠨ ᠤ ᠲᠠᠢᠵᠢ}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{tlb|cmg|historical}} [[thân vương]] một k…” 2513926 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== Gọi tắt từ {{mention|cmg|ᠬᠣᠰᠢᠭᠤᠨ ᠤ ᠪᠡᠢᠰᠡ}}. {{bor+|cmg|mnc|ᠪᡝᡳᠰᡝ}}, dạng số nhiều của {{mention|mnc|ᠪᡝᡳᠯᡝ}}, từ {{der|cmg|juc|}}. Thay thế tước hiệu Mông Cổ bản địa {{mention|cmg| ᠬᠣᠰᠢᠭᠤᠨ ᠤ ᠲᠠᠢᠵᠢ}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{tlb|cmg|historical}} [[thân vương]] một [[kỳ]] {{gl|tước hiệu quý tộc thời nhà Thanh}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ}} ===={{ĐM|desc}}==== * {{desc|mn|ᠪᠡᠢᠰᠡ}} *: {{desc|mn|бэйс|sclang=1}} * {{desc|tyv|бээзи|bor=1}} d6080okuz0tq84934q11m6p75qr1b5r ᠪᠡᠰᠦᠳ 0 412097 2513927 2026-08-14T04:06:49Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== {{rfe|cmg}} ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # Besut {{gloss|một trong bảy ''otog''/''khoshuu'' của ''Ar Khalkh''}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ}}” 2513927 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== {{rfe|cmg}} ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # Besut {{gloss|một trong bảy ''otog''/''khoshuu'' của ''Ar Khalkh''}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ}} e34kjz9ehqsidxkvltc88kc9t7dq3ie ᠠ᠌ 0 412098 2513928 2026-08-14T04:09:04Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|suffix}}=== {{head|cmg|Hậu tố|tr=-a}} # {{ng|Dạng sở hữu cách-vị trí cách sau ''n'' và ''r''.}} #: {{sufex|mn|ᠣᠨ|ᠠ᠌|[[ᠣᠨ]] '''ᠠ᠌'''}} #: {{sufex|mn|ᠭᠠᠵᠠᠷ|ᠠ᠌|[[ᠭᠠᠵᠠᠷ]] '''ᠠ᠌'''}} ===={{ĐM|desc}}==== * {{desc|mn|ᠠ᠌}} *: {{desc|mn|-аа|-оо|sclang=1}}” 2513928 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|suffix}}=== {{head|cmg|Hậu tố|tr=-a}} # {{ng|Dạng sở hữu cách-vị trí cách sau ''n'' và ''r''.}} #: {{sufex|mn|ᠣᠨ|ᠠ᠌|[[ᠣᠨ]] '''ᠠ᠌'''}} #: {{sufex|mn|ᠭᠠᠵᠠᠷ|ᠠ᠌|[[ᠭᠠᠵᠠᠷ]] '''ᠠ᠌'''}} ===={{ĐM|desc}}==== * {{desc|mn|ᠠ᠌}} *: {{desc|mn|-аа|-оо|sclang=1}} 355qghgutxsw9uk8ihf82165x96uzlh Thể loại:Hậu tố tiếng Mông Cổ cổ điển 14 412099 2513929 2026-08-14T04:09:18Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513929 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx ᠠᠪᠤᠭ ᠠ 0 412100 2513930 2026-08-14T04:14:17Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ|head=ᠠᠪᠤᠭ᠎ᠠ}} # {{taxfmt|Capsella bursa-pastoris|species}} ===={{ĐM|desc}}==== * {{desc|mn|авга|ᠠᠪᠤᠭ᠎ᠠ}}” 2513930 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ|head=ᠠᠪᠤᠭ᠎ᠠ}} # {{taxfmt|Capsella bursa-pastoris|species}} ===={{ĐM|desc}}==== * {{desc|mn|авга|ᠠᠪᠤᠭ᠎ᠠ}} ijcr3fmgkgsnzpy76hne9q14a52hr4o ᠠᠭᠤ 0 412101 2513931 2026-08-14T04:17:00Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|alter form}}=== * {{alt|cmg|ᠠᠭ᠍ᠤ||không dấu}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{place|cmg|sông|krai/Zabaykalsky Krai|c/Nga|t=[[Aga]]}} # {{place|cmg|khu định cư kiểu đô thị|krai/Zabaykalsky Krai|c/Nga|t=Aginskoye}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ riêng}} ===={{ĐM|desc}}==== * {{desc|mn|-}} *: {{desc|mn|ᠠᠭᠤ|sclang=1}} *: {{desc|mn|Ага|sclang=1}} * {{desc|ykh|Агу<tr:Agu>…” 2513931 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|alter form}}=== * {{alt|cmg|ᠠᠭ᠍ᠤ||không dấu}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{place|cmg|sông|krai/Zabaykalsky Krai|c/Nga|t=[[Aga]]}} # {{place|cmg|khu định cư kiểu đô thị|krai/Zabaykalsky Krai|c/Nga|t=Aginskoye}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ riêng}} ===={{ĐM|desc}}==== * {{desc|mn|-}} *: {{desc|mn|ᠠᠭᠤ|sclang=1}} *: {{desc|mn|Ага|sclang=1}} * {{desc|ykh|Агу<tr:Agu>|Ага<tr:Aga>}} * {{desc|bua|Ага|bor=1}} * {{desc|mnc|ᠠᡤᡡ|bor=1}} *: {{desc|zh|阿古|tr=Āgǔ|bor=1}} * {{desc|ru|Ага́|g=m-in|bor=1}} *: {{desc|zh|阿加|tr=Ājiā|bor=1}} f7wwd3osgghoviywpjmpnphzuzazbh2 ᠠᠭ᠍ᠤ 0 412102 2513932 2026-08-14T04:19:13Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{alt form of|cmg|ᠠᠭᠤ}}” 2513932 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{alt form of|cmg|ᠠᠭᠤ}} f1kkpk15qgi6hv6k20210ia20s7pqg5 ᠡᠳᠦ᠋ᠷ 0 412103 2513933 2026-08-14T04:20:23Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alternative spelling of|cmg|ᠡᠳᠦᠷ|t=ngày}} ===={{ĐM|desc}}==== {{rfinfl|cmg|danh từ}}” 2513933 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alternative spelling of|cmg|ᠡᠳᠦᠷ|t=ngày}} ===={{ĐM|desc}}==== {{rfinfl|cmg|danh từ}} o9kb95p3io9hoiaft88jvxxidkx67w1 ᠡᠳᠦᠷ 0 412104 2513934 2026-08-14T04:25:19Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # [[ngày]].” 2513934 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # [[ngày]]. pir5cixgdwjn15fgt19b1e7u2m0furt Thành viên:Hiyuune/Gadget-popups.js 2 412105 2513935 2026-08-14T04:32:05Z Hiyuune 50834 Trang mới: “// Page Previews for Wiktionary, by User:Ioaxxere // <nowiki> // Nhập từ [[:en:MediaWiki:Gadget-PagePreviews.js]] // Generated using: [[Thành viên:Hiyuune/Gadget-PagePreviews.js/ConstGeneration]] const INTERWIKI_PREFIXES = new Map([["aa","aa"],["ab","ab"],["abstract","abstract"],["acc",0],["ace","ace"],["acronym",0],["advisory",0],["ady","ady"],["af","af"],["ak","ak"],["als","als"],["alt","alt"],["am","am"],["ami","ami"],["an","an"],["ang","ang"],["ann","ann"],["anp…” 2513935 javascript text/javascript // Page Previews for Wiktionary, by User:Ioaxxere // <nowiki> // Nhập từ [[:en:MediaWiki:Gadget-PagePreviews.js]] // Generated using: [[Thành viên:Hiyuune/Gadget-PagePreviews.js/ConstGeneration]] const INTERWIKI_PREFIXES = new Map([["aa","aa"],["ab","ab"],["abstract","abstract"],["acc",0],["ace","ace"],["acronym",0],["advisory",0],["ady","ady"],["af","af"],["ak","ak"],["als","als"],["alt","alt"],["am","am"],["ami","ami"],["an","an"],["ang","ang"],["ann","ann"],["anp","anp"],["antwiki",0],["appropedia",0],["aquariumwiki",0],["ar","ar"],["arbcom-zh",0],["arborwiki",0],["arc","arc"],["arxiv",0],["ary","ary"],["arz","arz"],["as","as"],["ast","ast"],["atj","atj"],["av","av"],["avk","avk"],["awa","awa"],["ay","ay"],["az","az"],["azb","azb"],["b",0],["ba","ba"],["ban","ban"],["bar","bar"],["bat-smg","bat-smg"],["battlestarwiki",0],["bbc","bbc"],["bcl","bcl"],["bcnbio",0],["bdr","bdr"],["be","be"],["be-tarask","be-tarask"],["be-x-old","be-tarask"],["beacha",0],["betawiki",0],["betawikiversity",0],["bew","bew"],["bg","bg"],["bh","bh"],["bi","bi"],["bibcode",0],["bibliowiki",0],["bjn","bjn"],["blk","blk"],["bm","bm"],["bn","bn"],["bo","bo"],["botwiki",0],["boxrec",0],["bpy","bpy"],["br","br"],["bs","bs"],["btm","btm"],["bug","bug"],["bugzilla",0],["bulba",0],["bxr","bxr"],["c",0],["c2",0],["ca","ca"],["cache",0],["cbk-zam","cbk-zam"],["ccorg",0],["cdo","cdo"],["ce","ce"],["ceb","ceb"],["centralwikia",0],["ch","ch"],["chapter",0],["cho","cho"],["choralwiki",0],["chr","chr"],["chy","chy"],["citizendium",0],["ckb","ckb"],["cmn","zh"],["co","co"],["commons",0],["communitywiki",0],["comune",0],["cr","cr"],["creativecommons",0],["creativecommonswiki",0],["crh","crh"],["cs","cs"],["csb","csb"],["cu","cu"],["cv","cv"],["cy","cy"],["cz","cs"],["d",0],["da","da"],["dag","dag"],["dbdump",0],["dcdatabase",0],["dcw",0],["de","de"],["debian",0],["devmo",0],["dga","dga"],["dico",0],["dicoado",0],["dict",0],["dictionary",0],["diffblog",0],["din","din"],["diq","diq"],["discord",0],["disinfopedia",0],["dk","da"],["dmoz",0],["dmozs",0],["doi",0],["donate",0],["doom_wiki",0],["download",0],["dpd",0],["dpla",0],["drae",0],["dsb","dsb"],["dtp","dtp"],["dty","dty"],["dv","dv"],["dz","dz"],["ee","ee"],["egl","eml"],["el","el"],["elibre",0],["emacswiki",0],["eml","eml"],["en","en"],["en-simple","simple"],["encyc",0],["englyphwiki",0],["enkol",0],["eo","eo"],["epo","eo"],["es","es"],["esolang",0],["et","et"],["etherpad",0],["etherpadbackup",0],["ethnologue",0],["ethnologuefamily",0],["eu","eu"],["exkcd",0],["exotica",0],["ext","ext"],["f",0],["fa","fa"],["fandom",0],["fanimutationwiki",0],["fat","fat"],["fedora",0],["ff","ff"],["fi","fi"],["finalfantasy",0],["finnix",0],["fiu-vro","fiu-vro"],["fj","fj"],["flickrphoto",0],["flickruser",0],["fo","fo"],["foldoc",0],["fon","fon"],["foundation",0],["foundationsite",0],["fr","fr"],["freebsdman",0],["freedomdefined",0],["freenode",0],["freesoft",0],["frp","frp"],["frr","frr"],["fur","fur"],["fy","fy"],["ga","ga"],["gag","gag"],["gan","gan"],["gcr","gcr"],["gd","gd"],["gentoo",0],["genwiki",0],["gerrit",0],["git",0],["github",0],["gitiles",0],["gitlab",0],["gl","gl"],["glk","glk"],["globalcontribs",0],["glottolog",0],["glottopedia",0],["gn","gn"],["gom","gom"],["google",0],["googledefine",0],["googlegroups",0],["gor","gor"],["got","got"],["gpe","gpe"],["gs",0],["gsw","als"],["gu","gu"],["guc","guc"],["gucprefix",0],["guildwarswiki",0],["gur","gur"],["gutenberg",0],["gutenbergwiki",0],["guw","guw"],["gv","gv"],["ha","ha"],["hackerspaces",0],["hak","hak"],["hammondwiki",0],["haw","haw"],["hdl",0],["he","he"],["heraldik",0],["hi","hi"],["hif","hif"],["ho","ho"],["horizonlabs",0],["hr","hr"],["hrfwiki",0],["hrwiki",0],["hsb","hsb"],["ht","ht"],["hu","hu"],["hy","hy"],["hyw","hyw"],["hz","hz"],["ia","ia"],["iarchive",0],["iba","iba"],["id","id"],["ie","ie"],["ig","ig"],["igl","igl"],["ii","ii"],["ik","ik"],["ilo","ilo"],["imdbcompany",0],["imdbname",0],["imdbtitle",0],["incubator",0],["infosphere",0],["inh","inh"],["io","io"],["irc",0],["ircrc",0],["ircs",0],["is","is"],["isni",0],["iso639-3",0],["issn",0],["it","it"],["iu","iu"],["ja","ja"],["jaglyphwiki",0],["jam","jam"],["jbo","jbo"],["jira",0],["jp","ja"],["jstor",0],["jv","jv"],["ka","ka"],["kaa","kaa"],["kab","kab"],["kai","kai"],["kaj","kaj"],["kamelo",0],["karlsruhe",0],["kbd","kbd"],["kbp","kbp"],["kcg","kcg"],["kg","kg"],["kge","kge"],["ki","ki"],["kj","kj"],["kk","kk"],["kl","kl"],["km","km"],["kn","kn"],["knc","knc"],["ko","ko"],["koi","koi"],["kr","kr"],["krc","krc"],["ks","ks"],["ksh","ksh"],["ku","ku"],["kus","kus"],["kv","kv"],["kw","kw"],["ky","ky"],["la","la"],["labsconsole",0],["lad","lad"],["lb","lb"],["lbe","lbe"],["lexemes",0],["lez","lez"],["lfn","lfn"],["lg","lg"],["li","li"],["liberachat",0],["libreplanet",0],["lij","lij"],["lingualibre",0],["linguistlist",0],["listarchive",0],["lld","lld"],["lmo","lmo"],["ln","ln"],["lo","lo"],["localwiki",0],["lofc",0],["lojban",0],["lokalhistoriewiki",0],["lostpedia",0],["lrc","lrc"],["lt","lt"],["ltg","ltg"],["luxo",0],["lv","lv"],["lzh","zh-classical"],["m",0],["mad","mad"],["mag","mag"],["mai","mai"],["mail",0],["mailarchive",0],["map-bms","map-bms"],["mariowiki",0],["marveldatabase",0],["mdf","mdf"],["mdwiki",0],["meatball",0],["mediawikiwiki",0],["mediazilla",0],["memoryalpha",0],["meta",0],["metawiki",0],["metawikimedia",0],["metawikipedia",0],["mg","mg"],["mh","mh"],["mhr","mhr"],["mi","mi"],["min","min"],["mineralienatlas",0],["miraheze",0],["mixnmatch",0],["mk","mk"],["ml","ml"],["mn","mn"],["mni","mni"],["mnw","mnw"],["mo","mo"],["moinmoin",0],["mos","mos"],["mosapedia",0],["mozillawiki",0],["mozillazinekb",0],["mr","mr"],["mrj","mrj"],["ms","ms"],["mt","mt"],["mus","mus"],["mw",0],["mwl","mwl"],["mwod",0],["mwot",0],["my","my"],["myv","myv"],["mzn","mzn"],["n",0],["na","na"],["nah","nah"],["nan","zh-min-nan"],["nap","nap"],["nara",0],["nb","no"],["nds","nds"],["nds-nl","nds-nl"],["ne","ne"],["new","new"],["ng","ng"],["nia","nia"],["nl","nl"],["nlab",0],["nn","nn"],["no","no"],["nost","nostalgia"],["nostalgia","nostalgia"],["nov","nov"],["nqo","nqo"],["nr","nr"],["nrm","nrm"],["nso","nso"],["nup","nup"],["nv","nv"],["ny","ny"],["oc","oc"],["oclc",0],["oeis",0],["oewiki",0],["oldwikisource",0],["olo","olo"],["olpc",0],["om","om"],["openlibrary",0],["openstreetmap",0],["openwetware",0],["or","or"],["organicdesign",0],["orthodoxwiki",0],["os","os"],["osmwiki",0],["otrs",0],["otrswiki",0],["outreach",0],["outreachwiki",0],["owasp",0],["pa","pa"],["pag","pag"],["pam","pam"],["pap","pap"],["paws",0],["pcd","pcd"],["pcm","pcm"],["pdc","pdc"],["petscan",0],["pfl","pfl"],["phab",0],["phabricator",0],["pi","pi"],["pih","pih"],["pl","pl"],["planetmath",0],["pmid",0],["pms","pms"],["pnb","pnb"],["pnt","pnt"],["pokewiki",0],["pokéwiki",0],["policy",0],["ppl","ppl"],["proofwiki",0],["ps","ps"],["pt","pt"],["pwn","pwn"],["pyrev",0],["pythoninfo",0],["q",0],["qu","qu"],["quality",0],["quarry",0],["rcirc",0],["regiowiki",0],["rev",0],["revo",0],["rfc",0],["rheinneckar",0],["rki","rki"],["rm","rm"],["rmy","rmy"],["rn","rn"],["ro","ro"],["roa-rup","roa-rup"],["roa-tara","roa-tara"],["rodovid",0],["rsk","rsk"],["rt",0],["ru","ru"],["rue","rue"],["rup","roa-rup"],["rw","rw"],["s",0],["sa","sa"],["sah","sah"],["sat","sat"],["sc","sc"],["scholar",0],["schoolswp",0],["schoolwiki",0],["scn","scn"],["sco","sco"],["scores",0],["scoutwiki",0],["sd","sd"],["se","se"],["securewikidc",0],["semantic-mw",0],["senseislibrary",0],["sep11",0],["sg","sg"],["sgs","bat-smg"],["sh","sh"],["sharemap",0],["shi","shi"],["shn","shn"],["shoutwiki",0],["shy","shy"],["si","si"],["silcode",0],["simple","simple"],["sk","sk"],["skr","skr"],["sl","sl"],["slashdot",0],["sm","sm"],["smn","smn"],["sn","sn"],["so","so"],["sourceforge",0],["spamcheck",0],["spcom",0],["species",0],["sq","sq"],["sr","sr"],["srn","srn"],["ss","ss"],["st","st"],["stats",0],["stewardry",0],["stq","stq"],["strategy",0],["strategywiki",0],["su","su"],["sulutil",0],["sv","sv"],["svn",0],["sw","sw"],["swtrain",0],["syl","syl"],["szl","szl"],["szy","szy"],["ta","ta"],["tardis",0],["tay","tay"],["tclerswiki",0],["tcy","tcy"],["tdd","tdd"],["te","te"],["tenwiki","ten"],["test2wiki","test2"],["testcommons",0],["testwiki","test"],["testwikidata",0],["tet","tet"],["tfwiki",0],["tg","tg"],["th","th"],["thelemapedia",0],["theopedia",0],["ti","ti"],["ticket",0],["tig","tig"],["tk","tk"],["tl","tl"],["tly","tly"],["tmbw",0],["tn","tn"],["to","to"],["tok","tok"],["tolkiengateway",0],["toolforge",0],["toolhub",0],["toollabs",0],["tools",0],["tpi","tpi"],["tr","tr"],["translatewiki",0],["trv","trv"],["ts","ts"],["tswiki",0],["tt","tt"],["tum","tum"],["tviv",0],["tw","tw"],["twiki",0],["twl",0],["ty","ty"],["tyv","tyv"],["tyvawiki",0],["udm","udm"],["ug","ug"],["uk","uk"],["umap",0],["uncyclopedia",0],["unihan",0],["ur","ur"],["urbandict",0],["usability",0],["usemod",0],["utrs",0],["uz","uz"],["v",0],["ve","ve"],["vec","vec"],["vep","vep"],["vi","vi"],["viaf",0],["vikidia",0],["vlos",0],["vls","vls"],["vo","vo"],["votewiki",0],["voy",0],["vro","fiu-vro"],["vrts",0],["vrtwiki",0],["w","en"],["wa","wa"],["war","war"],["wcna",0],["weirdgloop",0],["werelate",0],["wg","wg-en"],["wikia",0],["wikiapiary",0],["wikiasite",0],["wikibooks",0],["wikicities",0],["wikicity",0],["wikiconference",0],["wikidata",0],["wikiedudashboard",0],["wikifunctions",0],["wikifur",0],["wikihow",0],["wikiindex",0],["wikilivres",0],["wikilivresru",0],["wikimania",0],["wikimedia",0],["wikinews",0],["wikinfo",0],["wikinvest",0],["wikipapers",0],["wikipedia",0],["wikipediawikipedia",0],["wikiquote",0],["wikiskripta",0],["wikisophia",0],["wikisource",0],["wikisp",0],["wikispecies",0],["wikispore",0],["wikispot",0],["wikitech",0],["wikiti",0],["wikitrek",0],["wikiversity",0],["wikivoyage",0],["wikiwikiweb",0],["wikt",0],["wiktionary",0],["wm2005",0],["wm2006",0],["wm2007",0],["wm2008",0],["wm2009",0],["wm2010",0],["wm2011",0],["wm2012",0],["wm2013",0],["wm2014",0],["wm2015",0],["wm2016",0],["wm2017",0],["wm2018",0],["wmam",0],["wmania",0],["wmar",0],["wmat",0],["wmau",0],["wmbd",0],["wmbe",0],["wmbr",0],["wmca",0],["wmch",0],["wmcl",0],["wmcn",0],["wmco",0],["wmcz",0],["wmcz_docs",0],["wmcz_old",0],["wmdc",0],["wmde",0],["wmdeblog",0],["wmdk",0],["wmdoc",0],["wmec",0],["wmee",0],["wmes",0],["wmet",0],["wmf",0],["wmfblog",0],["wmfdashboard",0],["wmfi",0],["wmfr",0],["wmge",0],["wmhi",0],["wmhk",0],["wmhu",0],["wmid",0],["wmil",0],["wmin",0],["wmit",0],["wmke",0],["wmmk",0],["wmmx",0],["wmnl",0],["wmno",0],["wmnoc",0],["wmnoch",0],["wmnyc",0],["wmpa-us",0],["wmph",0],["wmpl",0],["wmplsite",0],["wmpt",0],["wmpunjabi",0],["wmromd",0],["wmrs",0],["wmru",0],["wmse",0],["wmsk",0],["wmteam",0],["wmtr",0],["wmtw",0],["wmua",0],["wmuk",0],["wmve",0],["wmza",0],["wo","wo"],["wookieepedia",0],["wowwiki",0],["wplibrary",0],["wurmpedia",0],["wuu","wuu"],["xal","xal"],["xh","xh"],["xkcd",0],["xmf","xmf"],["xtools",0],["yi","yi"],["yo","yo"],["yue","zh-yue"],["za","za"],["zea","zea"],["zgh","zgh"],["zh","zh"],["zh-classical","zh-classical"],["zh-cn","zh"],["zh-min-nan","zh-min-nan"],["zh-tw","zh"],["zh-yue","zh-yue"],["zu","zu"],["zum",0]]); const WP_LANGUAGES = new Map([["ab","Abkhazian"],["ace","Acehnese"],["ady","Adyghe"],["af","Afrikaans"],["als","Alemannic"],["alt","Southern Altai"],["am","Amharic"],["ami","Amis"],["an","Aragonese"],["ang","Old English"],["ann","Obolo"],["anp","Angika"],["ar","Arabic"],["arc","Aramaic"],["ary","Moroccan Arabic"],["arz","Egyptian Arabic"],["as","Assamese"],["ast","Asturian"],["atj","Atikamekw"],["av","Avaric"],["avk","Kotava"],["awa","Awadhi"],["ay","Aymara"],["az","Azerbaijani"],["azb","South Azerbaijani"],["ba","Bashkir"],["ban","Balinese"],["bar","Bavarian"],["bat-smg","Samogitian"],["bbc","Batak Toba"],["bcl","Central Bikol"],["bdr","West Coast Bajau"],["be","Belarusian"],["be-tarask","Belarusian (Taraškievica orthography)"],["bew","Betawi"],["bg","Bulgarian"],["bh","Bhojpuri"],["bi","Bislama"],["bjn","Banjar"],["blk","Pa'O"],["bm","Bambara"],["bn","Bangla"],["bo","Tibetan"],["bpy","Bishnupriya"],["br","Breton"],["bs","Bosnian"],["btm","Batak Mandailing"],["bug","Buginese"],["bxr","Russia Buriat"],["ca","Catalan"],["cbk-zam","Chavacano"],["cdo","Mindong"],["ce","Chechen"],["ceb","Cebuano"],["ch","Chamorro"],["chr","Cherokee"],["chy","Cheyenne"],["ckb","Central Kurdish"],["co","Corsican"],["crh","Crimean Tatar"],["cs","Czech"],["csb","Kashubian"],["cu","Church Slavic"],["cv","Chuvash"],["cy","Welsh"],["da","Danish"],["dag","Dagbani"],["de","German"],["dga","Southern Dagaare"],["din","Dinka"],["diq","Dimli"],["dsb","Lower Sorbian"],["dtp","Central Dusun"],["dty","Doteli"],["dv","Divehi"],["dz","Dzongkha"],["ee","Ewe"],["el","Greek"],["eml","Emiliano-Romagnolo"],["en","English"],["eo","Esperanto"],["es","Spanish"],["et","Estonian"],["eu","Basque"],["ext","Extremaduran"],["fa","Persian"],["fat","Fanti"],["ff","Fula"],["fi","Finnish"],["fiu-vro","Võro"],["fj","Fijian"],["fo","Faroese"],["fon","Fon"],["fr","French"],["frp","Arpitan"],["frr","Northern Frisian"],["fur","Friulian"],["fy","Western Frisian"],["ga","Irish"],["gag","Gagauz"],["gan","Gan"],["gcr","Guianan Creole"],["gd","Scottish Gaelic"],["gl","Galician"],["glk","Gilaki"],["gn","Guarani"],["gom","Goan Konkani"],["gor","Gorontalo"],["got","Gothic"],["gpe","Ghanaian Pidgin"],["gu","Gujarati"],["guc","Wayuu"],["gur","Frafra"],["guw","Gun"],["gv","Manx"],["ha","Hausa"],["hak","Hakka Chinese"],["haw","Hawaiian"],["he","Hebrew"],["hi","Hindi"],["hif","Fiji Hindi"],["hr","Croatian"],["hsb","Upper Sorbian"],["ht","Haitian Creole"],["hu","Hungarian"],["hy","Armenian"],["hyw","Western Armenian"],["ia","Interlingua"],["iba","Iban"],["id","Indonesian"],["ie","Interlingue"],["ig","Igbo"],["igl","Igala"],["ik","Inupiaq"],["ilo","Iloko"],["inh","Ingush"],["io","Ido"],["is","Icelandic"],["it","Italian"],["iu","Inuktitut"],["ja","Japanese"],["jam","Jamaican Creole English"],["jbo","Lojban"],["jv","Javanese"],["ka","Georgian"],["kaa","Kara-Kalpak"],["kab","Kabyle"],["kai","Karekare"],["kaj","Jju"],["kbd","Kabardian"],["kbp","Kabiye"],["kcg","Tyap"],["kg","Kongo"],["kge","Komering"],["ki","Kikuyu"],["kk","Kazakh"],["km","Khmer"],["kn","Kannada"],["knc","Central Kanuri"],["ko","Korean"],["koi","Komi-Permyak"],["krc","Karachay-Balkar"],["ks","Kashmiri"],["ksh","Colognian"],["ku","Kurdish"],["kus","Kusaal"],["kv","Komi"],["kw","Cornish"],["ky","Kyrgyz"],["la","Latin"],["lad","Ladino"],["lb","Luxembourgish"],["lbe","Lak"],["lez","Lezghian"],["lfn","Lingua Franca Nova"],["lg","Ganda"],["li","Limburgish"],["lij","Ligurian"],["lld","Ladin"],["lmo","Lombard"],["ln","Lingala"],["lo","Lao"],["lt","Lithuanian"],["ltg","Latgalian"],["lv","Latvian"],["mad","Madurese"],["mag","Magahi"],["mai","Maithili"],["map-bms","Banyumasan"],["mdf","Moksha"],["mg","Malagasy"],["mhr","Eastern Mari"],["mi","Māori"],["min","Minangkabau"],["mk","Macedonian"],["ml","Malayalam"],["mn","Mongolian"],["mni","Manipuri"],["mnw","Mon"],["mos","Mossi"],["mr","Marathi"],["mrj","Western Mari"],["ms","Malay"],["mt","Maltese"],["mwl","Mirandese"],["my","Burmese"],["myv","Erzya"],["mzn","Mazanderani"],["nah","Nahuatl"],["nap","Neapolitan"],["nds","Low German"],["nds-nl","Low Saxon"],["ne","Nepali"],["new","Newari"],["nia","Nias"],["nl","Dutch"],["nn","Norwegian Nynorsk"],["no","Norwegian"],["nov","Novial"],["nqo","N’Ko"],["nr","South Ndebele"],["nrm","Norman"],["nso","Northern Sotho"],["nup","Nupe"],["nv","Navajo"],["ny","Nyanja"],["oc","Occitan"],["olo","Livvi-Karelian"],["om","Oromo"],["or","Odia"],["os","Ossetic"],["pa","Punjabi"],["pag","Pangasinan"],["pam","Pampanga"],["pap","Papiamento"],["pcd","Picard"],["pcm","Nigerian Pidgin"],["pdc","Pennsylvania German"],["pfl","Palatine German"],["pi","Pali"],["pl","Polish"],["pms","Piedmontese"],["pnb","Western Punjabi"],["pnt","Pontic"],["ppl","Nawat"],["ps","Pashto"],["pt","Portuguese"],["pwn","Paiwan"],["qu","Quechua"],["rki","Arakanese"],["rm","Romansh"],["rmy","Vlax Romani"],["rn","Rundi"],["ro","Romanian"],["roa-rup","Aromanian"],["roa-tara","Tarantino"],["rsk","Pannonian Rusyn"],["ru","Russian"],["rue","Rusyn"],["rw","Kinyarwanda"],["sa","Sanskrit"],["sah","Yakut"],["sat","Santali"],["sc","Sardinian"],["scn","Sicilian"],["sco","Scots"],["sd","Sindhi"],["se","Northern Sami"],["sg","Sango"],["sh","Serbo-Croatian"],["shi","Tachelhit"],["shn","Shan"],["si","Sinhala"],["simple","Simple English"],["sk","Slovak"],["skr","Saraiki"],["sl","Slovenian"],["sm","Samoan"],["smn","Inari Sami"],["sn","Shona"],["so","Somali"],["sq","Albanian"],["sr","Serbian"],["srn","Sranan Tongo"],["ss","Swati"],["st","Southern Sotho"],["stq","Saterland Frisian"],["su","Sundanese"],["sv","Swedish"],["sw","Swahili"],["syl","Sylheti"],["szl","Silesian"],["szy","Sakizaya"],["ta","Tamil"],["tay","Atayal"],["tcy","Tulu"],["tdd","Tai Nuea"],["te","Telugu"],["tet","Tetum"],["tg","Tajik"],["th","Thai"],["ti","Tigrinya"],["tig","Tigre"],["tk","Turkmen"],["tl","Tagalog"],["tly","Talysh"],["tn","Tswana"],["to","Tongan"],["tok","Toki Pona"],["tpi","Tok Pisin"],["tr","Turkish"],["trv","Taroko"],["ts","Tsonga"],["tt","Tatar"],["tum","Tumbuka"],["tw","Twi"],["ty","Tahitian"],["tyv","Tuvinian"],["udm","Udmurt"],["ug","Uyghur"],["uk","Ukrainian"],["ur","Urdu"],["uz","Uzbek"],["ve","Venda"],["vec","Venetian"],["vep","Veps"],["vi","Vietnamese"],["vls","West Flemish"],["vo","Volapük"],["wa","Walloon"],["war","Waray"],["wo","Wolof"],["wuu","Wu"],["xal","Kalmyk"],["xh","Xhosa"],["xmf","Mingrelian"],["yi","Yiddish"],["yo","Yoruba"],["za","Zhuang"],["zea","Zeelandic"],["zgh","Standard Moroccan Tamazight"],["zh","Chinese"],["zh-classical","Literary Chinese"],["zh-min-nan","Minnan"],["zh-yue","Cantonese"],["zu","Zulu"]]); const appendixWithSlash = ["Adûni", "Afrihili", "Arabic_roots", "Belter_Creole", "Bibliography", "Black_Speech", "Bolak", "Communicationssprache", "Dothraki", "Eloi", "Gestures", "Glosa", "Goa'uld", "High_Valyrian", "Idiom_Neutral", "Interlingue", "Interslavic", "Klingon", "Kotava", "Láadan", "Lapine", "Lingua_Franca_Nova", "Lojban", "Mandalorian", "Medefaidrin", "Minecraft", "Mundolinco", "Na'vi", "Neo", "Novial", "Noxilo", "Protologisms/Long_words", "Quenya", "Romanova", "Sindarin", "Snowclones", "Talossan", "Toki_Pona", "Unas"]; const appendixNoSlash = ["Boldface", "Capital_letter", "Italics", "Lowercase_letter", "Possessive", "Repetition", "Small_caps", "Strikethrough", "Subscript", "Superscript", "Underline", "Uppercase_letter"]; const animationSpeed = 0.2; // seconds mw.util.addCSS(` .wikt-page-preview ol { margin: 0 0.5em 0 1.5em; padding: 0; } .wikt-page-preview dl { margin-bottom: 0; } .wikt-page-preview p { margin: 0; } /* popupContainer has the opacity animation, while popup gets translated. */ .popup-fade-in-up, .popup-fade-in-down { animation: popup-fade-in ${animationSpeed}s ease forwards; } .popup-fade-out-up, .popup-fade-out-down { animation: popup-fade-out ${animationSpeed}s ease forwards; } .popup-fade-in-up > div { animation: popup-move-in-up ${animationSpeed}s ease forwards; } .popup-fade-in-down > div { animation: popup-move-in-down ${animationSpeed}s ease forwards; } .popup-fade-out-up > div { animation: popup-move-out-up ${animationSpeed}s ease forwards; } .popup-fade-out-down > div { animation: popup-move-out-down ${animationSpeed}s ease forwards; } @keyframes popup-move-in-up { 0% { transform: translate(0, 20px); } } @keyframes popup-move-in-down { 0% { transform: translate(0, -20px); } } @keyframes popup-move-out-up { 100% { transform: translate(0, -20px); } } @keyframes popup-move-out-down { 100% { transform: translate(0, 20px); } } @keyframes popup-fade-out { 100% { opacity: 0; } } @keyframes popup-fade-in { 0% { opacity: 0; } } .ring-loader { margin: auto; width: 24px; height: 24px; border-radius: 50%; border-top: 5px solid var(--wikt-palette-black); border-bottom: 5px solid var(--wikt-palette-black); border-left: 5px solid transparent; border-right: 5px solid transparent; animation: spin 1.2s linear infinite; } @keyframes spin { 100% { transform: rotate(360deg); } } .preview-headerlink, .preview-headerlink:visited { color: inherit; font-weight: bold; } /* Hack: get the speaker icon on Wikipedia articles without having to load Phonos. */ .ext-phonos .oo-ui-buttonElement-button:after { content: "🔊"; } .ext-phonos * { display: inline !important; padding: 0 !important; margin: 0 !important; } `); const definitionIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true" fill="currentColor"><g><path d="M15 2a7.65 7.65 0 00-5 2 7.65 7.65 0 00-5-2H1v15h4a7.65 7.65 0 015 2 7.65 7.65 0 015-2h4V2zm2.5 13.5H14a4.38 4.38 0 00-3 1V5s1-1.5 4-1.5h2.5z"></path></g></svg>`; const glossaryIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true" fill="currentColor"><g><path d="M12.43 14.34A5 5 0 0110 15a5 5 0 113.95-2L17 16.09V3a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 001.45-.63z"></path><circle cx="10" cy="10" r="3"></circle></g></svg>`; const articleIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true" fill="currentColor"><g><path d="M5 1a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 002-2V3a2 2 0 00-2-2zm0 3h5v1H5zm0 2h5v1H5zm0 2h5v1H5zm10 7H5v-1h10zm0-2H5v-1h10zm0-2H5v-1h10zm0-2h-4V4h4z"></path></g></svg>`; const bibliographyIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true" fill="currentColor"><g><path d="M3 2h12c.5 0 1 .5 1 1v1c0 .5-.5 1-1 1H3c0 0 .5-.5.5-1V3c0-.5-.5-1-.5-1zM4 6h12c.5 0 1 .5 1 1v1c0 .5-.5 1-1 1H4c0 0 .5-.5.5-1V7c0-.5-.5-1-.5-1zM3 10h12c.5 0 1 .5 1 1v1c0 .5-.5 1-1 1H3c0 0 .5-.5.5-1v-1c0-.5-.5-1-.5-1zM4 14h12c.5 0 1 .5 1 1v1c0 .5-.5 1-1 1H4c0 0 .5-.5.5-1v-1c0-.5-.5-1-.5-1z"/></g></svg>`; const loader = document.createElement("div"); loader.className = "ring-loader"; const popupContainer = document.createElement("div"); const popup = document.createElement("div"); const popupContent = document.createElement("div"); popupContainer.style = `display: none; position: absolute; filter: drop-shadow(0px 30px 30px rgba(0, 0, 0, 0.15)) drop-shadow(0px 0px 0.75px var(--wikt-palette-dullblue)); z-index: 801`; popupContainer.className = "wikt-page-preview"; popup.style = "box-sizing: border-box; height: 100%; background: var(--wikt-palette-white)"; popupContent.style = "display: flex; flex-direction: column; border-radius: 2px; box-sizing: border-box; color: var(--wikt-palette-black); overflow: auto; height: 100%; overflow-wrap: break-word; scrollbar-width: thin"; document.body.append(popupContainer); popupContainer.append(popup); popup.append(popupContent); let popupTimer; let openLink; let mouseX, mouseY; let API_controller = new AbortController(); function closePopup() { if (!openLink) return; openLink = null; API_controller.abort(); API_controller = new AbortController(); if (popupContainer.classList.contains("popup-fade-in-up")) { popupContainer.classList.remove("popup-fade-in-up"); popupContainer.classList.add("popup-fade-out-down"); } else { popupContainer.classList.remove("popup-fade-in-down"); popupContainer.classList.add("popup-fade-out-up"); } // After the animation has completed, reset the popup to its initial state. setTimeout(() => { popupContainer.style.display = "none"; popupContainer.classList.remove("popup-fade-out-down", "popup-fade-out-up"); }, animationSpeed * 1000); } popup.addEventListener("pointerenter", () => { clearTimeout(popupTimer); }); popup.addEventListener("pointerleave", () => { clearTimeout(popupTimer); popupTimer = setTimeout(closePopup, 300); }); function processLink(link) { let linkTitle = decodeURIComponent(link.pathname.split("/wiki/")[1]); let titlePrefix = linkTitle.includes(":") ? linkTitle.toLowerCase().split(":")[0] : ""; let resolvedTitle = linkTitle; let API_domain = link.origin; let linkAnchor = decodeURIComponent(link.hash.slice(1) || ""); let isWPlink = link.href.match(/^https:\/\/[^.]+.wikipedia.org\/wiki\//); let isPreviewLink = Boolean(link.closest(".wikt-page-preview")); let isGlossary = /^Appendix:.*[Gg]lossary$/.test(linkTitle); // Start with various checks to determine whether a link should be processed. if (link.matches(`nav a, .cancelLink a, .mw-widget-titleOptionWidget a, .new, .preview-headerlink, .external, [href$="#"]`)) { return; } else if (isGlossary) { // Links to glossary *must* have an anchor. if (!linkAnchor) return; } else if (linkTitle.startsWith("Appendix:Bibliography/")) { // Bibliography subpages must have an anchor starting with `bib-`. if (!linkAnchor || !linkAnchor.startsWith("bib-")) return; } else if (!isWPlink) { // Other Wiktionary pages must not have a lowercase-starting anchor (filters out some unhelpful links). if (/^[a-z]/.test(linkAnchor)) return; } if (link.href.startsWith("https://en.wiktionary.org/wiki/")) { // Filter out all interwiki prefixes. if (INTERWIKI_PREFIXES.has(titlePrefix)) return; let {namespace, title} = new mw.Title(linkTitle); if (namespace === 0) { // Mainspace if (title.endsWith("/translations")) return; } else if (namespace === 100) { // Appendix space if (!appendixWithSlash.some(lang => title.startsWith(lang + "/")) && !appendixNoSlash.includes(title) && !isGlossary) return; } else if (namespace !== 118) { // Reconstruction space return; } } else if (isWPlink) { // Filter out invalid interwiki prefixes. if (INTERWIKI_PREFIXES.get(titlePrefix) === 0) return; // Get the resolved title if it's a language interwiki. if (INTERWIKI_PREFIXES.has(titlePrefix)) { API_domain = `https://${INTERWIKI_PREFIXES.get(titlePrefix)}.wikipedia.org`; resolvedTitle = linkTitle.substring(linkTitle.indexOf(":") + 1); // If the resolved title contains another interwiki, return. let resolvedTitlePrefix = resolvedTitle.includes(":") ? resolvedTitle.toLowerCase().split(":")[0] : ""; // same as titlePrefix if (INTERWIKI_PREFIXES.has(resolvedTitlePrefix)) return; } } else { return; } link.addEventListener("pointerover", event => { [mouseX, mouseY] = [event.clientX, event.clientY]; clearTimeout(popupTimer); // Ignore links which are already open. if (link === openLink) return; if (!isPreviewLink) closePopup(); // Fetch popup text immediately on hover to reduce delay. let responsePromise = fetch(`${API_domain}/api/rest_v1/page/html/${encodeURIComponent(resolvedTitle)}`, { headers: {"Api-User-Agent": "Tiện ích được phát triển bởi User:Ioaxxere"}, signal: API_controller.signal }).then(r => r.text()).catch(() => { /* fetch was aborted */ }); popupTimer = setTimeout(() => { popupContainer.style.display = ""; popupContent.innerHTML = ""; popupContent.append(loader); popupContent.style.padding = "14px 16px 8px"; // Disable link hovering until animation has completed. popupContent.style.pointerEvents = "none"; setTimeout(() => popupContent.style.pointerEvents = "", animationSpeed * 1000); // Adapt the popup for large, standard, and small size preference in Vector 2022. let sizeSetting = 0; if (document.documentElement.matches(".vector-feature-custom-font-size-clientpref-1")) sizeSetting = 1; else if (document.documentElement.matches(".vector-feature-custom-font-size-clientpref-2")) sizeSetting = 2; let width = [320, 340, 360][sizeSetting]; let height = [210, 225, 240][sizeSetting]; let triangleSize = [9, 10, 11][sizeSetting]; popupContainer.style.fontSize = ["14px", "15px", "17px"][sizeSetting]; popupContainer.style.lineHeight = ["21px", "22px", "24px"][sizeSetting]; popupContainer.style.width = width + "px"; popupContainer.style.height = height + "px"; if (isPreviewLink) { // Trigger fade-in-down animation. popupContainer.classList.remove("popup-fade-in-up", "popup-fade-in-down"); void popupContainer.offsetLeft; // force reflow popupContainer.classList.add("popup-fade-in-down"); } else { openLink = link; // Get list of rects (lines) of the target element, then find the one whose midpoint is closest to mouseY. // This ensures that the code can correctly handle multi-line links. let linkBlock = [...event.target.getClientRects()].reduce((prev, next) => Math.abs((next.top + next.bottom) / 2 - mouseY) < Math.abs((prev.top + prev.bottom) / 2 - mouseY) ? next : prev); // Horizontal position. Choose left or right depending on the side of the screen the mouse is in. let leftPosition = mouseX - 30; if (mouseX > document.documentElement.clientWidth / 2) leftPosition = mouseX - width + 30; // Ensure that the popup is at least 5px away from the side of the screen. leftPosition = Math.max(5, Math.min(leftPosition, document.documentElement.clientWidth - width - 5)); popupContainer.style.left = leftPosition + document.documentElement.scrollLeft + "px"; // Vertical position. Prioritize fade-in-down unless there would be less than 10px of room above. if (linkBlock.top > height + 10 || linkBlock.top > document.documentElement.clientHeight - linkBlock.bottom) { popupContainer.style.top = linkBlock.top + document.documentElement.scrollTop - height + "px"; popupContainer.classList.add("popup-fade-in-down"); popup.style.padding = `0 0 ${triangleSize}px`; // Create a triangle on the bottom. popup.style.clipPath = `polygon(0 0, 100% 0, 100% calc(100% - ${triangleSize}px), ${mouseX - leftPosition + triangleSize}px calc(100% - ${triangleSize}px), ${mouseX - leftPosition}px 100%, ${mouseX - leftPosition - triangleSize}px calc(100% - ${triangleSize}px), 0 calc(100% - ${triangleSize}px))`; } else { popupContainer.style.top = linkBlock.bottom + document.documentElement.scrollTop + "px"; popupContainer.classList.add("popup-fade-in-up"); popup.style.padding = `${triangleSize}px 0 0`; // Create a triangle on top. popup.style.clipPath = `polygon(0 ${triangleSize}px, ${mouseX - leftPosition - triangleSize}px ${triangleSize}px, ${mouseX - leftPosition}px 0, ${mouseX - leftPosition + triangleSize}px ${triangleSize}px, 100% ${triangleSize}px, 100% 100%, 0 100%)`; } } responsePromise.then(response => { if (!response) return; // if the fetch was aborted popupContent.innerHTML = ""; let responseDocument = new DOMParser().parseFromString(response, "text/html"); // Convert to absolute URLs. responseDocument.querySelectorAll("a[href]").forEach(link => link.setAttribute("href", link.href)); let anchoredElement = responseDocument.getElementById(linkAnchor); let popupHeader = document.createElement("div"); popupHeader.style = "font-size: 90%; color: var(--wikt-palette-deepblue)"; popupContent.append(popupHeader); let iconContainer = document.createElement("span"); iconContainer.style = "float: right; margin-left: 10px; height: 20px; color: var(--wikt-palette-black)"; popupHeader.append(iconContainer); let titleLink = document.createElement("a"); titleLink.href = link.href; titleLink.title = link.title; titleLink.className = "skin-invert-image preview-headerlink"; // Scrape entry content. if (isWPlink) { iconContainer.innerHTML = articleIcon; titleLink.textContent = linkTitle.replaceAll("_", " "); if (responseDocument.title) titleLink.innerHTML = responseDocument.title; // sometimes gives HTML text let WikipediaName = WP_LANGUAGES.get(API_domain.substr(8).split(".")[0]); popupHeader.append(WikipediaName + " Wikipedia có bài viết về ", titleLink); let articleContent = document.createElement("div"); articleContent.style.margin = "5px 0 0 10px"; let firstSection = responseDocument.querySelector("section"); if (firstSection && responseDocument.querySelector(`meta[property="mw:pageNamespace"][content="0"]`)) { firstSection.querySelectorAll("b").forEach(boldElem => boldElem.outerHTML = boldElem.innerHTML); firstSection.querySelectorAll(":scope > p, :scope > ul, :scope > ol").forEach(elem => articleContent.append(elem)); } if (articleContent.childElementCount) popupContent.append(articleContent); } else if (isGlossary) { iconContainer.innerHTML = glossaryIcon; titleLink.textContent = linkAnchor.replaceAll("_", " "); popupHeader.append("Glossary definition of ", titleLink); if (anchoredElement && anchoredElement.matches(".template-anchor")) { // Get succeeding `dd` elements. The HTML is <dt>...<span class="template-anchor"></span>...</dt><dd>...</dd><dd>...</dd> for (let elem = anchoredElement.parentElement.nextElementSibling; elem && elem.matches("dd"); elem = elem.nextElementSibling) { elem.style = "margin: 5px 0 0 15px"; popupContent.append(elem.cloneNode(true)); } } } else if (linkTitle.startsWith("Phụ lục:Thư mục học/")) { iconContainer.innerHTML = bibliographyIcon; let language = linkTitle.slice(22).replaceAll("_", " "); // trim "Appendix:Bibliography/" prefix popupHeader.append(`${language} bibliography`); if (anchoredElement && anchoredElement.matches(".bibliography-id")) { let bibliographyCitation = document.createElement("div"); bibliographyCitation.style = "margin: 5px 0 0 15px"; bibliographyCitation.innerHTML = anchoredElement.innerHTML; popupContent.append(bibliographyCitation); } } else { const hasDefinitions = elem => Boolean(elem.querySelector("ol:not(.references), .ja-see, .zh-see")); iconContainer.innerHTML = definitionIcon; // Try to resolve the anchor element to #Chinese. if (!anchoredElement && ["Tiếng Quan Thoại", "Gan", "Hakka", "Hokkien", "Literary_Chinese", "Mandarin", "Middle_Chinese", "Old_Chinese", "Wu", "Xiang"].includes(linkAnchor)) anchoredElement = responseDocument.querySelector("#Tiếng Trung Quốc"); // Try to guess the anchor target in the following order: English, Chinese, Translingual, [first h2 on page] // Always prioritize an L2 section which contains definitions. if (!linkAnchor) { let pageH2s = [...responseDocument.querySelectorAll("h2")]; if (pageH2s.some(h2 => hasDefinitions(h2.parentElement))) pageH2s = pageH2s.filter(h2 => hasDefinitions(h2.parentElement)); anchoredElement = pageH2s.find(h2 => h2.id === "English"); if (!anchoredElement) anchoredElement = pageH2s.find(h2 => h2.id === "Chinese"); if (!anchoredElement) anchoredElement = pageH2s.find(h2 => h2.id === "Translingual"); if (!anchoredElement) anchoredElement = pageH2s[0]; } let displayTitle = document.createElement("strong"); displayTitle.textContent = linkTitle.split("/").pop().replaceAll("_", " "); if (linkTitle.startsWith("Reconstruction:")) displayTitle.textContent = "*" + displayTitle.textContent; // Find localest section containing an h2. let languageSection = anchoredElement; while (languageSection && !languageSection.querySelector(":scope > h2")) languageSection = languageSection.parentElement.closest("section"); // Make sure that the entry is well-formed. if (languageSection) { let language = languageSection.querySelector(":scope > h2").textContent; let scrapeSection = anchoredElement.closest("section"); // Find localest section which contains any definitions. while (scrapeSection && !hasDefinitions(scrapeSection)) scrapeSection = scrapeSection.parentElement.closest("section"); if (scrapeSection) { let headwords = [...scrapeSection.querySelectorAll(".headword-line > strong")]; if (new Set(headwords.map(h => h.textContent)).size === 1) { // If there is a single unique headword, replace the display title with that. displayTitle = headwords[0]; if (displayTitle.querySelector("a > img")) displayTitle = displayTitle.querySelector("a > img").cloneNode(); } else if (new Set(headwords.map(h => h.cloneNode().outerHTML)).size === 1) { // If there is a single unique set of element attributes, use that with the display title. headwords[0].innerHTML = displayTitle.innerHTML; displayTitle = headwords[0]; } // Remove existing links in the headword. displayTitle.querySelectorAll("a").forEach(elem => elem.replaceWith(elem.textContent)); let ols = scrapeSection.querySelectorAll(":scope > ol:not(.references), section > ol:not(.references)"); if (anchoredElement.matches(".senseid")) { [...anchoredElement.parentElement.childNodes].forEach(child => { if (child !== anchoredElement) child.remove(); }); ols = [anchoredElement.closest("ol")]; } for (let ol of ols) { let POS_container = document.createElement("div"); POS_container.style.margin = "8px 0 5px 0"; let POS = document.createElement("span"); POS.style = "font-size: 110%; font-weight: bold"; POS.textContent = ol.closest("section").firstChild.textContent; POS_container.append(POS); let tlb = ol.closest("section").querySelector(".headword-line ~ .usage-label-term"); if (tlb) POS_container.append(document.createElement("br"), tlb); popupContent.append(POS_container, ol); } // {{ja-see}} and {{zh-see}} don't follow the normal format, so just insert them verbatim. for (let seeTemplate of scrapeSection.querySelectorAll(".ja-see, .zh-see")) popupContent.append(seeTemplate); } titleLink.append(displayTitle); popupHeader.append("Xem trước định nghĩa " + language + " ", titleLink); } else { titleLink.append(displayTitle); popupHeader.append("Xem trước định nghĩa ", titleLink); } } // Post-fetch error handling. if (popupContent.childElementCount > 1) { // Clean up HTML. popupContent.querySelectorAll("link, .previewonly, .maintenance-line, .mw-empty-elt, .reference, .Inline-Template, .vsHide").forEach(elem => elem.remove()); if (!isWPlink) popupContent.querySelectorAll("li > ul").forEach(elem => elem.remove()); // remove quotations for (let elem of popupContent.querySelectorAll("*")) elem.removeAttribute("id"); // avoid inadvertently repeating IDs within a page } else if (isWPlink) { // Display a message if the Wikipedia article was invalid or not in mainspace. let noArticle = document.createElement("div"); noArticle.style = "margin: 5px 0 0 15px; font-size: 90%"; noArticle.textContent = "(article content could not be previewed)"; popupContent.append(noArticle); } else if (linkAnchor && !anchoredElement) { // Display a message if the anchor is invalid. let noSectionFound = document.createElement("div"); noSectionFound.style = "margin: 10px 0 0 7.5px; font-size: 90%"; let strong = document.createElement("strong"); strong.textContent = linkAnchor.replaceAll("_", " "); noSectionFound.append("The ", strong, " section was not found on this page."); popupContent.append(noSectionFound); } else { // Display a message if no definitions were found in the section. let noDefinitionsFound = document.createElement("div"); noDefinitionsFound.style = "margin: 5px 0 0 15px; font-size: 90%"; noDefinitionsFound.textContent = "(không tìm thấy định nghĩa)"; popupContent.append(noDefinitionsFound); } // Reduce the padding if a scrollbar is present. The intended padding is 16px on each side. if (width - popupHeader.clientWidth > 32) popupContent.style.paddingRight = Math.max(4, 48 + popupHeader.clientWidth - width) + "px"; }); // (Hack?) make sure that `link pointerleave` doesn't cause the popup to immediately close. setTimeout(() => clearTimeout(popupTimer), 0); }, isPreviewLink ? 1200 : 400); }); link.addEventListener("pointerout", () => { clearTimeout(popupTimer); if (link === openLink) { popupTimer = setTimeout(closePopup, 300); } else { API_controller.abort(); API_controller = new AbortController(); } }); link.addEventListener("pointermove", event => [mouseX, mouseY] = [event.clientX, event.clientY]); link.addEventListener("click", () => clearTimeout(popupTimer)); } // Process all links. document.querySelectorAll("a").forEach(processLink); // Process links which are added to the DOM after the gadget has run. (new MutationObserver(events => events.flatMap(event => [...event.addedNodes]).forEach(node => { if (node instanceof HTMLAnchorElement) processLink(node); if (node instanceof HTMLElement) node.querySelectorAll("a").forEach(processLink); }))).observe(document.body, {childList: true, subtree: true}); // Dispatch hover events to support [[w:MediaWiki:Gadget-ReferenceTooltips.js]]. const linkEvent = ev => openLink && openLink.dispatchEvent(new Event(ev, {bubbles: true, cancelable: true})); // Note: for some reason an event listener on `popup` doesn't catch everything... popupContainer.addEventListener("pointerenter", () => setTimeout(() => linkEvent("mouseover"))); popupContainer.addEventListener("pointerleave", () => linkEvent("mouseout")); // Custom events popupContainer.addEventListener("pointerenter", () => linkEvent("linkPopupHover")); popupContainer.addEventListener("pointerleave", () => linkEvent("linkPopupUnhover")); // </nowiki> ovuydfpj83ga0m085ik9m34d0led8pu 2513942 2513935 2026-08-14T04:41:35Z Hiyuune 50834 Hiyuune đã đổi [[Thành viên:Hiyuune/Gadget-PagePreviews.js]] thành [[Thành viên:Hiyuune/Gadget-popups.js]] 2513935 javascript text/javascript // Page Previews for Wiktionary, by User:Ioaxxere // <nowiki> // Nhập từ [[:en:MediaWiki:Gadget-PagePreviews.js]] // Generated using: [[Thành viên:Hiyuune/Gadget-PagePreviews.js/ConstGeneration]] const INTERWIKI_PREFIXES = new Map([["aa","aa"],["ab","ab"],["abstract","abstract"],["acc",0],["ace","ace"],["acronym",0],["advisory",0],["ady","ady"],["af","af"],["ak","ak"],["als","als"],["alt","alt"],["am","am"],["ami","ami"],["an","an"],["ang","ang"],["ann","ann"],["anp","anp"],["antwiki",0],["appropedia",0],["aquariumwiki",0],["ar","ar"],["arbcom-zh",0],["arborwiki",0],["arc","arc"],["arxiv",0],["ary","ary"],["arz","arz"],["as","as"],["ast","ast"],["atj","atj"],["av","av"],["avk","avk"],["awa","awa"],["ay","ay"],["az","az"],["azb","azb"],["b",0],["ba","ba"],["ban","ban"],["bar","bar"],["bat-smg","bat-smg"],["battlestarwiki",0],["bbc","bbc"],["bcl","bcl"],["bcnbio",0],["bdr","bdr"],["be","be"],["be-tarask","be-tarask"],["be-x-old","be-tarask"],["beacha",0],["betawiki",0],["betawikiversity",0],["bew","bew"],["bg","bg"],["bh","bh"],["bi","bi"],["bibcode",0],["bibliowiki",0],["bjn","bjn"],["blk","blk"],["bm","bm"],["bn","bn"],["bo","bo"],["botwiki",0],["boxrec",0],["bpy","bpy"],["br","br"],["bs","bs"],["btm","btm"],["bug","bug"],["bugzilla",0],["bulba",0],["bxr","bxr"],["c",0],["c2",0],["ca","ca"],["cache",0],["cbk-zam","cbk-zam"],["ccorg",0],["cdo","cdo"],["ce","ce"],["ceb","ceb"],["centralwikia",0],["ch","ch"],["chapter",0],["cho","cho"],["choralwiki",0],["chr","chr"],["chy","chy"],["citizendium",0],["ckb","ckb"],["cmn","zh"],["co","co"],["commons",0],["communitywiki",0],["comune",0],["cr","cr"],["creativecommons",0],["creativecommonswiki",0],["crh","crh"],["cs","cs"],["csb","csb"],["cu","cu"],["cv","cv"],["cy","cy"],["cz","cs"],["d",0],["da","da"],["dag","dag"],["dbdump",0],["dcdatabase",0],["dcw",0],["de","de"],["debian",0],["devmo",0],["dga","dga"],["dico",0],["dicoado",0],["dict",0],["dictionary",0],["diffblog",0],["din","din"],["diq","diq"],["discord",0],["disinfopedia",0],["dk","da"],["dmoz",0],["dmozs",0],["doi",0],["donate",0],["doom_wiki",0],["download",0],["dpd",0],["dpla",0],["drae",0],["dsb","dsb"],["dtp","dtp"],["dty","dty"],["dv","dv"],["dz","dz"],["ee","ee"],["egl","eml"],["el","el"],["elibre",0],["emacswiki",0],["eml","eml"],["en","en"],["en-simple","simple"],["encyc",0],["englyphwiki",0],["enkol",0],["eo","eo"],["epo","eo"],["es","es"],["esolang",0],["et","et"],["etherpad",0],["etherpadbackup",0],["ethnologue",0],["ethnologuefamily",0],["eu","eu"],["exkcd",0],["exotica",0],["ext","ext"],["f",0],["fa","fa"],["fandom",0],["fanimutationwiki",0],["fat","fat"],["fedora",0],["ff","ff"],["fi","fi"],["finalfantasy",0],["finnix",0],["fiu-vro","fiu-vro"],["fj","fj"],["flickrphoto",0],["flickruser",0],["fo","fo"],["foldoc",0],["fon","fon"],["foundation",0],["foundationsite",0],["fr","fr"],["freebsdman",0],["freedomdefined",0],["freenode",0],["freesoft",0],["frp","frp"],["frr","frr"],["fur","fur"],["fy","fy"],["ga","ga"],["gag","gag"],["gan","gan"],["gcr","gcr"],["gd","gd"],["gentoo",0],["genwiki",0],["gerrit",0],["git",0],["github",0],["gitiles",0],["gitlab",0],["gl","gl"],["glk","glk"],["globalcontribs",0],["glottolog",0],["glottopedia",0],["gn","gn"],["gom","gom"],["google",0],["googledefine",0],["googlegroups",0],["gor","gor"],["got","got"],["gpe","gpe"],["gs",0],["gsw","als"],["gu","gu"],["guc","guc"],["gucprefix",0],["guildwarswiki",0],["gur","gur"],["gutenberg",0],["gutenbergwiki",0],["guw","guw"],["gv","gv"],["ha","ha"],["hackerspaces",0],["hak","hak"],["hammondwiki",0],["haw","haw"],["hdl",0],["he","he"],["heraldik",0],["hi","hi"],["hif","hif"],["ho","ho"],["horizonlabs",0],["hr","hr"],["hrfwiki",0],["hrwiki",0],["hsb","hsb"],["ht","ht"],["hu","hu"],["hy","hy"],["hyw","hyw"],["hz","hz"],["ia","ia"],["iarchive",0],["iba","iba"],["id","id"],["ie","ie"],["ig","ig"],["igl","igl"],["ii","ii"],["ik","ik"],["ilo","ilo"],["imdbcompany",0],["imdbname",0],["imdbtitle",0],["incubator",0],["infosphere",0],["inh","inh"],["io","io"],["irc",0],["ircrc",0],["ircs",0],["is","is"],["isni",0],["iso639-3",0],["issn",0],["it","it"],["iu","iu"],["ja","ja"],["jaglyphwiki",0],["jam","jam"],["jbo","jbo"],["jira",0],["jp","ja"],["jstor",0],["jv","jv"],["ka","ka"],["kaa","kaa"],["kab","kab"],["kai","kai"],["kaj","kaj"],["kamelo",0],["karlsruhe",0],["kbd","kbd"],["kbp","kbp"],["kcg","kcg"],["kg","kg"],["kge","kge"],["ki","ki"],["kj","kj"],["kk","kk"],["kl","kl"],["km","km"],["kn","kn"],["knc","knc"],["ko","ko"],["koi","koi"],["kr","kr"],["krc","krc"],["ks","ks"],["ksh","ksh"],["ku","ku"],["kus","kus"],["kv","kv"],["kw","kw"],["ky","ky"],["la","la"],["labsconsole",0],["lad","lad"],["lb","lb"],["lbe","lbe"],["lexemes",0],["lez","lez"],["lfn","lfn"],["lg","lg"],["li","li"],["liberachat",0],["libreplanet",0],["lij","lij"],["lingualibre",0],["linguistlist",0],["listarchive",0],["lld","lld"],["lmo","lmo"],["ln","ln"],["lo","lo"],["localwiki",0],["lofc",0],["lojban",0],["lokalhistoriewiki",0],["lostpedia",0],["lrc","lrc"],["lt","lt"],["ltg","ltg"],["luxo",0],["lv","lv"],["lzh","zh-classical"],["m",0],["mad","mad"],["mag","mag"],["mai","mai"],["mail",0],["mailarchive",0],["map-bms","map-bms"],["mariowiki",0],["marveldatabase",0],["mdf","mdf"],["mdwiki",0],["meatball",0],["mediawikiwiki",0],["mediazilla",0],["memoryalpha",0],["meta",0],["metawiki",0],["metawikimedia",0],["metawikipedia",0],["mg","mg"],["mh","mh"],["mhr","mhr"],["mi","mi"],["min","min"],["mineralienatlas",0],["miraheze",0],["mixnmatch",0],["mk","mk"],["ml","ml"],["mn","mn"],["mni","mni"],["mnw","mnw"],["mo","mo"],["moinmoin",0],["mos","mos"],["mosapedia",0],["mozillawiki",0],["mozillazinekb",0],["mr","mr"],["mrj","mrj"],["ms","ms"],["mt","mt"],["mus","mus"],["mw",0],["mwl","mwl"],["mwod",0],["mwot",0],["my","my"],["myv","myv"],["mzn","mzn"],["n",0],["na","na"],["nah","nah"],["nan","zh-min-nan"],["nap","nap"],["nara",0],["nb","no"],["nds","nds"],["nds-nl","nds-nl"],["ne","ne"],["new","new"],["ng","ng"],["nia","nia"],["nl","nl"],["nlab",0],["nn","nn"],["no","no"],["nost","nostalgia"],["nostalgia","nostalgia"],["nov","nov"],["nqo","nqo"],["nr","nr"],["nrm","nrm"],["nso","nso"],["nup","nup"],["nv","nv"],["ny","ny"],["oc","oc"],["oclc",0],["oeis",0],["oewiki",0],["oldwikisource",0],["olo","olo"],["olpc",0],["om","om"],["openlibrary",0],["openstreetmap",0],["openwetware",0],["or","or"],["organicdesign",0],["orthodoxwiki",0],["os","os"],["osmwiki",0],["otrs",0],["otrswiki",0],["outreach",0],["outreachwiki",0],["owasp",0],["pa","pa"],["pag","pag"],["pam","pam"],["pap","pap"],["paws",0],["pcd","pcd"],["pcm","pcm"],["pdc","pdc"],["petscan",0],["pfl","pfl"],["phab",0],["phabricator",0],["pi","pi"],["pih","pih"],["pl","pl"],["planetmath",0],["pmid",0],["pms","pms"],["pnb","pnb"],["pnt","pnt"],["pokewiki",0],["pokéwiki",0],["policy",0],["ppl","ppl"],["proofwiki",0],["ps","ps"],["pt","pt"],["pwn","pwn"],["pyrev",0],["pythoninfo",0],["q",0],["qu","qu"],["quality",0],["quarry",0],["rcirc",0],["regiowiki",0],["rev",0],["revo",0],["rfc",0],["rheinneckar",0],["rki","rki"],["rm","rm"],["rmy","rmy"],["rn","rn"],["ro","ro"],["roa-rup","roa-rup"],["roa-tara","roa-tara"],["rodovid",0],["rsk","rsk"],["rt",0],["ru","ru"],["rue","rue"],["rup","roa-rup"],["rw","rw"],["s",0],["sa","sa"],["sah","sah"],["sat","sat"],["sc","sc"],["scholar",0],["schoolswp",0],["schoolwiki",0],["scn","scn"],["sco","sco"],["scores",0],["scoutwiki",0],["sd","sd"],["se","se"],["securewikidc",0],["semantic-mw",0],["senseislibrary",0],["sep11",0],["sg","sg"],["sgs","bat-smg"],["sh","sh"],["sharemap",0],["shi","shi"],["shn","shn"],["shoutwiki",0],["shy","shy"],["si","si"],["silcode",0],["simple","simple"],["sk","sk"],["skr","skr"],["sl","sl"],["slashdot",0],["sm","sm"],["smn","smn"],["sn","sn"],["so","so"],["sourceforge",0],["spamcheck",0],["spcom",0],["species",0],["sq","sq"],["sr","sr"],["srn","srn"],["ss","ss"],["st","st"],["stats",0],["stewardry",0],["stq","stq"],["strategy",0],["strategywiki",0],["su","su"],["sulutil",0],["sv","sv"],["svn",0],["sw","sw"],["swtrain",0],["syl","syl"],["szl","szl"],["szy","szy"],["ta","ta"],["tardis",0],["tay","tay"],["tclerswiki",0],["tcy","tcy"],["tdd","tdd"],["te","te"],["tenwiki","ten"],["test2wiki","test2"],["testcommons",0],["testwiki","test"],["testwikidata",0],["tet","tet"],["tfwiki",0],["tg","tg"],["th","th"],["thelemapedia",0],["theopedia",0],["ti","ti"],["ticket",0],["tig","tig"],["tk","tk"],["tl","tl"],["tly","tly"],["tmbw",0],["tn","tn"],["to","to"],["tok","tok"],["tolkiengateway",0],["toolforge",0],["toolhub",0],["toollabs",0],["tools",0],["tpi","tpi"],["tr","tr"],["translatewiki",0],["trv","trv"],["ts","ts"],["tswiki",0],["tt","tt"],["tum","tum"],["tviv",0],["tw","tw"],["twiki",0],["twl",0],["ty","ty"],["tyv","tyv"],["tyvawiki",0],["udm","udm"],["ug","ug"],["uk","uk"],["umap",0],["uncyclopedia",0],["unihan",0],["ur","ur"],["urbandict",0],["usability",0],["usemod",0],["utrs",0],["uz","uz"],["v",0],["ve","ve"],["vec","vec"],["vep","vep"],["vi","vi"],["viaf",0],["vikidia",0],["vlos",0],["vls","vls"],["vo","vo"],["votewiki",0],["voy",0],["vro","fiu-vro"],["vrts",0],["vrtwiki",0],["w","en"],["wa","wa"],["war","war"],["wcna",0],["weirdgloop",0],["werelate",0],["wg","wg-en"],["wikia",0],["wikiapiary",0],["wikiasite",0],["wikibooks",0],["wikicities",0],["wikicity",0],["wikiconference",0],["wikidata",0],["wikiedudashboard",0],["wikifunctions",0],["wikifur",0],["wikihow",0],["wikiindex",0],["wikilivres",0],["wikilivresru",0],["wikimania",0],["wikimedia",0],["wikinews",0],["wikinfo",0],["wikinvest",0],["wikipapers",0],["wikipedia",0],["wikipediawikipedia",0],["wikiquote",0],["wikiskripta",0],["wikisophia",0],["wikisource",0],["wikisp",0],["wikispecies",0],["wikispore",0],["wikispot",0],["wikitech",0],["wikiti",0],["wikitrek",0],["wikiversity",0],["wikivoyage",0],["wikiwikiweb",0],["wikt",0],["wiktionary",0],["wm2005",0],["wm2006",0],["wm2007",0],["wm2008",0],["wm2009",0],["wm2010",0],["wm2011",0],["wm2012",0],["wm2013",0],["wm2014",0],["wm2015",0],["wm2016",0],["wm2017",0],["wm2018",0],["wmam",0],["wmania",0],["wmar",0],["wmat",0],["wmau",0],["wmbd",0],["wmbe",0],["wmbr",0],["wmca",0],["wmch",0],["wmcl",0],["wmcn",0],["wmco",0],["wmcz",0],["wmcz_docs",0],["wmcz_old",0],["wmdc",0],["wmde",0],["wmdeblog",0],["wmdk",0],["wmdoc",0],["wmec",0],["wmee",0],["wmes",0],["wmet",0],["wmf",0],["wmfblog",0],["wmfdashboard",0],["wmfi",0],["wmfr",0],["wmge",0],["wmhi",0],["wmhk",0],["wmhu",0],["wmid",0],["wmil",0],["wmin",0],["wmit",0],["wmke",0],["wmmk",0],["wmmx",0],["wmnl",0],["wmno",0],["wmnoc",0],["wmnoch",0],["wmnyc",0],["wmpa-us",0],["wmph",0],["wmpl",0],["wmplsite",0],["wmpt",0],["wmpunjabi",0],["wmromd",0],["wmrs",0],["wmru",0],["wmse",0],["wmsk",0],["wmteam",0],["wmtr",0],["wmtw",0],["wmua",0],["wmuk",0],["wmve",0],["wmza",0],["wo","wo"],["wookieepedia",0],["wowwiki",0],["wplibrary",0],["wurmpedia",0],["wuu","wuu"],["xal","xal"],["xh","xh"],["xkcd",0],["xmf","xmf"],["xtools",0],["yi","yi"],["yo","yo"],["yue","zh-yue"],["za","za"],["zea","zea"],["zgh","zgh"],["zh","zh"],["zh-classical","zh-classical"],["zh-cn","zh"],["zh-min-nan","zh-min-nan"],["zh-tw","zh"],["zh-yue","zh-yue"],["zu","zu"],["zum",0]]); const WP_LANGUAGES = new Map([["ab","Abkhazian"],["ace","Acehnese"],["ady","Adyghe"],["af","Afrikaans"],["als","Alemannic"],["alt","Southern Altai"],["am","Amharic"],["ami","Amis"],["an","Aragonese"],["ang","Old English"],["ann","Obolo"],["anp","Angika"],["ar","Arabic"],["arc","Aramaic"],["ary","Moroccan Arabic"],["arz","Egyptian Arabic"],["as","Assamese"],["ast","Asturian"],["atj","Atikamekw"],["av","Avaric"],["avk","Kotava"],["awa","Awadhi"],["ay","Aymara"],["az","Azerbaijani"],["azb","South Azerbaijani"],["ba","Bashkir"],["ban","Balinese"],["bar","Bavarian"],["bat-smg","Samogitian"],["bbc","Batak Toba"],["bcl","Central Bikol"],["bdr","West Coast Bajau"],["be","Belarusian"],["be-tarask","Belarusian (Taraškievica orthography)"],["bew","Betawi"],["bg","Bulgarian"],["bh","Bhojpuri"],["bi","Bislama"],["bjn","Banjar"],["blk","Pa'O"],["bm","Bambara"],["bn","Bangla"],["bo","Tibetan"],["bpy","Bishnupriya"],["br","Breton"],["bs","Bosnian"],["btm","Batak Mandailing"],["bug","Buginese"],["bxr","Russia Buriat"],["ca","Catalan"],["cbk-zam","Chavacano"],["cdo","Mindong"],["ce","Chechen"],["ceb","Cebuano"],["ch","Chamorro"],["chr","Cherokee"],["chy","Cheyenne"],["ckb","Central Kurdish"],["co","Corsican"],["crh","Crimean Tatar"],["cs","Czech"],["csb","Kashubian"],["cu","Church Slavic"],["cv","Chuvash"],["cy","Welsh"],["da","Danish"],["dag","Dagbani"],["de","German"],["dga","Southern Dagaare"],["din","Dinka"],["diq","Dimli"],["dsb","Lower Sorbian"],["dtp","Central Dusun"],["dty","Doteli"],["dv","Divehi"],["dz","Dzongkha"],["ee","Ewe"],["el","Greek"],["eml","Emiliano-Romagnolo"],["en","English"],["eo","Esperanto"],["es","Spanish"],["et","Estonian"],["eu","Basque"],["ext","Extremaduran"],["fa","Persian"],["fat","Fanti"],["ff","Fula"],["fi","Finnish"],["fiu-vro","Võro"],["fj","Fijian"],["fo","Faroese"],["fon","Fon"],["fr","French"],["frp","Arpitan"],["frr","Northern Frisian"],["fur","Friulian"],["fy","Western Frisian"],["ga","Irish"],["gag","Gagauz"],["gan","Gan"],["gcr","Guianan Creole"],["gd","Scottish Gaelic"],["gl","Galician"],["glk","Gilaki"],["gn","Guarani"],["gom","Goan Konkani"],["gor","Gorontalo"],["got","Gothic"],["gpe","Ghanaian Pidgin"],["gu","Gujarati"],["guc","Wayuu"],["gur","Frafra"],["guw","Gun"],["gv","Manx"],["ha","Hausa"],["hak","Hakka Chinese"],["haw","Hawaiian"],["he","Hebrew"],["hi","Hindi"],["hif","Fiji Hindi"],["hr","Croatian"],["hsb","Upper Sorbian"],["ht","Haitian Creole"],["hu","Hungarian"],["hy","Armenian"],["hyw","Western Armenian"],["ia","Interlingua"],["iba","Iban"],["id","Indonesian"],["ie","Interlingue"],["ig","Igbo"],["igl","Igala"],["ik","Inupiaq"],["ilo","Iloko"],["inh","Ingush"],["io","Ido"],["is","Icelandic"],["it","Italian"],["iu","Inuktitut"],["ja","Japanese"],["jam","Jamaican Creole English"],["jbo","Lojban"],["jv","Javanese"],["ka","Georgian"],["kaa","Kara-Kalpak"],["kab","Kabyle"],["kai","Karekare"],["kaj","Jju"],["kbd","Kabardian"],["kbp","Kabiye"],["kcg","Tyap"],["kg","Kongo"],["kge","Komering"],["ki","Kikuyu"],["kk","Kazakh"],["km","Khmer"],["kn","Kannada"],["knc","Central Kanuri"],["ko","Korean"],["koi","Komi-Permyak"],["krc","Karachay-Balkar"],["ks","Kashmiri"],["ksh","Colognian"],["ku","Kurdish"],["kus","Kusaal"],["kv","Komi"],["kw","Cornish"],["ky","Kyrgyz"],["la","Latin"],["lad","Ladino"],["lb","Luxembourgish"],["lbe","Lak"],["lez","Lezghian"],["lfn","Lingua Franca Nova"],["lg","Ganda"],["li","Limburgish"],["lij","Ligurian"],["lld","Ladin"],["lmo","Lombard"],["ln","Lingala"],["lo","Lao"],["lt","Lithuanian"],["ltg","Latgalian"],["lv","Latvian"],["mad","Madurese"],["mag","Magahi"],["mai","Maithili"],["map-bms","Banyumasan"],["mdf","Moksha"],["mg","Malagasy"],["mhr","Eastern Mari"],["mi","Māori"],["min","Minangkabau"],["mk","Macedonian"],["ml","Malayalam"],["mn","Mongolian"],["mni","Manipuri"],["mnw","Mon"],["mos","Mossi"],["mr","Marathi"],["mrj","Western Mari"],["ms","Malay"],["mt","Maltese"],["mwl","Mirandese"],["my","Burmese"],["myv","Erzya"],["mzn","Mazanderani"],["nah","Nahuatl"],["nap","Neapolitan"],["nds","Low German"],["nds-nl","Low Saxon"],["ne","Nepali"],["new","Newari"],["nia","Nias"],["nl","Dutch"],["nn","Norwegian Nynorsk"],["no","Norwegian"],["nov","Novial"],["nqo","N’Ko"],["nr","South Ndebele"],["nrm","Norman"],["nso","Northern Sotho"],["nup","Nupe"],["nv","Navajo"],["ny","Nyanja"],["oc","Occitan"],["olo","Livvi-Karelian"],["om","Oromo"],["or","Odia"],["os","Ossetic"],["pa","Punjabi"],["pag","Pangasinan"],["pam","Pampanga"],["pap","Papiamento"],["pcd","Picard"],["pcm","Nigerian Pidgin"],["pdc","Pennsylvania German"],["pfl","Palatine German"],["pi","Pali"],["pl","Polish"],["pms","Piedmontese"],["pnb","Western Punjabi"],["pnt","Pontic"],["ppl","Nawat"],["ps","Pashto"],["pt","Portuguese"],["pwn","Paiwan"],["qu","Quechua"],["rki","Arakanese"],["rm","Romansh"],["rmy","Vlax Romani"],["rn","Rundi"],["ro","Romanian"],["roa-rup","Aromanian"],["roa-tara","Tarantino"],["rsk","Pannonian Rusyn"],["ru","Russian"],["rue","Rusyn"],["rw","Kinyarwanda"],["sa","Sanskrit"],["sah","Yakut"],["sat","Santali"],["sc","Sardinian"],["scn","Sicilian"],["sco","Scots"],["sd","Sindhi"],["se","Northern Sami"],["sg","Sango"],["sh","Serbo-Croatian"],["shi","Tachelhit"],["shn","Shan"],["si","Sinhala"],["simple","Simple English"],["sk","Slovak"],["skr","Saraiki"],["sl","Slovenian"],["sm","Samoan"],["smn","Inari Sami"],["sn","Shona"],["so","Somali"],["sq","Albanian"],["sr","Serbian"],["srn","Sranan Tongo"],["ss","Swati"],["st","Southern Sotho"],["stq","Saterland Frisian"],["su","Sundanese"],["sv","Swedish"],["sw","Swahili"],["syl","Sylheti"],["szl","Silesian"],["szy","Sakizaya"],["ta","Tamil"],["tay","Atayal"],["tcy","Tulu"],["tdd","Tai Nuea"],["te","Telugu"],["tet","Tetum"],["tg","Tajik"],["th","Thai"],["ti","Tigrinya"],["tig","Tigre"],["tk","Turkmen"],["tl","Tagalog"],["tly","Talysh"],["tn","Tswana"],["to","Tongan"],["tok","Toki Pona"],["tpi","Tok Pisin"],["tr","Turkish"],["trv","Taroko"],["ts","Tsonga"],["tt","Tatar"],["tum","Tumbuka"],["tw","Twi"],["ty","Tahitian"],["tyv","Tuvinian"],["udm","Udmurt"],["ug","Uyghur"],["uk","Ukrainian"],["ur","Urdu"],["uz","Uzbek"],["ve","Venda"],["vec","Venetian"],["vep","Veps"],["vi","Vietnamese"],["vls","West Flemish"],["vo","Volapük"],["wa","Walloon"],["war","Waray"],["wo","Wolof"],["wuu","Wu"],["xal","Kalmyk"],["xh","Xhosa"],["xmf","Mingrelian"],["yi","Yiddish"],["yo","Yoruba"],["za","Zhuang"],["zea","Zeelandic"],["zgh","Standard Moroccan Tamazight"],["zh","Chinese"],["zh-classical","Literary Chinese"],["zh-min-nan","Minnan"],["zh-yue","Cantonese"],["zu","Zulu"]]); const appendixWithSlash = ["Adûni", "Afrihili", "Arabic_roots", "Belter_Creole", "Bibliography", "Black_Speech", "Bolak", "Communicationssprache", "Dothraki", "Eloi", "Gestures", "Glosa", "Goa'uld", "High_Valyrian", "Idiom_Neutral", "Interlingue", "Interslavic", "Klingon", "Kotava", "Láadan", "Lapine", "Lingua_Franca_Nova", "Lojban", "Mandalorian", "Medefaidrin", "Minecraft", "Mundolinco", "Na'vi", "Neo", "Novial", "Noxilo", "Protologisms/Long_words", "Quenya", "Romanova", "Sindarin", "Snowclones", "Talossan", "Toki_Pona", "Unas"]; const appendixNoSlash = ["Boldface", "Capital_letter", "Italics", "Lowercase_letter", "Possessive", "Repetition", "Small_caps", "Strikethrough", "Subscript", "Superscript", "Underline", "Uppercase_letter"]; const animationSpeed = 0.2; // seconds mw.util.addCSS(` .wikt-page-preview ol { margin: 0 0.5em 0 1.5em; padding: 0; } .wikt-page-preview dl { margin-bottom: 0; } .wikt-page-preview p { margin: 0; } /* popupContainer has the opacity animation, while popup gets translated. */ .popup-fade-in-up, .popup-fade-in-down { animation: popup-fade-in ${animationSpeed}s ease forwards; } .popup-fade-out-up, .popup-fade-out-down { animation: popup-fade-out ${animationSpeed}s ease forwards; } .popup-fade-in-up > div { animation: popup-move-in-up ${animationSpeed}s ease forwards; } .popup-fade-in-down > div { animation: popup-move-in-down ${animationSpeed}s ease forwards; } .popup-fade-out-up > div { animation: popup-move-out-up ${animationSpeed}s ease forwards; } .popup-fade-out-down > div { animation: popup-move-out-down ${animationSpeed}s ease forwards; } @keyframes popup-move-in-up { 0% { transform: translate(0, 20px); } } @keyframes popup-move-in-down { 0% { transform: translate(0, -20px); } } @keyframes popup-move-out-up { 100% { transform: translate(0, -20px); } } @keyframes popup-move-out-down { 100% { transform: translate(0, 20px); } } @keyframes popup-fade-out { 100% { opacity: 0; } } @keyframes popup-fade-in { 0% { opacity: 0; } } .ring-loader { margin: auto; width: 24px; height: 24px; border-radius: 50%; border-top: 5px solid var(--wikt-palette-black); border-bottom: 5px solid var(--wikt-palette-black); border-left: 5px solid transparent; border-right: 5px solid transparent; animation: spin 1.2s linear infinite; } @keyframes spin { 100% { transform: rotate(360deg); } } .preview-headerlink, .preview-headerlink:visited { color: inherit; font-weight: bold; } /* Hack: get the speaker icon on Wikipedia articles without having to load Phonos. */ .ext-phonos .oo-ui-buttonElement-button:after { content: "🔊"; } .ext-phonos * { display: inline !important; padding: 0 !important; margin: 0 !important; } `); const definitionIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true" fill="currentColor"><g><path d="M15 2a7.65 7.65 0 00-5 2 7.65 7.65 0 00-5-2H1v15h4a7.65 7.65 0 015 2 7.65 7.65 0 015-2h4V2zm2.5 13.5H14a4.38 4.38 0 00-3 1V5s1-1.5 4-1.5h2.5z"></path></g></svg>`; const glossaryIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true" fill="currentColor"><g><path d="M12.43 14.34A5 5 0 0110 15a5 5 0 113.95-2L17 16.09V3a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 001.45-.63z"></path><circle cx="10" cy="10" r="3"></circle></g></svg>`; const articleIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true" fill="currentColor"><g><path d="M5 1a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 002-2V3a2 2 0 00-2-2zm0 3h5v1H5zm0 2h5v1H5zm0 2h5v1H5zm10 7H5v-1h10zm0-2H5v-1h10zm0-2H5v-1h10zm0-2h-4V4h4z"></path></g></svg>`; const bibliographyIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true" fill="currentColor"><g><path d="M3 2h12c.5 0 1 .5 1 1v1c0 .5-.5 1-1 1H3c0 0 .5-.5.5-1V3c0-.5-.5-1-.5-1zM4 6h12c.5 0 1 .5 1 1v1c0 .5-.5 1-1 1H4c0 0 .5-.5.5-1V7c0-.5-.5-1-.5-1zM3 10h12c.5 0 1 .5 1 1v1c0 .5-.5 1-1 1H3c0 0 .5-.5.5-1v-1c0-.5-.5-1-.5-1zM4 14h12c.5 0 1 .5 1 1v1c0 .5-.5 1-1 1H4c0 0 .5-.5.5-1v-1c0-.5-.5-1-.5-1z"/></g></svg>`; const loader = document.createElement("div"); loader.className = "ring-loader"; const popupContainer = document.createElement("div"); const popup = document.createElement("div"); const popupContent = document.createElement("div"); popupContainer.style = `display: none; position: absolute; filter: drop-shadow(0px 30px 30px rgba(0, 0, 0, 0.15)) drop-shadow(0px 0px 0.75px var(--wikt-palette-dullblue)); z-index: 801`; popupContainer.className = "wikt-page-preview"; popup.style = "box-sizing: border-box; height: 100%; background: var(--wikt-palette-white)"; popupContent.style = "display: flex; flex-direction: column; border-radius: 2px; box-sizing: border-box; color: var(--wikt-palette-black); overflow: auto; height: 100%; overflow-wrap: break-word; scrollbar-width: thin"; document.body.append(popupContainer); popupContainer.append(popup); popup.append(popupContent); let popupTimer; let openLink; let mouseX, mouseY; let API_controller = new AbortController(); function closePopup() { if (!openLink) return; openLink = null; API_controller.abort(); API_controller = new AbortController(); if (popupContainer.classList.contains("popup-fade-in-up")) { popupContainer.classList.remove("popup-fade-in-up"); popupContainer.classList.add("popup-fade-out-down"); } else { popupContainer.classList.remove("popup-fade-in-down"); popupContainer.classList.add("popup-fade-out-up"); } // After the animation has completed, reset the popup to its initial state. setTimeout(() => { popupContainer.style.display = "none"; popupContainer.classList.remove("popup-fade-out-down", "popup-fade-out-up"); }, animationSpeed * 1000); } popup.addEventListener("pointerenter", () => { clearTimeout(popupTimer); }); popup.addEventListener("pointerleave", () => { clearTimeout(popupTimer); popupTimer = setTimeout(closePopup, 300); }); function processLink(link) { let linkTitle = decodeURIComponent(link.pathname.split("/wiki/")[1]); let titlePrefix = linkTitle.includes(":") ? linkTitle.toLowerCase().split(":")[0] : ""; let resolvedTitle = linkTitle; let API_domain = link.origin; let linkAnchor = decodeURIComponent(link.hash.slice(1) || ""); let isWPlink = link.href.match(/^https:\/\/[^.]+.wikipedia.org\/wiki\//); let isPreviewLink = Boolean(link.closest(".wikt-page-preview")); let isGlossary = /^Appendix:.*[Gg]lossary$/.test(linkTitle); // Start with various checks to determine whether a link should be processed. if (link.matches(`nav a, .cancelLink a, .mw-widget-titleOptionWidget a, .new, .preview-headerlink, .external, [href$="#"]`)) { return; } else if (isGlossary) { // Links to glossary *must* have an anchor. if (!linkAnchor) return; } else if (linkTitle.startsWith("Appendix:Bibliography/")) { // Bibliography subpages must have an anchor starting with `bib-`. if (!linkAnchor || !linkAnchor.startsWith("bib-")) return; } else if (!isWPlink) { // Other Wiktionary pages must not have a lowercase-starting anchor (filters out some unhelpful links). if (/^[a-z]/.test(linkAnchor)) return; } if (link.href.startsWith("https://en.wiktionary.org/wiki/")) { // Filter out all interwiki prefixes. if (INTERWIKI_PREFIXES.has(titlePrefix)) return; let {namespace, title} = new mw.Title(linkTitle); if (namespace === 0) { // Mainspace if (title.endsWith("/translations")) return; } else if (namespace === 100) { // Appendix space if (!appendixWithSlash.some(lang => title.startsWith(lang + "/")) && !appendixNoSlash.includes(title) && !isGlossary) return; } else if (namespace !== 118) { // Reconstruction space return; } } else if (isWPlink) { // Filter out invalid interwiki prefixes. if (INTERWIKI_PREFIXES.get(titlePrefix) === 0) return; // Get the resolved title if it's a language interwiki. if (INTERWIKI_PREFIXES.has(titlePrefix)) { API_domain = `https://${INTERWIKI_PREFIXES.get(titlePrefix)}.wikipedia.org`; resolvedTitle = linkTitle.substring(linkTitle.indexOf(":") + 1); // If the resolved title contains another interwiki, return. let resolvedTitlePrefix = resolvedTitle.includes(":") ? resolvedTitle.toLowerCase().split(":")[0] : ""; // same as titlePrefix if (INTERWIKI_PREFIXES.has(resolvedTitlePrefix)) return; } } else { return; } link.addEventListener("pointerover", event => { [mouseX, mouseY] = [event.clientX, event.clientY]; clearTimeout(popupTimer); // Ignore links which are already open. if (link === openLink) return; if (!isPreviewLink) closePopup(); // Fetch popup text immediately on hover to reduce delay. let responsePromise = fetch(`${API_domain}/api/rest_v1/page/html/${encodeURIComponent(resolvedTitle)}`, { headers: {"Api-User-Agent": "Tiện ích được phát triển bởi User:Ioaxxere"}, signal: API_controller.signal }).then(r => r.text()).catch(() => { /* fetch was aborted */ }); popupTimer = setTimeout(() => { popupContainer.style.display = ""; popupContent.innerHTML = ""; popupContent.append(loader); popupContent.style.padding = "14px 16px 8px"; // Disable link hovering until animation has completed. popupContent.style.pointerEvents = "none"; setTimeout(() => popupContent.style.pointerEvents = "", animationSpeed * 1000); // Adapt the popup for large, standard, and small size preference in Vector 2022. let sizeSetting = 0; if (document.documentElement.matches(".vector-feature-custom-font-size-clientpref-1")) sizeSetting = 1; else if (document.documentElement.matches(".vector-feature-custom-font-size-clientpref-2")) sizeSetting = 2; let width = [320, 340, 360][sizeSetting]; let height = [210, 225, 240][sizeSetting]; let triangleSize = [9, 10, 11][sizeSetting]; popupContainer.style.fontSize = ["14px", "15px", "17px"][sizeSetting]; popupContainer.style.lineHeight = ["21px", "22px", "24px"][sizeSetting]; popupContainer.style.width = width + "px"; popupContainer.style.height = height + "px"; if (isPreviewLink) { // Trigger fade-in-down animation. popupContainer.classList.remove("popup-fade-in-up", "popup-fade-in-down"); void popupContainer.offsetLeft; // force reflow popupContainer.classList.add("popup-fade-in-down"); } else { openLink = link; // Get list of rects (lines) of the target element, then find the one whose midpoint is closest to mouseY. // This ensures that the code can correctly handle multi-line links. let linkBlock = [...event.target.getClientRects()].reduce((prev, next) => Math.abs((next.top + next.bottom) / 2 - mouseY) < Math.abs((prev.top + prev.bottom) / 2 - mouseY) ? next : prev); // Horizontal position. Choose left or right depending on the side of the screen the mouse is in. let leftPosition = mouseX - 30; if (mouseX > document.documentElement.clientWidth / 2) leftPosition = mouseX - width + 30; // Ensure that the popup is at least 5px away from the side of the screen. leftPosition = Math.max(5, Math.min(leftPosition, document.documentElement.clientWidth - width - 5)); popupContainer.style.left = leftPosition + document.documentElement.scrollLeft + "px"; // Vertical position. Prioritize fade-in-down unless there would be less than 10px of room above. if (linkBlock.top > height + 10 || linkBlock.top > document.documentElement.clientHeight - linkBlock.bottom) { popupContainer.style.top = linkBlock.top + document.documentElement.scrollTop - height + "px"; popupContainer.classList.add("popup-fade-in-down"); popup.style.padding = `0 0 ${triangleSize}px`; // Create a triangle on the bottom. popup.style.clipPath = `polygon(0 0, 100% 0, 100% calc(100% - ${triangleSize}px), ${mouseX - leftPosition + triangleSize}px calc(100% - ${triangleSize}px), ${mouseX - leftPosition}px 100%, ${mouseX - leftPosition - triangleSize}px calc(100% - ${triangleSize}px), 0 calc(100% - ${triangleSize}px))`; } else { popupContainer.style.top = linkBlock.bottom + document.documentElement.scrollTop + "px"; popupContainer.classList.add("popup-fade-in-up"); popup.style.padding = `${triangleSize}px 0 0`; // Create a triangle on top. popup.style.clipPath = `polygon(0 ${triangleSize}px, ${mouseX - leftPosition - triangleSize}px ${triangleSize}px, ${mouseX - leftPosition}px 0, ${mouseX - leftPosition + triangleSize}px ${triangleSize}px, 100% ${triangleSize}px, 100% 100%, 0 100%)`; } } responsePromise.then(response => { if (!response) return; // if the fetch was aborted popupContent.innerHTML = ""; let responseDocument = new DOMParser().parseFromString(response, "text/html"); // Convert to absolute URLs. responseDocument.querySelectorAll("a[href]").forEach(link => link.setAttribute("href", link.href)); let anchoredElement = responseDocument.getElementById(linkAnchor); let popupHeader = document.createElement("div"); popupHeader.style = "font-size: 90%; color: var(--wikt-palette-deepblue)"; popupContent.append(popupHeader); let iconContainer = document.createElement("span"); iconContainer.style = "float: right; margin-left: 10px; height: 20px; color: var(--wikt-palette-black)"; popupHeader.append(iconContainer); let titleLink = document.createElement("a"); titleLink.href = link.href; titleLink.title = link.title; titleLink.className = "skin-invert-image preview-headerlink"; // Scrape entry content. if (isWPlink) { iconContainer.innerHTML = articleIcon; titleLink.textContent = linkTitle.replaceAll("_", " "); if (responseDocument.title) titleLink.innerHTML = responseDocument.title; // sometimes gives HTML text let WikipediaName = WP_LANGUAGES.get(API_domain.substr(8).split(".")[0]); popupHeader.append(WikipediaName + " Wikipedia có bài viết về ", titleLink); let articleContent = document.createElement("div"); articleContent.style.margin = "5px 0 0 10px"; let firstSection = responseDocument.querySelector("section"); if (firstSection && responseDocument.querySelector(`meta[property="mw:pageNamespace"][content="0"]`)) { firstSection.querySelectorAll("b").forEach(boldElem => boldElem.outerHTML = boldElem.innerHTML); firstSection.querySelectorAll(":scope > p, :scope > ul, :scope > ol").forEach(elem => articleContent.append(elem)); } if (articleContent.childElementCount) popupContent.append(articleContent); } else if (isGlossary) { iconContainer.innerHTML = glossaryIcon; titleLink.textContent = linkAnchor.replaceAll("_", " "); popupHeader.append("Glossary definition of ", titleLink); if (anchoredElement && anchoredElement.matches(".template-anchor")) { // Get succeeding `dd` elements. The HTML is <dt>...<span class="template-anchor"></span>...</dt><dd>...</dd><dd>...</dd> for (let elem = anchoredElement.parentElement.nextElementSibling; elem && elem.matches("dd"); elem = elem.nextElementSibling) { elem.style = "margin: 5px 0 0 15px"; popupContent.append(elem.cloneNode(true)); } } } else if (linkTitle.startsWith("Phụ lục:Thư mục học/")) { iconContainer.innerHTML = bibliographyIcon; let language = linkTitle.slice(22).replaceAll("_", " "); // trim "Appendix:Bibliography/" prefix popupHeader.append(`${language} bibliography`); if (anchoredElement && anchoredElement.matches(".bibliography-id")) { let bibliographyCitation = document.createElement("div"); bibliographyCitation.style = "margin: 5px 0 0 15px"; bibliographyCitation.innerHTML = anchoredElement.innerHTML; popupContent.append(bibliographyCitation); } } else { const hasDefinitions = elem => Boolean(elem.querySelector("ol:not(.references), .ja-see, .zh-see")); iconContainer.innerHTML = definitionIcon; // Try to resolve the anchor element to #Chinese. if (!anchoredElement && ["Tiếng Quan Thoại", "Gan", "Hakka", "Hokkien", "Literary_Chinese", "Mandarin", "Middle_Chinese", "Old_Chinese", "Wu", "Xiang"].includes(linkAnchor)) anchoredElement = responseDocument.querySelector("#Tiếng Trung Quốc"); // Try to guess the anchor target in the following order: English, Chinese, Translingual, [first h2 on page] // Always prioritize an L2 section which contains definitions. if (!linkAnchor) { let pageH2s = [...responseDocument.querySelectorAll("h2")]; if (pageH2s.some(h2 => hasDefinitions(h2.parentElement))) pageH2s = pageH2s.filter(h2 => hasDefinitions(h2.parentElement)); anchoredElement = pageH2s.find(h2 => h2.id === "English"); if (!anchoredElement) anchoredElement = pageH2s.find(h2 => h2.id === "Chinese"); if (!anchoredElement) anchoredElement = pageH2s.find(h2 => h2.id === "Translingual"); if (!anchoredElement) anchoredElement = pageH2s[0]; } let displayTitle = document.createElement("strong"); displayTitle.textContent = linkTitle.split("/").pop().replaceAll("_", " "); if (linkTitle.startsWith("Reconstruction:")) displayTitle.textContent = "*" + displayTitle.textContent; // Find localest section containing an h2. let languageSection = anchoredElement; while (languageSection && !languageSection.querySelector(":scope > h2")) languageSection = languageSection.parentElement.closest("section"); // Make sure that the entry is well-formed. if (languageSection) { let language = languageSection.querySelector(":scope > h2").textContent; let scrapeSection = anchoredElement.closest("section"); // Find localest section which contains any definitions. while (scrapeSection && !hasDefinitions(scrapeSection)) scrapeSection = scrapeSection.parentElement.closest("section"); if (scrapeSection) { let headwords = [...scrapeSection.querySelectorAll(".headword-line > strong")]; if (new Set(headwords.map(h => h.textContent)).size === 1) { // If there is a single unique headword, replace the display title with that. displayTitle = headwords[0]; if (displayTitle.querySelector("a > img")) displayTitle = displayTitle.querySelector("a > img").cloneNode(); } else if (new Set(headwords.map(h => h.cloneNode().outerHTML)).size === 1) { // If there is a single unique set of element attributes, use that with the display title. headwords[0].innerHTML = displayTitle.innerHTML; displayTitle = headwords[0]; } // Remove existing links in the headword. displayTitle.querySelectorAll("a").forEach(elem => elem.replaceWith(elem.textContent)); let ols = scrapeSection.querySelectorAll(":scope > ol:not(.references), section > ol:not(.references)"); if (anchoredElement.matches(".senseid")) { [...anchoredElement.parentElement.childNodes].forEach(child => { if (child !== anchoredElement) child.remove(); }); ols = [anchoredElement.closest("ol")]; } for (let ol of ols) { let POS_container = document.createElement("div"); POS_container.style.margin = "8px 0 5px 0"; let POS = document.createElement("span"); POS.style = "font-size: 110%; font-weight: bold"; POS.textContent = ol.closest("section").firstChild.textContent; POS_container.append(POS); let tlb = ol.closest("section").querySelector(".headword-line ~ .usage-label-term"); if (tlb) POS_container.append(document.createElement("br"), tlb); popupContent.append(POS_container, ol); } // {{ja-see}} and {{zh-see}} don't follow the normal format, so just insert them verbatim. for (let seeTemplate of scrapeSection.querySelectorAll(".ja-see, .zh-see")) popupContent.append(seeTemplate); } titleLink.append(displayTitle); popupHeader.append("Xem trước định nghĩa " + language + " ", titleLink); } else { titleLink.append(displayTitle); popupHeader.append("Xem trước định nghĩa ", titleLink); } } // Post-fetch error handling. if (popupContent.childElementCount > 1) { // Clean up HTML. popupContent.querySelectorAll("link, .previewonly, .maintenance-line, .mw-empty-elt, .reference, .Inline-Template, .vsHide").forEach(elem => elem.remove()); if (!isWPlink) popupContent.querySelectorAll("li > ul").forEach(elem => elem.remove()); // remove quotations for (let elem of popupContent.querySelectorAll("*")) elem.removeAttribute("id"); // avoid inadvertently repeating IDs within a page } else if (isWPlink) { // Display a message if the Wikipedia article was invalid or not in mainspace. let noArticle = document.createElement("div"); noArticle.style = "margin: 5px 0 0 15px; font-size: 90%"; noArticle.textContent = "(article content could not be previewed)"; popupContent.append(noArticle); } else if (linkAnchor && !anchoredElement) { // Display a message if the anchor is invalid. let noSectionFound = document.createElement("div"); noSectionFound.style = "margin: 10px 0 0 7.5px; font-size: 90%"; let strong = document.createElement("strong"); strong.textContent = linkAnchor.replaceAll("_", " "); noSectionFound.append("The ", strong, " section was not found on this page."); popupContent.append(noSectionFound); } else { // Display a message if no definitions were found in the section. let noDefinitionsFound = document.createElement("div"); noDefinitionsFound.style = "margin: 5px 0 0 15px; font-size: 90%"; noDefinitionsFound.textContent = "(không tìm thấy định nghĩa)"; popupContent.append(noDefinitionsFound); } // Reduce the padding if a scrollbar is present. The intended padding is 16px on each side. if (width - popupHeader.clientWidth > 32) popupContent.style.paddingRight = Math.max(4, 48 + popupHeader.clientWidth - width) + "px"; }); // (Hack?) make sure that `link pointerleave` doesn't cause the popup to immediately close. setTimeout(() => clearTimeout(popupTimer), 0); }, isPreviewLink ? 1200 : 400); }); link.addEventListener("pointerout", () => { clearTimeout(popupTimer); if (link === openLink) { popupTimer = setTimeout(closePopup, 300); } else { API_controller.abort(); API_controller = new AbortController(); } }); link.addEventListener("pointermove", event => [mouseX, mouseY] = [event.clientX, event.clientY]); link.addEventListener("click", () => clearTimeout(popupTimer)); } // Process all links. document.querySelectorAll("a").forEach(processLink); // Process links which are added to the DOM after the gadget has run. (new MutationObserver(events => events.flatMap(event => [...event.addedNodes]).forEach(node => { if (node instanceof HTMLAnchorElement) processLink(node); if (node instanceof HTMLElement) node.querySelectorAll("a").forEach(processLink); }))).observe(document.body, {childList: true, subtree: true}); // Dispatch hover events to support [[w:MediaWiki:Gadget-ReferenceTooltips.js]]. const linkEvent = ev => openLink && openLink.dispatchEvent(new Event(ev, {bubbles: true, cancelable: true})); // Note: for some reason an event listener on `popup` doesn't catch everything... popupContainer.addEventListener("pointerenter", () => setTimeout(() => linkEvent("mouseover"))); popupContainer.addEventListener("pointerleave", () => linkEvent("mouseout")); // Custom events popupContainer.addEventListener("pointerenter", () => linkEvent("linkPopupHover")); popupContainer.addEventListener("pointerleave", () => linkEvent("linkPopupUnhover")); // </nowiki> ovuydfpj83ga0m085ik9m34d0led8pu 2513945 2513942 2026-08-14T04:42:50Z Hiyuune 50834 2513945 javascript text/javascript // {{documentation}} // _________________________________________________________________________________________ // | | // | === WARNING: GLOBAL GADGET FILE === | // | Changes to this page affect many users. | // | Please discuss changes on the [[Wiktionary:Grease Pit]] before editing. | // |_________________________________________________________________________________________| mw.loader.load('//en.wikipedia.org/w/index.php?action=raw&title=MediaWiki:Gadget-navpop.css&ctype=text/css', "text/css"); // Forked from [[:w:MediaWiki:Gadget-popups.js]] $(function () { ////////////////////////////////////////////////// // Globals // // Trying to shove as many of these as possible into the pg (popup globals) object var pg = { re: {}, // regexps ns: {}, // namespaces string: {}, // translatable strings wiki: {}, // local site info user: {}, // current user info misc: {}, // YUCK PHOOEY option: {}, // options, see newOption etc optionDefault: {}, // default option values flag: {}, // misc flags cache: {}, // page and image cache structures: {}, // navlink structures timer: {}, // all sorts of timers (too damn many) counter: {}, // .. and all sorts of counters current: {}, // state info fn: {}, // functions endoflist: null }; /* Bail if the gadget/script is being loaded twice */ if( window.pg ) { return; } /* Export to global context */ window.pg = pg; /// Local Variables: /// /// mode:c /// /// End: /// // ENDFILE: main.js // STARTFILE: actions.js function setupTooltips(container, remove, force, popData) { log('setupTooltips, container='+container+', remove='+remove); if (!container) { //<NOLITE> // the main initial call if (getValueOf('popupOnEditSelection') && document && document.editform && document.editform.wpTextbox1) { document.editform.wpTextbox1.onmouseup=doSelectionPopup; } //</NOLITE> // article/content is a structure-dependent thing container = defaultPopupsContainer(); } if (!remove && !force && container.ranSetupTooltipsAlready) { return; } container.ranSetupTooltipsAlready = !remove; var anchors; anchors=container.getElementsByTagName('A'); setupTooltipsLoop(anchors, 0, 250, 100, remove, popData); } function defaultPopupsContainer() { if (getValueOf('popupOnlyArticleLinks')) { return document.getElementById('mw_content') || document.getElementById('content') || document.getElementById('article') || document; } return document; } function setupTooltipsLoop(anchors,begin,howmany,sleep, remove, popData) { log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments)); var finish=begin+howmany; var loopend = Math.min(finish, anchors.length); var j=loopend - begin; log ('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin + ', howmany=' + howmany + ', loopend=' + loopend + ', remove=' + remove); var doTooltip= remove ? removeTooltip : addTooltip; // try a faster (?) loop construct if (j > 0) { do { var a=anchors[loopend - j]; if (typeof a==='undefined' || !a || !a.href) { log('got null anchor at index ' + loopend - j); continue; } doTooltip(a, popData); } while (--j); } if (finish < anchors.length) { setTimeout(function() { setupTooltipsLoop(anchors,finish,howmany,sleep,remove,popData);}, sleep); } else { if ( !remove && ! getValueOf('popupTocLinks')) { rmTocTooltips(); } pg.flag.finishedLoading=true; } } // eliminate popups from the TOC // This also kills any onclick stuff that used to be going on in the toc function rmTocTooltips() { var toc=document.getElementById('toc'); if (toc) { var tocLinks=toc.getElementsByTagName('A'); var tocLen = tocLinks.length; for (var j=0; j<tocLen; ++j) { removeTooltip(tocLinks[j], true); } } } function addTooltip(a, popData) { if ( !isPopupLink(a) ) { return; } a.onmouseover=mouseOverWikiLink; a.onmouseout= mouseOutWikiLink; a.onmousedown = killPopup; a.hasPopup = true; a.popData = popData; } function removeTooltip(a) { if ( !a.hasPopup ) { return; } a.onmouseover = null; a.onmouseout = null; if (a.originalTitle) { a.title = a.originalTitle; } a.hasPopup=false; } function removeTitle(a) { if (!a.originalTitle) { a.originalTitle=a.title; } a.title=''; } function restoreTitle(a) { if ( a.title || !a.originalTitle ) { return; } a.title = a.originalTitle; } function registerHooks(np) { var popupMaxWidth=getValueOf('popupMaxWidth'); if (typeof popupMaxWidth === 'number') { var setMaxWidth = function () { np.mainDiv.style.maxWidth = popupMaxWidth + 'px'; np.maxWidth = popupMaxWidth; }; np.addHook(setMaxWidth, 'unhide', 'before'); } //<NOLITE> np.addHook(addPopupShortcuts, 'unhide', 'after'); np.addHook(rmPopupShortcuts, 'hide', 'before'); //</NOLITE> } function removeModifierKeyHandler(a) { //remove listeners for modifier key if any that were added in mouseOverWikiLink document.removeEventListener('keydown', a.modifierKeyHandler, false); document.removeEventListener('keyup', a.modifierKeyHandler, false); } function mouseOverWikiLink(evt) { if (!evt && window.event) {evt=window.event;} // if the modifier is needed, listen for it, // we will remove the listener when we mouseout of this link or kill popup. if (getValueOf('popupModifier')) { // if popupModifierAction = enable, we should popup when the modifier is pressed // if popupModifierAction = disable, we should popup unless the modifier is pressed var action = getValueOf('popupModifierAction'); var key = action=='disable' ? 'keyup' : 'keydown'; var a = this; a.modifierKeyHandler = function(evt) { mouseOverWikiLink2(a, evt); }; document.addEventListener(key, a.modifierKeyHandler, false); } return mouseOverWikiLink2(this, evt); } /** * Gets the references list item that the provided footnote link targets. This * is typically a li element within the ol.references element inside the reflist. * @param {Element} a - A footnote link. * @returns {Element|boolean} The targeted element, or false if one can't be found. */ function footnoteTarget(a) { var aTitle=Title.fromAnchor(a); // We want ".3A" rather than "%3A" or "?" here, so use the anchor property directly var anch = aTitle.anchor; if ( ! /^(cite_note-|_note-|endnote)/.test(anch) ) { return false; } var lTitle=Title.fromURL(location.href); if ( lTitle.toString(true) !== aTitle.toString(true) ) { return false; } var el=document.getElementById(anch); while ( el && typeof el.nodeName === 'string') { var nt = el.nodeName.toLowerCase(); if ( nt === 'li' ) { return el; } else if ( nt === 'body' ) { return false; } else if ( el.parentNode ) { el=el.parentNode; } else { return false; } } return false; } function footnotePreview(x, navpop) { setPopupHTML('<hr />' + x.innerHTML, 'popupPreview', navpop.idNumber); } function modifierPressed(evt) { var mod=getValueOf('popupModifier'); if (!mod) { return false; } if (!evt && window.event) {evt=window.event;} return ( evt && mod && evt[mod.toLowerCase() + 'Key'] ); } // Checks if the correct modifier pressed/unpressed if needed function isCorrectModifier(a,evt) { if (!getValueOf('popupModifier')) { return true; } // if popupModifierAction = enable, we should popup when the modifier is pressed // if popupModifierAction = disable, we should popup unless the modifier is pressed var action = getValueOf('popupModifierAction'); return ( action == 'enable' && modifierPressed(evt) || action == 'disable' && !modifierPressed(evt) ); } function mouseOverWikiLink2(a, evt) { if (!isCorrectModifier(a,evt)) { return; } if ( getValueOf('removeTitles') ) { removeTitle(a); } if ( a==pg.current.link && a.navpopup && a.navpopup.isVisible() ) { return; } pg.current.link=a; if (getValueOf('simplePopups') && !pg.option.popupStructure) { // reset *default value* of popupStructure setDefault('popupStructure', 'original'); } var article=(new Title()).fromAnchor(a); // set global variable (ugh) to hold article (wikipage) pg.current.article = article; if (!a.navpopup) { a.navpopup=newNavpopup(a, article); pg.current.linksHash[a.href] = a.navpopup; pg.current.links.push(a); } if (a.navpopup.pending === null || a.navpopup.pending !== 0) { // either fresh popups or those with unfinshed business are redone from scratch simplePopupContent(a, article); } a.navpopup.showSoonIfStable(a.navpopup.delay); clearInterval(pg.timer.checkPopupPosition); pg.timer.checkPopupPosition=setInterval(checkPopupPosition, 600); if(getValueOf('simplePopups')) { if (getValueOf('popupPreviewButton') && !a.simpleNoMore) { var d=document.createElement('div'); d.className='popupPreviewButtonDiv'; var s=document.createElement('span'); d.appendChild(s); s.className='popupPreviewButton'; s['on' + getValueOf('popupPreviewButtonEvent')] = function() { a.simpleNoMore=true; d.style.display = "none"; nonsimplePopupContent(a,article); }; s.innerHTML=popupString('show preview'); setPopupHTML(d, 'popupPreview', a.navpopup.idNumber); } } if (a.navpopup.pending !== 0 ) { nonsimplePopupContent(a, article); } } // simplePopupContent: the content that do not require additional download // (it is shown even when simplePopups is true) function simplePopupContent(a, article) { /* FIXME hack */ a.navpopup.hasPopupMenu=false; a.navpopup.setInnerHTML(popupHTML(a)); fillEmptySpans({navpopup:a.navpopup}); if (getValueOf('popupDraggable')) { var dragHandle = getValueOf('popupDragHandle') || null; if (dragHandle && dragHandle != 'all') { dragHandle += a.navpopup.idNumber; } setTimeout(function(){a.navpopup.makeDraggable(dragHandle);}, 150); } //<NOLITE> if (getValueOf('popupRedlinkRemoval') && a.className=='new') { setPopupHTML('<br>'+popupRedlinkHTML(article), 'popupRedlink', a.navpopup.idNumber); } //</NOLITE> } function debugData(navpopup) { if(getValueOf('popupDebugging') && navpopup.idNumber) { setPopupHTML('idNumber='+navpopup.idNumber + ', pending=' + navpopup.pending, 'popupError', navpopup.idNumber); } } function newNavpopup(a, article) { var navpopup = new Navpopup(); navpopup.fuzz=5; navpopup.delay=getValueOf('popupDelay')*1000; // increment global counter now navpopup.idNumber = ++pg.idNumber; navpopup.parentAnchor = a; navpopup.parentPopup = (a.popData && a.popData.owner); navpopup.article = article; registerHooks(navpopup); return navpopup; } // Should we show nonsimple context? // If simplePopups is set to true, then we do not show nonsimple context, // but if a bottom "show preview" was clicked we do show nonsimple context function shouldShowNonSimple(a) { return !getValueOf('simplePopups') || a.simpleNoMore; } // Should we show nonsimple context govern by the option (e.g. popupUserInfo)? // If the user explicitly asked for nonsimple context by setting the option to true, // then we show it even in nonsimple mode. function shouldShow(a,option) { if (shouldShowNonSimple(a)) { return getValueOf(option); } else { return (typeof window[option] != 'undefined' ) && window[option]; } } function nonsimplePopupContent(a, article) { var diff=null, history=null; var params=parseParams(a.href); var oldid=(typeof params.oldid=='undefined' ? null : params.oldid); //<NOLITE> if(shouldShow(a,'popupPreviewDiffs')) { diff=params.diff; } if(shouldShow(a,'popupPreviewHistory')) { history=(params.action=='history'); } //</NOLITE> a.navpopup.pending=0; var referenceElement = footnoteTarget(a); if (referenceElement) { footnotePreview(referenceElement, a.navpopup); //<NOLITE> } else if ( diff || diff === 0 ) { loadDiff(article, oldid, diff, a.navpopup); } else if ( history ) { loadAPIPreview('history', article, a.navpopup); } else if ( shouldShowNonSimple(a) && pg.re.contribs.test(a.href) ) { loadAPIPreview('contribs', article, a.navpopup); } else if ( shouldShowNonSimple(a) && pg.re.backlinks.test(a.href) ) { loadAPIPreview('backlinks', article, a.navpopup); } else if ( // FIXME should be able to get all preview combinations with options article.namespaceId()==pg.nsImageId && ( shouldShow(a,'imagePopupsForImages') || ! anchorContainsImage(a) ) ) { loadAPIPreview('imagepagepreview', article, a.navpopup); loadImage(article, a.navpopup); //</NOLITE> } else { if (article.namespaceId() == pg.nsCategoryId && shouldShow(a,'popupCategoryMembers')) { loadAPIPreview('category', article, a.navpopup); } else if ((article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) && shouldShow(a,'popupUserInfo')) { loadAPIPreview('userinfo', article, a.navpopup); } if (shouldShowNonSimple(a)) startArticlePreview(article, oldid, a.navpopup); } } function pendingNavpopTask(navpop) { if (navpop && navpop.pending === null) { navpop.pending=0; } ++navpop.pending; debugData(navpop); } function completedNavpopTask(navpop) { if (navpop && navpop.pending) { --navpop.pending; } debugData(navpop); } function startArticlePreview(article, oldid, navpop) { navpop.redir=0; loadPreview(article, oldid, navpop); } function loadPreview(article, oldid, navpop) { if (!navpop.redir) { navpop.originalArticle=article; } article.oldid = oldid; loadAPIPreview('revision', article, navpop); } function loadPreviewFromRedir(redirMatch, navpop) { // redirMatch is a regex match var target = new Title().fromWikiText(redirMatch[2]); // overwrite (or add) anchor from original target // mediawiki does overwrite; eg User:Lupin/foo3#Done if ( navpop.article.anchor ) { target.anchor = navpop.article.anchor; } navpop.redir++; navpop.redirTarget=target; //<NOLITE> var warnRedir = redirLink(target, navpop.article); setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber); //</NOLITE> navpop.article=target; fillEmptySpans({redir: true, redirTarget: target, navpopup:navpop}); return loadPreview(target, null, navpop); } function insertPreview(download) { if (!download.owner) { return; } var redirMatch = pg.re.redirect.exec(download.data); if (download.owner.redir === 0 && redirMatch) { loadPreviewFromRedir(redirMatch, download.owner); return; } if (download.owner.visible || !getValueOf('popupLazyPreviews')) { insertPreviewNow(download); } else { var id=(download.owner.redir) ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK'; download.owner.addHook( function(){insertPreviewNow(download); return true;}, 'unhide', 'after', id ); } } function insertPreviewNow(download) { if (!download.owner) { return; } var wikiText=download.data; var navpop=download.owner; var art=navpop.redirTarget || navpop.originalArticle; //<NOLITE> makeFixDabs(wikiText, navpop); if (getValueOf('popupSummaryData')) { getPageInfo(wikiText, download); setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber); } var imagePage=''; if (art.namespaceId()==pg.nsImageId) { imagePage=art.toString(); } else { imagePage=getValidImageFromWikiText(wikiText); } if(imagePage) { loadImage(Title.fromWikiText(imagePage), navpop); } //</NOLITE> if (getValueOf('popupPreviews')) { insertArticlePreview(download, art, navpop); } } function insertArticlePreview(download, art, navpop) { if (download && typeof download.data == typeof ''){ if (art.namespaceId()==pg.nsTemplateId && getValueOf('popupPreviewRawTemplates')) { // FIXME compare/consolidate with diff escaping code for wikitext var h='<hr /><span style="font-family: monospace;">' + download.data.entify().split('\\n').join('<br />\\n') + '</span>'; setPopupHTML(h, 'popupPreview', navpop.idNumber); } else { var p=prepPreviewmaker(download.data, art, navpop); p.showPreview(); } } } function prepPreviewmaker(data, article, navpop) { // deal with tricksy anchors var d=anchorize(data, article.anchorString()); var urlBase=joinPath([pg.wiki.articlebase, article.urlString()]); var p=new Previewmaker(d, urlBase, navpop); return p; } // Try to imitate the way mediawiki generates HTML anchors from section titles function anchorize(d, anch) { if (!anch) { return d; } var anchRe=RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*'+getValueOf('popupAnchorRegexp')+'\\s*(?:\\|[^|}]*)*?\\s*'+literalizeRegex(anch)+'\\s*(?:\\|[^}]*)?}})'); var match=d.match(anchRe); if(match && match.length > 0 && match[0]) { return d.substring(d.indexOf(match[0])); } // now try to deal with == foo [[bar|baz]] boom == -> #foo_baz_boom var lines=d.split('\n'); for (var i=0; i<lines.length; ++i) { lines[i]=lines[i].replace(RegExp('[[]{2}([^|\\]]*?[|])?(.*?)[\\]]{2}', 'g'), '$2') .replace(/'''([^'])/g, '$1').replace(RegExp("''([^'])", 'g'), '$1'); if (lines[i].match(anchRe)) { return d.split('\n').slice(i).join('\n').replace(RegExp('^[^=]*'), ''); } } return d; } function killPopup() { removeModifierKeyHandler(this); if (getValueOf('popupShortcutKeys')) { rmPopupShortcuts(); } if (!pg) { return; } if (pg.current.link && pg.current.link.navpopup) { pg.current.link.navpopup.banish(); } pg.current.link=null; abortAllDownloads(); if (pg.timer.checkPopupPosition) { clearInterval(pg.timer.checkPopupPosition); pg.timer.checkPopupPosition=null; } return true; // preserve default action } // ENDFILE: actions.js // STARTFILE: domdrag.js /** @fileoverview The {@link Drag} object, which enables objects to be dragged around. <pre> ************************************************* dom-drag.js 09.25.2001 www.youngpup.net ************************************************** 10.28.2001 - fixed minor bug where events sometimes fired off the handle, not the root. ************************************************* Pared down, some hooks added by User:Lupin Copyright Aaron Boodman. Saying stupid things daily since March 2001. </pre> */ /** Creates a new Drag object. This is used to make various DOM elements draggable. @constructor */ function Drag () { /** Condition to determine whether or not to drag. This function should take one parameter, an Event. To disable this, set it to <code>null</code>. @type Function */ this.startCondition = null; /** Hook to be run when the drag finishes. This is passed the final coordinates of the dragged object (two integers, x and y). To disables this, set it to <code>null</code>. @type Function */ this.endHook = null; } /** Gets an event in a cross-browser manner. @param {Event} e @private */ Drag.prototype.fixE = function(e) { if (typeof e == 'undefined') { e = window.event; } if (typeof e.layerX == 'undefined') { e.layerX = e.offsetX; } if (typeof e.layerY == 'undefined') { e.layerY = e.offsetY; } return e; }; /** Initialises the Drag instance by telling it which object you want to be draggable, and what you want to drag it by. @param {DOMElement} o The "handle" by which <code>oRoot</code> is dragged. @param {DOMElement} oRoot The object which moves when <code>o</code> is dragged, or <code>o</code> if omitted. */ Drag.prototype.init = function(o, oRoot) { var dragObj = this; this.obj = o; o.onmousedown = function(e) { dragObj.start.apply( dragObj, [e]); }; o.dragging = false; o.popups_draggable = true; o.hmode = true; o.vmode = true; o.root = oRoot ? oRoot : o ; if (isNaN(parseInt(o.root.style.left, 10))) { o.root.style.left = "0px"; } if (isNaN(parseInt(o.root.style.top, 10))) { o.root.style.top = "0px"; } o.root.onthisStart = function(){}; o.root.onthisEnd = function(){}; o.root.onthis = function(){}; }; /** Starts the drag. @private @param {Event} e */ Drag.prototype.start = function(e) { var o = this.obj; // = this; e = this.fixE(e); if (this.startCondition && !this.startCondition(e)) { return; } var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10); o.root.onthisStart(x, y); o.lastMouseX = e.clientX; o.lastMouseY = e.clientY; var dragObj = this; o.onmousemoveDefault = document.onmousemove; o.dragging = true; document.onmousemove = function(e) { dragObj.drag.apply( dragObj, [e] ); }; document.onmouseup = function(e) { dragObj.end.apply( dragObj, [e] ); }; return false; }; /** Does the drag. @param {Event} e @private */ Drag.prototype.drag = function(e) { e = this.fixE(e); var o = this.obj; var ey = e.clientY; var ex = e.clientX; var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10 ); var nx, ny; nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1)); ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1)); this.obj.root.style[o.hmode ? "left" : "right"] = nx + "px"; this.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px"; this.obj.lastMouseX = ex; this.obj.lastMouseY = ey; this.obj.root.onthis(nx, ny); return false; }; /** Ends the drag. @private */ Drag.prototype.end = function() { document.onmousemove=this.obj.onmousemoveDefault; document.onmouseup = null; this.obj.dragging = false; if (this.endHook) { this.endHook( parseInt(this.obj.root.style[this.obj.hmode ? "left" : "right"], 10), parseInt(this.obj.root.style[this.obj.vmode ? "top" : "bottom"], 10)); } }; // ENDFILE: domdrag.js // STARTFILE: structures.js //<NOLITE> pg.structures.original={}; pg.structures.original.popupLayout=function () { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupUserData', 'popupData', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; pg.structures.original.popupRedirSpans=function () { return ['popupRedir', 'popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks']; }; pg.structures.original.popupTitle=function (x) { log ('defaultstructure.popupTitle'); if (!getValueOf('popupNavLinks')) { return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params); } return ''; }; pg.structures.original.popupTopLinks=function (x) { log ('defaultstructure.popupTopLinks'); if (getValueOf('popupNavLinks')) { return navLinksHTML(x.article, x.hint, x.params); } return ''; }; pg.structures.original.popupImage=function(x) { log ('original.popupImage, x.article='+x.article+', x.navpop.idNumber='+x.navpop.idNumber); return imageHTML(x.article, x.navpop.idNumber); }; pg.structures.original.popupRedirTitle=pg.structures.original.popupTitle; pg.structures.original.popupRedirTopLinks=pg.structures.original.popupTopLinks; function copyStructure(oldStructure, newStructure) { pg.structures[newStructure]={}; for (var prop in pg.structures[oldStructure]) { pg.structures[newStructure][prop]=pg.structures[oldStructure][prop]; } } copyStructure('original', 'nostalgia'); pg.structures.nostalgia.popupTopLinks=function(x) { var str=''; str += '<b><<mainlink|shortcut= >></b>'; // user links // contribs - log - count - email - block // count only if applicable; block only if popupAdminLinks str += 'if(user){<br><<contribs|shortcut=c>>'; str+='if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}'; // editing links // talkpage -> edit|new - history - un|watch - article|edit // other page -> edit - history - un|watch - talk|edit|new var editstr='<<edit|shortcut=e>>'; var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'; var historystr='<<history|shortcut=h>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; str += '<br>if(talk){' + editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + // not a talk page editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}'; // misc links str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>'; str += 'if(admin){<br>}else{*}<<move|shortcut=m>>'; // admin links str += 'if(admin){*<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>}'; return navlinkStringToHTML(str, x.article, x.params); }; pg.structures.nostalgia.popupRedirTopLinks=pg.structures.nostalgia.popupTopLinks; /** -- fancy -- **/ copyStructure('original', 'fancy'); pg.structures.fancy.popupTitle=function (x) { return navlinkStringToHTML('<font size=+0><<mainlink>></font>',x.article,x.params); }; pg.structures.fancy.popupTopLinks=function(x) { var hist='<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>|<<editors|shortcut=E|eds>>'; var watch='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; var move='<<move|shortcut=m|move>>'; return navlinkStringToHTML('if(talk){' + '<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' + hist + '*' + '<<article|shortcut=a>>|<<editArticle|edit>>' + '*' + watch + '*' + move + '}else{<<edit|shortcut=e>>*' + hist + '*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' + '*' + watch + '*' + move+'}<br>', x.article, x.params); }; pg.structures.fancy.popupOtherLinks=function(x) { var admin='<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*<<undelete|undeleteShort>>|<<delete|shortcut=d|del>>'; var user='<<contribs|shortcut=c>>if(wikimedia){|<<count|shortcut=#|#>>}'; user+='if(ipuser){|<<arin>>}else{*<<email|shortcut=E|'+ popupString('email')+'>>}if(admin){*<<block|shortcut=b>>}'; var normal='<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>'; return navlinkStringToHTML('<br>if(user){' + user + '*}if(admin){'+admin+'if(user){<br>}else{*}}' + normal, x.article, x.params); }; pg.structures.fancy.popupRedirTitle=pg.structures.fancy.popupTitle; pg.structures.fancy.popupRedirTopLinks=pg.structures.fancy.popupTopLinks; pg.structures.fancy.popupRedirOtherLinks=pg.structures.fancy.popupOtherLinks; /** -- fancy2 -- **/ // hack for User:MacGyverMagic copyStructure('fancy', 'fancy2'); pg.structures.fancy2.popupTopLinks=function(x) { // hack out the <br> at the end and put one at the beginning return '<br>'+pg.structures.fancy.popupTopLinks(x).replace(RegExp('<br>$','i'),''); }; pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title return ['popupError', 'popupImage', 'popupTitle', 'popupUserData', 'popupData', 'popupTopLinks', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; /** -- menus -- **/ copyStructure('original', 'menus'); pg.structures.menus.popupLayout=function () { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupUserData', 'popupData', 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; pg.structures.menus.popupTopLinks = function (x, shorter) { // FIXME maybe this stuff should be cached var s=[]; var dropdiv='<div class="popup_drop">'; var enddiv='</div>'; var hist='<<history|shortcut=h>>'; if (!shorter) { hist = '<menurow>' + hist + '|<<historyfeed|rss>>|<<editors|shortcut=E>></menurow>'; } var lastedit='<<lastEdit|shortcut=/|show last edit>>'; var thank='if(diff){<<thank|send thanks>>}'; var jsHistory='<<lastContrib|last set of edits>><<sinceMe|changes since mine>>'; var linkshere='<<whatLinksHere|shortcut=l|what links here>>'; var related='<<relatedChanges|shortcut=r|related changes>>'; var search='<menurow><<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}' + '|<<google|shortcut=G|web>></menurow>'; var watch='<menurow><<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>></menurow>'; var protect='<menurow><<unprotect|unprotectShort>>|' + '<<protect|shortcut=p>>|<<protectlog|log>></menurow>'; var del='<menurow><<undelete|undeleteShort>>|<<delete|shortcut=d>>|' + '<<deletelog|log>></menurow>'; var move='<<move|shortcut=m|move page>>'; var nullPurge='<menurow><<nullEdit|shortcut=n|null edit>>|<<purge|shortcut=P>></menurow>'; var viewOptions='<menurow><<view|shortcut=v>>|<<render|shortcut=S>>|<<raw>></menurow>'; var editRow='if(oldid){' + '<menurow><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></menurow>' + '<menurow><<revert|shortcut=v>>|<<undo>></menurow>' + '}else{<<edit|shortcut=e>>}'; var markPatrolled='if(rcid){<<markpatrolled|mark patrolled>>}'; var newTopic='if(talk){<<new|shortcut=+|new topic>>}'; var protectDelete='if(admin){' + protect + del + '}'; if (getValueOf('popupActionsMenu')) { s.push( '<<mainlink>>*' + dropdiv + menuTitle('actions')); } else { s.push( dropdiv + '<<mainlink>>'); } s.push( '<menu>'); s.push( editRow + markPatrolled + newTopic + hist + lastedit + thank ); if (!shorter) { s.push(jsHistory); } s.push( move + linkshere + related); if (!shorter) { s.push(nullPurge + search); } if (!shorter) { s.push(viewOptions); } s.push('<hr />' + watch + protectDelete); s.push('<hr />' + 'if(talk){<<article|shortcut=a|view article>><<editArticle|edit article>>}' + 'else{<<talk|shortcut=t|talk page>><<editTalk|edit talk>>' + '<<newTalk|shortcut=+|new topic>>}</menu>' + enddiv); // user menu starts here var email='<<email|shortcut=E|email user>>'; var contribs= 'if(wikimedia){<menurow>}<<contribs|shortcut=c|contributions>>if(wikimedia){</menurow>}' + 'if(admin){<menurow><<deletedContribs>></menurow>}'; s.push('if(user){*' + dropdiv + menuTitle('user')); s.push('<menu>'); s.push('<menurow><<userPage|shortcut=u|user&nbsp;page>>|<<userSpace|space>></menurow>'); s.push('<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' + '<<newUserTalk|shortcut=+|leave comment>>'); if(!shorter) { s.push( 'if(ipuser){<<arin>>}else{' + email + '}' ); } else { s.push( 'if(ipuser){}else{' + email + '}' ); } s.push('<hr />' + contribs + '<<userlog|shortcut=L|user log>>'); s.push('if(wikimedia){<<count|shortcut=#|edit counter>>}'); s.push('if(admin){<menurow><<unblock|unblockShort>>|<<block|shortcut=b|block user>></menurow>}'); s.push('<<blocklog|shortcut=B|block log>>'); s.push('</menu>' + enddiv + '}'); // popups menu starts here if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */) { x.navpop.hasPopupMenu=true; s.push('*' + dropdiv + menuTitle('popupsMenu') + '<menu>'); s.push('<<togglePreviews|toggle previews>>'); s.push('<<purgePopups|reset>>'); s.push('<<disablePopups|disable>>'); s.push('</menu>'+enddiv); } return navlinkStringToHTML(s.join(''), x.article, x.params); }; function menuTitle(s) { return '<a href="#" noPopup=1>' + popupString(s) + '</a>'; } pg.structures.menus.popupRedirTitle=pg.structures.menus.popupTitle; pg.structures.menus.popupRedirTopLinks=pg.structures.menus.popupTopLinks; copyStructure('menus', 'shortmenus'); pg.structures.shortmenus.popupTopLinks=function(x) { return pg.structures.menus.popupTopLinks(x,true); }; pg.structures.shortmenus.popupRedirTopLinks=pg.structures.shortmenus.popupTopLinks; //</NOLITE> pg.structures.lite={}; pg.structures.lite.popupLayout=function () { return ['popupTitle', 'popupPreview' ]; }; pg.structures.lite.popupTitle=function (x) { log (x.article + ': structures.lite.popupTitle'); //return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params); return '<div><span class="popup_mainlink"><b>' + x.article.toString() + '</b></span></div>'; }; // ENDFILE: structures.js // STARTFILE: autoedit.js //<NOLITE> function substitute(data,cmdBody) { // alert('sub\nfrom: '+cmdBody.from+'\nto: '+cmdBody.to+'\nflags: '+cmdBody.flags); var fromRe=RegExp(cmdBody.from, cmdBody.flags); return data.replace(fromRe, cmdBody.to); } function execCmds(data, cmdList) { for (var i=0; i<cmdList.length; ++i) { data=cmdList[i].action(data, cmdList[i]); } return data; } function parseCmd(str) { // returns a list of commands if (!str.length) { return []; } var p=false; switch (str.charAt(0)) { case 's': p=parseSubstitute(str); break; default: return false; } if (p) { return [p].concat(parseCmd(p.remainder)); } return false; } function unEscape(str, sep) { return str.split('\\\\').join('\\').split('\\'+sep).join(sep).split('\\n').join('\n'); } function parseSubstitute(str) { // takes a string like s/a/b/flags;othercmds and parses it var from,to,flags,tmp; if (str.length<4) { return false; } var sep=str.charAt(1); str=str.substring(2); tmp=skipOver(str,sep); if (tmp) { from=tmp.segment; str=tmp.remainder; } else { return false; } tmp=skipOver(str,sep); if (tmp) { to=tmp.segment; str=tmp.remainder; } else { return false; } flags=''; if (str.length) { tmp=skipOver(str,';') || skipToEnd(str, ';'); if (tmp) {flags=tmp.segment; str=tmp.remainder; } } return {action: substitute, from: from, to: to, flags: flags, remainder: str}; } function skipOver(str,sep) { var endSegment=findNext(str,sep); if (endSegment<0) { return false; } var segment=unEscape(str.substring(0,endSegment), sep); return {segment: segment, remainder: str.substring(endSegment+1)}; } /*eslint-disable*/ function skipToEnd(str,sep) { return {segment: str, remainder: ''}; } /*eslint-enable */ function findNext(str, ch) { for (var i=0; i<str.length; ++i) { if (str.charAt(i)=='\\') { i+=2; } if (str.charAt(i)==ch) { return i; } } return -1; } function setCheckbox(param, box) { var val=mw.util.getParamValue(param); if (val) { switch (val) { case '1': case 'yes': case 'true': box.checked=true; break; case '0': case 'no': case 'false': box.checked=false; } } } function autoEdit() { setupPopups( function () { if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version') ) { return false; } if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken')===autoClickToken()) { pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action')); } if (!document.editform) { return false; } if (autoEdit.alreadyRan) { return false; } autoEdit.alreadyRan=true; var cmdString=mw.util.getParamValue('autoedit'); if (cmdString) { try { var editbox=document.editform.wpTextbox1; var cmdList=parseCmd(cmdString); var input=editbox.value; var output=execCmds(input, cmdList); editbox.value=output; } catch (dang) { return; } // wikEd user script compatibility if (typeof(wikEdUseWikEd) != 'undefined') { if (wikEdUseWikEd === true) { WikEdUpdateFrame(); } } } setCheckbox('autominor', document.editform.wpMinoredit); setCheckbox('autowatch', document.editform.wpWatchthis); var rvid = mw.util.getParamValue('autorv'); if (rvid) { var url=pg.wiki.apiwikibase + '?action=query&format=json&formatversion=2&prop=revisions&revids='+rvid; startDownload(url, null, autoEdit2); } else { autoEdit2(); } } ); } function autoEdit2(d) { var summary=mw.util.getParamValue('autosummary'); var summaryprompt=mw.util.getParamValue('autosummaryprompt'); var summarynotice=''; if (d && d.data && mw.util.getParamValue('autorv')) { var s = getRvSummary(summary, d.data); if (s === false) { summaryprompt=true; summarynotice=popupString('Failed to get revision information, please edit manually.\n\n'); summary = simplePrintf(summary, [mw.util.getParamValue('autorv'), '(unknown)', '(unknown)']); } else { summary = s; } } if (summaryprompt) { var txt= summarynotice + popupString('Enter a non-empty edit summary or press cancel to abort'); var response=prompt(txt, summary); if (response) { summary=response; } else { return; } } if (summary) { document.editform.wpSummary.value=summary; } // Attempt to avoid possible premature clicking of the save button // (maybe delays in updates to the DOM are to blame?? or a red herring) setTimeout(autoEdit3, 100); } function autoClickToken() { return mw.user.sessionId(); } function autoEdit3() { if( mw.util.getParamValue('actoken') != autoClickToken()) { return; } var btn=mw.util.getParamValue('autoclick'); if (btn) { if (document.editform && document.editform[btn]) { var button=document.editform[btn]; var msg=tprintf('The %s button has been automatically clicked. Please wait for the next page to load.', [ button.value ]); bannerMessage(msg); document.title='('+document.title+')'; button.click(); } else { alert(tprintf('Could not find button %s. Please check the settings in your javascript file.', [ btn ])); } } } function bannerMessage(s) { var headings=document.getElementsByTagName('h1'); if (headings) { var div=document.createElement('div'); div.innerHTML='<font size=+1><b>' + s + '</b></font>'; headings[0].parentNode.insertBefore(div, headings[0]); } } function getRvSummary(template, json) { try { var o=getJsObj(json); var edit = anyChild(o.query.pages).revisions[0]; var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, ''); return simplePrintf(template, [edit.revid, timestamp, edit.userhidden ? '(hidden)' : edit.user ]); } catch (badness) { return false; } } //</NOLITE> // ENDFILE: autoedit.js // STARTFILE: downloader.js /** @fileoverview {@link Downloader}, a xmlhttprequest wrapper, and helper functions. */ /** Creates a new Downloader @constructor @class The Downloader class. Create a new instance of this class to download stuff. @param {String} url The url to download. This can be omitted and supplied later. */ function Downloader(url) { if (typeof XMLHttpRequest!='undefined') { this.http = new XMLHttpRequest(); } /** The url to download @type String */ this.url = url; /** A universally unique ID number @type integer */ this.id=null; /** Modification date, to be culled from the incoming headers @type Date @private */ this.lastModified = null; /** What to do when the download completes successfully @type Function @private */ this.callbackFunction = null; /** What to do on failure @type Function @private */ this.onFailure = null; /** Flag set on <code>abort</code> @type boolean */ this.aborted = false; /** HTTP method. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for possibilities. @type String */ this.method='GET'; /** Async flag. @type boolean */ this.async=true; } new Downloader(); /** Submits the http request. */ Downloader.prototype.send = function (x) { if (!this.http) { return null; } return this.http.send(x); }; /** Aborts the download, setting the <code>aborted</code> field to true. */ Downloader.prototype.abort = function () { if (!this.http) { return null; } this.aborted=true; return this.http.abort(); }; /** Returns the downloaded data. */ Downloader.prototype.getData = function () {if (!this.http) { return null; } return this.http.responseText;}; /** Prepares the download. */ Downloader.prototype.setTarget = function () { if (!this.http) { return null; } this.http.open(this.method, this.url, this.async); this.http.setRequestHeader( 'Api-User-Agent', pg.misc.userAgent ); }; /** Gets the state of the download. */ Downloader.prototype.getReadyState=function () {if (!this.http) { return null; } return this.http.readyState;}; pg.misc.downloadsInProgress = { }; /** Starts the download. Note that setTarget {@link Downloader#setTarget} must be run first */ Downloader.prototype.start=function () { if (!this.http) { return; } pg.misc.downloadsInProgress[this.id] = this; this.http.send(null); }; /** Gets the 'Last-Modified' date from the download headers. Should be run after the download completes. Returns <code>null</code> on failure. @return {Date} */ Downloader.prototype.getLastModifiedDate=function () { if(!this.http) { return null; } var lastmod=null; try { lastmod=this.http.getResponseHeader('Last-Modified'); } catch (err) {} if (lastmod) { return new Date(lastmod); } return null; }; /** Sets the callback function. @param {Function} f callback function, called as <code>f(this)</code> on success */ Downloader.prototype.setCallback = function (f) { if(!this.http) { return; } this.http.onreadystatechange = f; }; Downloader.prototype.getStatus = function() { if (!this.http) { return null; } return this.http.status; }; ////////////////////////////////////////////////// // helper functions /** Creates a new {@link Downloader} and prepares it for action. @param {String} url The url to download @param {integer} id The ID of the {@link Downloader} object @param {Function} callback The callback function invoked on success @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser */ function newDownload(url, id, callback, onfailure) { var d=new Downloader(url); if (!d.http) { return 'ohdear'; } d.id=id; d.setTarget(); if (!onfailure) { onfailure=2; } var f = function () { if (d.getReadyState() == 4) { delete pg.misc.downloadsInProgress[this.id]; try { if ( d.getStatus() == 200 ) { d.data=d.getData(); d.lastModified=d.getLastModifiedDate(); callback(d); } else if (typeof onfailure == typeof 1) { if (onfailure > 0) { // retry newDownload(url, id, callback, onfailure - 1); } } else if (typeof onfailure === 'function') { onfailure(d,url,id,callback); } } catch (somerr) { /* ignore it */ } } }; d.setCallback(f); return d; } /** Simulates a download from cached data. The supplied data is put into a {@link Downloader} as if it had downloaded it. @param {String} url The url. @param {integer} id The ID. @param {Function} callback The callback, which is invoked immediately as <code>callback(d)</code>, where <code>d</code> is the new {@link Downloader}. @param {String} data The (cached) data. @param {Date} lastModified The (cached) last modified date. */ function fakeDownload(url, id, callback, data, lastModified, owner) { var d=newDownload(url,callback); d.owner=owner; d.id=id; d.data=data; d.lastModified=lastModified; return callback(d); } /** Starts a download. @param {String} url The url to download @param {integer} id The ID of the {@link Downloader} object @param {Function} callback The callback function invoked on success @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser */ function startDownload(url, id, callback) { var d=newDownload(url, id, callback); if (typeof d == typeof '' ) { return d; } d.start(); return d; } /** Aborts all downloads which have been started. */ function abortAllDownloads() { for ( var x in pg.misc.downloadsInProgress ) { try { pg.misc.downloadsInProgress[x].aborted=true; pg.misc.downloadsInProgress[x].abort(); delete pg.misc.downloadsInProgress[x]; } catch (e) {} } } // ENDFILE: downloader.js // STARTFILE: livepreview.js // TODO: location is often not correct (eg relative links in previews) // NOTE: removed md5 and image and math parsing. was broken, lots of bytes. /** * InstaView - a Mediawiki to HTML converter in JavaScript * Version 0.6.1 * Copyright (C) Pedro Fayolle 2005-2006 * https://en.wikipedia.org/wiki/User:Pilaf * Distributed under the BSD license * * Changelog: * * 0.6.1 * - Fixed problem caused by \r characters * - Improved inline formatting parser * * 0.6 * - Changed name to InstaView * - Some major code reorganizations and factored out some common functions * - Handled conversion of relative links (i.e. [[/foo]]) * - Fixed misrendering of adjacent definition list items * - Fixed bug in table headings handling * - Changed date format in signatures to reflect Mediawiki's * - Fixed handling of [[:Image:...]] * - Updated MD5 function (hopefully it will work with UTF-8) * - Fixed bug in handling of links inside images * * To do: * - Better support for math tags * - Full support for <nowiki> * - Parser-based (as opposed to RegExp-based) inline wikicode handling (make it one-pass and bullet-proof) * - Support for templates (through AJAX) * - Support for coloured links (AJAX) */ var Insta = {}; function setupLivePreview() { // options Insta.conf = { baseUrl: '', user: {}, wiki: { lang: pg.wiki.lang, interwiki: pg.wiki.interwiki, default_thumb_width: 180 }, paths: { articles: pg.wiki.articlePath + '/', // Only used for Insta previews with images. (not in popups) math: '/math/', images: '//upload.wikimedia.org/wikipedia/en/', // FIXME getImageUrlStart(pg.wiki.hostname), images_fallback: '//upload.wikimedia.org/wikipedia/commons/', }, locale: { user: mw.config.get('wgFormattedNamespaces')[pg.nsUserId], image: mw.config.get('wgFormattedNamespaces')[pg.nsImageId], category: mw.config.get('wgFormattedNamespaces')[pg.nsCategoryId], // shouldn't be used in popup previews, i think months: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'] } }; // options with default values or backreferences Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian'; Insta.conf.user.signature = '[['+Insta.conf.locale.user+':'+Insta.conf.user.name+'|'+Insta.conf.user.name+']]'; //Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/'; // define constants Insta.BLOCK_IMAGE = new RegExp('^\\[\\[(?:File|Image|'+Insta.conf.locale.image+ '):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)', 'i'); } Insta.dump = function(from, to) { if (typeof from == 'string') { from = document.getElementById(from); } if (typeof to == 'string') { to = document.getElementById(to); } to.innerHTML = this.convert(from.value); }; Insta.convert = function(wiki) { var ll = (typeof wiki == 'string')? wiki.replace(/\r/g,'').split(/\n/): wiki, // lines of wikicode o = '', // output p = 0, // para flag $r; // result of passing a regexp to $() // some shorthands function remain() { return ll.length; } function sh() { return ll.shift(); } // shift function ps(s) { o += s; } // push // similar to C's printf, uses ? as placeholders, ?? to escape question marks function f() { var i=1, a=arguments, f=a[0], o='', c, p; for (; i<a.length; i++) { if ((p=f.indexOf('?'))+1) { // allow character escaping i -= c = f.charAt(p+1)=='?' ? 1 : 0; o += f.substring(0,p) + (c ? '?' : a[i]); f = f.substr(p+1+c); } else { break; } } return o+f; } function html_entities(s) { return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); } // Wiki text parsing to html is a nightmare. // The below functions deliberately don't escape the ampersand since this would make it more difficult, // and we don't absolutely need to for how we need it. // This means that any unescaped ampersands in wikitext will remain unescaped and can cause invalid HTML. // Browsers should all be able to handle it though. // We also escape significant wikimarkup characters to prevent further matching on the processed text function htmlescape_text(s) { return s.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/:/g,"&#58;").replace(/\[/g,"&#91;").replace(/]/g,"&#93;"); } function htmlescape_attr(s) { return htmlescape_text(s).replace(/'/g,"&#39;").replace(/"/g,"&quot;"); } // return the first non matching character position between two strings function str_imatch(a, b) { for (var i=0, l=Math.min(a.length, b.length); i<l; i++) { if (a.charAt(i)!=b.charAt(i)) { break; } } return i; } // compare current line against a string or regexp // if passed a string it will compare only the first string.length characters // if passed a regexp the result is stored in $r function $(c) { return (typeof c == 'string') ? (ll[0].substr(0,c.length)==c) : ($r = ll[0].match(c)); } function $$(c) { return ll[0]==c; } // compare current line against a string function _(p) { return ll[0].charAt(p); } // return char at pos p function endl(s) { ps(s); sh(); } function parse_list() { var prev=''; while (remain() && $(/^([*#:;]+)(.*)$/)) { var l_match = $r; sh(); var ipos = str_imatch(prev, l_match[1]); // close uncontinued lists for (var prevPos=prev.length-1; prevPos >= ipos; prevPos--) { var pi = prev.charAt(prevPos); if (pi=='*') { ps('</ul>'); } else if (pi=='#') { ps('</ol>'); } // close a dl only if the new item is not a dl item (:, ; or empty) else if($.inArray(l_match[1].charAt(prevPos), ['','*','#'])) { ps('</dl>'); } } // open new lists for (var matchPos=ipos; matchPos<l_match[1].length; matchPos++) { var li = l_match[1].charAt(matchPos); if (li=='*') { ps('<ul>'); } else if (li=='#') { ps('<ol>'); } // open a new dl only if the prev item is not a dl item (:, ; or empty) else if ($.inArray(prev.charAt(matchPos), ['','*','#'])) { ps('<dl>'); } } switch (l_match[1].charAt(l_match[1].length-1)) { case '*': case '#': ps('<li>' + parse_inline_nowiki(l_match[2])); break; case ';': ps('<dt>'); var dt_match = l_match[2].match(/(.*?)(:.*?)$/); // handle ;dt :dd format if (dt_match) { ps(parse_inline_nowiki(dt_match[1])); ll.unshift(dt_match[2]); } else ps(parse_inline_nowiki(l_match[2])); break; case ':': ps('<dd>' + parse_inline_nowiki(l_match[2])); } prev=l_match[1]; } // close remaining lists for (var i=prev.length-1; i>=0; i--) { ps(f('</?>', (prev.charAt(i)=='*')? 'ul': ((prev.charAt(i)=='#')? 'ol': 'dl'))); } } function parse_table() { endl(f('<table>', $(/^\{\|( .*)$/)? $r[1]: '')); for (;remain();) if ($('|')) switch (_(1)) { case '}': endl('</table>'); return; case '-': endl(f('<tr>', $(/\|-*(.*)/)[1])); break; default: parse_table_data(); } else if ($('!')) { parse_table_data(); } else { sh(); } } function parse_table_data() { var td_line, match_i; // 1: "|+", '|' or '+' // 2: ?? // 3: attributes ?? // TODO: finish commenting this regexp var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/); if (td_match[1] == '|+') ps('<caption'); else ps('<t' + ((td_match[1]=='|')?'d':'h')); if (typeof td_match[3] != 'undefined') { //ps(' ' + td_match[3]) match_i = 4; } else match_i = 2; ps('>'); if (td_match[1] != '|+') { // use || or !! as a cell separator depending on context // NOTE: when split() is passed a regexp make sure to use non-capturing brackets td_line = td_match[match_i].split((td_match[1] == '|')? '||': /(?:\|\||!!)/); ps(parse_inline_nowiki(td_line.shift())); while (td_line.length) ll.unshift(td_match[1] + td_line.pop()); } else ps(td_match[match_i]); var tc = 0, td = []; while (remain()) { td.push(sh()); if ($('|')) { if (!tc) break; // we're at the outer-most level (no nested tables), skip to td parse else if (_(1)=='}') tc--; } else if (!tc && $('!')) break; else if ($('{|')) tc++; } if (td.length) ps(Insta.convert(td)); } function parse_pre() { ps('<pre>'); do { endl(parse_inline_nowiki(ll[0].substring(1)) + "\n"); } while (remain() && $(' ')); ps('</pre>'); } function parse_block_image() { ps(parse_image(sh())); } function parse_image(str) { //<NOLITE> // get what's in between "[[Image:" and "]]" var tag = str.substring(str.indexOf(':') + 1, str.length - 2); /* eslint-disable no-unused-vars */ var width; var attr = [], filename, caption = ''; var thumb=0, frame=0, center=0; var align=''; /* eslint-enable no-unused-vars */ if (tag.match(/\|/)) { // manage nested links var nesting = 0; var last_attr; for (var i = tag.length-1; i > 0; i--) { if (tag.charAt(i) == '|' && !nesting) { last_attr = tag.substr(i+1); tag = tag.substring(0, i); break; } else switch (tag.substr(i-1, 2)) { case ']]': nesting++; i--; break; case '[[': nesting--; i--; } } attr = tag.split(/\s*\|\s*/); attr.push(last_attr); filename = attr.shift(); var w_match; for (;attr.length; attr.shift()) { w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/); if (w_match) width = w_match[1]; else switch(attr[0]) { case 'thumb': case 'thumbnail': thumb=true; frame=true; break; case 'frame': frame=true; break; case 'none': case 'right': case 'left': center=false; align=attr[0]; break; case 'center': center=true; align='none'; break; default: if (attr.length == 1) caption = attr[0]; } } } else filename = tag; return ''; //</NOLITE> } function parse_inline_nowiki(str) { var start, lastend=0; var substart=0, nestlev=0, open, close, subloop; var html=''; while (-1 != (start = str.indexOf('<nowiki>', substart))) { html += parse_inline_wiki(str.substring(lastend, start)); start += 8; substart = start; subloop = true; do { open = str.indexOf('<nowiki>', substart); close = str.indexOf('</nowiki>', substart); if (close<=open || open==-1) { if (close==-1) { return html + html_entities(str.substr(start)); } substart = close+9; if (nestlev) { nestlev--; } else { lastend = substart; html += html_entities(str.substring(start, lastend-9)); subloop = false; } } else { substart = open+8; nestlev++; } } while (subloop); } return html + parse_inline_wiki(str.substr(lastend)); } function parse_inline_images(str) { //<NOLITE> var start, substart=0, nestlev=0; var loop, close, open, wiki, html; while (-1 != (start=str.indexOf('[[', substart))) { if(str.substr(start+2).match(RegExp('^(Image|File|' + Insta.conf.locale.image + '):','i'))) { loop=true; substart=start; do { substart+=2; close=str.indexOf(']]',substart); open=str.indexOf('[[',substart); if (close<=open||open==-1) { if (close==-1) return str; substart=close; if (nestlev) { nestlev--; } else { wiki=str.substring(start,close+2); html=parse_image(wiki); str=str.replace(wiki,html); substart=start+html.length; loop=false; } } else { substart=open; nestlev++; } } while (loop); } else break; } //</NOLITE> return str; } // the output of this function doesn't respect the FILO structure of HTML // but since most browsers can handle it I'll save myself the hassle function parse_inline_formatting(str) { var em,st,i,li,o=''; while ((i=str.indexOf("''",li))+1) { o += str.substring(li,i); li=i+2; if (str.charAt(i+2)=="'") { li++; st=!st; o+=st?'<strong>':'</strong>'; } else { em=!em; o+=em?'<em>':'</em>'; } } return o+str.substr(li); } function parse_inline_wiki(str) { str = parse_inline_images(str); str = parse_inline_formatting(str); // math str = str.replace(/<(?:)math>(.*?)<\/math>/ig, ''); // Build a Mediawiki-formatted date string var date = new Date(); var minutes = date.getUTCMinutes(); if (minutes < 10) minutes = '0' + minutes; date = f("?:?, ? ? ? (UTC)", date.getUTCHours(), minutes, date.getUTCDate(), Insta.conf.locale.months[date.getUTCMonth()], date.getUTCFullYear()); // text formatting return str. // signatures replace(/~{5}(?!~)/g, date). replace(/~{4}(?!~)/g, Insta.conf.user.name+' '+date). replace(/~{3}(?!~)/g, Insta.conf.user.name). // [[:Category:...]], [[:Image:...]], etc... replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):[^|]*?)\\]\\](\\w*)','gi'), function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2));}). // remove straight category and interwiki tags replace(RegExp('\\[\\[(?:'+Insta.conf.locale.category+'|'+Insta.conf.wiki.interwiki+'):.*?\\]\\]','gi'),''). // [[:Category:...|Links]], [[:Image:...|Links]], etc... replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):.*?)\\|([^\\]]+?)\\]\\](\\w*)','gi'), function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));}). // [[/Relative links]] replace(/\[\[(\/[^|]*?)\]\]/g, function($0,$1){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($1)); }). // [[/Replaced|Relative links]] replace(/\[\[(\/.*?)\|(.+?)\]\]/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($2)); }). // [[Common links]] replace(/\[\[([^[|]*?)\]\](\w*)/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2)); }). // [[Replaced|Links]] replace(/\[\[([^[]*?)\|([^\]]+?)\]\](\w*)/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3)); }). // [[Stripped:Namespace|Namespace]] replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1) + htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($2)); }). // External links replace(/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g, function($0,$1,$2,$3,$4){return f("<a class='external' href='?:?'>?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($4)); }). replace(/\[http:\/\/(.*?)\]/g, function($0,$1){return f("<a class='external' href='http://?'>[#]</a>", htmlescape_attr($1)); }). replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, function($0,$1,$2,$3){return f("<a class='external' href='?:?'>?:?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($1), htmlescape_text($2) + htmlescape_text($3)); }). replace(/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*[^.,!?;: $])/g, function($0,$1,$2,$3,$4){return f("?<a class='external' href='?:?'>?:?</a>", htmlescape_text($1), htmlescape_attr($2), htmlescape_attr($3) + htmlescape_attr($4), htmlescape_text($2), htmlescape_text($3) + htmlescape_text($4)); }). replace('__NOTOC__',''). replace('__NOEDITSECTION__',''); } // begin parsing for (;remain();) if ($(/^(={1,6})(.*)\1(.*)$/)) { p=0; endl(f('<h?>?</h?>?', $r[1].length, parse_inline_nowiki($r[2]), $r[1].length, $r[3])); } else if ($(/^[*#:;]/)) { p=0; parse_list(); } else if ($(' ')) { p=0; parse_pre(); } else if ($('{|')) { p=0; parse_table(); } else if ($(/^----+$/)) { p=0; endl('<hr />'); } else if ($(Insta.BLOCK_IMAGE)) { p=0; parse_block_image(); } else { // handle paragraphs if ($$('')) { p = (remain()>1 && ll[1]===('')); if (p) endl('<p><br>'); } else { if(!p) { ps('<p>'); p=1; } ps(parse_inline_nowiki(ll[0]) + ' '); } sh(); } return o; }; function wiki2html(txt,baseurl) { Insta.conf.baseUrl=baseurl; return Insta.convert(txt); } // ENDFILE: livepreview.js // STARTFILE: pageinfo.js //<NOLITE> function popupFilterPageSize(data) { return formatBytes(data.length); } function popupFilterCountLinks(data) { var num=countLinks(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('wikiLinks'):popupString('wikiLink')); } function popupFilterCountImages(data) { var num=countImages(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('images'):popupString('image')); } function popupFilterCountCategories(data) { var num=countCategories(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('categories'):popupString('category')); } function popupFilterLastModified(data,download) { var lastmod=download.lastModified; var now=new Date(); var age=now-lastmod; if (lastmod && getValueOf('popupLastModified')) { return (tprintf('%s old', [formatAge(age)])).replace(RegExp(' ','g'), '&nbsp;'); } return ''; } function formatAge(age) { // coerce into a number var a=0+age, aa=a; var seclen = 1000; var minlen = 60*seclen; var hourlen = 60*minlen; var daylen = 24*hourlen; var weeklen = 7*daylen; var numweeks = (a-a%weeklen)/weeklen; a = a-numweeks*weeklen; var sweeks = addunit(numweeks, 'week'); var numdays = (a-a%daylen)/daylen; a = a-numdays*daylen; var sdays = addunit(numdays, 'day'); var numhours = (a-a%hourlen)/hourlen; a = a-numhours*hourlen; var shours = addunit(numhours,'hour'); var nummins = (a-a%minlen)/minlen; a = a-nummins*minlen; var smins = addunit(nummins, 'minute'); var numsecs = (a-a%seclen)/seclen; a = a-numsecs*seclen; var ssecs = addunit(numsecs, 'second'); if (aa > 4*weeklen) { return sweeks; } if (aa > weeklen) { return sweeks + ' ' + sdays; } if (aa > daylen) { return sdays + ' ' + shours; } if (aa > 6*hourlen) { return shours; } if (aa > hourlen) { return shours + ' ' + smins; } if (aa > 10*minlen) { return smins; } if (aa > minlen) { return smins + ' ' + ssecs; } return ssecs; } function addunit(num,str) { return '' + num + ' ' + ((num!=1) ? popupString(str+'s') : popupString(str)) ;} function runPopupFilters(list, data, download) { var ret=[]; for (var i=0; i<list.length; ++i) { if (list[i] && typeof list[i] == 'function') { var s=list[i](data, download, download.owner.article); if (s) { ret.push(s); } } } return ret; } function getPageInfo(data, download) { if (!data || data.length === 0) { return popupString('Empty page'); } var popupFilters=getValueOf('popupFilters') || []; var extraPopupFilters = getValueOf('extraPopupFilters') || []; var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download); var pageInfo=pageInfoArray.join(', '); if (pageInfo !== '' ) { pageInfo = upcaseFirst(pageInfo); } return pageInfo; } // this could be improved! function countLinks(wikiText) { return wikiText.split('[[').length - 1; } // if N = # matches, n = # brackets, then // String.parenSplit(regex) intersperses the N+1 split elements // with Nn other elements. So total length is // L= N+1 + Nn = N(n+1)+1. So N=(L-1)/(n+1). function countImages(wikiText) { return (wikiText.parenSplit(pg.re.image).length - 1) / (pg.re.imageBracketCount + 1); } function countCategories(wikiText) { return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1); } function popupFilterStubDetect(data, download, article) { var counts=stubCount(data, article); if (counts.real) { return popupString('stub'); } if (counts.sect) { return popupString('section stub'); } return ''; } function popupFilterDisambigDetect(data, download, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return ''; } return (isDisambig(data, article)) ? popupString('disambig') : ''; } function formatBytes(num) { return (num > 949) ? (Math.round(num/100)/10+popupString('kB')) : (num +'&nbsp;' + popupString('bytes')) ; } //</NOLITE> // ENDFILE: pageinfo.js // STARTFILE: titles.js /** @fileoverview Defines the {@link Title} class, and associated crufty functions. <code>Title</code> deals with article titles and their various forms. {@link Stringwrapper} is the parent class of <code>Title</code>, which exists simply to make things a little neater. */ /** Creates a new Stringwrapper. @constructor @class the Stringwrapper class. This base class is not really useful on its own; it just wraps various common string operations. */ function Stringwrapper() { /** Wrapper for this.toString().indexOf() @param {String} x @type integer */ this.indexOf=function(x){return this.toString().indexOf(x);}; /** Returns this.value. @type String */ this.toString=function(){return this.value;}; /** Wrapper for {@link String#parenSplit} applied to this.toString() @param {RegExp} x @type Array */ this.parenSplit=function(x){return this.toString().parenSplit(x);}; /** Wrapper for this.toString().substring() @param {String} x @param {String} y (optional) @type String */ this.substring=function(x,y){ if (typeof y=='undefined') { return this.toString().substring(x); } return this.toString().substring(x,y); }; /** Wrapper for this.toString().split() @param {String} x @type Array */ this.split=function(x){return this.toString().split(x);}; /** Wrapper for this.toString().replace() @param {String} x @param {String} y @type String */ this.replace=function(x,y){ return this.toString().replace(x,y); }; } /** Creates a new <code>Title</code>. @constructor @class The Title class. Holds article titles and converts them into various forms. Also deals with anchors, by which we mean the bits of the article URL after a # character, representing locations within an article. @param {String} value The initial value to assign to the article. This must be the canonical title (see {@link Title#value}. Omit this in the constructor and use another function to set the title if this is unavailable. */ function Title(val) { /** The canonical article title. This must be in UTF-8 with no entities, escaping or nasties. Also, underscores should be replaced with spaces. @type String @private */ this.value=null; /** The canonical form of the anchor. This should be exactly as it appears in the URL, i.e. with the .C3.0A bits in. @type String */ this.anchor=''; this.setUtf(val); } Title.prototype=new Stringwrapper(); /** Returns the canonical representation of the article title, optionally without anchor. @param {boolean} omitAnchor @fixme Decide specs for anchor @return String The article title and the anchor. */ Title.prototype.toString=function(omitAnchor) { return this.value + ( (!omitAnchor && this.anchor) ? '#' + this.anchorString() : '' ); }; Title.prototype.anchorString=function() { if (!this.anchor) { return ''; } var split=this.anchor.parenSplit(/((?:[.][0-9A-F]{2})+)/); var len=split.length; for (var j=1; j<len; j+=2) { // FIXME s/decodeURI/decodeURIComponent/g ? split[j]=decodeURIComponent(split[j].split('.').join('%')).split('_').join(' '); } return split.join(''); }; Title.prototype.urlAnchor=function() { var split=this.anchor.parenSplit('/((?:[%][0-9A-F]{2})+)/'); var len=split.length; for (var j=1; j<len; j+=2) { split[j]=split[j].split('%').join('.'); } return split.join(''); }; Title.prototype.anchorFromUtf=function(str) { this.anchor=encodeURIComponent(str.split(' ').join('_')) .split('%3A').join(':').split("'").join('%27').split('%').join('.'); }; Title.fromURL=function(h) { return new Title().fromURL(h); }; Title.prototype.fromURL=function(h) { if (typeof h != 'string') { this.value=null; return this; } // NOTE : playing with decodeURI, encodeURI, escape, unescape, // we seem to be able to replicate the IE borked encoding // IE doesn't do this new-fangled utf-8 thing. // and it's worse than that. // IE seems to treat the query string differently to the rest of the url // the query is treated as bona-fide utf8, but the first bit of the url is pissed around with // we fix up & for all browsers, just in case. var splitted=h.split('?'); splitted[0]=splitted[0].split('&').join('%26'); h=splitted.join('?'); var contribs=pg.re.contribs.exec(h); if (contribs) { if (contribs[1]=='title=') { contribs[3]=contribs[3].split('+').join(' '); } var u=new Title(contribs[3]); this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + u.stripNamespace())); return this; } var email=pg.re.email.exec(h); if (email) { this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(email[3]).stripNamespace())); return this; } var backlinks=pg.re.backlinks.exec(h); if (backlinks) { this.setUtf(this.decodeNasties(new Title(backlinks[3]))); return this; } //A dummy title object for a Special:Diff link. var specialdiff=pg.re.specialdiff.exec(h); if (specialdiff) { this.setUtf(this.decodeNasties(new Title(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Diff'))); return this; } // no more special cases to check -- // hopefully it's not a disguised user-related or specially treated special page var m=pg.re.main.exec(h); if(m === null) { this.value=null; } else { var fromBotInterface = /[?](.+[&])?title=/.test(h); if (fromBotInterface) { m[2]=m[2].split('+').join('_'); } var extracted = m[2] + (m[3] ? '#' + m[3] : ''); if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) { // Fix Safari issue // Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3. this.setUtf(decodeURIComponent(unescape(extracted))); } else { this.setUtf(this.decodeNasties(extracted)); } } return this; }; Title.prototype.decodeNasties=function(txt) { var ret= this.decodeEscapes(decodeURI(txt)); ret = ret.replace(/[_ ]*$/, ''); return ret; }; Title.prototype.decodeEscapes=function(txt) { var split=txt.parenSplit(/((?:[%][0-9A-Fa-f]{2})+)/); var len=split.length; for (var i=1; i<len; i=i+2) { // FIXME is decodeURIComponent better? split[i]=unescape(split[i]); } return split.join(''); }; Title.fromAnchor=function(a) { return new Title().fromAnchor(a); }; Title.prototype.fromAnchor=function(a) { if (!a) { this.value=null; return this; } return this.fromURL(a.href); }; Title.fromWikiText=function(txt) { return new Title().fromWikiText(txt); }; Title.prototype.fromWikiText=function(txt) { // FIXME - testing needed txt=myDecodeURI(txt); this.setUtf(txt); return this; }; Title.prototype.hintValue=function(){ if(!this.value) { return ''; } return safeDecodeURI(this.value); }; //<NOLITE> Title.prototype.toUserName=function(withNs) { if (this.namespaceId() != pg.nsUserId && this.namespaceId() != pg.nsUsertalkId) { this.value=null; return; } this.value = (withNs ? mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' : '') + this.stripNamespace().split('/')[0]; }; Title.prototype.userName=function(withNs) { var t=(new Title(this.value)); t.toUserName(withNs); if (t.value) { return t; } return null; }; Title.prototype.toTalkPage=function() { // convert article to a talk page, or if we can't, return null // In other words: return null if this ALREADY IS a talk page // and return the corresponding talk page otherwise // // Per https://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces // * All discussion namespaces have odd-integer indices // * The discussion namespace index for a specific namespace with index n is n + 1 if (this.value === null) { return null; } var namespaceId = this.namespaceId(); if (namespaceId>=0 && namespaceId % 2 === 0) //non-special and subject namespace { var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId+1]; if (typeof localizedNamespace!=='undefined') { if (localizedNamespace === '') { this.value = this.stripNamespace(); } else { this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace(); } return this.value; } } this.value=null; return null; }; //</NOLITE> // Return canonical, localized namespace Title.prototype.namespace=function() { return mw.config.get('wgFormattedNamespaces')[this.namespaceId()]; }; Title.prototype.namespaceId=function() { var n=this.value.indexOf(':'); if (n<0) { return 0; } //mainspace var namespaceId = mw.config.get('wgNamespaceIds')[this.value.substring(0,n).split(' ').join('_').toLowerCase()]; if (typeof namespaceId=='undefined') return 0; //mainspace return namespaceId; }; //<NOLITE> Title.prototype.talkPage=function() { var t=new Title(this.value); t.toTalkPage(); if (t.value) { return t; } return null; }; Title.prototype.isTalkPage=function() { if (this.talkPage()===null) { return true; } return false; }; Title.prototype.toArticleFromTalkPage=function() { //largely copy/paste from toTalkPage above. if (this.value === null) { return null; } var namespaceId = this.namespaceId(); if (namespaceId >= 0 && namespaceId % 2 == 1) //non-special and talk namespace { var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId-1]; if (typeof localizedNamespace!=='undefined') { if (localizedNamespace === '') { this.value = this.stripNamespace(); } else { this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace(); } return this.value; } } this.value=null; return null; }; Title.prototype.articleFromTalkPage=function() { var t=new Title(this.value); t.toArticleFromTalkPage(); if (t.value) { return t; } return null; }; Title.prototype.articleFromTalkOrArticle=function() { var t=new Title(this.value); if ( t.toArticleFromTalkPage() ) { return t; } return this; }; Title.prototype.isIpUser=function() { return pg.re.ipUser.test(this.userName()); }; //</NOLITE> Title.prototype.stripNamespace=function(){ // returns a string, not a Title var n=this.value.indexOf(':'); if (n<0) { return this.value; } var namespaceId = this.namespaceId(); if (namespaceId === pg.nsMainspaceId) return this.value; return this.value.substring(n+1); }; Title.prototype.setUtf=function(value){ if (!value) { this.value=''; return; } var anch=value.indexOf('#'); if(anch < 0) { this.value=value.split('_').join(' '); this.anchor=''; return; } this.value=value.substring(0,anch).split('_').join(' '); this.anchor=value.substring(anch+1); this.ns=null; // wait until namespace() is called }; Title.prototype.setUrl=function(urlfrag) { var anch=urlfrag.indexOf('#'); this.value=safeDecodeURI(urlfrag.substring(0,anch)); this.anchor=this.value.substring(anch+1); }; Title.prototype.append=function(x){ this.setUtf(this.value + x); }; Title.prototype.urlString=function(x) { if(!x) { x={}; } var v=this.toString(true); if (!x.omitAnchor && this.anchor) { v+= '#' + this.urlAnchor(); } if (!x.keepSpaces) { v=v.split(' ').join('_'); } return encodeURI(v).split('&').join('%26').split('?').join('%3F').split('+').join('%2B'); }; Title.prototype.removeAnchor=function() { return new Title(this.toString(true)); }; Title.prototype.toUrl=function() { return pg.wiki.titlebase + this.urlString(); }; function parseParams(url) { var specialDiff = pg.re.specialdiff.exec(url); if (specialDiff) { var split= specialDiff[1].split('/'); if (split.length==1) return {oldid:split[0], diff: 'prev'}; else if (split.length==2) return {oldid: split[0], diff: split[1]}; } var ret={}; if (url.indexOf('?')==-1) { return ret; } url = url.split('#')[0]; var s=url.split('?').slice(1).join(); var t=s.split('&'); for (var i=0; i<t.length; ++i) { var z=t[i].split('='); z.push(null); ret[z[0]]=z[1]; } //Diff revision with no oldid is interpreted as a diff to the previous revision by MediaWiki if (ret.diff && typeof(ret.oldid)==='undefined') { ret.oldid = "prev"; } //Documentation seems to say something different, but oldid can also accept prev/next, and Echo is emitting such URLs. Simple fixup during parameter decoding: if (ret.oldid && (ret.oldid==='prev' || ret.oldid==='next' || ret.oldid==='cur')) { var helper = ret.diff; ret.diff = ret.oldid; ret.oldid = helper; } return ret; } // (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup) // (b) change spaces to underscores // (c) encodeURI (just the straight one, no pg.re.urlNoPopup) function myDecodeURI (str) { var ret; // FIXME decodeURIComponent?? try { ret=decodeURI(str.toString()); } catch (summat) { return str; } for (var i=0; i<pg.misc.decodeExtras.length; ++i) { var from=pg.misc.decodeExtras[i].from; var to=pg.misc.decodeExtras[i].to; ret=ret.split(from).join(to); } return ret; } function safeDecodeURI(str) { var ret=myDecodeURI(str); return ret || str; } /////////// // TESTS // /////////// //<NOLITE> function isDisambig(data, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; } return ! article.isTalkPage() && pg.re.disambig.test(data); } function stubCount(data, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; } var sectStub=0; var realStub=0; if (pg.re.stub.test(data)) { var s=data.parenSplit(pg.re.stub); for (var i=1; i<s.length; i=i+2) { if (s[i]) { ++sectStub; } else { ++realStub; } } } return { real: realStub, sect: sectStub }; } function isValidImageName(str){ // extend as needed... return ( str.indexOf('{') == -1 ); } function isInStrippableNamespace(article) { // Does the namespace allow subpages // Note, would be better if we had access to wgNamespacesWithSubpages return ( article.namespaceId() !== 0 ); } function isInMainNamespace(article) { return article.namespaceId() === 0; } function anchorContainsImage(a) { // iterate over children of anchor a // see if any are images if (a === null) { return false; } var kids=a.childNodes; for (var i=0; i<kids.length; ++i) { if (kids[i].nodeName=='IMG') { return true; } } return false; } //</NOLITE> function isPopupLink(a) { // NB for performance reasons, TOC links generally return true // they should be stripped out later if (!markNopopupSpanLinks.done) { markNopopupSpanLinks(); } if (a.inNopopupSpan) { return false; } // FIXME is this faster inline? if (a.onmousedown || a.getAttribute('nopopup')) { return false; } var h=a.href; if (h === document.location.href+'#') { return false; } if (!pg.re.basenames.test(h)) { return false; } if (!pg.re.urlNoPopup.test(h)) { return true; } return ( (pg.re.email.test(h) || pg.re.contribs.test(h) || pg.re.backlinks.test(h) || pg.re.specialdiff.test(h)) && h.indexOf('&limit=') == -1 ); } function markNopopupSpanLinks() { if( !getValueOf('popupOnlyArticleLinks')) fixVectorMenuPopups(); var s = $('.nopopups').toArray(); for (var i=0; i<s.length; ++i) { var as=s[i].getElementsByTagName('a'); for (var j=0; j<as.length; ++j) { as[j].inNopopupSpan=true; } } markNopopupSpanLinks.done=true; } function fixVectorMenuPopups() { $('div.vectorMenu h3:first a:first').prop('inNopopupSpan', true); } // ENDFILE: titles.js // STARTFILE: getpage.js ////////////////////////////////////////////////// // Wiki-specific downloading // // Schematic for a getWiki call // // getPageWithCaching // | // false | true // getPage<-[findPictureInCache]->-onComplete(a fake download) // \. // (async)->addPageToCache(download)->-onComplete(download) // check cache to see if page exists function getPageWithCaching(url, onComplete, owner) { log('getPageWithCaching, url='+url); var i=findInPageCache(url); var d; if (i > -1) { d=fakeDownload(url, owner.idNumber, onComplete, pg.cache.pages[i].data, pg.cache.pages[i].lastModified, owner); } else { d=getPage(url, onComplete, owner); if (d && owner && owner.addDownload) { owner.addDownload(d); d.owner=owner; } } } function getPage(url, onComplete, owner) { log('getPage'); var callback= function (d) { if (!d.aborted) {addPageToCache(d); onComplete(d);} }; return startDownload(url, owner.idNumber, callback); } function findInPageCache(url) { for (var i=0; i<pg.cache.pages.length; ++i) { if (url==pg.cache.pages[i].url) { return i; } } return -1; } function addPageToCache(download) { log('addPageToCache '+download.url); var page = {url: download.url, data: download.data, lastModified: download.lastModified}; return pg.cache.pages.push(page); } // ENDFILE: getpage.js // STARTFILE: parensplit.js ////////////////////////////////////////////////// // parenSplit // String.prototype.parenSplit should do what ECMAscript says String.prototype.split does, // interspersing paren matches (regex capturing groups) between the split elements. // i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c'] if (String('abc'.split(/(b)/))!='a,b,c') { // broken String.split, e.g. konq, IE < 10 String.prototype.parenSplit=function (re) { re=nonGlobalRegex(re); var s=this; var m=re.exec(s); var ret=[]; while (m && s) { // without the following loop, we have // 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/) for(var i=0; i<m.length; ++i) { if (typeof m[i]=='undefined') m[i]=''; } ret.push(s.substring(0,m.index)); ret = ret.concat(m.slice(1)); s=s.substring(m.index + m[0].length); m=re.exec(s); } ret.push(s); return ret; }; } else { String.prototype.parenSplit=function (re) { return this.split(re); }; String.prototype.parenSplit.isNative=true; } function nonGlobalRegex(re) { var s=re.toString(); var flags=''; for (var j=s.length; s.charAt(j) != '/'; --j) { if (s.charAt(j) != 'g') { flags += s.charAt(j); } } var t=s.substring(1,j); return RegExp(t,flags); } // ENDFILE: parensplit.js // STARTFILE: tools.js // IE madness with encoding // ======================== // // suppose throughout that the page is in utf8, like wikipedia // // if a is an anchor DOM element and a.href should consist of // // http://host.name.here/wiki/foo?bar=baz // // then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie)) // but IE gives bar=baz correctly as plain utf8 // // --------------------------------- // // IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here. // // --------------------------------- // // summat else // Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm //<NOLITE> function getJsObj(json) { try { var json_ret = JSON.parse(json); if( json_ret.warnings ) { for( var w=0; w < json_ret.warnings.length; w++ ) { if( json_ret.warnings[w]['*'] ) { log( json_ret.warnings[w]['*'] ); } else { log( json_ret.warnings[w]['warnings'] ); } } } else if ( json_ret.error ) { errlog( json_ret.error.code + ': ' + json_ret.error.info ); } return json_ret; } catch (someError) { errlog('Something went wrong with getJsObj, json='+json); return 1; } } function anyChild(obj) { for (var p in obj) { return obj[p]; } return null; } //</NOLITE> function upcaseFirst(str) { if (typeof str != typeof '' || str === '') return ''; return str.charAt(0).toUpperCase() + str.substring(1); } function findInArray(arr, foo) { if (!arr || !arr.length) { return -1; } var len=arr.length; for (var i=0; i<len; ++i) { if (arr[i]==foo) { return i; } } return -1; } /* eslint-disable no-unused-vars */ function nextOne (array, value) { // NB if the array has two consecutive entries equal // then this will loop on successive calls var i=findInArray(array, value); if (i<0) { return null; } return array[i+1]; } /* eslint-enable no-unused-vars */ function literalizeRegex(str){ return mw.util.escapeRegExp(str); } String.prototype.entify=function() { //var shy='&shy;'; return this.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;'/*+shy*/).split('"').join('&quot;'); }; // Array filter function function removeNulls(val) { return val !== null; } function joinPath(list) { return list.filter(removeNulls).join('/'); } function simplePrintf(str, subs) { if (!str || !subs) { return str; } var ret=[]; var s=str.parenSplit(/(%s|\$[0-9]+)/); var i=0; do { ret.push(s.shift()); if ( !s.length ) { break; } var cmd=s.shift(); if (cmd == '%s') { if ( i < subs.length ) { ret.push(subs[i]); } else { ret.push(cmd); } ++i; } else { var j=parseInt( cmd.replace('$', ''), 10 ) - 1; if ( j > -1 && j < subs.length ) { ret.push(subs[j]); } else { ret.push(cmd); } } } while (s.length > 0); return ret.join(''); } /* eslint-disable no-unused-vars */ function isString(x) { return (typeof x === 'string' || x instanceof String); } function isNumber(x) { return (typeof x === 'number' || x instanceof Number); } function isRegExp(x) { return x instanceof RegExp; } function isArray (x) { return x instanceof Array; } function isObject(x) { return x instanceof Object; } function isFunction(x) { return !isRegExp(x) && (typeof x === 'function' || x instanceof Function); } /* eslint-enable no-unused-vars */ function repeatString(s,mult) { var ret=''; for (var i=0; i<mult; ++i) { ret += s; } return ret; } function zeroFill(s, min) { min = min || 2; var t=s.toString(); return repeatString('0', min - t.length) + t; } function map(f, o) { if (isArray(o)) { return map_array(f,o); } return map_object(f,o); } function map_array(f,o) { var ret=[]; for (var i=0; i<o.length; ++i) { ret.push(f(o[i])); } return ret; } function map_object(f,o) { var ret={}; for (var i in o) { ret[o]=f(o[i]); } return ret; } pg.escapeQuotesHTML = function ( text ) { return text .replace(/&/g, "&amp;") .replace(/"/g, "&quot;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;"); }; // ENDFILE: tools.js // STARTFILE: dab.js //<NOLITE> ////////////////////////////////////////////////// // Dab-fixing code // function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) { log('retargetDab: newTarget='+newTarget + ' oldTarget=' + oldTarget); return changeLinkTargetLink( {newTarget: newTarget, text: newTarget.split(' ').join('&nbsp;'), hint: tprintf('disambigHint', [newTarget]), summary: simplePrintf( getValueOf('popupFixDabsSummary'), [friendlyCurrentArticleName, newTarget ]), clickButton: getValueOf('popupDabsAutoClick'), minor: true, oldTarget: oldTarget, watch: getValueOf('popupWatchDisambiggedPages'), title: titleToEdit}); } function listLinks(wikitext, oldTarget, titleToEdit) { // mediawiki strips trailing spaces, so we do the same // testcase: https://en.wikipedia.org/w/index.php?title=Radial&oldid=97365633 var reg=RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi'); var ret=[]; var splitted=wikitext.parenSplit(reg); // ^[a-z]+ should match interwiki links, hopefully (case-insensitive) // and ^[a-z]* should match those and [[:Category...]] style links too var omitRegex=RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory'); var friendlyCurrentArticleName= oldTarget.toString(); var wikPos = getValueOf('popupDabWiktionary'); for (var i=1; i<splitted.length; i=i+3) { if (typeof splitted[i] == typeof 'string' && splitted[i].length>0 && !omitRegex.test(splitted[i])) { ret.push( retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit) ); } /* if */ } /* for loop */ ret = rmDupesFromSortedList(ret.sort()); if (wikPos) { var wikTarget='wiktionary:' + friendlyCurrentArticleName.replace( RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1' ); var meth; if (wikPos.toLowerCase() == 'first') { meth = 'unshift'; } else { meth = 'push'; } ret[meth]( retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) ); } ret.push(changeLinkTargetLink( { newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'), hint: popupString("remove all links to this disambig page from this article"), clickButton: getValueOf('popupDabsAutoClick'), oldTarget: oldTarget, summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]), watch: getValueOf('popupWatchDisambiggedPages'), title: titleToEdit })); return ret; } function rmDupesFromSortedList(list) { var ret=[]; for (var i=0; i<list.length; ++i) { if (ret.length === 0 || list[i]!=ret[ret.length-1]) { ret.push(list[i]); } } return ret; } function makeFixDab(data, navpop) { // grab title from parent popup if there is one; default exists in changeLinkTargetLink var titleToEdit=(navpop.parentPopup && navpop.parentPopup.article.toString()); var list=listLinks(data, navpop.originalArticle, titleToEdit); if (list.length === 0) { log('listLinks returned empty list'); return null; } var html='<hr />' + popupString('Click to disambiguate this link to:') + '<br>'; html+=list.join(', '); return html; } function makeFixDabs(wikiText, navpop) { if (getValueOf('popupFixDabs') && isDisambig(wikiText, navpop.article) && Title.fromURL(location.href).namespaceId() != pg.nsSpecialId && navpop.article.talkPage() ) { setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber); } } function popupRedlinkHTML(article) { return changeLinkTargetLink( { newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'), hint: popupString("remove all links to this page from this article"), clickButton: getValueOf('popupRedlinkAutoClick'), oldTarget: article.toString(), summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()])}); } //</NOLITE> // ENDFILE: dab.js // STARTFILE: htmloutput.js // this has to use a timer loop as we don't know if the DOM element exists when we want to set the text function setPopupHTML (str, elementId, popupId, onSuccess, append) { if (typeof popupId === 'undefined') { //console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100)); popupId = pg.idNumber; } var popupElement=document.getElementById(elementId+popupId); if (popupElement) { if (!append) { popupElement.innerHTML=''; } if (isString(str)) { popupElement.innerHTML+=str; } else { popupElement.appendChild(str); } if (onSuccess) { onSuccess(); } setTimeout(checkPopupPosition, 100); return true; } else { // call this function again in a little while... setTimeout(function(){ setPopupHTML(str,elementId,popupId,onSuccess); }, 600); } return null; } //<NOLITE> function setPopupTrailer(str,id) {return setPopupHTML(str, 'popupData', id);} //</NOLITE> // args.navpopup is mandatory // optional: args.redir, args.redirTarget // FIXME: ye gods, this is ugly stuff function fillEmptySpans(args) { // if redir is present and true then redirTarget is mandatory var redir=true; var rcid; if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) { redir=false; } var a=args.navpopup.parentAnchor; var article, hint=null, oldid=null, params={}; if (redir && typeof args.redirTarget == typeof {}) { article=args.redirTarget; //hint=article.hintValue(); } else { article=(new Title()).fromAnchor(a); hint=a.originalTitle || article.hintValue(); params=parseParams(a.href); oldid=(getValueOf('popupHistoricalLinks')) ? params.oldid : null; rcid=params.rcid; } var x={ article:article, hint: hint, oldid: oldid, rcid: rcid, navpop:args.navpopup, params:params }; var structure=pg.structures[getValueOf('popupStructure')]; if (typeof structure != 'object') { setPopupHTML('popupError', 'Unknown structure (this should never happen): '+ pg.option.popupStructure, args.navpopup.idNumber); return; } var spans=flatten(pg.misc.layout); var numspans = spans.length; var redirs=pg.misc.redirSpans; for (var i=0; i<numspans; ++i) { var found = redirs && (redirs.indexOf( spans[i] ) !== -1); //log('redir='+redir+', found='+found+', spans[i]='+spans[i]); if ( (found && !redir) || (!found && redir) ) { //log('skipping this set of the loop'); continue; } var structurefn=structure[spans[i]]; var setfn = setPopupHTML; if (getValueOf('popupActiveNavlinks') && (spans[i].indexOf('popupTopLinks')===0 || spans[i].indexOf('popupRedirTopLinks')===0) ) { setfn = setPopupTipsAndHTML; } switch (typeof structurefn) { case 'function': log('running '+spans[i]+'({article:'+x.article+', hint:'+x.hint+', oldid: '+x.oldid+'})'); setfn(structurefn(x), spans[i], args.navpopup.idNumber); break; case 'string': setfn(structurefn, spans[i], args.navpopup.idNumber); break; default: errlog('unknown thing with label '+spans[i] + ' (span index was ' + i + ')'); break; } } } // flatten an array function flatten(list, start) { var ret=[]; if (typeof start == 'undefined') { start=0; } for (var i=start; i<list.length; ++i) { if (typeof list[i] == typeof []) { return ret.concat(flatten(list[i])).concat(flatten(list, i+1)); } else { ret.push(list[i]); } } return ret; } // Generate html for whole popup function popupHTML (a) { getValueOf('popupStructure'); var structure=pg.structures[pg.option.popupStructure]; if (typeof structure != 'object') { //return 'Unknown structure: '+pg.option.popupStructure; // override user choice pg.option.popupStructure=pg.optionDefault.popupStructure; return popupHTML(a); } if (typeof structure.popupLayout != 'function') { return 'Bad layout'; } pg.misc.layout=structure.popupLayout(); if (typeof structure.popupRedirSpans === 'function') { pg.misc.redirSpans=structure.popupRedirSpans(); } else { pg.misc.redirSpans=[]; } return makeEmptySpans(pg.misc.layout, a.navpopup); } function makeEmptySpans (list, navpop) { var ret=''; for (var i=0; i<list.length; ++i) { if (typeof list[i] == typeof '') { ret += emptySpanHTML(list[i], navpop.idNumber, 'div'); } else if (typeof list[i] == typeof [] && list[i].length > 0 ) { ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop)); } else if (typeof list[i] == typeof {} && list[i].nodeType ) { ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType); } } return ret; } function emptySpanHTML(name, id, tag, classname) { tag = tag || 'span'; if (!classname) { classname = emptySpanHTML.classAliases[name]; } classname = classname || name; if (name == getValueOf('popupDragHandle')) { classname += ' popupDragHandle'; } return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]); } emptySpanHTML.classAliases={ 'popupSecondPreview': 'popupPreview' }; // generate html for popup image // <a id="popupImageLinkn"><img id="popupImagen"> // where n=idNumber function imageHTML(article, idNumber) { return simplePrintf('<a id="popupImageLink$1">' + '<img align="right" valign="top" id="popupImg$1" style="display: none;"></img>' + '</a>', [ idNumber ]); } function popTipsSoonFn(id, when, popData) { if (!when) { when=250; } var popTips=function(){ setupTooltips(document.getElementById(id), false, true, popData); }; return function() { setTimeout( popTips, when, popData ); }; } function setPopupTipsAndHTML(html, divname, idnumber, popData) { setPopupHTML(html, divname, idnumber, getValueOf('popupSubpopups') ? popTipsSoonFn(divname + idnumber, null, popData) : null); } // ENDFILE: htmloutput.js // STARTFILE: mouseout.js ////////////////////////////////////////////////// // fuzzy checks function fuzzyCursorOffMenus(x,y, fuzz, parent) { if (!parent) { return null; } var uls=parent.getElementsByTagName('ul'); for (var i=0; i<uls.length; ++i) { if (uls[i].className=='popup_menu') { if (uls[i].offsetWidth > 0) return false; } // else {document.title+='.';} } return true; } function checkPopupPosition () { // stop the popup running off the right of the screen // FIXME avoid pg.current.link if (pg.current.link && pg.current.link.navpopup) pg.current.link.navpopup.limitHorizontalPosition(); } function mouseOutWikiLink () { //console ('mouseOutWikiLink'); var a=this; removeModifierKeyHandler(a); if (a.navpopup === null || typeof a.navpopup === 'undefined') return; if ( ! a.navpopup.isVisible() ) { a.navpopup.banish(); return; } restoreTitle(a); Navpopup.tracker.addHook(posCheckerHook(a.navpopup)); } function posCheckerHook(navpop) { return function() { if (!navpop.isVisible()) { return true; /* remove this hook */ } if (Navpopup.tracker.dirty) { return false; } var x=Navpopup.tracker.x, y=Navpopup.tracker.y; var mouseOverNavpop = navpop.isWithin(x,y,navpop.fuzz, navpop.mainDiv) || !fuzzyCursorOffMenus(x,y,navpop.fuzz, navpop.mainDiv); // FIXME it'd be prettier to do this internal to the Navpopup objects var t=getValueOf('popupHideDelay'); if (t) { t = t * 1000; } if (!t) { if(!mouseOverNavpop) { if(navpop.parentAnchor) { restoreTitle( navpop.parentAnchor ); } navpop.banish(); return true; /* remove this hook */ } return false; } // we have a hide delay set var d=+(new Date()); if ( !navpop.mouseLeavingTime ) { navpop.mouseLeavingTime = d; return false; } if ( mouseOverNavpop ) { navpop.mouseLeavingTime=null; return false; } if (d - navpop.mouseLeavingTime > t) { navpop.mouseLeavingTime=null; navpop.banish(); return true; /* remove this hook */ } return false; }; } function runStopPopupTimer(navpop) { // at this point, we should have left the link but remain within the popup // so we call this function again until we leave the popup. if (!navpop.stopPopupTimer) { navpop.stopPopupTimer=setInterval(posCheckerHook(navpop), 500); navpop.addHook(function(){clearInterval(navpop.stopPopupTimer);}, 'hide', 'before'); } } // ENDFILE: mouseout.js // STARTFILE: previewmaker.js /** @fileoverview Defines the {@link Previewmaker} object, which generates short previews from wiki markup. */ /** Creates a new Previewmaker @constructor @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext. @param {String} wikiText The Wikitext source of the page we wish to preview. @param {String} baseUrl The url we should prepend when creating relative urls. @param {Navpopup} owner The navpop associated to this preview generator */ function Previewmaker(wikiText, baseUrl, owner) { /** The wikitext which is manipulated to generate the preview. */ this.originalData=wikiText; this.baseUrl=baseUrl; this.owner=owner; this.maxCharacters=getValueOf('popupMaxPreviewCharacters'); this.maxSentences=getValueOf('popupMaxPreviewSentences'); this.setData(); } Previewmaker.prototype.setData=function() { var maxSize=Math.max(10000, 2*this.maxCharacters); this.data=this.originalData.substring(0,maxSize); }; /** Remove HTML comments @private */ Previewmaker.prototype.killComments = function () { // this also kills one trailing newline, eg [[diamyo]] this.data=this.data.replace(RegExp('^<!--[^$]*?-->\\n|\\n<!--[^$]*?-->(?=\\n)|<!--[^$]*?-->', 'g'), ''); }; /** @private */ Previewmaker.prototype.killDivs = function () { // say goodbye, divs (can be nested, so use * not *?) this.data=this.data.replace(RegExp('< *div[^>]* *>[\\s\\S]*?< */ *div *>', 'gi'), ''); }; /** @private */ Previewmaker.prototype.killGalleries = function () { this.data=this.data.replace(RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>', 'gi'), ''); }; /** @private */ Previewmaker.prototype.kill = function(opening, closing, subopening, subclosing, repl) { var oldk=this.data; var k=this.killStuff(this.data, opening, closing, subopening, subclosing, repl); while (k.length < oldk.length) { oldk=k; k=this.killStuff(k, opening, closing, subopening, subclosing, repl); } this.data=k; }; /** @private */ Previewmaker.prototype.killStuff = function (txt, opening, closing, subopening, subclosing, repl) { var op=this.makeRegexp(opening); var cl=this.makeRegexp(closing, '^'); var sb=subopening ? this.makeRegexp(subopening, '^') : null; var sc=subclosing ? this.makeRegexp(subclosing, '^') : cl; if (!op || !cl) { alert('Navigation Popups error: op or cl is null! something is wrong.'); return; } if (!op.test(txt)) { return txt; } var ret=''; var opResult = op.exec(txt); ret = txt.substring(0,opResult.index); txt=txt.substring(opResult.index+opResult[0].length); var depth = 1; while (txt.length > 0) { var removal=0; if (depth==1 && cl.test(txt)) { depth--; removal=cl.exec(txt)[0].length; } else if (depth > 1 && sc.test(txt)) { depth--; removal=sc.exec(txt)[0].length; }else if (sb && sb.test(txt)) { depth++; removal=sb.exec(txt)[0].length; } if ( !removal ) { removal = 1; } txt=txt.substring(removal); if (depth === 0) { break; } } return ret + (repl || '') + txt; }; /** @private */ Previewmaker.prototype.makeRegexp = function (x, prefix, suffix) { prefix = prefix || ''; suffix = suffix || ''; var reStr=''; var flags=''; if (isString(x)) { reStr=prefix + literalizeRegex(x) + suffix; } else if (isRegExp(x)) { var s=x.toString().substring(1); var sp=s.split('/'); flags=sp[sp.length-1]; sp[sp.length-1]=''; s=sp.join('/'); s=s.substring(0,s.length-1); reStr= prefix + s + suffix; } else { log ('makeRegexp failed'); } log ('makeRegexp: got reStr=' + reStr + ', flags=' + flags); return RegExp(reStr, flags); }; /** @private */ Previewmaker.prototype.killBoxTemplates = function () { // taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general // also, have float_begin, ... float_end this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /[}][}]\s*/, '{{'); // infoboxes etc // from User:Zyxw/popups.js: kill frames too this.kill(RegExp('[{][{][^{}\\s|]*?(infobox|elementbox|frame)[_ ]', 'i'), /[}][}]\s*/, '{{'); }; /** @private */ Previewmaker.prototype.killTemplates = function () { // the plus sign is to avoid transcluding [[Template:', ']] this.kill('{'+'{', '}'+'}', '{', '}', ' '); }; /** @private */ Previewmaker.prototype.killTables = function () { // tables are bad, too // this can be slow, but it's an inprovement over a browser hang // torture test: [[Comparison_of_Intel_Central_Processing_Units]] this.kill('{|', /[|]}\s*/, '{|'); this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i); // remove lines starting with a pipe for the hell of it (?) this.data=this.data.replace(RegExp('^[|].*$', 'mg'), ''); }; /** @private */ Previewmaker.prototype.killImages = function () { var forbiddenNamespaceAliases = []; jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) { if (_namespaceId!=pg.nsImageId && _namespaceId!=pg.nsCategoryId) return; forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments! }); // images and categories are a nono this.kill(RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'), /\]\]\s*/, '[', ']'); }; /** @private */ Previewmaker.prototype.killHTML = function () { // kill <ref ...>...</ref> this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i); // let's also delete entire lines starting with <. it's worth a try. this.data=this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n'); // and those pesky html tags, but not <nowiki> or <blockquote> var splitted=this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/); var len=splitted.length; for (var i=1; i<len; i=i+2) { switch (splitted[i]) { case '<nowiki>': case '</nowiki>': case '<blockquote>': case '</blockquote>': break; default: splitted[i]=''; } } this.data=splitted.join(''); }; /** @private */ Previewmaker.prototype.killChunks = function() { // heuristics alert // chunks of italic text? you crazy, man? var italicChunkRegex=new RegExp ("((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+", 'g'); // keep stuff separated, though, so stick in \n (fixes [[Union Jack]]? this.data=this.data.replace(italicChunkRegex, '\n'); }; /** @private */ Previewmaker.prototype.mopup = function () { // we simply *can't* be doing with horizontal rules right now this.data=this.data.replace(RegExp('^-{4,}','mg'),''); // no indented lines this.data=this.data.replace(RegExp('(^|\\n) *:[^\\n]*','g'), ''); // replace __TOC__, __NOTOC__ and whatever else there is // this'll probably do this.data=this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'),''); }; /** @private */ Previewmaker.prototype.firstBit = function () { // dont't be givin' me no subsequent paragraphs, you hear me? /// first we "normalize" section headings, removing whitespace after, adding before var d=this.data; if (getValueOf('popupPreviewCutHeadings')) { this.data=this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 '); /// then we want to get rid of paragraph breaks whose text ends badly this.data=this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n'); this.data=this.data.replace(RegExp('^[\\s\\n]*'), ''); var stuff=(RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data); if (stuff) { d = stuff[0]; } if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; } /// now put \n\n after sections so that bullets and numbered lists work d=d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n'); } // Split sentences. Superfluous sentences are RIGHT OUT. // note: exactly 1 set of parens here needed to make the slice work d = d.parenSplit(RegExp('([!?.]+["'+"'"+']*\\s)','g')); // leading space is bad, mmkay? d[0]=d[0].replace(RegExp('^\\s*'), ''); var notSentenceEnds=RegExp('([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$', 'i'); d = this.fixSentenceEnds(d, notSentenceEnds); this.fullLength=d.join('').length; var n=this.maxSentences; var dd=this.firstSentences(d,n); do { dd=this.firstSentences(d,n); --n; } while ( dd.length > this.maxCharacters && n !== 0 ); this.data = dd; }; /** @private */ Previewmaker.prototype.fixSentenceEnds = function(strs, reg) { // take an array of strings, strs // join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg for (var i=0; i<strs.length-2; ++i) { if (reg.test(strs[i])) { var a=[]; for (var j=0; j<strs.length; ++j) { if (j<i) a[j]=strs[j]; if (j==i) a[i]=strs[i]+strs[i+1]+strs[i+2]; if (j>i+2) a[j-2]=strs[j]; } return this.fixSentenceEnds(a,reg); } } return strs; }; /** @private */ Previewmaker.prototype.firstSentences = function(strs, howmany) { var t=strs.slice(0, 2*howmany); return t.join(''); }; /** @private */ Previewmaker.prototype.killBadWhitespace = function() { // also cleans up isolated '''', eg [[Suntory Sungoliath]] this.data=this.data.replace(RegExp('^ *\'+ *$', 'gm'), ''); }; /** Runs the various methods to generate the preview. The preview is stored in the <code>html</html> field. @private */ Previewmaker.prototype.makePreview = function() { if (this.owner.article.namespaceId()!=pg.nsTemplateId && this.owner.article.namespaceId()!=pg.nsImageId ) { this.killComments(); this.killDivs(); this.killGalleries(); this.killBoxTemplates(); if (getValueOf('popupPreviewKillTemplates')) { this.killTemplates(); } else { this.killMultilineTemplates(); } this.killTables(); this.killImages(); this.killHTML(); this.killChunks(); this.mopup(); this.firstBit(); this.killBadWhitespace(); } else { this.killHTML(); } this.html=wiki2html(this.data, this.baseUrl); // needs livepreview this.fixHTML(); this.stripLongTemplates(); }; /** @private */ Previewmaker.prototype.esWiki2HtmlPart = function(data) { var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink reLinks.lastIndex = 0; //reset regex var match; var result = ""; var postfixIndex = 0; while ((match = reLinks.exec(data))) //match all wikilinks { //FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases. result += pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) + '<a href="'+Insta.conf.paths.articles+pg.escapeQuotesHTML(match[1])+'">'+pg.escapeQuotesHTML((match[2]?match[2]:match[1])+match[3])+"</a>"; postfixIndex = reLinks.lastIndex; } //append the rest result += pg.escapeQuotesHTML(data.substring(postfixIndex)); return result; }; Previewmaker.prototype.editSummaryPreview=function() { var reAes = /\/\* *(.*?) *\*\//g; //match the first section marker reAes.lastIndex = 0; //reset regex var match; match = reAes.exec(this.data); if (match) { //we have a section link. Split it, process it, combine it. var prefix = this.data.substring(0,match.index-1); var section = match[1]; var postfix = this.data.substring(reAes.lastIndex); var start = "<span class='autocomment'>"; var end = "</span>"; if (prefix.length>0) start = this.esWiki2HtmlPart(prefix) + " " + start + "- "; if (postfix.length>0) end = ": " + end + this.esWiki2HtmlPart(postfix); var t=new Title().fromURL(this.baseUrl); t.anchorFromUtf(section); var sectionLink = Insta.conf.paths.articles + pg.escapeQuotesHTML(t.toString(true)) + '#' + pg.escapeQuotesHTML(t.anchor); return start + '<a href="'+sectionLink+'">&rarr;</a> '+pg.escapeQuotesHTML(section) + end; } //else there's no section link, htmlify the whole thing. return this.esWiki2HtmlPart(this.data); }; //<NOLITE> /** Test function for debugging preview problems one step at a time. */ /*eslint-disable */ function previewSteps(txt) { try { txt=txt || document.editform.wpTextbox1.value; } catch (err) { if (pg.cache.pages.length > 0) { txt=pg.cache.pages[pg.cache.pages.length-1].data; } else { alert('provide text or use an edit page'); } } txt=txt.substring(0,10000); var base=pg.wiki.articlebase + Title.fromURL(document.location.href).urlString(); var p=new Previewmaker(txt, base, pg.current.link.navpopup); if (this.owner.article.namespaceId() != pg.nsTemplateId) { p.killComments(); if (!confirm('done killComments(). Continue?\n---\n' + p.data)) { return; } p.killDivs(); if (!confirm('done killDivs(). Continue?\n---\n' + p.data)) { return; } p.killGalleries(); if (!confirm('done killGalleries(). Continue?\n---\n' + p.data)) { return; } p.killBoxTemplates(); if (!confirm('done killBoxTemplates(). Continue?\n---\n' + p.data)) { return; } if (getValueOf('popupPreviewKillTemplates')) { p.killTemplates(); if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) { return; } } else { p.killMultilineTemplates(); if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) { return; } } p.killTables(); if (!confirm('done killTables(). Continue?\n---\n' + p.data)) { return; } p.killImages(); if (!confirm('done killImages(). Continue?\n---\n' + p.data)) { return; } p.killHTML(); if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) { return; } p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; } p.mopup(); if (!confirm('done mopup(). Continue?\n---\n' + p.data)) { return; } p.firstBit(); if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) { return; } p.killBadWhitespace(); if (!confirm('done killBadWhitespace(). Continue?\n---\n' + p.data)) { return; } } p.html=wiki2html(p.data, base); // needs livepreview p.fixHTML(); if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) { return; } p.stripLongTemplates(); if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) { return; } alert('finished preview - end result follows.\n---\n' + p.html); } /*eslint-enable */ //</NOLITE> /** Works around livepreview bugs. @private */ Previewmaker.prototype.fixHTML = function() { if(!this.html) return; var ret = this.html; // fix question marks in wiki links // maybe this'll break some stuff :-( ret=ret.replace(RegExp('(<a href="' + pg.wiki.articlePath + '/[^"]*)[?](.*?")', 'g'), '$1%3F$2'); ret=ret.replace(RegExp('(<a href=\'' + pg.wiki.articlePath + '/[^\']*)[?](.*?\')', 'g'), '$1%3F$2'); // FIXME fix up % too this.html=ret; }; /** Generates the preview and displays it in the current popup. Does nothing if the generated preview is invalid or consists of whitespace only. Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true. */ Previewmaker.prototype.showPreview = function () { this.makePreview(); if (typeof this.html != typeof '') return; if (RegExp('^\\s*$').test(this.html)) return; setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber); setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, { owner: this.owner }); var more = (this.fullLength > this.data.length) ? this.moreLink() : ''; setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber); }; /** @private */ Previewmaker.prototype.moreLink=function() { var a=document.createElement('a'); a.className='popupMoreLink'; a.innerHTML=popupString('more...'); var savedThis=this; a.onclick=function() { savedThis.maxCharacters+=2000; savedThis.maxSentences+=20; savedThis.setData(); savedThis.showPreview(); }; return a; }; /** @private */ Previewmaker.prototype.stripLongTemplates = function() { // operates on the HTML! this.html=this.html.replace(RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'), ''); this.html=this.html.split('\n').join(' '); // workaround for <pre> templates this.html=this.html.replace(RegExp('[{][{][^}]*<pre>[^}]*[}][}]','gi'), ''); }; /** @private */ Previewmaker.prototype.killMultilineTemplates = function() { this.kill('{{{', '}}}'); this.kill(RegExp('\\s*[{][{][^{}]*\\n'), '}}', '{{'); }; // ENDFILE: previewmaker.js // STARTFILE: querypreview.js function loadAPIPreview(queryType, article, navpop) { var art=new Title(article).urlString(); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&'; var htmlGenerator=function(/*a, d*/){alert('invalid html generator');}; var usernameart = ''; switch (queryType) { case 'history': url += 'titles=' + art + '&prop=revisions&rvlimit=' + getValueOf('popupHistoryPreviewLimit'); htmlGenerator=APIhistoryPreviewHTML; break; case 'category': url += 'list=categorymembers&cmtitle=' + art; htmlGenerator=APIcategoryPreviewHTML; break; case 'userinfo': var username = new Title( article ).userName(); usernameart = encodeURIComponent( username ); if (pg.re.ipUser.test(username)) { url += 'list=blocks&bkprop=range|restrictions&bkip=' + usernameart; } else { url += 'list=users|usercontribs&usprop=blockinfo|groups|editcount|registration|gender&ususers=' + usernameart + "&meta=globaluserinfo&guiprop=groups|unattached&guiuser="+ usernameart + "&uclimit=1&ucprop=timestamp&ucuser=" + usernameart; } htmlGenerator=APIuserInfoPreviewHTML; break; case 'contribs': usernameart = encodeURIComponent( new Title( article ).userName() ); url += 'list=usercontribs&ucuser=' + usernameart + '&uclimit=' + getValueOf('popupContribsPreviewLimit'); htmlGenerator=APIcontribsPreviewHTML; break; case 'imagepagepreview': var trail=''; if (getValueOf('popupImageLinks')) { trail = '&list=imageusage&iutitle=' + art; } url += 'titles=' + art + '&prop=revisions|imageinfo&rvprop=content' + trail; htmlGenerator=APIimagepagePreviewHTML; break; case 'backlinks': url += 'list=backlinks&bltitle=' + art; htmlGenerator=APIbacklinksPreviewHTML; break; case 'revision': if (article.oldid) { url += 'revids=' + article.oldid; } else { url += 'titles=' + article.removeAnchor().urlString(); } url += '&prop=revisions|pageprops|info|images|categories&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max'; htmlGenerator=APIrevisionPreviewHTML; break; } pendingNavpopTask(navpop); var callback=function(d){ log( "callback of API functions was hit" ); showAPIPreview(queryType, htmlGenerator(article,d,navpop), navpop.idNumber, navpop, d); }; var go = function(){ getPageWithCaching(url, callback, navpop); return true; }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_'+queryType+'_QUERY_DATA'); } } function linkList(list) { list.sort(function(x,y) { return (x==y ? 0 : (x<y ? -1 : 1)); }); var buf=[]; for (var i=0; i<list.length; ++i) { buf.push(wikiLink({article: new Title(list[i]), text: list[i].split(' ').join('&nbsp;'), action: 'view'})); } return buf.join(', '); } function getTimeOffset() { var tz = mw.user.options.get('timecorrection'); if(tz) { if( tz.indexOf('|') > -1 ) { // New format return parseInt(tz.split('|')[1],10); } else if ( tz.indexOf(':') > -1 ) { // Old format return( parseInt(tz,10)*60 + parseInt(tz.split(':')[1],10) ); } } return 0; } /* * Creates a HTML table that's shown in the history and user-contribs popups. * @param {Object[]} h - a list of revisions, returned from the API * @param {boolean} reallyContribs - true only if we're displaying user contributions */ function editPreviewTable(article, h, reallyContribs, timeOffset) { var html=['<table>']; var day=null; var curart=article; var page=null; var makeFirstColumnLinks; if(reallyContribs) { // We're showing user contributions, so make (diff | hist) links makeFirstColumnLinks = function(currentRevision) { var result = '('; result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&diff=prev' + '&oldid=' + currentRevision.revid + '">' + popupString('diff') + '</a>'; result += '&nbsp;|&nbsp;'; result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&action=history">' + popupString('hist') + '</a>'; result += ')'; return result; }; } else { // It's a regular history page, so make (cur | last) links var firstRevid = h[0].revid; makeFirstColumnLinks = function(currentRevision) { var result = '('; result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=' + firstRevid + '&oldid=' + currentRevision.revid + '">' + popupString('cur') + '</a>'; result += '&nbsp;|&nbsp;'; result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=prev&oldid=' + currentRevision.revid + '">' + popupString('last') + '</a>'; result += ')'; return result; }; } for (var i=0; i<h.length; ++i) { if (reallyContribs) { page = h[i].title; curart = new Title(page); } var minor = h[i].minor ? '<b>m </b>' : ''; var editDate = adjustDate(getDateFromTimestamp(h[i].timestamp), timeOffset); var thisDay = dayFormat(editDate); var thisTime = timeFormat(editDate); if (thisDay == day) { thisDay = ''; } else { day = thisDay; } if (thisDay) { html.push( '<tr><td colspan=3><span class="popup_history_date">' + thisDay+'</span></td></tr>' ); } html.push('<tr class="popup_history_row_' + ( (i%2) ? 'odd' : 'even') + '">'); html.push('<td>' + makeFirstColumnLinks(h[i]) + '</td>'); html.push('<td>' + '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&oldid=' + h[i].revid + '">' + thisTime + '</a></td>'); var col3url='', col3txt=''; if (!reallyContribs) { var user=h[i].user; if( !h[i].userhidden ) { if( pg.re.ipUser.test(user) ) { col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Contributions&target=' + new Title(user).urlString(); } else { col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(user).urlString(); } col3txt=pg.escapeQuotesHTML(user); } else { col3url=getValueOf('popupRevDelUrl'); col3txt=pg.escapeQuotesHTML( popupString('revdel')); } } else { col3url=pg.wiki.titlebase + curart.urlString(); col3txt=pg.escapeQuotesHTML(page); } html.push('<td>' + (reallyContribs ? minor : '') + '<a href="' + col3url + '">' + col3txt + '</a></td>'); var comment=''; var c=h[i].comment || h[i].content; if (c) { comment=new Previewmaker(c, new Title(curart).toUrl()).editSummaryPreview(); } else if ( h[i].commenthidden ) { comment=popupString('revdel'); } html.push('<td>' + (!reallyContribs ? minor : '') + comment + '</td>'); html.push('</tr>'); html=[html.join('')]; } html.push('</table>'); return html.join(''); } function getDateFromTimestamp(t) { var s=t.split(/[^0-9]/); switch(s.length) { case 0: return null; case 1: return new Date(s[0]); case 2: return new Date(s[0], s[1]-1); case 3: return new Date(s[0], s[1]-1, s[2]); case 4: return new Date(s[0], s[1]-1, s[2], s[3]); case 5: return new Date(s[0], s[1]-1, s[2], s[3], s[4]); case 6: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5]); default: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5], s[6]); } } function adjustDate(d, offset) { // offset is in minutes var o=offset * 60 * 1000; return new Date( +d + o); } function dayFormat(editDate, utc) { if (utc) { return map(zeroFill, [editDate.getUTCFullYear(), editDate.getUTCMonth()+1, editDate.getUTCDate()]).join('-'); } return map(zeroFill, [editDate.getFullYear(), editDate.getMonth()+1, editDate.getDate()]).join('-'); } function timeFormat(editDate, utc) { if (utc) { return map(zeroFill, [editDate.getUTCHours(), editDate.getUTCMinutes(), editDate.getUTCSeconds()]).join(':'); } return map(zeroFill, [editDate.getHours(), editDate.getMinutes(), editDate.getSeconds()]).join(':'); } function showAPIPreview(queryType, html, id, navpop, download) { // DJ: done var target='popupPreview'; completedNavpopTask(navpop); switch (queryType) { case 'imagelinks': case 'category': target='popupPostPreview'; break; case 'userinfo': target='popupUserData'; break; case 'revision': insertPreview(download); return; } setPopupTipsAndHTML(html, target, id); } function APIrevisionPreviewHTML(article, download) { try{ var jsObj=getJsObj(download.data); var page=anyChild(jsObj.query.pages); if( page.missing ) { // TODO we need to fix this proper later on download.owner = null; return; } var content = ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; if( typeof content === 'string' ) { download.data = content; download.lastModified = new Date(page.revisions[0].timestamp); } } catch(someError) { return 'Revision preview failed :('; } } function APIbacklinksPreviewHTML(article, download/*, navpop*/ ) { try { var jsObj=getJsObj(download.data); var list=jsObj.query.backlinks; var html=[]; if (!list) { return popupString('No backlinks found'); } for ( var i=0; i < list.length; i++ ) { var t=new Title(list[i].title); html.push('<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t + '</a>'); } html=html.join(', '); if (jsObj['continue'] && jsObj['continue'].blcontinue) { html += popupString(' and more'); } return html; } catch (someError) { return 'backlinksPreviewHTML went wonky'; } } pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) { log( "APIsharedImagePagePreviewHTML" ); var popupid = obj.requestid; if( obj.query && obj.query.pages ) { var page=anyChild(obj.query.pages ); var content = ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; if( typeof content === 'string' ) { /* Not entirely safe, but the best we can do */ var p=new Previewmaker(content, pg.current.link.navpopup.article, pg.current.link.navpopup); p.makePreview(); setPopupHTML( p.html, "popupSecondPreview", popupid ); } } }; function APIimagepagePreviewHTML(article, download, navpop) { try { var jsObj=getJsObj(download.data); var page=anyChild(jsObj.query.pages); var content= ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; var ret=''; var alt=''; try{alt=navpop.parentAnchor.childNodes[0].alt;} catch(e){} if (alt) { ret = ret + '<hr /><b>' + popupString('Alt text:') + '</b> ' + pg.escapeQuotesHTML(alt); } if (typeof content === 'string') { var p=prepPreviewmaker(content, article, navpop); p.makePreview(); if (p.html) { ret += '<hr />' + p.html; } if (getValueOf('popupSummaryData')) { var info=getPageInfo(content, download); log(info); setPopupTrailer(info, navpop.idNumber); } } if (page && page.imagerepository == "shared" ) { var art=new Title(article); var encart = encodeURIComponent( "File:" + art.stripNamespace() ); var shared_url = pg.wiki.apicommonsbase + '?format=json&formatversion=2' + '&callback=pg.fn.APIsharedImagePagePreviewHTML' + '&requestid=' + navpop.idNumber + '&action=query&prop=revisions&rvprop=content&titles=' + encart; ret = ret +'<hr />' + popupString( 'Image from Commons') + ': <a href="' + pg.wiki.commonsbase + '?title=' + encart + '">' + popupString( 'Description page') + '</a>'; mw.loader.load( shared_url ); } showAPIPreview('imagelinks', APIimagelinksPreviewHTML(article,download), navpop.idNumber, download); return ret; } catch (someError) { return 'API imagepage preview failed :('; } } function APIimagelinksPreviewHTML(article, download) { try { var jsobj=getJsObj(download.data); var list=jsobj.query.imageusage; if (list) { var ret=[]; for (var i=0; i < list.length; i++) { ret.push(list[i].title); } if (ret.length === 0) { return popupString('No image links found'); } return '<h2>' + popupString('File links') + '</h2>' + linkList(ret); } else { return popupString('No image links found'); } } catch(someError) { return 'Image links preview generation failed :('; } } function APIcategoryPreviewHTML(article, download) { try{ var jsobj=getJsObj(download.data); var list=jsobj.query.categorymembers; var ret=[]; for (var p=0; p < list.length; p++) { ret.push(list[p].title); } if (ret.length === 0) { return popupString('Empty category'); } ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' +linkList(ret); if (jsobj['continue'] && jsobj['continue'].cmcontinue) { ret += popupString(' and more'); } return ret; } catch(someError) { return 'Category preview failed :('; } } function APIuserInfoPreviewHTML(article, download) { var ret=[]; var queryobj = {}; try{ queryobj=getJsObj(download.data).query; } catch(someError) { return 'Userinfo preview failed :('; } var user=anyChild(queryobj.users); if (user) { var globaluserinfo=queryobj.globaluserinfo; if (user.invalid === '') { ret.push( popupString( 'Invalid user') ); } else if (user.missing === '') { ret.push( popupString( 'Not a registered username') ); } if( user.blockedby ) { if( user.blockpartial ) { ret.push('<b>' + popupString('Has blocks') + '</b>'); } else { ret.push('<b>' + popupString('BLOCKED') + '</b>'); } } if( globaluserinfo && ( 'locked' in globaluserinfo || 'hidden' in globaluserinfo ) ) { var lockedSulAccountIsAttachedToThis = true; for( var i=0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) { if ( globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname') ) { lockedSulAccountIsAttachedToThis=false; break; } } if (lockedSulAccountIsAttachedToThis) { if ( 'locked' in globaluserinfo ) ret.push('<b><i>' + popupString('LOCKED') + '</i></b>'); if ( 'hidden' in globaluserinfo ) ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>'); } } if( getValueOf('popupShowGender') && user.gender ) { switch( user.gender ) { case "male": ret.push( popupString( "\u2642" ) ); break; case "female": ret.push( popupString( "\u2640" ) ); break; } } if( user.groups ) { for( var j=0; j < user.groups.length; j++) { var currentGroup = user.groups[j]; if( ["*", "user", "autoconfirmed", "extendedconfirmed"].indexOf( currentGroup ) === -1 ) { ret.push( pg.escapeQuotesHTML(user.groups[j]) ); } } } if( globaluserinfo && globaluserinfo.groups ) { for( var k=0; k < globaluserinfo.groups.length; k++) { ret.push( '<i>'+pg.escapeQuotesHTML(globaluserinfo.groups[k])+'</i>' ); } } if( user.registration ) ret.push( pg.escapeQuotesHTML((user.editcount?user.editcount:'0') + popupString(' edits since: ') + (user.registration?dayFormat(getDateFromTimestamp(user.registration)):'')) ); } if (queryobj.usercontribs && queryobj.usercontribs.length) { ret.push( popupString('last edit on ') + dayFormat(getDateFromTimestamp(queryobj.usercontribs[0].timestamp)) ); } if (queryobj.blocks) { ret.push( popupString( 'IP user') ); //we only request list=blocks for IPs for (var l=0; l<queryobj.blocks.length; l++) { console.log(queryobj); var rbstr = queryobj.blocks[l].rangestart === queryobj.blocks[l].rangeend ? 'BLOCK' : 'RANGEBLOCK'; rbstr = (!Array.isArray(queryobj.blocks[l].restrictions) ? 'Has ' + rbstr.toLowerCase() + 's' : rbstr + 'ED'); ret.push('<b>' + popupString(rbstr) + '</b>' ); } } ret = '<hr />' + ret.join( ', ' ); return ret; } function APIcontribsPreviewHTML(article, download, navpop) { return APIhistoryPreviewHTML(article, download, navpop, true); } function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) { try { var jsobj=getJsObj(download.data); var edits = []; if( reallyContribs ) { edits=jsobj.query.usercontribs; } else { edits=anyChild(jsobj.query.pages).revisions; } var ret=editPreviewTable(article, edits, reallyContribs, getTimeOffset()); return ret; } catch (someError) { return 'History preview failed :-('; } } //</NOLITE> // ENDFILE: querypreview.js // STARTFILE: debug.js //////////////////////////////////////////////////////////////////// // Debugging functions //////////////////////////////////////////////////////////////////// function setupDebugging() { //<NOLITE> if (window.popupDebug) { // popupDebug is set from .version window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; }; window.console.log(x); }; window.errlog=function(x) { window.console.error(x); }; log('Initializing logger'); } else { //</NOLITE> window.log = function() {}; window.errlog = function() {}; //<NOLITE> } //</NOLITE> } // ENDFILE: debug.js // STARTFILE: images.js // load image of type Title. function loadImage(image, navpop) { if (typeof image.stripNamespace != 'function') { alert('loadImages bad'); } // API call to retrieve image info. if ( !getValueOf('popupImages') ) return; if ( !isValidImageName(image) ) return false; var art=image.urlString(); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query'; url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge'); url += '&titles=' + art; pendingNavpopTask(navpop); var callback=function(d){ popupsInsertImage(navpop.idNumber, navpop, d); }; var go = function(){ getPageWithCaching(url, callback, navpop); return true; }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA'); } } function popupsInsertImage(id, navpop, download) { log( "popupsInsertImage"); var imageinfo; try { var jsObj=getJsObj(download.data); var imagepage=anyChild(jsObj.query.pages); if (typeof imagepage.imageinfo === 'undefined') return; imageinfo = imagepage.imageinfo[0]; } catch (someError) { log( "popupsInsertImage failed :(" ); return; } var popupImage = document.getElementById("popupImg"+id); if (!popupImage) { log( "could not find insertion point for image"); return; } popupImage.width=getValueOf('popupImageSize'); popupImage.style.display='inline'; // Set the source for the image. if( imageinfo.thumburl ) popupImage.src=imageinfo.thumburl; else if( imageinfo.mime.indexOf("image") === 0 ){ popupImage.src=imageinfo.url; log( "a thumb could not be found, using original image" ); } else log( "fullsize imagethumb, but not sure if it's an image"); var a=document.getElementById("popupImageLink"+id); if (a === null) { return null; } // Determine the action of the surrouding imagelink. switch (getValueOf('popupThumbAction')) { case 'imagepage': if (pg.current.article.namespaceId()!=pg.nsImageId) { a.href=imageinfo.descriptionurl; // FIXME: unreliable pg.idNumber popTipsSoonFn('popupImage' + id)(); break; } /* falls through */ case 'sizetoggle': a.onclick=toggleSize; a.title=popupString('Toggle image size'); return; case 'linkfull': a.href = imageinfo.url; a.title=popupString('Open full-size image'); return; } } // Toggles the image between inline small and navpop fullwidth. // It's the same image, no actual sizechange occurs, only display width. function toggleSize() { var imgContainer=this; if (!imgContainer) { alert('imgContainer is null :/'); return; } var img=imgContainer.firstChild; if (!img) { alert('img is null :/'); return; } if (!img.style.width || img.style.width==='') { img.style.width='100%'; } else { img.style.width=''; } } // Returns one title of an image from wikiText. function getValidImageFromWikiText(wikiText) { // nb in pg.re.image we're interested in the second bracketed expression // this may change if the regex changes :-( //var match=pg.re.image.exec(wikiText); var matched=null; var match; // strip html comments, used by evil bots :-( var t = removeMatchesUnless(wikiText, RegExp('(<!--[\\s\\S]*?-->)'), 1, RegExp('^<!--[^[]*popup', 'i')); while ( ( match = pg.re.image.exec(t) ) ) { // now find a sane image name - exclude templates by seeking { var m = match[2] || match[6]; if ( isValidImageName(m) ) { matched=m; break; } } pg.re.image.lastIndex=0; if (!matched) { return null; } return mw.config.get('wgFormattedNamespaces')[pg.nsImageId]+':'+upcaseFirst(matched); } function removeMatchesUnless(str, re1, parencount, re2) { var split=str.parenSplit(re1); var c=parencount + 1; for (var i=0; i<split.length; ++i) { if ( i%c === 0 || re2.test(split[i]) ) { continue; } split[i]=''; } return split.join(''); } //</NOLITE> // ENDFILE: images.js // STARTFILE: namespaces.js // Set up namespaces and other non-strings.js localization // (currently that means redirs too) function setNamespaces() { pg.nsSpecialId = -1; pg.nsMainspaceId = 0; pg.nsImageId = 6; pg.nsUserId = 2; pg.nsUsertalkId = 3; pg.nsCategoryId = 14; pg.nsTemplateId = 10; } function setRedirs() { var r='redirect'; var R='REDIRECT'; var redirLists={ //<NOLITE> 'ar': [ R, 'تحويل' ], 'be': [ r, 'перанакіраваньне' ], 'bg': [ r, 'пренасочване', 'виж' ], 'bs': [ r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI' ], 'cs': [ R, 'PŘESMĚRUJ' ], 'cy': [ r, 'ail-cyfeirio' ], 'de': [ R, 'WEITERLEITUNG' ], 'el': [ R, 'ΑΝΑΚΑΤΕΥΘΥΝΣΗ'], 'eo': [ R, 'ALIDIREKTU', 'ALIDIREKTI' ], 'es': [ R, 'REDIRECCIÓN' ], 'et': [ r, 'suuna' ], 'ga': [ r, 'athsheoladh' ], 'gl': [ r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'], 'he': [ R, 'הפניה' ], 'hu': [ R, 'ÁTIRÁNYÍTÁS' ], 'is': [ r, 'tilvísun', 'TILVÍSUN' ], 'it': [ R, 'RINVIA', 'Rinvia'], 'ja': [ R, '転送' ], 'mk': [ r, 'пренасочување', 'види' ], 'nds': [ r, 'wiederleiden' ], 'nl': [ R, 'DOORVERWIJZING' ], 'nn': [ r, 'omdiriger' ], 'pl': [ R, 'PATRZ', 'PRZEKIERUJ', 'TAM' ], 'pt': [ R, 'redir' ], 'ru': [ R, 'ПЕРЕНАПРАВЛЕНИЕ', 'ПЕРЕНАПР' ], 'sk': [ r, 'presmeruj' ], 'sr': [ r, 'Преусмери', 'преусмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI' ], 'tt': [ R, 'yünältü', 'перенаправление', 'перенапр' ], 'uk': [ R, 'ПЕРЕНАПРАВЛЕННЯ', 'ПЕРЕНАПР' ], 'vi': [ r, 'đổi' ], 'zh': [ R, '重定向'] // no comma //</NOLITE> }; var redirList=redirLists[ pg.wiki.lang ] || [r, R]; // Mediawiki is very tolerant about what comes after the #redirect at the start pg.re.redirect=RegExp('^\\s*[#](' + redirList.join('|') + ').*?\\[{2}([^\\|\\]]*)(|[^\\]]*)?\\]{2}\\s*(.*)', 'i'); } function setInterwiki() { if (pg.wiki.wikimedia) { // From https://meta.wikimedia.org/wiki/List_of_Wikipedias pg.wiki.interwiki='aa|ab|ace|af|ak|als|am|an|ang|ar|arc|arz|as|ast|av|ay|az|ba|bar|bat-smg|bcl|be|be-x-old|bg|bh|bi|bjn|bm|bn|bo|bpy|br|bs|bug|bxr|ca|cbk-zam|cdo|ce|ceb|ch|cho|chr|chy|ckb|co|cr|crh|cs|csb|cu|cv|cy|da|de|diq|dsb|dv|dz|ee|el|eml|en|eo|es|et|eu|ext|fa|ff|fi|fiu-vro|fj|fo|fr|frp|frr|fur|fy|ga|gag|gan|gd|gl|glk|gn|got|gu|gv|ha|hak|haw|he|hi|hif|ho|hr|hsb|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|ilo|io|is|it|iu|ja|jbo|jv|ka|kaa|kab|kbd|kg|ki|kj|kk|kl|km|kn|ko|koi|kr|krc|ks|ksh|ku|kv|kw|ky|la|lad|lb|lbe|lg|li|lij|lmo|ln|lo|lt|ltg|lv|map-bms|mdf|mg|mh|mhr|mi|mk|ml|mn|mo|mr|mrj|ms|mt|mus|mwl|my|myv|mzn|na|nah|nap|nds|nds-nl|ne|new|ng|nl|nn|no|nov|nrm|nv|ny|oc|om|or|os|pa|pag|pam|pap|pcd|pdc|pfl|pi|pih|pl|pms|pnb|pnt|ps|pt|qu|rm|rmy|rn|ro|roa-rup|roa-tara|ru|rue|rw|sa|sah|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|sn|so|sq|sr|srn|ss|st|stq|su|sv|sw|szl|ta|te|tet|tg|th|ti|tk|tl|tn|to|tpi|tr|ts|tt|tum|tw|ty|udm|ug|uk|ur|uz|ve|vec|vi|vls|vo|wa|war|wo|wuu|xal|xh|yi|yo|za|zea|zh|zh-classical|zh-min-nan|zh-yue|zu'; pg.re.interwiki=RegExp('^'+pg.wiki.interwiki+':'); } else { pg.wiki.interwiki=null; pg.re.interwiki=RegExp('^$'); } } // return a regexp pattern matching all variants to write the given namespace function nsRe(namespaceId) { var imageNamespaceVariants = []; jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) { if (_namespaceId!=namespaceId) return; _localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc); imageNamespaceVariants.push(mw.util.escapeRegExp(_localizedNamespaceLc).split(' ').join('[ _]')); imageNamespaceVariants.push(mw.util.escapeRegExp(encodeURI(_localizedNamespaceLc))); }); return '(?:' + imageNamespaceVariants.join('|') + ')'; } function nsReImage() { return nsRe(pg.nsImageId); } // ENDFILE: namespaces.js // STARTFILE: selpop.js //<NOLITE> function getEditboxSelection() { // see http://www.webgurusforum.com/8/12/0 var editbox; try { editbox=document.editform.wpTextbox1; } catch (dang) { return; } // IE, Opera if (document.selection) { return document.selection.createRange().text; } // Mozilla var selStart = editbox.selectionStart; var selEnd = editbox.selectionEnd; return (editbox.value).substring(selStart, selEnd); } function doSelectionPopup() { // popup if the selection looks like [[foo|anything afterwards at all // or [[foo|bar]]text without ']]' // or [[foo|bar]] var sel=getEditboxSelection(); var open=sel.indexOf('[['); var pipe=sel.indexOf('|'); var close=sel.indexOf(']]'); if (open == -1 || ( pipe == -1 && close == -1) ) { return; } if (pipe != -1 && open > pipe || close != -1 && open > close) { return; } if (getValueOf('popupOnEditSelection')=='boxpreview') { return doSeparateSelectionPopup(sel); } var article=new Title(sel.substring(open+2, (pipe < 0) ? close : pipe)).urlString(); if (close > 0 && sel.substring(close+2).indexOf('[[') >= 0) { return; } var a=document.createElement('a'); a.href=pg.wiki.titlebase + article; mouseOverWikiLink2(a); if (a.navpopup) { a.navpopup.addHook(function(){runStopPopupTimer(a.navpopup);}, 'unhide', 'after'); } } function doSeparateSelectionPopup(str) { var div=document.getElementById('selectionPreview'); if (!div) { div = document.createElement('div'); div.id='selectionPreview'; try { var box=document.editform.wpTextbox1; box.parentNode.insertBefore(div, box); } catch (error) { return; } } div.innerHTML=wiki2html(str); div.ranSetupTooltipsAlready = false; popTipsSoonFn('selectionPreview')(); } //</NOLITE> // ENDFILE: selpop.js // STARTFILE: navpopup.js /** @fileoverview Defines two classes: {@link Navpopup} and {@link Mousetracker}. <code>Navpopup</code> describes popups: when they appear, where, what they look like and so on. <code>Mousetracker</code> "captures" the mouse using <code>document.onmousemove</code>. */ /** Creates a new Mousetracker. @constructor @class The Mousetracker class. This monitors mouse movements and manages associated hooks. */ function Mousetracker() { /** Interval to regularly run the hooks anyway, in milliseconds. @type Integer */ this.loopDelay=400; /** Timer for the loop. @type Timer */ this.timer=null; /** Flag - are we switched on? @type Boolean */ this.active=false; /** Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position? */ this.dirty=true; /** Array of hook functions. @private @type Array */ this.hooks=[]; } /** Adds a hook, to be called when we get events. @param {Function} f A function which is called as <code>f(x,y)</code>. It should return <code>true</code> when it wants to be removed, and <code>false</code> otherwise. */ Mousetracker.prototype.addHook = function (f) { this.hooks.push(f); }; /** Runs hooks, passing them the x and y coords of the mouse. Hook functions that return true are passed to {@link Mousetracker#removeHooks} for removal. @private */ Mousetracker.prototype.runHooks = function () { if (!this.hooks || !this.hooks.length) { return; } //log('Mousetracker.runHooks; we got some hooks to run'); var remove=false; var removeObj={}; // this method gets called a LOT - // pre-cache some variables var x=this.x, y=this.y, len = this.hooks.length; for (var i=0; i<len; ++i) { //~ run the hook function, and remove it if it returns true if (this.hooks[i](x, y)===true) { remove=true; removeObj[i]=true; } } if (remove) { this.removeHooks(removeObj); } }; /** Removes hooks. @private @param {Object} removeObj An object whose keys are the index numbers of functions for removal, with values that evaluate to true */ Mousetracker.prototype.removeHooks = function(removeObj) { var newHooks=[]; var len = this.hooks.length; for (var i=0; i<len; ++i) { if (! removeObj[i]) { newHooks.push(this.hooks[i]); } } this.hooks=newHooks; }; /** Event handler for mouse wiggles. We simply grab the event, set x and y and run the hooks. This makes the cpu all hot and bothered :-( @private @param {Event} e Mousemove event */ Mousetracker.prototype.track=function (e) { //~ Apparently this is needed in IE. e = e || window.event; var x, y; if (e) { if (e.pageX) { x=e.pageX; y=e.pageY; } else if (typeof e.clientX!='undefined') { var left, top, docElt = document.documentElement; if (docElt) { left=docElt.scrollLeft; } left = left || document.body.scrollLeft || document.scrollLeft || 0; if (docElt) { top=docElt.scrollTop; } top = top || document.body.scrollTop || document.scrollTop || 0; x=e.clientX + left; y=e.clientY + top; } else { return; } this.setPosition(x,y); } }; /** Sets the x and y coordinates stored and takes appropriate action, running hooks as appropriate. @param {Integer} x, y Screen coordinates to set */ Mousetracker.prototype.setPosition=function(x,y) { this.x = x; this.y = y; if (this.dirty || this.hooks.length === 0) { this.dirty=false; return; } if (typeof this.lastHook_x != 'number') { this.lastHook_x = -100; this.lastHook_y=-100; } var diff = (this.lastHook_x - x)*(this.lastHook_y - y); diff = (diff >= 0) ? diff : -diff; if ( diff > 1 ) { this.lastHook_x=x; this.lastHook_y=y; if (this.dirty) { this.dirty = false; } else { this.runHooks(); } } }; /** Sets things in motion, unless they are already that is, registering an event handler on <code>document.onmousemove</code>. A half-hearted attempt is made to preserve the old event handler if there is one. */ Mousetracker.prototype.enable = function () { if (this.active) { return; } this.active=true; //~ Save the current handler for mousemove events. This isn't too //~ robust, of course. this.savedHandler=document.onmousemove; //~ Gotta save @tt{this} again for the closure, and use apply for //~ the member function. var savedThis=this; document.onmousemove=function (e) {savedThis.track.apply(savedThis, [e]);}; if (this.loopDelay) { this.timer = setInterval(function() { //log('loop delay in mousetracker is working'); savedThis.runHooks();}, this.loopDelay); } }; /** Disables the tracker, removing the event handler. */ Mousetracker.prototype.disable = function () { if (!this.active) { return; } if (typeof this.savedHandler === 'function') { document.onmousemove=this.savedHandler; } else { delete document.onmousemove; } if (this.timer) { clearInterval(this.timer); } this.active=false; }; /** Creates a new Navpopup. Gets a UID for the popup and @param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable. @constructor @class The Navpopup class. This generates popup hints, and does some management of them. */ function Navpopup(/*init*/) { //alert('new Navpopup(init)'); /** UID for each Navpopup instance. Read-only. @type integer */ this.uid=Navpopup.uid++; /** Read-only flag for current visibility of the popup. @type boolean @private */ this.visible=false; /** Flag to be set when we want to cancel a previous request to show the popup in a little while. @private @type boolean */ this.noshow=false; /** Categorised list of hooks. @see #runHooks @see #addHook @private @type Object */ this.hooks={ 'create': [], 'unhide': [], 'hide': [] }; /** list of unique IDs of hook functions, to avoid duplicates @private */ this.hookIds={}; /** List of downloads associated with the popup. @private @type Array */ this.downloads=[]; /** Number of uncompleted downloads. @type integer */ this.pending=null; /** Tolerance in pixels when detecting whether the mouse has left the popup. @type integer */ this.fuzz=5; /** Flag to toggle running {@link #limitHorizontalPosition} to regulate the popup's position. @type boolean */ this.constrained=true; /** The popup width in pixels. @private @type integer */ this.width=0; /** The popup width in pixels. @private @type integer */ this.height=0; /** The main content DIV element. @type HTMLDivElement */ this.mainDiv=null; this.createMainDiv(); // if (!init || typeof init.popups_draggable=='undefined' || init.popups_draggable) { // this.makeDraggable(true); // } } /** A UID for each Navpopup. This constructor property is just a counter. @type integer @private */ Navpopup.uid=0; /** Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible. @type boolean */ Navpopup.prototype.isVisible=function() { return this.visible; }; /** Repositions popup using CSS style. @private @param {integer} x x-coordinate (px) @param {integer} y y-coordinate (px) @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition} */ Navpopup.prototype.reposition= function (x,y, noLimitHor) { log ('reposition('+x+','+y+','+noLimitHor+')'); if (typeof x != 'undefined' && x !== null) { this.left=x; } if (typeof y != 'undefined' && y !== null) { this.top=y; } if (typeof this.left != 'undefined' && typeof this.top != 'undefined') { this.mainDiv.style.left=this.left + 'px'; this.mainDiv.style.top=this.top + 'px'; } if (!noLimitHor) { this.limitHorizontalPosition(); } //console.log('navpop'+this.uid+' - (left,top)=(' + this.left + ',' + this.top + '), css=(' //+ this.mainDiv.style.left + ',' + this.mainDiv.style.top + ')'); }; /** Prevents popups from being in silly locations. Hopefully. Should not be run if {@link #constrained} is true. @private */ Navpopup.prototype.limitHorizontalPosition=function() { if (!this.constrained || this.tooWide) { return; } this.updateDimensions(); var x=this.left; var w=this.width; var cWidth=document.body.clientWidth; // log('limitHorizontalPosition: x='+x+ // ', this.left=' + this.left + // ', this.width=' + this.width + // ', cWidth=' + cWidth); if ( (x+w) >= cWidth || ( x > 0 && this.maxWidth && this.width < this.maxWidth && this.height > this.width && x > cWidth - this.maxWidth ) ) { // This is a very nasty hack. There has to be a better way! // We find the "natural" width of the div by positioning it at the far left // then reset it so that it should be flush right (well, nearly) this.mainDiv.style.left='-10000px'; this.mainDiv.style.width = this.maxWidth + 'px'; var naturalWidth=parseInt(this.mainDiv.offsetWidth, 10); var newLeft=cWidth - naturalWidth - 1; if (newLeft < 0) { newLeft = 0; this.tooWide=true; } // still unstable for really wide popups? log ('limitHorizontalPosition: moving to ('+newLeft + ','+ this.top+');' + ' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth); this.reposition(newLeft, null, true); } }; /** Counter indicating the z-order of the "highest" popup. We start the z-index at 1000 so that popups are above everything else on the screen. @private @type integer */ Navpopup.highest=1000; /** Brings popup to the top of the z-order. We increment the {@link #highest} property of the contructor here. @private */ Navpopup.prototype.raise = function () { this.mainDiv.style.zIndex=Navpopup.highest + 1; ++Navpopup.highest; }; /** Shows the popup provided {@link #noshow} is not true. Updates the position, brings the popup to the top of the z-order and unhides it. */ Navpopup.prototype.show = function () { //document.title+='s'; if (this.noshow) { return; } //document.title+='t'; this.reposition(); this.raise(); this.unhide(); }; /** Checks to see if the mouse pointer has stabilised (checking every <code>time</code>/2 milliseconds) and runs the {@link #show} method if it has. @param {integer} time The minimum time (ms) before the popup may be shown. */ Navpopup.prototype.showSoonIfStable = function (time) { log ('showSoonIfStable, time='+time); if (this.visible) { return; } this.noshow = false; //~ initialize these variables so that we never run @tt{show} after //~ just half the time this.stable_x = -10000; this.stable_y = -10000; var stableShow = function() { log('stableShow called'); var new_x = Navpopup.tracker.x, new_y = Navpopup.tracker.y; var dx = savedThis.stable_x - new_x, dy = savedThis.stable_y - new_y; var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz; //document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] '; if ( dx * dx <= fuzz2 && dy * dy <= fuzz2 ) { log ('mouse is stable'); clearInterval(savedThis.showSoonStableTimer); savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]); savedThis.show.apply(savedThis, []); savedThis.limitHorizontalPosition.apply(savedThis, []); return; } savedThis.stable_x = new_x; savedThis.stable_y = new_y; }; var savedThis = this; this.showSoonStableTimer = setInterval(stableShow, time/2); }; /** Sets the {@link #noshow} flag and hides the popup. This should be called when the mouse leaves the link before (or after) it's actually been displayed. */ Navpopup.prototype.banish = function () { log ('banish called'); // hide and prevent showing with showSoon in the future this.noshow=true; if (this.showSoonStableTimer) { log('clearing showSoonStableTimer'); clearInterval(this.showSoonStableTimer); } this.hide(); }; /** Runs hooks added with {@link #addHook}. @private @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide' @param {String} when Controls exactly when the hook is run: either 'before' or 'after' */ Navpopup.prototype.runHooks = function (key, when) { if (!this.hooks[key]) { return; } var keyHooks=this.hooks[key]; var len=keyHooks.length; for (var i=0; i< len; ++i) { if (keyHooks[i] && keyHooks[i].when == when) { if (keyHooks[i].hook.apply(this, [])) { // remove the hook if (keyHooks[i].hookId) { delete this.hookIds[keyHooks[i].hookId]; } keyHooks[i]=null; } } } }; /** Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the Navpopup instance, and no arguments. @param {Function} hook The hook function. Functions that return true are deleted. @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide' @param {String} when Controls exactly when the hook is run: either 'before' or 'after' @param {String} uid A truthy string identifying the hook function; if it matches another hook in this position, it won't be added again. */ Navpopup.prototype.addHook = function ( hook, key, when, uid ) { when = when || 'after'; if (!this.hooks[key]) { return; } // if uid is specified, don't add duplicates var hookId=null; if (uid) { hookId=[key,when,uid].join('|'); if (this.hookIds[hookId]) { return; } this.hookIds[hookId]=true; } this.hooks[key].push( {hook: hook, when: when, hookId: hookId} ); }; /** Creates the main DIV element, which contains all the actual popup content. Runs hooks with key 'create'. @private */ Navpopup.prototype.createMainDiv = function () { if (this.mainDiv) { return; } this.runHooks('create', 'before'); var mainDiv=document.createElement('div'); var savedThis=this; mainDiv.onclick=function(e) {savedThis.onclickHandler(e);}; mainDiv.className=(this.className) ? this.className : 'navpopup_maindiv'; mainDiv.id=mainDiv.className + this.uid; mainDiv.style.position='absolute'; mainDiv.style.minWidth = '350px'; mainDiv.style.display='none'; mainDiv.className='navpopup'; // easy access to javascript object through DOM functions mainDiv.navpopup=this; this.mainDiv=mainDiv; document.body.appendChild(mainDiv); this.runHooks('create', 'after'); }; /** Calls the {@link #raise} method. @private */ Navpopup.prototype.onclickHandler=function(/*e*/) { this.raise(); }; /** Makes the popup draggable, using a {@link Drag} object. @private */ Navpopup.prototype.makeDraggable=function(handleName) { if (!this.mainDiv) { this.createMainDiv(); } var drag=new Drag(); if (!handleName) { drag.startCondition=function(e) { try { if (!e.shiftKey) { return false; } } catch (err) { return false; } return true; }; } var dragHandle; if (handleName) dragHandle = document.getElementById(handleName); if (!dragHandle) dragHandle = this.mainDiv; var np=this; drag.endHook=function(x,y) { Navpopup.tracker.dirty=true; np.reposition(x,y); }; drag.init(dragHandle,this.mainDiv); }; /** Hides the popup using CSS. Runs hooks with key 'hide'. Sets {@link #visible} appropriately. {@link #banish} should be called externally instead of this method. @private */ Navpopup.prototype.hide = function () { this.runHooks('hide', 'before'); this.abortDownloads(); if (typeof this.visible != 'undefined' && this.visible) { this.mainDiv.style.display='none'; this.visible=false; } this.runHooks('hide', 'after'); }; /** Shows the popup using CSS. Runs hooks with key 'unhide'. Sets {@link #visible} appropriately. {@link #show} should be called externally instead of this method. @private */ Navpopup.prototype.unhide = function () { this.runHooks('unhide', 'before'); if (typeof this.visible != 'undefined' && !this.visible) { this.mainDiv.style.display='inline'; this.visible=true; } this.runHooks('unhide', 'after'); }; /** Sets the <code>innerHTML</code> attribute of the main div containing the popup content. @param {String} html The HTML to set. */ Navpopup.prototype.setInnerHTML = function (html) { this.mainDiv.innerHTML = html; }; /** Updates the {@link #width} and {@link #height} attributes with the CSS properties. @private */ Navpopup.prototype.updateDimensions = function () { this.width=parseInt(this.mainDiv.offsetWidth, 10); this.height=parseInt(this.mainDiv.offsetHeight, 10); }; /** Checks if the point (x,y) is within {@link #fuzz} of the {@link #mainDiv}. @param {integer} x x-coordinate (px) @param {integer} y y-coordinate (px) @type boolean */ Navpopup.prototype.isWithin = function(x,y) { //~ If we're not even visible, no point should be considered as //~ being within the popup. if (!this.visible) { return false; } this.updateDimensions(); var fuzz=this.fuzz || 0; //~ Use a simple box metric here. return (x+fuzz >= this.left && x-fuzz <= this.left + this.width && y+fuzz >= this.top && y-fuzz <= this.top + this.height); }; /** Adds a download to {@link #downloads}. @param {Downloader} download */ Navpopup.prototype.addDownload=function(download) { if (!download) { return; } this.downloads.push(download); }; /** Aborts the downloads listed in {@link #downloads}. @see Downloader#abort */ Navpopup.prototype.abortDownloads=function() { for(var i=0; i<this.downloads.length; ++i) { var d=this.downloads[i]; if (d && d.abort) { d.abort(); } } this.downloads=[]; }; /** A {@link Mousetracker} instance which is a property of the constructor (pseudo-global). */ Navpopup.tracker=new Mousetracker(); // ENDFILE: navpopup.js // STARTFILE: diff.js //<NOLITE> /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) and [[:en:User:Lupin]] * * More Info: * http://ejohn.org/projects/javascript-diff-algorithm/ */ function delFmt(x) { if (!x.length) { return ''; } return "<del class='popupDiff'>" + x.join('') +"</del>"; } function insFmt(x) { if (!x.length) { return ''; } return "<ins class='popupDiff'>" + x.join('') +"</ins>"; } function countCrossings(a, b, i, eject) { // count the crossings on the edge starting at b[i] if (!b[i].row && b[i].row !== 0) { return -1; } var count=0; for (var j=0; j<a.length; ++j) { if (!a[j].row && a[j].row !== 0) { continue; } if ( (j-b[i].row)*(i-a[j].row) > 0) { if(eject) { return true; } count++; } } return count; } function shortenDiffString(str, context) { var re=RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)'); var splitted=str.parenSplit(re); var ret=['']; for (var i=0; i<splitted.length; i+=2) { if (splitted[i].length < 2*context) { ret[ret.length-1] += splitted[i]; if (i+1<splitted.length) { ret[ret.length-1] += splitted[i+1]; } continue; } else { if (i > 0) { ret[ret.length-1] += splitted[i].substring(0,context); } if (i+1 < splitted.length) { ret.push(splitted[i].substring(splitted[i].length-context) + splitted[i+1]); } } } while (ret.length > 0 && !ret[0]) { ret = ret.slice(1); } return ret; } function diffString( o, n, simpleSplit ) { var splitRe=RegExp('([[]{2}|[\\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\\s|\\b)'); // We need to split the strings o and n first, and entify() the parts // individually, so that the HTML entities are never cut apart. (AxelBoldt) var out, i, oSplitted, nSplitted; if (simpleSplit) { oSplitted=o.split(/\b/); nSplitted=n.split(/\b/); } else { oSplitted=o.parenSplit(splitRe); nSplitted=n.parenSplit(splitRe); } for (i=0; i<oSplitted.length; ++i) {oSplitted[i]=oSplitted[i].entify();} for (i=0; i<nSplitted.length; ++i) {nSplitted[i]=nSplitted[i].entify();} out = diff (oSplitted, nSplitted); var str = ""; var acc=[]; // accumulator for prettier output // crossing pairings -- eg 'A B' vs 'B A' -- cause problems, so let's iron them out // this doesn't always do things optimally but it should be fast enough var maxOutputPair=0; for (i=0; i<out.n.length; ++i) { if ( out.n[i].paired ) { if( maxOutputPair > out.n[i].row ) { // tangle - delete pairing out.o[ out.n[i].row ]=out.o[ out.n[i].row ].text; out.n[i]=out.n[i].text; } if (maxOutputPair < out.n[i].row) { maxOutputPair = out.n[i].row; } } } // output the stuff preceding the first paired old line for (i=0; i<out.o.length && !out.o[i].paired; ++i) { acc.push( out.o[i] ); } str += delFmt(acc); acc=[]; // main loop for ( i = 0; i < out.n.length; ++i ) { // output unpaired new "lines" while ( i < out.n.length && !out.n[i].paired ) { acc.push( out.n[i++] ); } str += insFmt(acc); acc=[]; if ( i < out.n.length ) { // this new "line" is paired with the (out.n[i].row)th old "line" str += out.n[i].text; // output unpaired old rows starting after this new line's partner var m = out.n[i].row + 1; while ( m < out.o.length && !out.o[m].paired ) { acc.push ( out.o[m++] ); } str += delFmt(acc); acc=[]; } } return str; } // see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object // FIXME: use obj.hasOwnProperty instead of this kludge! var jsReservedProperties=RegExp('^(constructor|prototype|__((define|lookup)[GS]etter)__' + '|eval|hasOwnProperty|propertyIsEnumerable' + '|to(Source|String|LocaleString)|(un)?watch|valueOf)$'); function diffBugAlert(word) { if (!diffBugAlert.list[word]) { diffBugAlert.list[word]=1; alert('Bad word: '+word+'\n\nPlease report this bug.'); } } diffBugAlert.list={}; function makeDiffHashtable(src) { var ret={}; for ( var i = 0; i < src.length; i++ ) { if ( jsReservedProperties.test(src[i]) ) { src[i] += '<!-- -->'; } if ( !ret[ src[i] ] ) { ret[ src[i] ] = []; } try { ret[ src[i] ].push( i ); } catch (err) { diffBugAlert(src[i]); } } return ret; } function diff( o, n ) { // pass 1: make hashtable ns with new rows as keys var ns = makeDiffHashtable(n); // pass 2: make hashtable os with old rows as keys var os = makeDiffHashtable(o); // pass 3: pair unique new rows and matching unique old rows var i; for ( i in ns ) { if ( ns[i].length == 1 && os[i] && os[i].length == 1 ) { n[ ns[i][0] ] = { text: n[ ns[i][0] ], row: os[i][0], paired: true }; o[ os[i][0] ] = { text: o[ os[i][0] ], row: ns[i][0], paired: true }; } } // pass 4: pair matching rows immediately following paired rows (not necessarily unique) for ( i = 0; i < n.length - 1; i++ ) { if ( n[i].paired && ! n[i+1].paired && n[i].row + 1 < o.length && ! o[ n[i].row + 1 ].paired && n[i+1] == o[ n[i].row + 1 ] ) { n[i+1] = { text: n[i+1], row: n[i].row + 1, paired: true }; o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1, paired: true }; } } // pass 5: pair matching rows immediately preceding paired rows (not necessarily unique) for ( i = n.length - 1; i > 0; i-- ) { if ( n[i].paired && ! n[i-1].paired && n[i].row > 0 && ! o[ n[i].row - 1 ].paired && n[i-1] == o[ n[i].row - 1 ] ) { n[i-1] = { text: n[i-1], row: n[i].row - 1, paired: true }; o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1, paired: true }; } } return { o: o, n: n }; } //</NOLITE> // ENDFILE: diff.js // STARTFILE: init.js function setSiteInfo() { if (window.popupLocalDebug) { pg.wiki.hostname = 'en.wikipedia.org'; } else { pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?) } pg.wiki.wikimedia=RegExp('(wiki([pm]edia|source|books|news|quote|versity)|wiktionary|mediawiki)[.]org').test(pg.wiki.hostname); pg.wiki.wikia=RegExp('[.]wikia[.]com$', 'i').test(pg.wiki.hostname); pg.wiki.isLocal=RegExp('^localhost').test(pg.wiki.hostname); pg.wiki.commons=( pg.wiki.wikimedia && pg.wiki.hostname != 'commons.wikimedia.org') ? 'commons.wikimedia.org' : null; pg.wiki.lang = mw.config.get('wgContentLanguage'); var port = location.port ? ':' + location.port : ''; pg.wiki.sitebase = pg.wiki.hostname + port; } function setUserInfo() { var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'query', list: 'users', ususers: mw.config.get('wgUserName'), usprop: 'rights' }; pg.user.canReview = false; if (getValueOf('popupReview')) { api.get(params).done(function(data){ var rights = data.query.users[0].rights; pg.user.canReview = rights.indexOf('review') !== -1; // TODO: Should it be a getValueOf('ReviewRight') ? }); } } function setTitleBase() { var protocol = ( window.popupLocalDebug ? 'http:' : location.protocol ); pg.wiki.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, ""); // as in http://some.thing.com/wiki/Article pg.wiki.botInterfacePath = mw.config.get('wgScript'); pg.wiki.APIPath = mw.config.get('wgScriptPath') +"/api.php"; // default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo var titletail = pg.wiki.botInterfacePath + '?title='; //var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?title=']); // other sites may need to add code here to set titletail depending on how their urls work pg.wiki.titlebase = protocol + '//' + pg.wiki.sitebase + titletail; //pg.wiki.titlebase2 = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]); pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath; pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath; pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath; pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.botInterfacePath; pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.APIPath; pg.re.basenames = RegExp( '^(' + map( literalizeRegex, [ pg.wiki.titlebase, //pg.wiki.titlebase2, pg.wiki.articlebase ]).join('|') + ')' ); } ////////////////////////////////////////////////// // Global regexps function setMainRegex() { var reStart='[^:]*://'; var preTitles = literalizeRegex( mw.config.get('wgScriptPath') ) + '/(?:index[.]php|wiki[.]phtml)[?]title='; preTitles += '|' + literalizeRegex( pg.wiki.articlePath + '/' ); var reEnd='(' + preTitles + ')([^&?#]*)[^#]*(?:#(.+))?'; pg.re.main = RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd); } function setRegexps() { // TODO: We shoud use an api call to get the aliases for special pages, now it does not work for non-English wikipedias: // E.g., https://ru.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=specialpagealiases&formatversion=2 setMainRegex(); var sp=nsRe(pg.nsSpecialId); pg.re.urlNoPopup=RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)') ; pg.re.contribs =RegExp('(title=|/)' + sp + '(?:%3A|:)Contributions' + '(&target=|/|/' + nsRe(pg.nsUserId)+':)(.*)') ; pg.re.email =RegExp('(title=|/)' + sp + '(?:%3A|:)EmailUser' + '(&target=|/|/(?:' + nsRe(pg.nsUserId)+':)?)(.*)') ; pg.re.backlinks =RegExp('(title=|/)' + sp + '(?:%3A|:)WhatLinksHere' + '(&target=|/)([^&]*)'); pg.re.specialdiff=RegExp('/' + sp + '(?:%3A|:)Diff/([^?#]*)'); //<NOLITE> var im=nsReImage(); // note: tries to get images in infobox templates too, e.g. movie pages, album pages etc // (^|\[\[)image: *([^|\]]*[^|\] ]) * // (^|\[\[)image: *([^|\]]*[^|\] ])([^0-9\]]*([0-9]+) *px)? // $4 = 120 as in 120px pg.re.image = RegExp('(^|\\[\\[)' + im + ': *([^|\\]]*[^|\\] ])' + '([^0-9\\]]*([0-9]+) *px)?|(?:\\n *[|]?|[|]) *' + '(' + getValueOf('popupImageVarsRegexp') + ')' + ' *= *(?:\\[\\[ *)?(?:' + im + ':)?' + '([^|]*?)(?:\\]\\])? *[|]? *\\n', 'img') ; pg.re.imageBracketCount = 6; pg.re.category = RegExp('\\[\\[' +nsRe(pg.nsCategoryId) + ': *([^|\\]]*[^|\\] ]) *', 'i'); pg.re.categoryBracketCount = 1; pg.re.ipUser=RegExp('^' + // IPv6 '(?::(?::|(?::[0-9A-Fa-f]{1,4}){1,7})|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,6}::|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){7})' + // IPv4 '|(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' + '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$'); pg.re.stub= RegExp(getValueOf('popupStubRegexp'), 'im'); pg.re.disambig=RegExp(getValueOf('popupDabRegexp'), 'im'); //</NOLITE> // FIXME replace with general parameter parsing function, this is daft pg.re.oldid=RegExp('[?&]oldid=([^&]*)'); pg.re.diff=RegExp('[?&]diff=([^&]*)'); } ////////////////////////////////////////////////// // miscellany function setupCache() { // page caching pg.cache.pages = []; } function setMisc() { pg.current.link=null; pg.current.links=[]; pg.current.linksHash={}; setupCache(); pg.timer.checkPopupPosition=null; pg.counter.loop=0; // ids change with each popup: popupImage0, popupImage1 etc pg.idNumber=0; // for myDecodeURI pg.misc.decodeExtras = [ {from: '%2C', to: ',' }, {from: '_', to: ' ' }, {from: '%24', to: '$'}, {from: '%26', to: '&' } // no , ]; pg.misc.userAgent = 'Navigation popups/1.0 (' + mw.config.get( 'wgServerName' ) +')'; } // We need a callback since this might end up asynchronous because of // the mw.loader.using() call. function setupPopups( callback ) { if ( setupPopups.completed ) { if ( typeof callback === 'function' ) { callback(); } return; } // These dependencies are also enforced from the gadget, // but not everyone loads this as a gadget, so double check mw.loader.using( ['mediawiki.util', 'mediawiki.user', 'user.options'] ).then( function() { // NB translatable strings should be set up first (strings.js) // basics setupDebugging(); setSiteInfo(); setTitleBase(); setOptions(); // see options.js setUserInfo(); // namespaces etc setNamespaces(); setInterwiki(); // regexps setRegexps(); setRedirs(); // other stuff setMisc(); setupLivePreview(); // main deal here setupTooltips(); log('In setupPopups(), just called setupTooltips()'); Navpopup.tracker.enable(); setupPopups.completed = true; if ( typeof callback === 'function' ) { callback(); } }); } // ENDFILE: init.js // STARTFILE: navlinks.js //<NOLITE> ////////////////////////////////////////////////// // navlinks... let the fun begin // function defaultNavlinkSpec() { var str=''; str += '<b><<mainlink|shortcut= >></b>'; if (getValueOf('popupLastEditLink')) { str += '*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}'; } // user links // contribs - log - count - email - block // count only if applicable; block only if popupAdminLinks str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>'; str+='if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}'; // editing links // talkpage -> edit|new - history - un|watch - article|edit // other page -> edit - history - un|watch - talk|edit|new var editstr='<<edit|shortcut=e>>'; var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'; var historystr='<<history|shortcut=h>>|<<editors|shortcut=E|>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; str+='<br>if(talk){' + editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + // not a talk page editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}'; // misc links str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>'; // admin links str += 'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}'; return str; } function navLinksHTML (article, hint, params) { //oldid, rcid) { var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>'; // BAM return navlinkStringToHTML(str, article, params); } function expandConditionalNavlinkString(s,article,z,recursionCount) { var oldid=z.oldid, rcid=z.rcid, diff=z.diff; // nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out) if (typeof recursionCount!=typeof 0) { recursionCount=0; } var conditionalSplitRegex=RegExp( //(1 if \\( (2 2) \\) {(3 3)} (4 else {(5 5)} 4)1) '(;?\\s*if\\s*\\(\\s*([\\w]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))', 'i'); var splitted=s.parenSplit(conditionalSplitRegex); // $1: whole conditional // $2: test condition // $3: true expansion // $4: else clause (possibly empty) // $5: false expansion (possibly null) var numParens=5; var ret = splitted[0]; for (var i=1; i<splitted.length; i=i+numParens+1) { var testString=splitted[i+2-1]; var trueString=splitted[i+3-1]; var falseString=splitted[i+5-1]; if (typeof falseString=='undefined' || !falseString) { falseString=''; } var testResult=null; switch (testString) { case 'user': testResult=(article.userName())?true:false; break; case 'talk': testResult=(article.talkPage())?false:true; // talkPage converts _articles_ to talkPages break; case 'admin': testResult=getValueOf('popupAdminLinks')?true:false; break; case 'oldid': testResult=(typeof oldid != 'undefined' && oldid)?true:false; break; case 'rcid': testResult=(typeof rcid != 'undefined' && rcid)?true:false; break; case 'ipuser': testResult=(article.isIpUser())?true:false; break; case 'mainspace_en': testResult=isInMainNamespace(article) && pg.wiki.hostname=='en.wikipedia.org'; break; case 'wikimedia': testResult=(pg.wiki.wikimedia) ? true : false; break; case 'diff': testResult=(typeof diff != 'undefined' && diff)?true:false; break; } switch(testResult) { case null: ret+=splitted[i]; break; case true: ret+=trueString; break; case false: ret+=falseString; break; } // append non-conditional string ret += splitted[i+numParens]; } if (conditionalSplitRegex.test(ret) && recursionCount < 10) { return expandConditionalNavlinkString(ret,article,z,recursionCount+1); } return ret; } function navlinkStringToArray(s, article, params) { s=expandConditionalNavlinkString(s,article,params); var splitted=s.parenSplit(RegExp('<<(.*?)>>')); var ret=[]; for (var i=0; i<splitted.length; ++i) { if (i%2) { // i odd, so s is a tag var t=new navlinkTag(); var ss=splitted[i].split('|'); t.id=ss[0]; for (var j=1; j<ss.length; ++j) { var sss=ss[j].split('='); if (sss.length>1) { t[sss[0]]=sss[1]; } else { // no assignment (no "="), so treat this as a title (overwriting the last one) t.text=popupString(sss[0]); } } t.article=article; var oldid=params.oldid, rcid=params.rcid, diff=params.diff; if (typeof oldid !== 'undefined' && oldid !== null) { t.oldid=oldid; } if (typeof rcid !== 'undefined' && rcid !== null) { t.rcid=rcid; } if (typeof diff !== 'undefined' && diff !== null) { t.diff=diff; } if (!t.text && t.id !== 'mainlink') { t.text=popupString(t.id); } ret.push(t); } else { // plain HTML ret.push(splitted[i]); } } return ret; } function navlinkSubstituteHTML(s) { return s.split('*').join(getValueOf('popupNavLinkSeparator')) .split('<menurow>').join('<li class="popup_menu_row">') .split('</menurow>').join('</li>') .split('<menu>').join('<ul class="popup_menu">') .split('</menu>').join('</ul>'); } function navlinkDepth(magic,s) { return s.split('<' + magic + '>').length - s.split('</' + magic + '>').length; } // navlinkString: * becomes the separator // <<foo|bar=baz|fubar>> becomes a foo-link with attribute bar='baz' // and visible text 'fubar' // if(test){...} and if(test){...}else{...} work too (nested ok) function navlinkStringToHTML(s,article,params) { //limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article)); var p=navlinkStringToArray(s,article,params); var html=''; var menudepth = 0; // nested menus not currently allowed, but doesn't do any harm to code for it var menurowdepth = 0; for (var i=0; i<p.length; ++i) { if (typeof p[i] == typeof '') { html+=navlinkSubstituteHTML(p[i]); menudepth += navlinkDepth('menu', p[i]); menurowdepth += navlinkDepth('menurow', p[i]); // if (menudepth === 0) { // tagType='span'; // } else if (menurowdepth === 0) { // tagType='li'; // } else { // tagType = null; // } } else if (typeof p[i].type != 'undefined' && p[i].type=='navlinkTag') { if (menudepth > 0 && menurowdepth === 0) { html += '<li class="popup_menu_item">' + p[i].html() + '</li>'; } else { html+=p[i].html(); } } } return html; } function navlinkTag() { this.type='navlinkTag'; } navlinkTag.prototype.html=function () { this.getNewWin(); this.getPrintFunction(); var html=''; var opening, closing; var tagType='span'; if (!tagType) { opening = ''; closing = ''; } else { opening = '<' + tagType + ' class="popup_' + this.id + '">'; closing = '</' + tagType + '>'; } if (typeof this.print!='function') { errlog ('Oh dear - invalid print function for a navlinkTag, id='+this.id); } else { html=this.print(this); if (typeof html != typeof '') {html='';} else if (typeof this.shortcut!='undefined') html=addPopupShortcut(html, this.shortcut); } return opening + html + closing; }; navlinkTag.prototype.getNewWin=function() { getValueOf('popupLinksNewWindow'); if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') { this.newWin=null; } this.newWin=pg.option.popupLinksNewWindow[this.id]; }; navlinkTag.prototype.getPrintFunction=function() { //think about this some more // this.id and this.article should already be defined if (typeof this.id!=typeof '' || typeof this.article!=typeof {} ) { return; } this.noPopup=1; switch (this.id) { case 'contribs': case 'history': case 'whatLinksHere': case 'userPage': case 'monobook': case 'userTalk': case 'talk': case 'article': case 'lastEdit': this.noPopup=null; } switch (this.id) { case 'email': case 'contribs': case 'block': case 'unblock': case 'userlog': case 'userSpace': case 'deletedContribs': this.article=this.article.userName(); } switch (this.id) { case 'userTalk': case 'newUserTalk': case 'editUserTalk': case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog': this.article=this.article.userName(true); /* fall through */ case 'pagelog': case 'deletelog': case 'protectlog': delete this.oldid; } if (this.id=='editMonobook' || this.id=='monobook') { this.article.append('/monobook.js'); } if (this.id != 'mainlink') { // FIXME anchor handling should be done differently with Title object this.article=this.article.removeAnchor(); // if (typeof this.text=='undefined') this.text=popupString(this.id); } switch (this.id) { case 'undelete': this.print=specialLink; this.specialpage='Undelete'; this.sep='/'; break; case 'whatLinksHere': this.print=specialLink; this.specialpage='Whatlinkshere'; break; case 'relatedChanges': this.print=specialLink; this.specialpage='Recentchangeslinked'; break; case 'move': this.print=specialLink; this.specialpage='Movepage'; break; case 'contribs': this.print=specialLink; this.specialpage='Contributions'; break; case 'deletedContribs':this.print=specialLink; this.specialpage='Deletedcontributions'; break; case 'email': this.print=specialLink; this.specialpage='EmailUser'; this.sep='/'; break; case 'block': this.print=specialLink; this.specialpage='Blockip'; this.sep='&ip='; break; case 'unblock': this.print=specialLink; this.specialpage='Ipblocklist'; this.sep='&action=unblock&ip='; break; case 'userlog': this.print=specialLink; this.specialpage='Log'; this.sep='&user='; break; case 'blocklog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=block&page='; break; case 'pagelog': this.print=specialLink; this.specialpage='Log'; this.sep='&page='; break; case 'protectlog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=protect&page='; break; case 'deletelog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=delete&page='; break; case 'userSpace': this.print=specialLink; this.specialpage='PrefixIndex'; this.sep='&namespace=2&prefix='; break; case 'search': this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break; case 'thank': this.print=specialLink; this.specialpage='Thanks'; this.sep='/'; this.article.value = this.diff; break; case 'unwatch': case 'watch': this.print=magicWatchLink; this.action=this.id+'&autowatchlist=1&autoimpl=' + popupString('autoedit_version') + '&actoken='+autoClickToken(); break; case 'history': case 'historyfeed': case 'unprotect': case 'protect': this.print=wikiLink; this.action=this.id; break; case 'delete': this.print=wikiLink; this.action='delete'; if (this.article.namespaceId()==pg.nsImageId) { var img=this.article.stripNamespace(); this.action+='&image='+img; } break; case 'markpatrolled': case 'edit': // editOld should keep the oldid, but edit should not. delete this.oldid; /* fall through */ case 'view': case 'purge': case 'render': this.print=wikiLink; this.action=this.id; break; case 'raw': this.print=wikiLink; this.action='raw'; break; case 'new': this.print=wikiLink; this.action='edit&section=new'; break; case 'mainlink': if (typeof this.text=='undefined') { this.text=this.article.toString().entify(); } if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) { // only show the /subpage part of the title text var s=this.text.split('/'); this.text=s[s.length-1]; if (this.text==='' && s.length > 1) { this.text=s[s.length-2]; } } this.print=titledWikiLink; if (typeof this.title==='undefined' && pg.current.link && typeof pg.current.link.href !== 'undefined') { this.title=safeDecodeURI((pg.current.link.originalTitle)?pg.current.link.originalTitle:this.article); if (typeof this.oldid !== 'undefined' && this.oldid) { this.title=tprintf('Revision %s of %s', [this.oldid, this.title]); } } this.action='view'; break; case 'userPage': case 'article': case 'monobook': case 'editMonobook': case 'editArticle': delete this.oldid; //alert(this.id+'\n'+this.article + '\n'+ typeof this.article); this.article=this.article.articleFromTalkOrArticle(); //alert(this.id+'\n'+this.article + '\n'+ typeof this.article); this.print=wikiLink; if (this.id.indexOf('edit')===0) { this.action='edit'; } else { this.action='view';} break; case 'userTalk': case 'talk': this.article=this.article.talkPage(); delete this.oldid; this.print=wikiLink; this.action='view'; break; case 'arin': this.print=arinLink; break; case 'count': this.print=editCounterLink; break; case 'google': this.print=googleLink; break; case 'editors': this.print=editorListLink; break; case 'globalsearch': this.print=globalSearchLink; break; case 'lastEdit': this.print=titledDiffLink; this.title=popupString('Show the last edit'); this.from='prev'; this.to='cur'; break; case 'oldEdit': this.print=titledDiffLink; this.title=popupString('Show the edit made to get revision') + ' ' + this.oldid; this.from='prev'; this.to=this.oldid; break; case 'editOld': this.print=wikiLink; this.action='edit'; break; case 'undo': this.print=wikiLink; this.action='edit&undo='; break; case 'revert': this.print=wikiLink; this.action='revert'; break; case 'nullEdit': this.print=wikiLink; this.action='nullEdit'; break; case 'diffCur': this.print=titledDiffLink; this.title=tprintf('Show changes since revision %s', [this.oldid]); this.from=this.oldid; this.to='cur'; break; case 'editUserTalk': case 'editTalk': delete this.oldid; this.article=this.article.talkPage(); this.action='edit'; this.print=wikiLink; break; case 'newUserTalk': case 'newTalk': this.article=this.article.talkPage(); this.action='edit&section=new'; this.print=wikiLink; break; case 'lastContrib': case 'sinceMe': this.print=magicHistoryLink; break; case 'togglePreviews': this.text=popupString(pg.option.simplePopups ? 'enable previews' : 'disable previews'); /* fall through */ case 'disablePopups': case 'purgePopups': this.print=popupMenuLink; break; default: this.print=function () {return 'Unknown navlink type: '+this.id+'';}; } }; // // end navlinks ////////////////////////////////////////////////// //</NOLITE> // ENDFILE: navlinks.js // STARTFILE: shortcutkeys.js //<NOLITE> function popupHandleKeypress(evt) { var keyCode = window.event ? window.event.keyCode : ( evt.keyCode ? evt.keyCode : evt.which); if (!keyCode || !pg.current.link || !pg.current.link.navpopup) { return; } if (keyCode==27) { // escape killPopup(); return false; // swallow keypress } var letter=String.fromCharCode(keyCode); var links=pg.current.link.navpopup.mainDiv.getElementsByTagName('A'); var startLink=0; var i,j; if (popupHandleKeypress.lastPopupLinkSelected) { for (i=0; i<links.length; ++i) { if (links[i]==popupHandleKeypress.lastPopupLinkSelected) { startLink=i; } } } for (j=0; j<links.length; ++j) { i=(startLink + j + 1) % links.length; if (links[i].getAttribute('popupkey')==letter) { if (evt && evt.preventDefault) evt.preventDefault(); links[i].focus(); popupHandleKeypress.lastPopupLinkSelected=links[i]; return false; // swallow keypress } } // pass keypress on if (document.oldPopupOnkeypress) { return document.oldPopupOnkeypress(evt); } return true; } function addPopupShortcuts() { if (document.onkeypress!=popupHandleKeypress) { document.oldPopupOnkeypress=document.onkeypress; } document.onkeypress=popupHandleKeypress; } function rmPopupShortcuts() { popupHandleKeypress.lastPopupLinkSelected=null; try { if (document.oldPopupOnkeypress && document.oldPopupOnkeypress==popupHandleKeypress) { // panic document.onkeypress=null; //function () {}; return; } document.onkeypress=document.oldPopupOnkeypress; } catch (nasties) { /* IE goes here */ } } function addLinkProperty(html, property) { // take "<a href=...>...</a> and add a property // not sophisticated at all, easily broken var i=html.indexOf('>'); if (i<0) { return html; } return html.substring(0,i) + ' ' + property + html.substring(i); } function addPopupShortcut(html, key) { if (!getValueOf('popupShortcutKeys')) { return html; } var ret= addLinkProperty(html, 'popupkey="'+key+'"'); if (key==' ') { key=popupString('spacebar'); } return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'),'$1$2$3 ['+key+']$4'); } //</NOLITE> // ENDFILE: shortcutkeys.js // STARTFILE: diffpreview.js //<NOLITE> //lets jump through hoops to find the rev ids we need to retrieve function loadDiff(article, oldid, diff, navpop) { navpop.diffData={ oldRev: {}, newRev: {} }; mw.loader.using( 'mediawiki.api' ).then( function() { var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'compare', prop: 'ids|title' }; if(article.title){ params.fromtitle = article.title; } switch (diff) { case 'cur': switch ( oldid ) { case null: case '': case 'prev': // this can only work if we have the title // cur -> prev params.torelative = 'prev'; break; default: params.fromrev = oldid; params.torelative = 'cur'; break; } break; case 'prev': if( oldid ) { params.fromrev = oldid; } else { params.fromtitle; } params.torelative = 'prev'; break; case 'next': params.fromrev = oldid || 0; params.torelative = 'next'; break; default: params.fromrev = oldid || 0; params.torev = diff || 0; break; } api.get( params ).then( function( data ) { navpop.diffData.oldRev.revid = data.compare.fromrevid; navpop.diffData.newRev.revid = data.compare.torevid; addReviewLink(navpop, 'popupMiscTools'); var go = function() { pendingNavpopTask(navpop); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&'; url += 'revids=' + navpop.diffData.oldRev.revid + '|' + navpop.diffData.newRev.revid; url += '&prop=revisions&rvprop=ids|timestamp|content'; getPageWithCaching(url, doneDiff, navpop); return true; // remove hook once run }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS'); } } ); } ); } // Put a "mark patrolled" link to an element target // TODO: Allow patrol a revision, as well as a diff function addReviewLink (navpop, target) { if (! pg.user.canReview) return; // If 'newRev' is older than 'oldRev' than it could be confusing, so we do not show the review link. if (navpop.diffData.newRev.revid <= navpop.diffData.oldRev.revid) return; var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'query', prop: 'info|flagged', revids: navpop.diffData.oldRev.revid, formatversion : 2 }; api.get (params).then(function(data){ var stable_revid = data.query.pages[0].flagged && data.query.pages[0].flagged.stable_revid || 0; // The diff can be reviewed if the old version is the last reviewed version // TODO: Other possible conditions that we may want to implement instead of this one: // * old version is patrolled and the new version is not patrolled // * old version is patrolled and the new version is more recent than the last reviewed version if (stable_revid == navpop.diffData.oldRev.revid) { var a = document.createElement('a'); a.innerHTML = popupString('mark patrolled'); a.title=popupString('markpatrolledHint'); a.onclick = function() { var params = { action: 'review', revid: navpop.diffData.newRev.revid, comment: tprintf('defaultpopupReviewedSummary', [navpop.diffData.oldRev.revid, navpop.diffData.newRev.revid]) }; api.postWithToken('csrf',params).done(function(){ a.style.display = "none"; // TODO: Update current page and other already constructed popups } ).fail(function(){ alert(popupString('Could not marked this edit as patrolled')); }); }; setPopupHTML(a, target, navpop.idNumber,null,true); } }); } function doneDiff(download) { if (!download.owner || !download.owner.diffData) { return; } var navpop=download.owner; completedNavpopTask(navpop); var pages, revisions=[]; try{ // Process the downloads pages = getJsObj(download.data).query.pages; for(var i=0; i < pages.length; i++ ) { revisions = revisions.concat(pages[i].revisions); } for(i=0; i< revisions.length; i++){ if(revisions[i].revid == navpop.diffData.oldRev.revid) { navpop.diffData.oldRev.revision = revisions[i]; } else if (revisions[i].revid == navpop.diffData.newRev.revid) { navpop.diffData.newRev.revision = revisions[i]; } } } catch(someError) { errlog( 'Could not get diff' ); } insertDiff(navpop); } function rmBoringLines(a,b,context) { if (typeof context == 'undefined') { context=2; } // this is fairly slow... i think it's quicker than doing a word-based diff from the off, though var aa=[], aaa=[]; var bb=[], bbb=[]; var i, j; // first, gather all disconnected nodes in a and all crossing nodes in a and b for (i=0; i<a.length; ++i ) { if(!a[i].paired) { aa[i]=1; } else if (countCrossings(b,a,i, true)) { aa[i]=1; bb[ a[i].row ] = 1; } } // pick up remaining disconnected nodes in b for (i=0; i<b.length; ++i ) { if (bb[i]==1) { continue; } if(!b[i].paired) { bb[i]=1; } } // another pass to gather context: we want the neighbours of included nodes which are not yet included // we have to add in partners of these nodes, but we don't want to add context for *those* nodes in the next pass for (i=0; i<b.length; ++i) { if ( bb[i] == 1 ) { for (j=Math.max(0,i-context); j < Math.min(b.length, i+context); ++j) { if ( !bb[j] ) { bb[j] = 1; aa[ b[j].row ] = 0.5; } } } } for (i=0; i<a.length; ++i) { if ( aa[i] == 1 ) { for (j=Math.max(0,i-context); j < Math.min(a.length, i+context); ++j) { if ( !aa[j] ) { aa[j] = 1; bb[ a[j].row ] = 0.5; } } } } for (i=0; i<bb.length; ++i) { if (bb[i] > 0) { // it's a row we need if (b[i].paired) { bbb.push(b[i].text); } // joined; partner should be in aa else { bbb.push(b[i]); } } } for (i=0; i<aa.length; ++i) { if (aa[i] > 0) { // it's a row we need if (a[i].paired) { aaa.push(a[i].text); } // joined; partner should be in aa else { aaa.push(a[i]); } } } return { a: aaa, b: bbb}; } function stripOuterCommonLines(a,b,context) { var i=0; while (i<a.length && i < b.length && a[i]==b[i]) { ++i; } var j=a.length-1; var k=b.length-1; while ( j>=0 && k>=0 && a[j]==b[k] ) { --j; --k; } return { a: a.slice(Math.max(0,i - 1 - context), Math.min(a.length+1, j + context+1)), b: b.slice(Math.max(0,i - 1 - context), Math.min(b.length+1, k + context+1)) }; } function insertDiff(navpop) { // for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff // FIXME: sometimes this gives misleading diffs as distant chunks are squashed together var oldlines = navpop.diffData.oldRev.revision.content.split('\n'); var newlines = navpop.diffData.newRev.revision.content.split('\n'); var inner=stripOuterCommonLines(oldlines,newlines,getValueOf('popupDiffContextLines')); oldlines=inner.a; newlines=inner.b; var truncated=false; getValueOf('popupDiffMaxLines'); if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) { // truncate truncated=true; inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines), newlines.slice(0,pg.option.popupDiffMaxLines), pg.option.popupDiffContextLines); oldlines=inner.a; newlines=inner.b; } var lineDiff=diff(oldlines, newlines); var lines2=rmBoringLines(lineDiff.o, lineDiff.n); var oldlines2=lines2.a; var newlines2=lines2.b; var simpleSplit = !String.prototype.parenSplit.isNative; var html='<hr />'; if (getValueOf('popupDiffDates')) { html += diffDatesTable(navpop); html += '<hr />'; } html += shortenDiffString( diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit), getValueOf('popupDiffContextCharacters') ).join('<hr />'); setPopupTipsAndHTML(html.split('\n').join('<br>') + (truncated ? '<hr /><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') , 'popupPreview', navpop.idNumber); } function diffDatesTable( navpop ) { var html='<table class="popup_diff_dates">'; html += diffDatesTableRow( navpop.diffData.newRev.revision, tprintf('New revision')); html += diffDatesTableRow( navpop.diffData.oldRev.revision, tprintf('Old revision')); html += '</table>'; return html; } function diffDatesTableRow( revision, label ) { var txt=''; var lastModifiedDate = new Date(revision.timestamp); var datePrint=getValueOf('popupDiffDatePrinter'); if (typeof lastModifiedDate[datePrint] == 'function') { var d2 = adjustDate(lastModifiedDate, getTimeOffset()); txt = dayFormat(d2, true) + ' ' + timeFormat(d2, true); } else { txt = tprintf('Invalid %s %s', ['popupDiffDatePrinter', datePrint]); } var revlink = generalLink({url: mw.config.get('wgScript') + '?oldid='+revision.revid, text: label, title: label}); return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [ revlink, txt ]); } //</NOLITE> // ENDFILE: diffpreview.js // STARTFILE: links.js //<NOLITE> ///////////////////// // LINK GENERATION // ///////////////////// // titledDiffLink --> titledWikiLink --> generalLink // wikiLink --> titledWikiLink --> generalLink // editCounterLink --> generalLink // TODO Make these functions return Element objects, not just raw HTML strings. function titledDiffLink(l) { // article, text, title, from, to) { return titledWikiLink({article: l.article, action: l.to + '&oldid=' + l.from, newWin: l.newWin, noPopup: l.noPopup, text: l.text, title: l.title, /* hack: no oldid here */ actionName: 'diff'}); } function wikiLink(l) { //{article:article, action:action, text:text, oldid, newid}) { if (! (typeof l.article == typeof {} && typeof l.action == typeof '' && typeof l.text==typeof '')) return null; if (typeof l.oldid == 'undefined') { l.oldid=null; } var savedOldid = l.oldid; if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) { l.oldid=null; } var hint=popupString(l.action + 'Hint'); // revertHint etc etc etc var oldidData=[l.oldid, safeDecodeURI(l.article)]; var revisionString = tprintf('revision %s of %s', oldidData); log('revisionString='+revisionString); switch (l.action) { case 'edit&section=new': hint = popupString('newSectionHint'); break; case 'edit&undo=': if (l.diff && l.diff != 'prev' && savedOldid ) { l.action += l.diff + '&undoafter=' + savedOldid; } else if (savedOldid) { l.action += savedOldid; } hint = popupString('undoHint'); break; case 'raw&ctype=text/css': hint=popupString('rawHint'); break; case 'revert': var p=parseParams(pg.current.link.href); l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=' + revertSummary(l.oldid, p.diff); if (p.diff=='prev') { l.action += '&direction=prev'; revisionString = tprintf('the revision prior to revision %s of %s', oldidData); } if (getValueOf('popupRevertSummaryPrompt')) { l.action += '&autosummaryprompt=true'; } if (getValueOf('popupMinorReverts')) { l.action += '&autominor=true'; } log('revisionString is now '+revisionString); break; case 'nullEdit': l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=null'; break; case 'historyfeed': l.action='history&feed=rss'; break; case 'markpatrolled': l.action='markpatrolled&rcid='+l.rcid; } if (hint) { if (l.oldid) { hint = simplePrintf(hint, [revisionString]); } else { hint = simplePrintf(hint, [safeDecodeURI(l.article)]); } } else { hint = safeDecodeURI(l.article + '&action=' + l.action) + (l.oldid) ? '&oldid='+l.oldid : ''; } return titledWikiLink({article: l.article, action: l.action, text: l.text, newWin:l.newWin, title: hint, oldid: l.oldid, noPopup: l.noPopup, onclick: l.onclick}); } function revertSummary(oldid, diff) { var ret=''; if (diff == 'prev') { ret=getValueOf('popupQueriedRevertToPreviousSummary'); } else { ret = getValueOf('popupQueriedRevertSummary'); } return ret + '&autorv=' + oldid; } function titledWikiLink(l) { // possible properties of argument: // article, action, text, title, oldid, actionName, className, noPopup // oldid = null is fine here // article and action are mandatory args if (typeof l.article == 'undefined' || typeof l.action=='undefined') { errlog('got undefined article or action in titledWikiLink'); return null; } var base = pg.wiki.titlebase + l.article.urlString(); var url=base; if (typeof l.actionName=='undefined' || !l.actionName) { l.actionName='action'; } // no need to add &action=view, and this confuses anchors if (l.action != 'view') { url = base + '&' + l.actionName + '=' + l.action; } if (typeof l.oldid!='undefined' && l.oldid) { url+='&oldid='+l.oldid; } var cssClass=pg.misc.defaultNavlinkClassname; if (typeof l.className!='undefined' && l.className) { cssClass=l.className; } return generalNavLink({url: url, newWin: l.newWin, title: (typeof l.title != 'undefined') ? l.title : null, text: (typeof l.text!='undefined')?l.text:null, className: cssClass, noPopup:l.noPopup, onclick:l.onclick}); } pg.fn.getLastContrib = function getLastContrib(wikipage, newWin) { getHistoryInfo(wikipage, function(x) { processLastContribInfo(x, {page: wikipage, newWin: newWin}); }); }; function processLastContribInfo(info, stuff) { if(!info.edits || !info.edits.length) { alert('Popups: an odd thing happened. Please retry.'); return; } if(!info.firstNewEditor) { alert(tprintf('Only found one editor: %s made %s edits', [info.edits[0].editor,info.edits.length])); return; } var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+info.firstNewEditor.oldid; displayUrl(newUrl, stuff.newWin); } pg.fn.getDiffSinceMyEdit = function getDiffSinceMyEdit(wikipage, newWin) { getHistoryInfo(wikipage, function(x){ processDiffSinceMyEdit(x, {page: wikipage, newWin: newWin}); }); }; function processDiffSinceMyEdit(info, stuff) { if(!info.edits || !info.edits.length) { alert('Popups: something fishy happened. Please try again.'); return; } var friendlyName=stuff.page.split('_').join(' '); if(!info.myLastEdit) { alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s', [info.userName, getValueOf('popupHistoryLimit'), friendlyName])); return; } if(info.myLastEdit.index === 0) { alert(tprintf("%s seems to be the last editor to the page %s", [info.userName, friendlyName])); return; } var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+ info.myLastEdit.oldid; displayUrl(newUrl, stuff.newWin); } function displayUrl(url, newWin){ if(newWin) { window.open(url); } else { document.location=url; } } pg.fn.purgePopups = function purgePopups() { processAllPopups(true); setupCache(); // deletes all cached items (not browser cached, though...) pg.option={}; abortAllDownloads(); }; function processAllPopups(nullify, banish) { for (var i=0; pg.current.links && i<pg.current.links.length; ++i) { if (!pg.current.links[i].navpopup) { continue; } if (nullify || banish) pg.current.links[i].navpopup.banish(); pg.current.links[i].simpleNoMore=false; if (nullify) pg.current.links[i].navpopup=null; } } pg.fn.disablePopups = function disablePopups(){ processAllPopups(false, true); setupTooltips(null, true); }; pg.fn.togglePreviews = function togglePreviews() { processAllPopups(true, true); pg.option.simplePopups=!pg.option.simplePopups; abortAllDownloads(); }; function magicWatchLink(l) { //Yuck!! Would require a thorough redesign to add this as a click event though ... l.onclick = simplePrintf( 'pg.fn.modifyWatchlist(\'%s\',\'%s\');return false;', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), this.id] ); return wikiLink(l); } pg.fn.modifyWatchlist = function modifyWatchlist(title, action) { var reqData = { 'action': 'watch', 'formatversion': 2, 'titles': title, 'uselang': mw.config.get('wgUserLanguage') }; if ( action === 'unwatch' ) reqData.unwatch = true; var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); // Load the Addedwatchtext or Removedwatchtext message and show it var mwTitle = mw.Title.newFromText( title ); var messageName; if ( mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1 ) { messageName = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk'; } else { messageName = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext'; } $.when( api.postWithToken( 'watch', reqData ), mw.loader.using( [ 'mediawiki.api', 'mediawiki.jqueryMsg' ] ).then( function () { return api.loadMessagesIfMissing( [ messageName ] ); } ) ).done( function () { mw.notify( mw.message( messageName, title ).parseDom() ); } ); }; function magicHistoryLink(l) { // FIXME use onclick change href trick to sort this out instead of window.open var jsUrl='', title='', onClick=''; switch(l.id) { case 'lastContrib': onClick=simplePrintf('pg.fn.getLastContrib(\'%s\',%s)', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]); title=popupString('lastContribHint'); break; case 'sinceMe': onClick=simplePrintf('pg.fn.getDiffSinceMyEdit(\'%s\',%s)', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]); title=popupString('sinceMeHint'); break; } jsUrl = 'javascript:' + onClick; // jshint ignore:line onClick += ';return false;'; return generalNavLink({url: jsUrl, newWin: false, // can't have new windows with JS links, I think title: title, text: l.text, noPopup: l.noPopup, onclick: onClick }); } function popupMenuLink(l) { var jsUrl=simplePrintf('javascript:pg.fn.%s()', [l.id]); // jshint ignore:line var title=popupString(simplePrintf('%sHint', [l.id])); var onClick=simplePrintf('pg.fn.%s();return false;', [l.id]); return generalNavLink({url: jsUrl, newWin:false, title:title, text:l.text, noPopup:l.noPopup, onclick: onClick}); } function specialLink(l) { // properties: article, specialpage, text, sep if (typeof l.specialpage=='undefined'||!l.specialpage) return null; var base = pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId]+':'+l.specialpage; if (typeof l.sep == 'undefined' || l.sep === null) l.sep='&target='; var article=l.article.urlString({keepSpaces: l.specialpage=='Search'}); var hint=popupString(l.specialpage+'Hint'); switch (l.specialpage) { case 'Log': switch (l.sep) { case '&user=': hint=popupString('userLogHint'); break; case '&type=block&page=': hint=popupString('blockLogHint'); break; case '&page=': hint=popupString('pageLogHint'); break; case '&type=protect&page=': hint=popupString('protectLogHint'); break; case '&type=delete&page=': hint=popupString('deleteLogHint'); break; default: log('Unknown log type, sep=' + l.sep); hint='Missing hint (FIXME)'; } break; case 'PrefixIndex': article += '/'; break; } if (hint) hint = simplePrintf(hint, [safeDecodeURI(l.article)]); else hint = safeDecodeURI(l.specialpage+':'+l.article) ; var url = base + l.sep + article; return generalNavLink({url: url, title: hint, text: l.text, newWin:l.newWin, noPopup:l.noPopup}); } function generalLink(l) { // l.url, l.text, l.title, l.newWin, l.className, l.noPopup, l.onclick if (typeof l.url=='undefined') return null; // only quotation marks in the url can screw us up now... I think var url=l.url.split('"').join('%22'); var ret='<a href="' + url + '"'; if (typeof l.title!='undefined' && l.title) { ret += ' title="' + pg.escapeQuotesHTML(l.title) + '"'; } if (typeof l.onclick!='undefined' && l.onclick) { ret += ' onclick="' + pg.escapeQuotesHTML(l.onclick) + '"'; } if (l.noPopup) { ret += ' noPopup=1'; } var newWin; if (typeof l.newWin=='undefined' || l.newWin === null) { newWin=getValueOf('popupNewWindows'); } else { newWin=l.newWin; } if (newWin) { ret += ' target="_blank"'; } if (typeof l.className!='undefined'&&l.className) { ret+=' class="'+l.className+'"'; } ret += '>'; if (typeof l.text==typeof '') { ret+= l.text; } ret +='</a>'; return ret; } function appendParamsToLink(linkstr, params) { var sp=linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i')); if (sp.length<2) return null; var ret=sp.shift() + sp.shift(); ret += '&' + params + '"'; ret += sp.join(''); return ret; } function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor, title (optional), alsoChangeLabel { if (x.newTarget) { log ('changeLinkTargetLink: newTarget=' + x.newTarget); } if (x.oldTarget !== decodeURIComponent( x.oldTarget ) ) { log ('This might be an input problem: ' + x.oldTarget ); } // FIXME: first character of page title as well as namespace should be case insensitive // eg [[category:X1]] and [[Category:X1]] are equivalent // this'll break if charAt(0) is nasty var cA = mw.util.escapeRegExp(x.oldTarget); var chs = cA.charAt(0).toUpperCase(); chs='['+chs + chs.toLowerCase()+']'; var currentArticleRegexBit=chs+cA.substring(1); currentArticleRegexBit=currentArticleRegexBit .split(RegExp('(?:[_ ]+|%20)', 'g')).join('(?:[_ ]+|%20)') .split('\\(').join('(?:%28|\\()') .split('\\)').join('(?:%29|\\))'); // why does this need to match encoded strings ? links in the document ? // leading and trailing space should be ignored, and anchor bits optional: currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + '(?:#[^\\[\\|]*)?)\\s*'; // e.g. Computer (archaic) -> \s*([Cc]omputer[_ ](?:%2528|\()archaic(?:%2528|\)))\s* // autoedit=s~\[\[([Cc]ad)\]\]~[[Computer-aided%20design|$1]]~g;s~\[\[([Cc]AD)[|]~[[Computer-aided%20design|~g var title=x.title || mw.config.get('wgPageName').split('_').join(' '); var lk=titledWikiLink({article: new Title(title), newWin:x.newWin, action: 'edit', text: x.text, title: x.hint, className: 'popup_change_title_link' }); var cmd=''; if (x.newTarget) { // escape '&' and other nasties var t = x.newTarget; var s = mw.util.escapeRegExp(x.newTarget); if (x.alsoChangeLabel) { cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + ']]~g;'; cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;'; cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g'; } else { cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + '|$1]]~g;'; cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;'; cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g'; } } else { cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~$1~g;'; cmd += 's~\\[\\['+currentArticleRegexBit+'[|](.*?)\\]\\]~$2~g'; } // Build query cmd = 'autoedit=' + encodeURIComponent ( cmd ); cmd += '&autoclick='+ encodeURIComponent( x.clickButton ) + '&actoken=' + encodeURIComponent( autoClickToken() ); cmd += ( x.minor === null ) ? '' : '&autominor='+ encodeURIComponent( x.minor ); cmd += ( x.watch === null ) ? '' : '&autowatch='+ encodeURIComponent( x.watch ); cmd += '&autosummary='+encodeURIComponent(x.summary); cmd += '&autoimpl='+encodeURIComponent( popupString('autoedit_version') ); return appendParamsToLink(lk, cmd); } function redirLink(redirMatch, article) { // NB redirMatch is in wikiText var ret=''; if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) { ret += '<hr />'; if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) { ret += popupString('Redirects to: (Fix '); log('redirLink: newTarget=' + redirMatch); ret += addPopupShortcut(changeLinkTargetLink({ newTarget: redirMatch, text: popupString('target'), hint: popupString('Fix this redirect, changing just the link target'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]), oldTarget: article.toString(), clickButton: getValueOf('popupRedirAutoClick'), minor: true, watch: getValueOf('popupWatchRedirredPages') }), 'R'); ret += popupString(' or '); ret += addPopupShortcut(changeLinkTargetLink({ newTarget: redirMatch, text: popupString('target & label'), hint: popupString('Fix this redirect, changing the link target and label'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]), oldTarget: article.toString(), clickButton: getValueOf('popupRedirAutoClick'), minor: true, watch: getValueOf('popupWatchRedirredPages'), alsoChangeLabel: true }), 'R'); ret += popupString(')'); } else ret += popupString('Redirects') + popupString(' to '); return ret; } else return '<br> ' + popupString('Redirects') + popupString(' to ') + titledWikiLink({article: new Title().fromWikiText(redirMatch), action: 'view', /* FIXME: newWin */ text: safeDecodeURI(redirMatch), title: popupString('Bypass redirect')}); } function arinLink(l) { if (!saneLinkCheck(l)) { return null; } if ( ! l.article.isIpUser() || ! pg.wiki.wikimedia) return null; var uN=l.article.userName(); return generalNavLink({url:'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + encodeURIComponent(uN), newWin:l.newWin, title: tprintf('Look up %s in ARIN whois database', [uN]), text: l.text, noPopup:1}); } function toolDbName(cookieStyle) { var ret = mw.config.get('wgDBname'); if (!cookieStyle) { ret+= '_p'; } return ret; } function saneLinkCheck(l) { if (typeof l.article != typeof {} || typeof l.text != typeof '') { return false; } return true; } function editCounterLink(l) { if(!saneLinkCheck(l)) return null; if (! pg.wiki.wikimedia) return null; var uN=l.article.userName(); var tool=getValueOf('popupEditCounterTool'); var url; var defaultToolUrl='//tools.wmflabs.org/supercount/index.php?user=$1&project=$2.$3'; switch(tool) { case 'custom': url=simplePrintf(getValueOf('popupEditCounterUrl'), [ encodeURIComponent(uN), toolDbName() ]); break; case 'soxred': // no longer available case 'kate': // no longer available case 'interiot':// no longer available /* fall through */ case 'supercount': default: var theWiki=pg.wiki.hostname.split('.'); url=simplePrintf(defaultToolUrl, [ encodeURIComponent(uN), theWiki[0], theWiki[1] ]); } return generalNavLink({url:url, title: tprintf('editCounterLinkHint', [uN]), newWin:l.newWin, text: l.text, noPopup:1}); } function globalSearchLink(l) { if(!saneLinkCheck(l)) return null; var base='http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search='; var article=l.article.urlString({keepSpaces:true}); return generalNavLink({url:base + article, newWin:l.newWin, title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]), text: l.text, noPopup:1}); } function googleLink(l) { if(!saneLinkCheck(l)) return null; var base='https://www.google.com/search?q='; var article=l.article.urlString({keepSpaces:true}); return generalNavLink({url:base + '%22' + article + '%22', newWin:l.newWin, title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]), text: l.text, noPopup:1}); } function editorListLink(l) { if(!saneLinkCheck(l)) return null; var article= l.article.articleFromTalkPage() || l.article; var url='https://xtools.wmflabs.org/articleinfo/' + encodeURI( pg.wiki.hostname ) + '/' + article.urlString() + '?uselang=' + mw.config.get('wgUserLanguage'); return generalNavLink({url:url, title: tprintf('editorListHint', [article]), newWin:l.newWin, text: l.text, noPopup:1}); } function generalNavLink(l) { l.className = (l.className === null) ? 'popupNavLink' : l.className; return generalLink(l); } ////////////////////////////////////////////////// // magic history links // function getHistoryInfo(wikipage, whatNext) { log('getHistoryInfo'); getHistory(wikipage, whatNext ? function(d){whatNext(processHistory(d));} : processHistory); } // FIXME eliminate pg.idNumber ... how? :-( function getHistory(wikipage, onComplete) { log('getHistory'); var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&prop=revisions&titles=' + new Title(wikipage).urlString() + '&rvlimit=' + getValueOf('popupHistoryLimit'); log('getHistory: url='+url); return startDownload(url, pg.idNumber+'history', onComplete); } function processHistory(download) { var jsobj = getJsObj(download.data); try { var revisions = anyChild(jsobj.query.pages).revisions; var edits=[]; for (var i=0; i<revisions.length; ++i) { edits.push({ oldid: revisions[i].revid, editor: revisions[i].user }); } log('processed ' + edits.length + ' edits'); return finishProcessHistory( edits, mw.config.get('wgUserName') ); } catch (someError) { log('Something went wrong with JSON business'); return finishProcessHistory([]); } } function finishProcessHistory(edits, userName) { var histInfo={}; histInfo.edits=edits; histInfo.userName=userName; for (var i=0; i<edits.length; ++i) { if (typeof histInfo.myLastEdit === 'undefined' && userName && edits[i].editor==userName) { histInfo.myLastEdit={index: i, oldid: edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)}; } if (typeof histInfo.firstNewEditor === 'undefined' && edits[i].editor != edits[0].editor) { histInfo.firstNewEditor={index:i, oldid:edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)}; } } //pg.misc.historyInfo=histInfo; return histInfo; } //</NOLITE> // ENDFILE: links.js // STARTFILE: options.js ////////////////////////////////////////////////// // options // check for existing value, else use default function defaultize(x) { if (pg.option[x]===null || typeof pg.option[x]=='undefined') { if (typeof window[x] != 'undefined' ) pg.option[x]=window[x]; else pg.option[x]=pg.optionDefault[x]; } } function newOption(x, def) { pg.optionDefault[x]=def; } function setDefault(x, def) { return newOption(x, def); } function getValueOf(varName) { defaultize(varName); return pg.option[varName]; } /*eslint-disable */ function useDefaultOptions() { // for testing for (var p in pg.optionDefault) { pg.option[p]=pg.optionDefault[p]; if (typeof window[p]!='undefined') { delete window[p]; } } } /*eslint-enable */ function setOptions() { // user-settable parameters and defaults var userIsSysop = false; if ( mw.config.get('wgUserGroups') ) { for ( var g = 0; g < mw.config.get('wgUserGroups').length; ++g ) { if ( mw.config.get('wgUserGroups')[g] == "sysop" ) userIsSysop = true; } } // Basic options newOption('popupDelay', 0.5); newOption('popupHideDelay', 0.5); newOption('simplePopups', false); newOption('popupStructure', 'shortmenus'); // see later - default for popupStructure is 'original' if simplePopups is true newOption('popupActionsMenu', true); newOption('popupSetupMenu', true); newOption('popupAdminLinks', userIsSysop); newOption('popupShortcutKeys', false); newOption('popupHistoricalLinks', true); newOption('popupOnlyArticleLinks', true); newOption('removeTitles', true); newOption('popupMaxWidth', 350); newOption('popupSimplifyMainLink', true); newOption('popupAppendRedirNavLinks', true); newOption('popupTocLinks', false); newOption('popupSubpopups', true); newOption('popupDragHandle', false /* 'popupTopLinks'*/); newOption('popupLazyPreviews', true); newOption('popupLazyDownloads', true); newOption('popupAllDabsStubs', false); newOption('popupDebugging', false); newOption('popupActiveNavlinks', true); newOption('popupModifier', false); // ctrl, shift, alt or meta newOption('popupModifierAction', 'enable'); // or 'disable' newOption('popupDraggable', true); newOption('popupReview', false); //<NOLITE> // images newOption('popupImages', true); newOption('imagePopupsForImages', true); newOption('popupNeverGetThumbs', false); //newOption('popupImagesToggleSize', true); newOption('popupThumbAction', 'imagepage'); //'sizetoggle'); newOption('popupImageSize', 60); newOption('popupImageSizeLarge', 200); // redirs, dabs, reversion newOption('popupFixRedirs', false); newOption('popupRedirAutoClick', 'wpDiff'); newOption('popupFixDabs', false); newOption('popupDabsAutoClick', 'wpDiff'); newOption('popupRevertSummaryPrompt', false); newOption('popupMinorReverts', false); newOption('popupRedlinkRemoval', false); newOption('popupRedlinkAutoClick', 'wpDiff'); newOption('popupWatchDisambiggedPages', null); newOption('popupWatchRedirredPages', null); newOption('popupDabWiktionary', 'last'); // navlinks newOption('popupNavLinks', true); newOption('popupNavLinkSeparator', ' &sdot; '); newOption('popupLastEditLink', true); newOption('popupEditCounterTool', 'supercount'); newOption('popupEditCounterUrl', ''); //</NOLITE> // previews etc newOption('popupPreviews', true); newOption('popupSummaryData', true); newOption('popupMaxPreviewSentences', 9); newOption('popupMaxPreviewCharacters', 900); newOption('popupLastModified', true); newOption('popupPreviewKillTemplates', true); newOption('popupPreviewRawTemplates', true); newOption('popupPreviewFirstParOnly', false); newOption('popupPreviewCutHeadings', false); newOption('popupPreviewButton', false); newOption('popupPreviewButtonEvent', 'click'); //<NOLITE> // diffs newOption('popupPreviewDiffs', true); newOption('popupDiffMaxLines', 100); newOption('popupDiffContextLines', 2); newOption('popupDiffContextCharacters', 40); newOption('popupDiffDates', true); newOption('popupDiffDatePrinter', 'toLocaleString'); // edit summaries. God, these are ugly. newOption('popupReviewedSummary', popupString('defaultpopupReviewedSummary') ); newOption('popupFixDabsSummary', popupString('defaultpopupFixDabsSummary') ); newOption('popupExtendedRevertSummary', popupString('defaultpopupExtendedRevertSummary') ); newOption('popupRevertSummary', popupString('defaultpopupRevertSummary') ); newOption('popupRevertToPreviousSummary', popupString('defaultpopupRevertToPreviousSummary') ); newOption('popupQueriedRevertSummary', popupString('defaultpopupQueriedRevertSummary') ); newOption('popupQueriedRevertToPreviousSummary', popupString('defaultpopupQueriedRevertToPreviousSummary') ); newOption('popupFixRedirsSummary', popupString('defaultpopupFixRedirsSummary') ); newOption('popupRedlinkSummary', popupString('defaultpopupRedlinkSummary') ); newOption('popupRmDabLinkSummary', popupString('defaultpopupRmDabLinkSummary') ); //</NOLITE> // misc newOption('popupHistoryLimit', 50); //<NOLITE> newOption('popupFilters', [popupFilterStubDetect, popupFilterDisambigDetect, popupFilterPageSize, popupFilterCountLinks, popupFilterCountImages, popupFilterCountCategories, popupFilterLastModified]); newOption('extraPopupFilters', []); newOption('popupOnEditSelection', 'cursor'); newOption('popupPreviewHistory', true); newOption('popupImageLinks', true); newOption('popupCategoryMembers', true); newOption('popupUserInfo', true); newOption('popupHistoryPreviewLimit', 25); newOption('popupContribsPreviewLimit',25); newOption('popupRevDelUrl', '//en.wikipedia.org/wiki/Wikipedia:Revision_deletion'); newOption('popupShowGender', true); //</NOLITE> // new windows newOption('popupNewWindows', false); newOption('popupLinksNewWindow', {'lastContrib': true, 'sinceMe': true}); // regexps newOption('popupDabRegexp', '(\\{\\{\\s*disambig(?!uation needed)|disambig(uation|)\\s*\\}\\}|disamb\\s*\\}\\}|dab\\s*\\}\\})|\\{\\{\\s*(((geo|hn|road?|school|number)dis)|[234][lc][acw]|(road|ship)index)(\\s*[|][^}]*)?\\s*[}][}]|is a .*disambiguation.*page'); newOption('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub'); newOption('popupImageVarsRegexp', 'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo'); } // ENDFILE: options.js // STARTFILE: strings.js //<NOLITE> ////////////////////////////////////////////////// // Translatable strings ////////////////////////////////////////////////// // // See instructions at // https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation pg.string = { ///////////////////////////////////// // summary data, searching etc. ///////////////////////////////////// 'article': 'article', 'category': 'category', 'categories': 'categories', 'image': 'image', 'images': 'images', 'stub': 'stub', 'section stub': 'section stub', 'Empty page': 'Empty page', 'kB': 'kB', 'bytes': 'bytes', 'day': 'day', 'days': 'days', 'hour': 'hour', 'hours': 'hours', 'minute': 'minute', 'minutes': 'minutes', 'second': 'second', 'seconds': 'seconds', 'week': 'week', 'weeks': 'weeks', 'search': 'search', 'SearchHint': 'Find English Wikipedia articles containing %s', 'web': 'web', 'global': 'global', 'globalSearchHint': 'Search across Wikipedias in different languages for %s', 'googleSearchHint': 'Google for %s', ///////////////////////////////////// // article-related actions and info // (some actions also apply to user pages) ///////////////////////////////////// 'actions': 'actions', ///// view articles and view talk 'popupsMenu': 'popups', 'togglePreviewsHint': 'Toggle preview generation in popups on this page', 'enable previews': 'enable previews', 'disable previews': 'disable previews', 'toggle previews': 'toggle previews', 'show preview': 'show preview', 'reset': 'reset', 'more...': 'more...', 'disable': 'disable popups', 'disablePopupsHint': 'Disable popups on this page. Reload page to re-enable.', 'historyfeedHint': 'RSS feed of recent changes to this page', 'purgePopupsHint': 'Reset popups, clearing all cached popup data.', 'PopupsHint': 'Reset popups, clearing all cached popup data.', 'spacebar': 'space', 'view': 'view', 'view article': 'view article', 'viewHint': 'Go to %s', 'talk': 'talk', 'talk page': 'talk page', 'this&nbsp;revision': 'this&nbsp;revision', 'revision %s of %s': 'revision %s of %s', 'Revision %s of %s': 'Revision %s of %s', 'the revision prior to revision %s of %s': 'the revision prior to revision %s of %s', 'Toggle image size': 'Click to toggle image size', 'del': 'del', ///// delete, protect, move 'delete': 'delete', 'deleteHint': 'Delete %s', 'undeleteShort': 'un', 'UndeleteHint': 'Show the deletion history for %s', 'protect': 'protect', 'protectHint': 'Restrict editing rights to %s', 'unprotectShort': 'un', 'unprotectHint': 'Allow %s to be edited by anyone again', 'send thanks': 'send thanks', 'ThanksHint': 'Send a thank you notification to this user', 'move': 'move', 'move page': 'move page', 'MovepageHint': 'Change the title of %s', 'edit': 'edit', ///// edit articles and talk 'edit article': 'edit article', 'editHint': 'Change the content of %s', 'edit talk': 'edit talk', 'new': 'new', 'new topic': 'new topic', 'newSectionHint': 'Start a new section on %s', 'null edit': 'null edit', 'nullEditHint': 'Submit an edit to %s, making no changes ', 'hist': 'hist', ///// history, diffs, editors, related 'history': 'history', 'historyHint': 'List the changes made to %s', 'last': 'prev', // For labelling the previous revision in history pages; the key is "last" for backwards compatibility 'lastEdit': 'lastEdit', 'mark patrolled': 'mark patrolled', 'markpatrolledHint': 'Mark this edit as patrolled', 'Could not marked this edit as patrolled': 'Could not marked this edit as patrolled', 'show last edit': 'most recent edit', 'Show the last edit': 'Show the effects of the most recent change', 'lastContrib': 'lastContrib', 'last set of edits': 'latest edits', 'lastContribHint': 'Show the net effect of changes made by the last editor', 'cur': 'cur', 'diffCur': 'diffCur', 'Show changes since revision %s': 'Show changes since revision %s', '%s old': '%s old', // as in 4 weeks old 'oldEdit': 'oldEdit', 'purge': 'purge', 'purgeHint': 'Demand a fresh copy of %s', 'raw': 'source', 'rawHint': 'Download the source of %s', 'render': 'simple', 'renderHint': 'Show a plain HTML version of %s', 'Show the edit made to get revision': 'Show the edit made to get revision', 'sinceMe': 'sinceMe', 'changes since mine': 'diff my edit', 'sinceMeHint': 'Show changes since my last edit', 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s': 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s', 'eds': 'eds', 'editors': 'editors', 'editorListHint': 'List the users who have edited %s', 'related': 'related', 'relatedChanges': 'relatedChanges', 'related changes': 'related changes', 'RecentchangeslinkedHint': 'Show changes in articles related to %s', 'editOld': 'editOld', ///// edit old version, or revert 'rv': 'rv', 'revert': 'revert', 'revertHint': 'Revert to %s', 'defaultpopupReviewedSummary': 'Accepted by reviewing the [[Special:diff/%s/%s|difference]] between this version and previously accepted version using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRedlinkSummary': 'Removing link to empty page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupFixDabsSummary': 'Disambiguate [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupFixRedirsSummary': 'Redirect bypass from [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupExtendedRevertSummary': 'Revert to revision dated %s by %s, oldid %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRevertToPreviousSummary': 'Revert to the revision prior to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRevertSummary': 'Revert to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupQueriedRevertToPreviousSummary': 'Revert to the revision prior to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupQueriedRevertSummary': 'Revert to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRmDabLinkSummary': 'Remove link to dab page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'Redirects': 'Redirects', // as in Redirects to ... ' to ': ' to ', // as in Redirects to ... 'Bypass redirect': 'Bypass redirect', 'Fix this redirect': 'Fix this redirect', 'disambig': 'disambig', ///// add or remove dab etc. 'disambigHint': 'Disambiguate this link to [[%s]]', 'Click to disambiguate this link to:': 'Click to disambiguate this link to:', 'remove this link': 'remove this link', 'remove all links to this page from this article': 'remove all links to this page from this article', 'remove all links to this disambig page from this article': 'remove all links to this disambig page from this article', 'mainlink': 'mainlink', ///// links, watch, unwatch 'wikiLink': 'wikiLink', 'wikiLinks': 'wikiLinks', 'links here': 'links here', 'whatLinksHere': 'whatLinksHere', 'what links here': 'what links here', 'WhatlinkshereHint': 'List the pages that are hyperlinked to %s', 'unwatchShort': 'un', 'watchThingy': 'watch', // called watchThingy because {}.watch is a function 'watchHint': 'Add %s to my watchlist', 'unwatchHint': 'Remove %s from my watchlist', 'Only found one editor: %s made %s edits': 'Only found one editor: %s made %s edits', '%s seems to be the last editor to the page %s': '%s seems to be the last editor to the page %s', 'rss': 'rss', ///////////////////////////////////// // diff previews ///////////////////////////////////// 'Diff truncated for performance reasons': 'Diff truncated for performance reasons', 'Old revision': 'Old revision', 'New revision': 'New revision', 'Something went wrong :-(': 'Something went wrong :-(', 'Empty revision, maybe non-existent': 'Empty revision, maybe non-existent', 'Unknown date': 'Unknown date', ///////////////////////////////////// // other special previews ///////////////////////////////////// 'Empty category': 'Empty category', 'Category members (%s shown)': 'Category members (%s shown)', 'No image links found': 'No image links found', 'File links': 'File links', 'No image found': 'No image found', 'Image from Commons': 'Image from Commons', 'Description page': 'Description page', 'Alt text:': 'Alt text:', 'revdel':'Hidden revision', ///////////////////////////////////// // user-related actions and info ///////////////////////////////////// 'user': 'user', ///// user page, talk, email, space 'user&nbsp;page': 'user&nbsp;page', 'user talk': 'user talk', 'edit user talk': 'edit user talk', 'leave comment': 'leave comment', 'email': 'email', 'email user': 'email user', 'EmailuserHint': 'Send an email to %s', 'space': 'space', // short form for userSpace link 'PrefixIndexHint': 'Show pages in the userspace of %s', 'count': 'count', ///// contributions, log 'edit counter': 'edit counter', 'editCounterLinkHint': 'Count the contributions made by %s', 'contribs': 'contribs', 'contributions': 'contributions', 'deletedContribs': 'deleted contributions', 'DeletedcontributionsHint': 'List deleted edits made by %s', 'ContributionsHint': 'List the contributions made by %s', 'log': 'log', 'user log': 'user log', 'userLogHint': 'Show %s\'s user log', 'arin': 'ARIN lookup', ///// ARIN lookup, block user or IP 'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database', 'unblockShort': 'un', 'block': 'block', 'block user': 'block user', 'IpblocklistHint': 'Unblock %s', 'BlockipHint': 'Prevent %s from editing', 'block log': 'block log', 'blockLogHint': 'Show the block log for %s', 'protectLogHint': 'Show the protection log for %s', 'pageLogHint': 'Show the page log for %s', 'deleteLogHint': 'Show the deletion log for %s', 'Invalid %s %s': 'The option %s is invalid: %s', 'No backlinks found': 'No backlinks found', ' and more': ' and more', 'undo': 'undo', 'undoHint': 'undo this edit', 'Download preview data': 'Download preview data', 'Invalid or IP user': 'Invalid or IP user', 'Not a registered username': 'Not a registered username', 'BLOCKED': 'BLOCKED', 'Has blocks': 'Has blocks', ' edits since: ': ' edits since: ', 'last edit on ': 'last edit on ', ///////////////////////////////////// // Autoediting ///////////////////////////////////// 'Enter a non-empty edit summary or press cancel to abort': 'Enter a non-empty edit summary or press cancel to abort', 'Failed to get revision information, please edit manually.\n\n': 'Failed to get revision information, please edit manually.\n\n', 'The %s button has been automatically clicked. Please wait for the next page to load.': 'The %s button has been automatically clicked. Please wait for the next page to load.', 'Could not find button %s. Please check the settings in your javascript file.': 'Could not find button %s. Please check the settings in your javascript file.', ///////////////////////////////////// // Popups setup ///////////////////////////////////// 'Open full-size image': 'Open full-size image', 'zxy': 'zxy', 'autoedit_version': 'np20140416' }; function popupString(str) { if (typeof popupStrings != 'undefined' && popupStrings && popupStrings[str]) { return popupStrings[str]; } if (pg.string[str]) { return pg.string[str]; } return str; } function tprintf(str,subs) { if (typeof subs != typeof []) { subs = [subs]; } return simplePrintf(popupString(str), subs); } //</NOLITE> // ENDFILE: strings.js // STARTFILE: run.js //////////////////////////////////////////////////////////////////// // Run things //////////////////////////////////////////////////////////////////// // For some reason popups requires a fully loaded page jQuery.ready(...) causes problems for some. // The old addOnloadHook did something similar to the below if (document.readyState=="complete") autoEdit(); //will setup popups else $( window ).on( 'load', autoEdit ); // Support for MediaWiki's live preview, VisualEditor's saves and Echo's flyout. ( function () { var once = true; function dynamicContentHandler( $content ) { // Try to detect the hook fired on initial page load and disregard // it, we already hook to onload (possibly to different parts of // page - it's configurable) and running twice might be bad. Ugly… if ( $content.attr( 'id' ) == 'mw-content-text' ) { if ( once ) { once = false; return; } } function registerHooksForVisibleNavpops () { for (var i=0; pg.current.links && i<pg.current.links.length; ++i) { var navpop = pg.current.links[i].navpopup; if (!navpop || !navpop.isVisible()) { continue; } Navpopup.tracker.addHook(posCheckerHook(navpop)); } } function doIt () { registerHooksForVisibleNavpops(); $content.each( function () { this.ranSetupTooltipsAlready = false; setupTooltips( this ); } ); } setupPopups( doIt ); } // This hook is also fired after page load. mw.hook( 'wikipage.content' ).add( dynamicContentHandler ); mw.hook( 'ext.echo.overlay.beforeShowingOverlay' ).add( function($overlay){ dynamicContentHandler( $overlay.find(".mw-echo-state") ); }); } )(); // Forked from User:Connel MacKenzie/mess-with-popups.js Previewmaker.prototype.firstBit = function() { try{ if (mw.config.get("wgNamespaceNumber") !== 0){ // dont't be givin' me no subsequent paragraphs, you hear me? /// first we "normalize" section headings, removing whitespace after, adding before var d = this.data; if (getValueOf('popupPreviewCutHeadings')) { this.data = this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 '); /// then we want to get rid of paragraph breaks whose text ends badly this.data = this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n'); this.data = this.data.replace(RegExp('^[\\s\\n]*'), ''); stuff = (RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data); if (stuff) { d = stuff[0]; } if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; } /// now put \n\n after sections so that bullets and numbered lists work d = d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n'); } // superfluous sentences are RIGHT OUT. // note: exactly 1 set of parens here needed to make the slice work d = d.parenSplit(RegExp('([!?.]+["' + "'" + ']*\\s)', 'g')); // leading space is bad, mmkay? d[0] = d[0].replace(RegExp('^\\s*'), ''); var notSentenceEnds = RegExp('([^.][a-z][.][a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|\\[[^\\]]*|\\s[A-Zvclm])$', 'i'); d = this.fixSentenceEnds(d, notSentenceEnds); var maxChars=getValueOf('popupMaxPreviewCharacters'); var n = getValueOf('popupMaxPreviewSentences'); var dd; do { dd = this.firstSentences(d, n); --n; } while (dd.length > maxChars && n > 0); this.data = dd; } else { //TODO: Limit this override to urls that start with http://*.wiktionary.org/ //TODO: extend the above section to also preview Wikipedia links var charsWikt = 0; var linesWikt = this.data.split('\n'); for (jWikt = 0; jWikt < linesWikt.length; jWikt++) { linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{[in]transitive}}/g, ''); linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{[un]countable}}/g, ''); linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{cattag\|(.*?)}}/g, ""); linesWikt[jWikt] = linesWikt[jWikt].replace(/[\{\}]/g, "'"); if (linesWikt[jWikt].search(/^[#]/) == -1) { if (linesWikt[jWikt].search(/^==[A-Z]/) == -1) { linesWikt[jWikt] = ''; } } charsWikt += linesWikt[jWikt].length; } if (charsWikt < 2) { linesWikt[0] = "No properly formatted content"; } var maxChars = getValueOf('popupMaxPreviewCharacters'); var n = getValueOf('popupMaxPreviewSentences'); var dd = ''; for (jWikt = 0; jWikt < linesWikt.length; jWikt++) { if (linesWikt[jWikt] !== '') { dd += linesWikt[jWikt] + '\n'; } if (dd.length < maxChars) { this.data = dd; } } } } catch (e) { console.log("error from mess with popups: ", e); } }; }); m8wnoaql8ojr2k0cjm9nwb8hb7yib6n 2513948 2513945 2026-08-14T04:45:50Z Hiyuune 50834 2513948 javascript text/javascript // {{documentation}} // _________________________________________________________________________________________ // | | // | === WARNING: GLOBAL GADGET FILE === | // | Changes to this page affect many users. | // | Please discuss changes on the [[Wiktionary:Grease Pit]] before editing. | // |_________________________________________________________________________________________| mw.loader.load('//en.wikipedia.org/w/index.php?action=raw&title=MediaWiki:Gadget-navpop.css&ctype=text/css', "text/css"); // Forked from [[:w:MediaWiki:Gadget-popups.js]] $(function () { ////////////////////////////////////////////////// // Globals // // Trying to shove as many of these as possible into the pg (popup globals) object var pg = { re: {}, // regexps ns: {}, // namespaces string: {}, // translatable strings wiki: {}, // local site info user: {}, // current user info misc: {}, // YUCK PHOOEY option: {}, // options, see newOption etc optionDefault: {}, // default option values flag: {}, // misc flags cache: {}, // page and image cache structures: {}, // navlink structures timer: {}, // all sorts of timers (too damn many) counter: {}, // .. and all sorts of counters current: {}, // state info fn: {}, // functions endoflist: null }; /* Bail if the gadget/script is being loaded twice */ if( window.pg ) { return; } /* Export to global context */ window.pg = pg; /// Local Variables: /// /// mode:c /// /// End: /// // ENDFILE: main.js // STARTFILE: actions.js function setupTooltips(container, remove, force, popData) { log('setupTooltips, container='+container+', remove='+remove); if (!container) { //<NOLITE> // the main initial call if (getValueOf('popupOnEditSelection') && document && document.editform && document.editform.wpTextbox1) { document.editform.wpTextbox1.onmouseup=doSelectionPopup; } //</NOLITE> // article/content is a structure-dependent thing container = defaultPopupsContainer(); } if (!remove && !force && container.ranSetupTooltipsAlready) { return; } container.ranSetupTooltipsAlready = !remove; var anchors; anchors=container.getElementsByTagName('A'); setupTooltipsLoop(anchors, 0, 250, 100, remove, popData); } function defaultPopupsContainer() { if (getValueOf('popupOnlyArticleLinks')) { return document.getElementById('mw_content') || document.getElementById('content') || document.getElementById('article') || document; } return document; } function setupTooltipsLoop(anchors,begin,howmany,sleep, remove, popData) { log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments)); var finish=begin+howmany; var loopend = Math.min(finish, anchors.length); var j=loopend - begin; log ('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin + ', howmany=' + howmany + ', loopend=' + loopend + ', remove=' + remove); var doTooltip= remove ? removeTooltip : addTooltip; // try a faster (?) loop construct if (j > 0) { do { var a=anchors[loopend - j]; if (typeof a==='undefined' || !a || !a.href) { log('got null anchor at index ' + loopend - j); continue; } doTooltip(a, popData); } while (--j); } if (finish < anchors.length) { setTimeout(function() { setupTooltipsLoop(anchors,finish,howmany,sleep,remove,popData);}, sleep); } else { if ( !remove && ! getValueOf('popupTocLinks')) { rmTocTooltips(); } pg.flag.finishedLoading=true; } } // eliminate popups from the TOC // This also kills any onclick stuff that used to be going on in the toc function rmTocTooltips() { var toc=document.getElementById('toc'); if (toc) { var tocLinks=toc.getElementsByTagName('A'); var tocLen = tocLinks.length; for (var j=0; j<tocLen; ++j) { removeTooltip(tocLinks[j], true); } } } function addTooltip(a, popData) { if ( !isPopupLink(a) ) { return; } a.onmouseover=mouseOverWikiLink; a.onmouseout= mouseOutWikiLink; a.onmousedown = killPopup; a.hasPopup = true; a.popData = popData; } function removeTooltip(a) { if ( !a.hasPopup ) { return; } a.onmouseover = null; a.onmouseout = null; if (a.originalTitle) { a.title = a.originalTitle; } a.hasPopup=false; } function removeTitle(a) { if (!a.originalTitle) { a.originalTitle=a.title; } a.title=''; } function restoreTitle(a) { if ( a.title || !a.originalTitle ) { return; } a.title = a.originalTitle; } function registerHooks(np) { var popupMaxWidth=getValueOf('popupMaxWidth'); if (typeof popupMaxWidth === 'number') { var setMaxWidth = function () { np.mainDiv.style.maxWidth = popupMaxWidth + 'px'; np.maxWidth = popupMaxWidth; }; np.addHook(setMaxWidth, 'unhide', 'before'); } //<NOLITE> np.addHook(addPopupShortcuts, 'unhide', 'after'); np.addHook(rmPopupShortcuts, 'hide', 'before'); //</NOLITE> } function removeModifierKeyHandler(a) { //remove listeners for modifier key if any that were added in mouseOverWikiLink document.removeEventListener('keydown', a.modifierKeyHandler, false); document.removeEventListener('keyup', a.modifierKeyHandler, false); } function mouseOverWikiLink(evt) { if (!evt && window.event) {evt=window.event;} // if the modifier is needed, listen for it, // we will remove the listener when we mouseout of this link or kill popup. if (getValueOf('popupModifier')) { // if popupModifierAction = enable, we should popup when the modifier is pressed // if popupModifierAction = disable, we should popup unless the modifier is pressed var action = getValueOf('popupModifierAction'); var key = action=='disable' ? 'keyup' : 'keydown'; var a = this; a.modifierKeyHandler = function(evt) { mouseOverWikiLink2(a, evt); }; document.addEventListener(key, a.modifierKeyHandler, false); } return mouseOverWikiLink2(this, evt); } /** * Gets the references list item that the provided footnote link targets. This * is typically a li element within the ol.references element inside the reflist. * @param {Element} a - A footnote link. * @returns {Element|boolean} The targeted element, or false if one can't be found. */ function footnoteTarget(a) { var aTitle=Title.fromAnchor(a); // We want ".3A" rather than "%3A" or "?" here, so use the anchor property directly var anch = aTitle.anchor; if ( ! /^(cite_note-|_note-|endnote)/.test(anch) ) { return false; } var lTitle=Title.fromURL(location.href); if ( lTitle.toString(true) !== aTitle.toString(true) ) { return false; } var el=document.getElementById(anch); while ( el && typeof el.nodeName === 'string') { var nt = el.nodeName.toLowerCase(); if ( nt === 'li' ) { return el; } else if ( nt === 'body' ) { return false; } else if ( el.parentNode ) { el=el.parentNode; } else { return false; } } return false; } function footnotePreview(x, navpop) { setPopupHTML('<hr />' + x.innerHTML, 'popupPreview', navpop.idNumber); } function modifierPressed(evt) { var mod=getValueOf('popupModifier'); if (!mod) { return false; } if (!evt && window.event) {evt=window.event;} return ( evt && mod && evt[mod.toLowerCase() + 'Key'] ); } // Checks if the correct modifier pressed/unpressed if needed function isCorrectModifier(a,evt) { if (!getValueOf('popupModifier')) { return true; } // if popupModifierAction = enable, we should popup when the modifier is pressed // if popupModifierAction = disable, we should popup unless the modifier is pressed var action = getValueOf('popupModifierAction'); return ( action == 'enable' && modifierPressed(evt) || action == 'disable' && !modifierPressed(evt) ); } function mouseOverWikiLink2(a, evt) { if (!isCorrectModifier(a,evt)) { return; } if ( getValueOf('removeTitles') ) { removeTitle(a); } if ( a==pg.current.link && a.navpopup && a.navpopup.isVisible() ) { return; } pg.current.link=a; if (getValueOf('simplePopups') && !pg.option.popupStructure) { // reset *default value* of popupStructure setDefault('popupStructure', 'original'); } var article=(new Title()).fromAnchor(a); // set global variable (ugh) to hold article (wikipage) pg.current.article = article; if (!a.navpopup) { a.navpopup=newNavpopup(a, article); pg.current.linksHash[a.href] = a.navpopup; pg.current.links.push(a); } if (a.navpopup.pending === null || a.navpopup.pending !== 0) { // either fresh popups or those with unfinshed business are redone from scratch simplePopupContent(a, article); } a.navpopup.showSoonIfStable(a.navpopup.delay); clearInterval(pg.timer.checkPopupPosition); pg.timer.checkPopupPosition=setInterval(checkPopupPosition, 600); if(getValueOf('simplePopups')) { if (getValueOf('popupPreviewButton') && !a.simpleNoMore) { var d=document.createElement('div'); d.className='popupPreviewButtonDiv'; var s=document.createElement('span'); d.appendChild(s); s.className='popupPreviewButton'; s['on' + getValueOf('popupPreviewButtonEvent')] = function() { a.simpleNoMore=true; d.style.display = "none"; nonsimplePopupContent(a,article); }; s.innerHTML=popupString('show preview'); setPopupHTML(d, 'popupPreview', a.navpopup.idNumber); } } if (a.navpopup.pending !== 0 ) { nonsimplePopupContent(a, article); } } // simplePopupContent: the content that do not require additional download // (it is shown even when simplePopups is true) function simplePopupContent(a, article) { /* FIXME hack */ a.navpopup.hasPopupMenu=false; a.navpopup.setInnerHTML(popupHTML(a)); fillEmptySpans({navpopup:a.navpopup}); if (getValueOf('popupDraggable')) { var dragHandle = getValueOf('popupDragHandle') || null; if (dragHandle && dragHandle != 'all') { dragHandle += a.navpopup.idNumber; } setTimeout(function(){a.navpopup.makeDraggable(dragHandle);}, 150); } //<NOLITE> if (getValueOf('popupRedlinkRemoval') && a.className=='new') { setPopupHTML('<br>'+popupRedlinkHTML(article), 'popupRedlink', a.navpopup.idNumber); } //</NOLITE> } function debugData(navpopup) { if(getValueOf('popupDebugging') && navpopup.idNumber) { setPopupHTML('idNumber='+navpopup.idNumber + ', pending=' + navpopup.pending, 'popupError', navpopup.idNumber); } } function newNavpopup(a, article) { var navpopup = new Navpopup(); navpopup.fuzz=5; navpopup.delay=getValueOf('popupDelay')*1000; // increment global counter now navpopup.idNumber = ++pg.idNumber; navpopup.parentAnchor = a; navpopup.parentPopup = (a.popData && a.popData.owner); navpopup.article = article; registerHooks(navpopup); return navpopup; } // Should we show nonsimple context? // If simplePopups is set to true, then we do not show nonsimple context, // but if a bottom "show preview" was clicked we do show nonsimple context function shouldShowNonSimple(a) { return !getValueOf('simplePopups') || a.simpleNoMore; } // Should we show nonsimple context govern by the option (e.g. popupUserInfo)? // If the user explicitly asked for nonsimple context by setting the option to true, // then we show it even in nonsimple mode. function shouldShow(a,option) { if (shouldShowNonSimple(a)) { return getValueOf(option); } else { return (typeof window[option] != 'undefined' ) && window[option]; } } function nonsimplePopupContent(a, article) { var diff=null, history=null; var params=parseParams(a.href); var oldid=(typeof params.oldid=='undefined' ? null : params.oldid); //<NOLITE> if(shouldShow(a,'popupPreviewDiffs')) { diff=params.diff; } if(shouldShow(a,'popupPreviewHistory')) { history=(params.action=='history'); } //</NOLITE> a.navpopup.pending=0; var referenceElement = footnoteTarget(a); if (referenceElement) { footnotePreview(referenceElement, a.navpopup); //<NOLITE> } else if ( diff || diff === 0 ) { loadDiff(article, oldid, diff, a.navpopup); } else if ( history ) { loadAPIPreview('history', article, a.navpopup); } else if ( shouldShowNonSimple(a) && pg.re.contribs.test(a.href) ) { loadAPIPreview('contribs', article, a.navpopup); } else if ( shouldShowNonSimple(a) && pg.re.backlinks.test(a.href) ) { loadAPIPreview('backlinks', article, a.navpopup); } else if ( // FIXME should be able to get all preview combinations with options article.namespaceId()==pg.nsImageId && ( shouldShow(a,'imagePopupsForImages') || ! anchorContainsImage(a) ) ) { loadAPIPreview('imagepagepreview', article, a.navpopup); loadImage(article, a.navpopup); //</NOLITE> } else { if (article.namespaceId() == pg.nsCategoryId && shouldShow(a,'popupCategoryMembers')) { loadAPIPreview('category', article, a.navpopup); } else if ((article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) && shouldShow(a,'popupUserInfo')) { loadAPIPreview('userinfo', article, a.navpopup); } if (shouldShowNonSimple(a)) startArticlePreview(article, oldid, a.navpopup); } } function pendingNavpopTask(navpop) { if (navpop && navpop.pending === null) { navpop.pending=0; } ++navpop.pending; debugData(navpop); } function completedNavpopTask(navpop) { if (navpop && navpop.pending) { --navpop.pending; } debugData(navpop); } function startArticlePreview(article, oldid, navpop) { navpop.redir=0; loadPreview(article, oldid, navpop); } function loadPreview(article, oldid, navpop) { if (!navpop.redir) { navpop.originalArticle=article; } article.oldid = oldid; loadAPIPreview('revision', article, navpop); } function loadPreviewFromRedir(redirMatch, navpop) { // redirMatch is a regex match var target = new Title().fromWikiText(redirMatch[2]); // overwrite (or add) anchor from original target // mediawiki does overwrite; eg User:Lupin/foo3#Done if ( navpop.article.anchor ) { target.anchor = navpop.article.anchor; } navpop.redir++; navpop.redirTarget=target; //<NOLITE> var warnRedir = redirLink(target, navpop.article); setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber); //</NOLITE> navpop.article=target; fillEmptySpans({redir: true, redirTarget: target, navpopup:navpop}); return loadPreview(target, null, navpop); } function insertPreview(download) { if (!download.owner) { return; } var redirMatch = pg.re.redirect.exec(download.data); if (download.owner.redir === 0 && redirMatch) { loadPreviewFromRedir(redirMatch, download.owner); return; } if (download.owner.visible || !getValueOf('popupLazyPreviews')) { insertPreviewNow(download); } else { var id=(download.owner.redir) ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK'; download.owner.addHook( function(){insertPreviewNow(download); return true;}, 'unhide', 'after', id ); } } function insertPreviewNow(download) { if (!download.owner) { return; } var wikiText=download.data; var navpop=download.owner; var art=navpop.redirTarget || navpop.originalArticle; //<NOLITE> makeFixDabs(wikiText, navpop); if (getValueOf('popupSummaryData')) { getPageInfo(wikiText, download); setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber); } var imagePage=''; if (art.namespaceId()==pg.nsImageId) { imagePage=art.toString(); } else { imagePage=getValidImageFromWikiText(wikiText); } if(imagePage) { loadImage(Title.fromWikiText(imagePage), navpop); } //</NOLITE> if (getValueOf('popupPreviews')) { insertArticlePreview(download, art, navpop); } } function insertArticlePreview(download, art, navpop) { if (download && typeof download.data == typeof ''){ if (art.namespaceId()==pg.nsTemplateId && getValueOf('popupPreviewRawTemplates')) { // FIXME compare/consolidate with diff escaping code for wikitext var h='<hr /><span style="font-family: monospace;">' + download.data.entify().split('\\n').join('<br />\\n') + '</span>'; setPopupHTML(h, 'popupPreview', navpop.idNumber); } else { var p=prepPreviewmaker(download.data, art, navpop); p.showPreview(); } } } function prepPreviewmaker(data, article, navpop) { // deal with tricksy anchors var d=anchorize(data, article.anchorString()); var urlBase=joinPath([pg.wiki.articlebase, article.urlString()]); var p=new Previewmaker(d, urlBase, navpop); return p; } // Try to imitate the way mediawiki generates HTML anchors from section titles function anchorize(d, anch) { if (!anch) { return d; } var anchRe=RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*'+getValueOf('popupAnchorRegexp')+'\\s*(?:\\|[^|}]*)*?\\s*'+literalizeRegex(anch)+'\\s*(?:\\|[^}]*)?}})'); var match=d.match(anchRe); if(match && match.length > 0 && match[0]) { return d.substring(d.indexOf(match[0])); } // now try to deal with == foo [[bar|baz]] boom == -> #foo_baz_boom var lines=d.split('\n'); for (var i=0; i<lines.length; ++i) { lines[i]=lines[i].replace(RegExp('[[]{2}([^|\\]]*?[|])?(.*?)[\\]]{2}', 'g'), '$2') .replace(/'''([^'])/g, '$1').replace(RegExp("''([^'])", 'g'), '$1'); if (lines[i].match(anchRe)) { return d.split('\n').slice(i).join('\n').replace(RegExp('^[^=]*'), ''); } } return d; } function killPopup() { removeModifierKeyHandler(this); if (getValueOf('popupShortcutKeys')) { rmPopupShortcuts(); } if (!pg) { return; } if (pg.current.link && pg.current.link.navpopup) { pg.current.link.navpopup.banish(); } pg.current.link=null; abortAllDownloads(); if (pg.timer.checkPopupPosition) { clearInterval(pg.timer.checkPopupPosition); pg.timer.checkPopupPosition=null; } return true; // preserve default action } // ENDFILE: actions.js // STARTFILE: domdrag.js /** @fileoverview The {@link Drag} object, which enables objects to be dragged around. <pre> ************************************************* dom-drag.js 09.25.2001 www.youngpup.net ************************************************** 10.28.2001 - fixed minor bug where events sometimes fired off the handle, not the root. ************************************************* Pared down, some hooks added by User:Lupin Copyright Aaron Boodman. Saying stupid things daily since March 2001. </pre> */ /** Creates a new Drag object. This is used to make various DOM elements draggable. @constructor */ function Drag () { /** Condition to determine whether or not to drag. This function should take one parameter, an Event. To disable this, set it to <code>null</code>. @type Function */ this.startCondition = null; /** Hook to be run when the drag finishes. This is passed the final coordinates of the dragged object (two integers, x and y). To disables this, set it to <code>null</code>. @type Function */ this.endHook = null; } /** Gets an event in a cross-browser manner. @param {Event} e @private */ Drag.prototype.fixE = function(e) { if (typeof e == 'undefined') { e = window.event; } if (typeof e.layerX == 'undefined') { e.layerX = e.offsetX; } if (typeof e.layerY == 'undefined') { e.layerY = e.offsetY; } return e; }; /** Initialises the Drag instance by telling it which object you want to be draggable, and what you want to drag it by. @param {DOMElement} o The "handle" by which <code>oRoot</code> is dragged. @param {DOMElement} oRoot The object which moves when <code>o</code> is dragged, or <code>o</code> if omitted. */ Drag.prototype.init = function(o, oRoot) { var dragObj = this; this.obj = o; o.onmousedown = function(e) { dragObj.start.apply( dragObj, [e]); }; o.dragging = false; o.popups_draggable = true; o.hmode = true; o.vmode = true; o.root = oRoot ? oRoot : o ; if (isNaN(parseInt(o.root.style.left, 10))) { o.root.style.left = "0px"; } if (isNaN(parseInt(o.root.style.top, 10))) { o.root.style.top = "0px"; } o.root.onthisStart = function(){}; o.root.onthisEnd = function(){}; o.root.onthis = function(){}; }; /** Starts the drag. @private @param {Event} e */ Drag.prototype.start = function(e) { var o = this.obj; // = this; e = this.fixE(e); if (this.startCondition && !this.startCondition(e)) { return; } var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10); o.root.onthisStart(x, y); o.lastMouseX = e.clientX; o.lastMouseY = e.clientY; var dragObj = this; o.onmousemoveDefault = document.onmousemove; o.dragging = true; document.onmousemove = function(e) { dragObj.drag.apply( dragObj, [e] ); }; document.onmouseup = function(e) { dragObj.end.apply( dragObj, [e] ); }; return false; }; /** Does the drag. @param {Event} e @private */ Drag.prototype.drag = function(e) { e = this.fixE(e); var o = this.obj; var ey = e.clientY; var ex = e.clientX; var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10 ); var nx, ny; nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1)); ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1)); this.obj.root.style[o.hmode ? "left" : "right"] = nx + "px"; this.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px"; this.obj.lastMouseX = ex; this.obj.lastMouseY = ey; this.obj.root.onthis(nx, ny); return false; }; /** Ends the drag. @private */ Drag.prototype.end = function() { document.onmousemove=this.obj.onmousemoveDefault; document.onmouseup = null; this.obj.dragging = false; if (this.endHook) { this.endHook( parseInt(this.obj.root.style[this.obj.hmode ? "left" : "right"], 10), parseInt(this.obj.root.style[this.obj.vmode ? "top" : "bottom"], 10)); } }; // ENDFILE: domdrag.js // STARTFILE: structures.js //<NOLITE> pg.structures.original={}; pg.structures.original.popupLayout=function () { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupUserData', 'popupData', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; pg.structures.original.popupRedirSpans=function () { return ['popupRedir', 'popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks']; }; pg.structures.original.popupTitle=function (x) { log ('defaultstructure.popupTitle'); if (!getValueOf('popupNavLinks')) { return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params); } return ''; }; pg.structures.original.popupTopLinks=function (x) { log ('defaultstructure.popupTopLinks'); if (getValueOf('popupNavLinks')) { return navLinksHTML(x.article, x.hint, x.params); } return ''; }; pg.structures.original.popupImage=function(x) { log ('original.popupImage, x.article='+x.article+', x.navpop.idNumber='+x.navpop.idNumber); return imageHTML(x.article, x.navpop.idNumber); }; pg.structures.original.popupRedirTitle=pg.structures.original.popupTitle; pg.structures.original.popupRedirTopLinks=pg.structures.original.popupTopLinks; function copyStructure(oldStructure, newStructure) { pg.structures[newStructure]={}; for (var prop in pg.structures[oldStructure]) { pg.structures[newStructure][prop]=pg.structures[oldStructure][prop]; } } copyStructure('original', 'nostalgia'); pg.structures.nostalgia.popupTopLinks=function(x) { var str=''; str += '<b><<mainlink|shortcut= >></b>'; // user links // contribs - log - count - email - block // count only if applicable; block only if popupAdminLinks str += 'if(user){<br><<contribs|shortcut=c>>'; str+='if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}'; // editing links // talkpage -> edit|new - history - un|watch - article|edit // other page -> edit - history - un|watch - talk|edit|new var editstr='<<edit|shortcut=e>>'; var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'; var historystr='<<history|shortcut=h>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; str += '<br>if(talk){' + editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + // not a talk page editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}'; // misc links str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>'; str += 'if(admin){<br>}else{*}<<move|shortcut=m>>'; // admin links str += 'if(admin){*<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>}'; return navlinkStringToHTML(str, x.article, x.params); }; pg.structures.nostalgia.popupRedirTopLinks=pg.structures.nostalgia.popupTopLinks; /** -- fancy -- **/ copyStructure('original', 'fancy'); pg.structures.fancy.popupTitle=function (x) { return navlinkStringToHTML('<font size=+0><<mainlink>></font>',x.article,x.params); }; pg.structures.fancy.popupTopLinks=function(x) { var hist='<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>|<<editors|shortcut=E|eds>>'; var watch='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; var move='<<move|shortcut=m|move>>'; return navlinkStringToHTML('if(talk){' + '<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' + hist + '*' + '<<article|shortcut=a>>|<<editArticle|edit>>' + '*' + watch + '*' + move + '}else{<<edit|shortcut=e>>*' + hist + '*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' + '*' + watch + '*' + move+'}<br>', x.article, x.params); }; pg.structures.fancy.popupOtherLinks=function(x) { var admin='<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*<<undelete|undeleteShort>>|<<delete|shortcut=d|del>>'; var user='<<contribs|shortcut=c>>if(wikimedia){|<<count|shortcut=#|#>>}'; user+='if(ipuser){|<<arin>>}else{*<<email|shortcut=E|'+ popupString('email')+'>>}if(admin){*<<block|shortcut=b>>}'; var normal='<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>'; return navlinkStringToHTML('<br>if(user){' + user + '*}if(admin){'+admin+'if(user){<br>}else{*}}' + normal, x.article, x.params); }; pg.structures.fancy.popupRedirTitle=pg.structures.fancy.popupTitle; pg.structures.fancy.popupRedirTopLinks=pg.structures.fancy.popupTopLinks; pg.structures.fancy.popupRedirOtherLinks=pg.structures.fancy.popupOtherLinks; /** -- fancy2 -- **/ // hack for User:MacGyverMagic copyStructure('fancy', 'fancy2'); pg.structures.fancy2.popupTopLinks=function(x) { // hack out the <br> at the end and put one at the beginning return '<br>'+pg.structures.fancy.popupTopLinks(x).replace(RegExp('<br>$','i'),''); }; pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title return ['popupError', 'popupImage', 'popupTitle', 'popupUserData', 'popupData', 'popupTopLinks', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; /** -- menus -- **/ copyStructure('original', 'menus'); pg.structures.menus.popupLayout=function () { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupUserData', 'popupData', 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; pg.structures.menus.popupTopLinks = function (x, shorter) { // FIXME maybe this stuff should be cached var s=[]; var dropdiv='<div class="popup_drop">'; var enddiv='</div>'; var hist='<<history|shortcut=h>>'; if (!shorter) { hist = '<menurow>' + hist + '|<<historyfeed|rss>>|<<editors|shortcut=E>></menurow>'; } var lastedit='<<lastEdit|shortcut=/|show last edit>>'; var thank='if(diff){<<thank|send thanks>>}'; var jsHistory='<<lastContrib|last set of edits>><<sinceMe|changes since mine>>'; var linkshere='<<whatLinksHere|shortcut=l|what links here>>'; var related='<<relatedChanges|shortcut=r|related changes>>'; var search='<menurow><<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}' + '|<<google|shortcut=G|web>></menurow>'; var watch='<menurow><<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>></menurow>'; var protect='<menurow><<unprotect|unprotectShort>>|' + '<<protect|shortcut=p>>|<<protectlog|log>></menurow>'; var del='<menurow><<undelete|undeleteShort>>|<<delete|shortcut=d>>|' + '<<deletelog|log>></menurow>'; var move='<<move|shortcut=m|move page>>'; var nullPurge='<menurow><<nullEdit|shortcut=n|null edit>>|<<purge|shortcut=P>></menurow>'; var viewOptions='<menurow><<view|shortcut=v>>|<<render|shortcut=S>>|<<raw>></menurow>'; var editRow='if(oldid){' + '<menurow><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></menurow>' + '<menurow><<revert|shortcut=v>>|<<undo>></menurow>' + '}else{<<edit|shortcut=e>>}'; var markPatrolled='if(rcid){<<markpatrolled|mark patrolled>>}'; var newTopic='if(talk){<<new|shortcut=+|new topic>>}'; var protectDelete='if(admin){' + protect + del + '}'; if (getValueOf('popupActionsMenu')) { s.push( '<<mainlink>>*' + dropdiv + menuTitle('actions')); } else { s.push( dropdiv + '<<mainlink>>'); } s.push( '<menu>'); s.push( editRow + markPatrolled + newTopic + hist + lastedit + thank ); if (!shorter) { s.push(jsHistory); } s.push( move + linkshere + related); if (!shorter) { s.push(nullPurge + search); } if (!shorter) { s.push(viewOptions); } s.push('<hr />' + watch + protectDelete); s.push('<hr />' + 'if(talk){<<article|shortcut=a|view article>><<editArticle|edit article>>}' + 'else{<<talk|shortcut=t|talk page>><<editTalk|edit talk>>' + '<<newTalk|shortcut=+|new topic>>}</menu>' + enddiv); // user menu starts here var email='<<email|shortcut=E|email user>>'; var contribs= 'if(wikimedia){<menurow>}<<contribs|shortcut=c|contributions>>if(wikimedia){</menurow>}' + 'if(admin){<menurow><<deletedContribs>></menurow>}'; s.push('if(user){*' + dropdiv + menuTitle('user')); s.push('<menu>'); s.push('<menurow><<userPage|shortcut=u|user&nbsp;page>>|<<userSpace|space>></menurow>'); s.push('<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' + '<<newUserTalk|shortcut=+|leave comment>>'); if(!shorter) { s.push( 'if(ipuser){<<arin>>}else{' + email + '}' ); } else { s.push( 'if(ipuser){}else{' + email + '}' ); } s.push('<hr />' + contribs + '<<userlog|shortcut=L|user log>>'); s.push('if(wikimedia){<<count|shortcut=#|edit counter>>}'); s.push('if(admin){<menurow><<unblock|unblockShort>>|<<block|shortcut=b|block user>></menurow>}'); s.push('<<blocklog|shortcut=B|block log>>'); s.push('</menu>' + enddiv + '}'); // popups menu starts here if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */) { x.navpop.hasPopupMenu=true; s.push('*' + dropdiv + menuTitle('popupsMenu') + '<menu>'); s.push('<<togglePreviews|toggle previews>>'); s.push('<<purgePopups|reset>>'); s.push('<<disablePopups|disable>>'); s.push('</menu>'+enddiv); } return navlinkStringToHTML(s.join(''), x.article, x.params); }; function menuTitle(s) { return '<a href="#" noPopup=1>' + popupString(s) + '</a>'; } pg.structures.menus.popupRedirTitle=pg.structures.menus.popupTitle; pg.structures.menus.popupRedirTopLinks=pg.structures.menus.popupTopLinks; copyStructure('menus', 'shortmenus'); pg.structures.shortmenus.popupTopLinks=function(x) { return pg.structures.menus.popupTopLinks(x,true); }; pg.structures.shortmenus.popupRedirTopLinks=pg.structures.shortmenus.popupTopLinks; //</NOLITE> pg.structures.lite={}; pg.structures.lite.popupLayout=function () { return ['popupTitle', 'popupPreview' ]; }; pg.structures.lite.popupTitle=function (x) { log (x.article + ': structures.lite.popupTitle'); //return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params); return '<div><span class="popup_mainlink"><b>' + x.article.toString() + '</b></span></div>'; }; // ENDFILE: structures.js // STARTFILE: autoedit.js //<NOLITE> function substitute(data,cmdBody) { // alert('sub\nfrom: '+cmdBody.from+'\nto: '+cmdBody.to+'\nflags: '+cmdBody.flags); var fromRe=RegExp(cmdBody.from, cmdBody.flags); return data.replace(fromRe, cmdBody.to); } function execCmds(data, cmdList) { for (var i=0; i<cmdList.length; ++i) { data=cmdList[i].action(data, cmdList[i]); } return data; } function parseCmd(str) { // returns a list of commands if (!str.length) { return []; } var p=false; switch (str.charAt(0)) { case 's': p=parseSubstitute(str); break; default: return false; } if (p) { return [p].concat(parseCmd(p.remainder)); } return false; } function unEscape(str, sep) { return str.split('\\\\').join('\\').split('\\'+sep).join(sep).split('\\n').join('\n'); } function parseSubstitute(str) { // takes a string like s/a/b/flags;othercmds and parses it var from,to,flags,tmp; if (str.length<4) { return false; } var sep=str.charAt(1); str=str.substring(2); tmp=skipOver(str,sep); if (tmp) { from=tmp.segment; str=tmp.remainder; } else { return false; } tmp=skipOver(str,sep); if (tmp) { to=tmp.segment; str=tmp.remainder; } else { return false; } flags=''; if (str.length) { tmp=skipOver(str,';') || skipToEnd(str, ';'); if (tmp) {flags=tmp.segment; str=tmp.remainder; } } return {action: substitute, from: from, to: to, flags: flags, remainder: str}; } function skipOver(str,sep) { var endSegment=findNext(str,sep); if (endSegment<0) { return false; } var segment=unEscape(str.substring(0,endSegment), sep); return {segment: segment, remainder: str.substring(endSegment+1)}; } /*eslint-disable*/ function skipToEnd(str,sep) { return {segment: str, remainder: ''}; } /*eslint-enable */ function findNext(str, ch) { for (var i=0; i<str.length; ++i) { if (str.charAt(i)=='\\') { i+=2; } if (str.charAt(i)==ch) { return i; } } return -1; } function setCheckbox(param, box) { var val=mw.util.getParamValue(param); if (val) { switch (val) { case '1': case 'yes': case 'true': box.checked=true; break; case '0': case 'no': case 'false': box.checked=false; } } } function autoEdit() { setupPopups( function () { if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version') ) { return false; } if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken')===autoClickToken()) { pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action')); } if (!document.editform) { return false; } if (autoEdit.alreadyRan) { return false; } autoEdit.alreadyRan=true; var cmdString=mw.util.getParamValue('autoedit'); if (cmdString) { try { var editbox=document.editform.wpTextbox1; var cmdList=parseCmd(cmdString); var input=editbox.value; var output=execCmds(input, cmdList); editbox.value=output; } catch (dang) { return; } // wikEd user script compatibility if (typeof(wikEdUseWikEd) != 'undefined') { if (wikEdUseWikEd === true) { WikEdUpdateFrame(); } } } setCheckbox('autominor', document.editform.wpMinoredit); setCheckbox('autowatch', document.editform.wpWatchthis); var rvid = mw.util.getParamValue('autorv'); if (rvid) { var url=pg.wiki.apiwikibase + '?action=query&format=json&formatversion=2&prop=revisions&revids='+rvid; startDownload(url, null, autoEdit2); } else { autoEdit2(); } } ); } function autoEdit2(d) { var summary=mw.util.getParamValue('autosummary'); var summaryprompt=mw.util.getParamValue('autosummaryprompt'); var summarynotice=''; if (d && d.data && mw.util.getParamValue('autorv')) { var s = getRvSummary(summary, d.data); if (s === false) { summaryprompt=true; summarynotice=popupString('Failed to get revision information, please edit manually.\n\n'); summary = simplePrintf(summary, [mw.util.getParamValue('autorv'), '(unknown)', '(unknown)']); } else { summary = s; } } if (summaryprompt) { var txt= summarynotice + popupString('Enter a non-empty edit summary or press cancel to abort'); var response=prompt(txt, summary); if (response) { summary=response; } else { return; } } if (summary) { document.editform.wpSummary.value=summary; } // Attempt to avoid possible premature clicking of the save button // (maybe delays in updates to the DOM are to blame?? or a red herring) setTimeout(autoEdit3, 100); } function autoClickToken() { return mw.user.sessionId(); } function autoEdit3() { if( mw.util.getParamValue('actoken') != autoClickToken()) { return; } var btn=mw.util.getParamValue('autoclick'); if (btn) { if (document.editform && document.editform[btn]) { var button=document.editform[btn]; var msg=tprintf('The %s button has been automatically clicked. Please wait for the next page to load.', [ button.value ]); bannerMessage(msg); document.title='('+document.title+')'; button.click(); } else { alert(tprintf('Could not find button %s. Please check the settings in your javascript file.', [ btn ])); } } } function bannerMessage(s) { var headings=document.getElementsByTagName('h1'); if (headings) { var div=document.createElement('div'); div.innerHTML='<font size=+1><b>' + s + '</b></font>'; headings[0].parentNode.insertBefore(div, headings[0]); } } function getRvSummary(template, json) { try { var o=getJsObj(json); var edit = anyChild(o.query.pages).revisions[0]; var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, ''); return simplePrintf(template, [edit.revid, timestamp, edit.userhidden ? '(hidden)' : edit.user ]); } catch (badness) { return false; } } //</NOLITE> // ENDFILE: autoedit.js // STARTFILE: downloader.js /** @fileoverview {@link Downloader}, a xmlhttprequest wrapper, and helper functions. */ /** Creates a new Downloader @constructor @class The Downloader class. Create a new instance of this class to download stuff. @param {String} url The url to download. This can be omitted and supplied later. */ function Downloader(url) { if (typeof XMLHttpRequest!='undefined') { this.http = new XMLHttpRequest(); } /** The url to download @type String */ this.url = url; /** A universally unique ID number @type integer */ this.id=null; /** Modification date, to be culled from the incoming headers @type Date @private */ this.lastModified = null; /** What to do when the download completes successfully @type Function @private */ this.callbackFunction = null; /** What to do on failure @type Function @private */ this.onFailure = null; /** Flag set on <code>abort</code> @type boolean */ this.aborted = false; /** HTTP method. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for possibilities. @type String */ this.method='GET'; /** Async flag. @type boolean */ this.async=true; } new Downloader(); /** Submits the http request. */ Downloader.prototype.send = function (x) { if (!this.http) { return null; } return this.http.send(x); }; /** Aborts the download, setting the <code>aborted</code> field to true. */ Downloader.prototype.abort = function () { if (!this.http) { return null; } this.aborted=true; return this.http.abort(); }; /** Returns the downloaded data. */ Downloader.prototype.getData = function () {if (!this.http) { return null; } return this.http.responseText;}; /** Prepares the download. */ Downloader.prototype.setTarget = function () { if (!this.http) { return null; } this.http.open(this.method, this.url, this.async); this.http.setRequestHeader( 'Api-User-Agent', pg.misc.userAgent ); }; /** Gets the state of the download. */ Downloader.prototype.getReadyState=function () {if (!this.http) { return null; } return this.http.readyState;}; pg.misc.downloadsInProgress = { }; /** Starts the download. Note that setTarget {@link Downloader#setTarget} must be run first */ Downloader.prototype.start=function () { if (!this.http) { return; } pg.misc.downloadsInProgress[this.id] = this; this.http.send(null); }; /** Gets the 'Last-Modified' date from the download headers. Should be run after the download completes. Returns <code>null</code> on failure. @return {Date} */ Downloader.prototype.getLastModifiedDate=function () { if(!this.http) { return null; } var lastmod=null; try { lastmod=this.http.getResponseHeader('Last-Modified'); } catch (err) {} if (lastmod) { return new Date(lastmod); } return null; }; /** Sets the callback function. @param {Function} f callback function, called as <code>f(this)</code> on success */ Downloader.prototype.setCallback = function (f) { if(!this.http) { return; } this.http.onreadystatechange = f; }; Downloader.prototype.getStatus = function() { if (!this.http) { return null; } return this.http.status; }; ////////////////////////////////////////////////// // helper functions /** Creates a new {@link Downloader} and prepares it for action. @param {String} url The url to download @param {integer} id The ID of the {@link Downloader} object @param {Function} callback The callback function invoked on success @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser */ function newDownload(url, id, callback, onfailure) { var d=new Downloader(url); if (!d.http) { return 'ohdear'; } d.id=id; d.setTarget(); if (!onfailure) { onfailure=2; } var f = function () { if (d.getReadyState() == 4) { delete pg.misc.downloadsInProgress[this.id]; try { if ( d.getStatus() == 200 ) { d.data=d.getData(); d.lastModified=d.getLastModifiedDate(); callback(d); } else if (typeof onfailure == typeof 1) { if (onfailure > 0) { // retry newDownload(url, id, callback, onfailure - 1); } } else if (typeof onfailure === 'function') { onfailure(d,url,id,callback); } } catch (somerr) { /* ignore it */ } } }; d.setCallback(f); return d; } /** Simulates a download from cached data. The supplied data is put into a {@link Downloader} as if it had downloaded it. @param {String} url The url. @param {integer} id The ID. @param {Function} callback The callback, which is invoked immediately as <code>callback(d)</code>, where <code>d</code> is the new {@link Downloader}. @param {String} data The (cached) data. @param {Date} lastModified The (cached) last modified date. */ function fakeDownload(url, id, callback, data, lastModified, owner) { var d=newDownload(url,callback); d.owner=owner; d.id=id; d.data=data; d.lastModified=lastModified; return callback(d); } /** Starts a download. @param {String} url The url to download @param {integer} id The ID of the {@link Downloader} object @param {Function} callback The callback function invoked on success @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser */ function startDownload(url, id, callback) { var d=newDownload(url, id, callback); if (typeof d == typeof '' ) { return d; } d.start(); return d; } /** Aborts all downloads which have been started. */ function abortAllDownloads() { for ( var x in pg.misc.downloadsInProgress ) { try { pg.misc.downloadsInProgress[x].aborted=true; pg.misc.downloadsInProgress[x].abort(); delete pg.misc.downloadsInProgress[x]; } catch (e) {} } } // ENDFILE: downloader.js // STARTFILE: livepreview.js // TODO: location is often not correct (eg relative links in previews) // NOTE: removed md5 and image and math parsing. was broken, lots of bytes. /** * InstaView - a Mediawiki to HTML converter in JavaScript * Version 0.6.1 * Copyright (C) Pedro Fayolle 2005-2006 * https://en.wikipedia.org/wiki/User:Pilaf * Distributed under the BSD license * * Changelog: * * 0.6.1 * - Fixed problem caused by \r characters * - Improved inline formatting parser * * 0.6 * - Changed name to InstaView * - Some major code reorganizations and factored out some common functions * - Handled conversion of relative links (i.e. [[/foo]]) * - Fixed misrendering of adjacent definition list items * - Fixed bug in table headings handling * - Changed date format in signatures to reflect Mediawiki's * - Fixed handling of [[:Image:...]] * - Updated MD5 function (hopefully it will work with UTF-8) * - Fixed bug in handling of links inside images * * To do: * - Better support for math tags * - Full support for <nowiki> * - Parser-based (as opposed to RegExp-based) inline wikicode handling (make it one-pass and bullet-proof) * - Support for templates (through AJAX) * - Support for coloured links (AJAX) */ var Insta = {}; function setupLivePreview() { // options Insta.conf = { baseUrl: '', user: {}, wiki: { lang: pg.wiki.lang, interwiki: pg.wiki.interwiki, default_thumb_width: 180 }, paths: { articles: pg.wiki.articlePath + '/', // Only used for Insta previews with images. (not in popups) math: '/math/', images: '//upload.wikimedia.org/wikipedia/en/', // FIXME getImageUrlStart(pg.wiki.hostname), images_fallback: '//upload.wikimedia.org/wikipedia/commons/', }, locale: { user: mw.config.get('wgFormattedNamespaces')[pg.nsUserId], image: mw.config.get('wgFormattedNamespaces')[pg.nsImageId], category: mw.config.get('wgFormattedNamespaces')[pg.nsCategoryId], // shouldn't be used in popup previews, i think months: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'] } }; // options with default values or backreferences Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian'; Insta.conf.user.signature = '[['+Insta.conf.locale.user+':'+Insta.conf.user.name+'|'+Insta.conf.user.name+']]'; //Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/'; // define constants Insta.BLOCK_IMAGE = new RegExp('^\\[\\[(?:File|Image|'+Insta.conf.locale.image+ '):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)', 'i'); } Insta.dump = function(from, to) { if (typeof from == 'string') { from = document.getElementById(from); } if (typeof to == 'string') { to = document.getElementById(to); } to.innerHTML = this.convert(from.value); }; Insta.convert = function(wiki) { var ll = (typeof wiki == 'string')? wiki.replace(/\r/g,'').split(/\n/): wiki, // lines of wikicode o = '', // output p = 0, // para flag $r; // result of passing a regexp to $() // some shorthands function remain() { return ll.length; } function sh() { return ll.shift(); } // shift function ps(s) { o += s; } // push // similar to C's printf, uses ? as placeholders, ?? to escape question marks function f() { var i=1, a=arguments, f=a[0], o='', c, p; for (; i<a.length; i++) { if ((p=f.indexOf('?'))+1) { // allow character escaping i -= c = f.charAt(p+1)=='?' ? 1 : 0; o += f.substring(0,p) + (c ? '?' : a[i]); f = f.substr(p+1+c); } else { break; } } return o+f; } function html_entities(s) { return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); } // Wiki text parsing to html is a nightmare. // The below functions deliberately don't escape the ampersand since this would make it more difficult, // and we don't absolutely need to for how we need it. // This means that any unescaped ampersands in wikitext will remain unescaped and can cause invalid HTML. // Browsers should all be able to handle it though. // We also escape significant wikimarkup characters to prevent further matching on the processed text function htmlescape_text(s) { return s.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/:/g,"&#58;").replace(/\[/g,"&#91;").replace(/]/g,"&#93;"); } function htmlescape_attr(s) { return htmlescape_text(s).replace(/'/g,"&#39;").replace(/"/g,"&quot;"); } // return the first non matching character position between two strings function str_imatch(a, b) { for (var i=0, l=Math.min(a.length, b.length); i<l; i++) { if (a.charAt(i)!=b.charAt(i)) { break; } } return i; } // compare current line against a string or regexp // if passed a string it will compare only the first string.length characters // if passed a regexp the result is stored in $r function $(c) { return (typeof c == 'string') ? (ll[0].substr(0,c.length)==c) : ($r = ll[0].match(c)); } function $$(c) { return ll[0]==c; } // compare current line against a string function _(p) { return ll[0].charAt(p); } // return char at pos p function endl(s) { ps(s); sh(); } function parse_list() { var prev=''; while (remain() && $(/^([*#:;]+)(.*)$/)) { var l_match = $r; sh(); var ipos = str_imatch(prev, l_match[1]); // close uncontinued lists for (var prevPos=prev.length-1; prevPos >= ipos; prevPos--) { var pi = prev.charAt(prevPos); if (pi=='*') { ps('</ul>'); } else if (pi=='#') { ps('</ol>'); } // close a dl only if the new item is not a dl item (:, ; or empty) else if($.inArray(l_match[1].charAt(prevPos), ['','*','#'])) { ps('</dl>'); } } // open new lists for (var matchPos=ipos; matchPos<l_match[1].length; matchPos++) { var li = l_match[1].charAt(matchPos); if (li=='*') { ps('<ul>'); } else if (li=='#') { ps('<ol>'); } // open a new dl only if the prev item is not a dl item (:, ; or empty) else if ($.inArray(prev.charAt(matchPos), ['','*','#'])) { ps('<dl>'); } } switch (l_match[1].charAt(l_match[1].length-1)) { case '*': case '#': ps('<li>' + parse_inline_nowiki(l_match[2])); break; case ';': ps('<dt>'); var dt_match = l_match[2].match(/(.*?)(:.*?)$/); // handle ;dt :dd format if (dt_match) { ps(parse_inline_nowiki(dt_match[1])); ll.unshift(dt_match[2]); } else ps(parse_inline_nowiki(l_match[2])); break; case ':': ps('<dd>' + parse_inline_nowiki(l_match[2])); } prev=l_match[1]; } // close remaining lists for (var i=prev.length-1; i>=0; i--) { ps(f('</?>', (prev.charAt(i)=='*')? 'ul': ((prev.charAt(i)=='#')? 'ol': 'dl'))); } } function parse_table() { endl(f('<table>', $(/^\{\|( .*)$/)? $r[1]: '')); for (;remain();) if ($('|')) switch (_(1)) { case '}': endl('</table>'); return; case '-': endl(f('<tr>', $(/\|-*(.*)/)[1])); break; default: parse_table_data(); } else if ($('!')) { parse_table_data(); } else { sh(); } } function parse_table_data() { var td_line, match_i; // 1: "|+", '|' or '+' // 2: ?? // 3: attributes ?? // TODO: finish commenting this regexp var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/); if (td_match[1] == '|+') ps('<caption'); else ps('<t' + ((td_match[1]=='|')?'d':'h')); if (typeof td_match[3] != 'undefined') { //ps(' ' + td_match[3]) match_i = 4; } else match_i = 2; ps('>'); if (td_match[1] != '|+') { // use || or !! as a cell separator depending on context // NOTE: when split() is passed a regexp make sure to use non-capturing brackets td_line = td_match[match_i].split((td_match[1] == '|')? '||': /(?:\|\||!!)/); ps(parse_inline_nowiki(td_line.shift())); while (td_line.length) ll.unshift(td_match[1] + td_line.pop()); } else ps(td_match[match_i]); var tc = 0, td = []; while (remain()) { td.push(sh()); if ($('|')) { if (!tc) break; // we're at the outer-most level (no nested tables), skip to td parse else if (_(1)=='}') tc--; } else if (!tc && $('!')) break; else if ($('{|')) tc++; } if (td.length) ps(Insta.convert(td)); } function parse_pre() { ps('<pre>'); do { endl(parse_inline_nowiki(ll[0].substring(1)) + "\n"); } while (remain() && $(' ')); ps('</pre>'); } function parse_block_image() { ps(parse_image(sh())); } function parse_image(str) { //<NOLITE> // get what's in between "[[Image:" and "]]" var tag = str.substring(str.indexOf(':') + 1, str.length - 2); /* eslint-disable no-unused-vars */ var width; var attr = [], filename, caption = ''; var thumb=0, frame=0, center=0; var align=''; /* eslint-enable no-unused-vars */ if (tag.match(/\|/)) { // manage nested links var nesting = 0; var last_attr; for (var i = tag.length-1; i > 0; i--) { if (tag.charAt(i) == '|' && !nesting) { last_attr = tag.substr(i+1); tag = tag.substring(0, i); break; } else switch (tag.substr(i-1, 2)) { case ']]': nesting++; i--; break; case '[[': nesting--; i--; } } attr = tag.split(/\s*\|\s*/); attr.push(last_attr); filename = attr.shift(); var w_match; for (;attr.length; attr.shift()) { w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/); if (w_match) width = w_match[1]; else switch(attr[0]) { case 'thumb': case 'thumbnail': thumb=true; frame=true; break; case 'frame': frame=true; break; case 'none': case 'right': case 'left': center=false; align=attr[0]; break; case 'center': center=true; align='none'; break; default: if (attr.length == 1) caption = attr[0]; } } } else filename = tag; return ''; //</NOLITE> } function parse_inline_nowiki(str) { var start, lastend=0; var substart=0, nestlev=0, open, close, subloop; var html=''; while (-1 != (start = str.indexOf('<nowiki>', substart))) { html += parse_inline_wiki(str.substring(lastend, start)); start += 8; substart = start; subloop = true; do { open = str.indexOf('<nowiki>', substart); close = str.indexOf('</nowiki>', substart); if (close<=open || open==-1) { if (close==-1) { return html + html_entities(str.substr(start)); } substart = close+9; if (nestlev) { nestlev--; } else { lastend = substart; html += html_entities(str.substring(start, lastend-9)); subloop = false; } } else { substart = open+8; nestlev++; } } while (subloop); } return html + parse_inline_wiki(str.substr(lastend)); } function parse_inline_images(str) { //<NOLITE> var start, substart=0, nestlev=0; var loop, close, open, wiki, html; while (-1 != (start=str.indexOf('[[', substart))) { if(str.substr(start+2).match(RegExp('^(Image|File|' + Insta.conf.locale.image + '):','i'))) { loop=true; substart=start; do { substart+=2; close=str.indexOf(']]',substart); open=str.indexOf('[[',substart); if (close<=open||open==-1) { if (close==-1) return str; substart=close; if (nestlev) { nestlev--; } else { wiki=str.substring(start,close+2); html=parse_image(wiki); str=str.replace(wiki,html); substart=start+html.length; loop=false; } } else { substart=open; nestlev++; } } while (loop); } else break; } //</NOLITE> return str; } // the output of this function doesn't respect the FILO structure of HTML // but since most browsers can handle it I'll save myself the hassle function parse_inline_formatting(str) { var em,st,i,li,o=''; while ((i=str.indexOf("''",li))+1) { o += str.substring(li,i); li=i+2; if (str.charAt(i+2)=="'") { li++; st=!st; o+=st?'<strong>':'</strong>'; } else { em=!em; o+=em?'<em>':'</em>'; } } return o+str.substr(li); } function parse_inline_wiki(str) { str = parse_inline_images(str); str = parse_inline_formatting(str); // math str = str.replace(/<(?:)math>(.*?)<\/math>/ig, ''); // Build a Mediawiki-formatted date string var date = new Date(); var minutes = date.getUTCMinutes(); if (minutes < 10) minutes = '0' + minutes; date = f("?:?, ? ? ? (UTC)", date.getUTCHours(), minutes, date.getUTCDate(), Insta.conf.locale.months[date.getUTCMonth()], date.getUTCFullYear()); // text formatting return str. // signatures replace(/~{5}(?!~)/g, date). replace(/~{4}(?!~)/g, Insta.conf.user.name+' '+date). replace(/~{3}(?!~)/g, Insta.conf.user.name). // [[:Category:...]], [[:Image:...]], etc... replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):[^|]*?)\\]\\](\\w*)','gi'), function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2));}). // remove straight category and interwiki tags replace(RegExp('\\[\\[(?:'+Insta.conf.locale.category+'|'+Insta.conf.wiki.interwiki+'):.*?\\]\\]','gi'),''). // [[:Category:...|Links]], [[:Image:...|Links]], etc... replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):.*?)\\|([^\\]]+?)\\]\\](\\w*)','gi'), function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));}). // [[/Relative links]] replace(/\[\[(\/[^|]*?)\]\]/g, function($0,$1){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($1)); }). // [[/Replaced|Relative links]] replace(/\[\[(\/.*?)\|(.+?)\]\]/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($2)); }). // [[Common links]] replace(/\[\[([^[|]*?)\]\](\w*)/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2)); }). // [[Replaced|Links]] replace(/\[\[([^[]*?)\|([^\]]+?)\]\](\w*)/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3)); }). // [[Stripped:Namespace|Namespace]] replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1) + htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($2)); }). // External links replace(/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g, function($0,$1,$2,$3,$4){return f("<a class='external' href='?:?'>?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($4)); }). replace(/\[http:\/\/(.*?)\]/g, function($0,$1){return f("<a class='external' href='http://?'>[#]</a>", htmlescape_attr($1)); }). replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, function($0,$1,$2,$3){return f("<a class='external' href='?:?'>?:?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($1), htmlescape_text($2) + htmlescape_text($3)); }). replace(/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*[^.,!?;: $])/g, function($0,$1,$2,$3,$4){return f("?<a class='external' href='?:?'>?:?</a>", htmlescape_text($1), htmlescape_attr($2), htmlescape_attr($3) + htmlescape_attr($4), htmlescape_text($2), htmlescape_text($3) + htmlescape_text($4)); }). replace('__NOTOC__',''). replace('__NOEDITSECTION__',''); } // begin parsing for (;remain();) if ($(/^(={1,6})(.*)\1(.*)$/)) { p=0; endl(f('<h?>?</h?>?', $r[1].length, parse_inline_nowiki($r[2]), $r[1].length, $r[3])); } else if ($(/^[*#:;]/)) { p=0; parse_list(); } else if ($(' ')) { p=0; parse_pre(); } else if ($('{|')) { p=0; parse_table(); } else if ($(/^----+$/)) { p=0; endl('<hr />'); } else if ($(Insta.BLOCK_IMAGE)) { p=0; parse_block_image(); } else { // handle paragraphs if ($$('')) { p = (remain()>1 && ll[1]===('')); if (p) endl('<p><br>'); } else { if(!p) { ps('<p>'); p=1; } ps(parse_inline_nowiki(ll[0]) + ' '); } sh(); } return o; }; function wiki2html(txt,baseurl) { Insta.conf.baseUrl=baseurl; return Insta.convert(txt); } // ENDFILE: livepreview.js // STARTFILE: pageinfo.js //<NOLITE> function popupFilterPageSize(data) { return formatBytes(data.length); } function popupFilterCountLinks(data) { var num=countLinks(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('wikiLinks'):popupString('wikiLink')); } function popupFilterCountImages(data) { var num=countImages(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('images'):popupString('image')); } function popupFilterCountCategories(data) { var num=countCategories(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('categories'):popupString('category')); } function popupFilterLastModified(data,download) { var lastmod=download.lastModified; var now=new Date(); var age=now-lastmod; if (lastmod && getValueOf('popupLastModified')) { return (tprintf('%s old', [formatAge(age)])).replace(RegExp(' ','g'), '&nbsp;'); } return ''; } function formatAge(age) { // coerce into a number var a=0+age, aa=a; var seclen = 1000; var minlen = 60*seclen; var hourlen = 60*minlen; var daylen = 24*hourlen; var weeklen = 7*daylen; var numweeks = (a-a%weeklen)/weeklen; a = a-numweeks*weeklen; var sweeks = addunit(numweeks, 'week'); var numdays = (a-a%daylen)/daylen; a = a-numdays*daylen; var sdays = addunit(numdays, 'day'); var numhours = (a-a%hourlen)/hourlen; a = a-numhours*hourlen; var shours = addunit(numhours,'hour'); var nummins = (a-a%minlen)/minlen; a = a-nummins*minlen; var smins = addunit(nummins, 'minute'); var numsecs = (a-a%seclen)/seclen; a = a-numsecs*seclen; var ssecs = addunit(numsecs, 'second'); if (aa > 4*weeklen) { return sweeks; } if (aa > weeklen) { return sweeks + ' ' + sdays; } if (aa > daylen) { return sdays + ' ' + shours; } if (aa > 6*hourlen) { return shours; } if (aa > hourlen) { return shours + ' ' + smins; } if (aa > 10*minlen) { return smins; } if (aa > minlen) { return smins + ' ' + ssecs; } return ssecs; } function addunit(num,str) { return '' + num + ' ' + ((num!=1) ? popupString(str+'s') : popupString(str)) ;} function runPopupFilters(list, data, download) { var ret=[]; for (var i=0; i<list.length; ++i) { if (list[i] && typeof list[i] == 'function') { var s=list[i](data, download, download.owner.article); if (s) { ret.push(s); } } } return ret; } function getPageInfo(data, download) { if (!data || data.length === 0) { return popupString('Empty page'); } var popupFilters=getValueOf('popupFilters') || []; var extraPopupFilters = getValueOf('extraPopupFilters') || []; var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download); var pageInfo=pageInfoArray.join(', '); if (pageInfo !== '' ) { pageInfo = upcaseFirst(pageInfo); } return pageInfo; } // this could be improved! function countLinks(wikiText) { return wikiText.split('[[').length - 1; } // if N = # matches, n = # brackets, then // String.parenSplit(regex) intersperses the N+1 split elements // with Nn other elements. So total length is // L= N+1 + Nn = N(n+1)+1. So N=(L-1)/(n+1). function countImages(wikiText) { return (wikiText.parenSplit(pg.re.image).length - 1) / (pg.re.imageBracketCount + 1); } function countCategories(wikiText) { return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1); } function popupFilterStubDetect(data, download, article) { var counts=stubCount(data, article); if (counts.real) { return popupString('stub'); } if (counts.sect) { return popupString('section stub'); } return ''; } function popupFilterDisambigDetect(data, download, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return ''; } return (isDisambig(data, article)) ? popupString('disambig') : ''; } function formatBytes(num) { return (num > 949) ? (Math.round(num/100)/10+popupString('kB')) : (num +'&nbsp;' + popupString('bytes')) ; } //</NOLITE> // ENDFILE: pageinfo.js // STARTFILE: titles.js /** @fileoverview Defines the {@link Title} class, and associated crufty functions. <code>Title</code> deals with article titles and their various forms. {@link Stringwrapper} is the parent class of <code>Title</code>, which exists simply to make things a little neater. */ /** Creates a new Stringwrapper. @constructor @class the Stringwrapper class. This base class is not really useful on its own; it just wraps various common string operations. */ function Stringwrapper() { /** Wrapper for this.toString().indexOf() @param {String} x @type integer */ this.indexOf=function(x){return this.toString().indexOf(x);}; /** Returns this.value. @type String */ this.toString=function(){return this.value;}; /** Wrapper for {@link String#parenSplit} applied to this.toString() @param {RegExp} x @type Array */ this.parenSplit=function(x){return this.toString().parenSplit(x);}; /** Wrapper for this.toString().substring() @param {String} x @param {String} y (optional) @type String */ this.substring=function(x,y){ if (typeof y=='undefined') { return this.toString().substring(x); } return this.toString().substring(x,y); }; /** Wrapper for this.toString().split() @param {String} x @type Array */ this.split=function(x){return this.toString().split(x);}; /** Wrapper for this.toString().replace() @param {String} x @param {String} y @type String */ this.replace=function(x,y){ return this.toString().replace(x,y); }; } /** Creates a new <code>Title</code>. @constructor @class The Title class. Holds article titles and converts them into various forms. Also deals with anchors, by which we mean the bits of the article URL after a # character, representing locations within an article. @param {String} value The initial value to assign to the article. This must be the canonical title (see {@link Title#value}. Omit this in the constructor and use another function to set the title if this is unavailable. */ function Title(val) { /** The canonical article title. This must be in UTF-8 with no entities, escaping or nasties. Also, underscores should be replaced with spaces. @type String @private */ this.value=null; /** The canonical form of the anchor. This should be exactly as it appears in the URL, i.e. with the .C3.0A bits in. @type String */ this.anchor=''; this.setUtf(val); } Title.prototype=new Stringwrapper(); /** Returns the canonical representation of the article title, optionally without anchor. @param {boolean} omitAnchor @fixme Decide specs for anchor @return String The article title and the anchor. */ Title.prototype.toString=function(omitAnchor) { return this.value + ( (!omitAnchor && this.anchor) ? '#' + this.anchorString() : '' ); }; Title.prototype.anchorString=function() { if (!this.anchor) { return ''; } var split=this.anchor.parenSplit(/((?:[.][0-9A-F]{2})+)/); var len=split.length; for (var j=1; j<len; j+=2) { // FIXME s/decodeURI/decodeURIComponent/g ? split[j]=decodeURIComponent(split[j].split('.').join('%')).split('_').join(' '); } return split.join(''); }; Title.prototype.urlAnchor=function() { var split=this.anchor.parenSplit('/((?:[%][0-9A-F]{2})+)/'); var len=split.length; for (var j=1; j<len; j+=2) { split[j]=split[j].split('%').join('.'); } return split.join(''); }; Title.prototype.anchorFromUtf=function(str) { this.anchor=encodeURIComponent(str.split(' ').join('_')) .split('%3A').join(':').split("'").join('%27').split('%').join('.'); }; Title.fromURL=function(h) { return new Title().fromURL(h); }; Title.prototype.fromURL=function(h) { if (typeof h != 'string') { this.value=null; return this; } // NOTE : playing with decodeURI, encodeURI, escape, unescape, // we seem to be able to replicate the IE borked encoding // IE doesn't do this new-fangled utf-8 thing. // and it's worse than that. // IE seems to treat the query string differently to the rest of the url // the query is treated as bona-fide utf8, but the first bit of the url is pissed around with // we fix up & for all browsers, just in case. var splitted=h.split('?'); splitted[0]=splitted[0].split('&').join('%26'); h=splitted.join('?'); var contribs=pg.re.contribs.exec(h); if (contribs) { if (contribs[1]=='title=') { contribs[3]=contribs[3].split('+').join(' '); } var u=new Title(contribs[3]); this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + u.stripNamespace())); return this; } var email=pg.re.email.exec(h); if (email) { this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(email[3]).stripNamespace())); return this; } var backlinks=pg.re.backlinks.exec(h); if (backlinks) { this.setUtf(this.decodeNasties(new Title(backlinks[3]))); return this; } //A dummy title object for a Special:Diff link. var specialdiff=pg.re.specialdiff.exec(h); if (specialdiff) { this.setUtf(this.decodeNasties(new Title(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Diff'))); return this; } // no more special cases to check -- // hopefully it's not a disguised user-related or specially treated special page var m=pg.re.main.exec(h); if(m === null) { this.value=null; } else { var fromBotInterface = /[?](.+[&])?title=/.test(h); if (fromBotInterface) { m[2]=m[2].split('+').join('_'); } var extracted = m[2] + (m[3] ? '#' + m[3] : ''); if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) { // Fix Safari issue // Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3. this.setUtf(decodeURIComponent(unescape(extracted))); } else { this.setUtf(this.decodeNasties(extracted)); } } return this; }; Title.prototype.decodeNasties=function(txt) { var ret= this.decodeEscapes(decodeURI(txt)); ret = ret.replace(/[_ ]*$/, ''); return ret; }; Title.prototype.decodeEscapes=function(txt) { var split=txt.parenSplit(/((?:[%][0-9A-Fa-f]{2})+)/); var len=split.length; for (var i=1; i<len; i=i+2) { // FIXME is decodeURIComponent better? split[i]=unescape(split[i]); } return split.join(''); }; Title.fromAnchor=function(a) { return new Title().fromAnchor(a); }; Title.prototype.fromAnchor=function(a) { if (!a) { this.value=null; return this; } return this.fromURL(a.href); }; Title.fromWikiText=function(txt) { return new Title().fromWikiText(txt); }; Title.prototype.fromWikiText=function(txt) { // FIXME - testing needed txt=myDecodeURI(txt); this.setUtf(txt); return this; }; Title.prototype.hintValue=function(){ if(!this.value) { return ''; } return safeDecodeURI(this.value); }; //<NOLITE> Title.prototype.toUserName=function(withNs) { if (this.namespaceId() != pg.nsUserId && this.namespaceId() != pg.nsUsertalkId) { this.value=null; return; } this.value = (withNs ? mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' : '') + this.stripNamespace().split('/')[0]; }; Title.prototype.userName=function(withNs) { var t=(new Title(this.value)); t.toUserName(withNs); if (t.value) { return t; } return null; }; Title.prototype.toTalkPage=function() { // convert article to a talk page, or if we can't, return null // In other words: return null if this ALREADY IS a talk page // and return the corresponding talk page otherwise // // Per https://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces // * All discussion namespaces have odd-integer indices // * The discussion namespace index for a specific namespace with index n is n + 1 if (this.value === null) { return null; } var namespaceId = this.namespaceId(); if (namespaceId>=0 && namespaceId % 2 === 0) //non-special and subject namespace { var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId+1]; if (typeof localizedNamespace!=='undefined') { if (localizedNamespace === '') { this.value = this.stripNamespace(); } else { this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace(); } return this.value; } } this.value=null; return null; }; //</NOLITE> // Return canonical, localized namespace Title.prototype.namespace=function() { return mw.config.get('wgFormattedNamespaces')[this.namespaceId()]; }; Title.prototype.namespaceId=function() { var n=this.value.indexOf(':'); if (n<0) { return 0; } //mainspace var namespaceId = mw.config.get('wgNamespaceIds')[this.value.substring(0,n).split(' ').join('_').toLowerCase()]; if (typeof namespaceId=='undefined') return 0; //mainspace return namespaceId; }; //<NOLITE> Title.prototype.talkPage=function() { var t=new Title(this.value); t.toTalkPage(); if (t.value) { return t; } return null; }; Title.prototype.isTalkPage=function() { if (this.talkPage()===null) { return true; } return false; }; Title.prototype.toArticleFromTalkPage=function() { //largely copy/paste from toTalkPage above. if (this.value === null) { return null; } var namespaceId = this.namespaceId(); if (namespaceId >= 0 && namespaceId % 2 == 1) //non-special and talk namespace { var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId-1]; if (typeof localizedNamespace!=='undefined') { if (localizedNamespace === '') { this.value = this.stripNamespace(); } else { this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace(); } return this.value; } } this.value=null; return null; }; Title.prototype.articleFromTalkPage=function() { var t=new Title(this.value); t.toArticleFromTalkPage(); if (t.value) { return t; } return null; }; Title.prototype.articleFromTalkOrArticle=function() { var t=new Title(this.value); if ( t.toArticleFromTalkPage() ) { return t; } return this; }; Title.prototype.isIpUser=function() { return pg.re.ipUser.test(this.userName()); }; //</NOLITE> Title.prototype.stripNamespace=function(){ // returns a string, not a Title var n=this.value.indexOf(':'); if (n<0) { return this.value; } var namespaceId = this.namespaceId(); if (namespaceId === pg.nsMainspaceId) return this.value; return this.value.substring(n+1); }; Title.prototype.setUtf=function(value){ if (!value) { this.value=''; return; } var anch=value.indexOf('#'); if(anch < 0) { this.value=value.split('_').join(' '); this.anchor=''; return; } this.value=value.substring(0,anch).split('_').join(' '); this.anchor=value.substring(anch+1); this.ns=null; // wait until namespace() is called }; Title.prototype.setUrl=function(urlfrag) { var anch=urlfrag.indexOf('#'); this.value=safeDecodeURI(urlfrag.substring(0,anch)); this.anchor=this.value.substring(anch+1); }; Title.prototype.append=function(x){ this.setUtf(this.value + x); }; Title.prototype.urlString=function(x) { if(!x) { x={}; } var v=this.toString(true); if (!x.omitAnchor && this.anchor) { v+= '#' + this.urlAnchor(); } if (!x.keepSpaces) { v=v.split(' ').join('_'); } return encodeURI(v).split('&').join('%26').split('?').join('%3F').split('+').join('%2B'); }; Title.prototype.removeAnchor=function() { return new Title(this.toString(true)); }; Title.prototype.toUrl=function() { return pg.wiki.titlebase + this.urlString(); }; function parseParams(url) { var specialDiff = pg.re.specialdiff.exec(url); if (specialDiff) { var split= specialDiff[1].split('/'); if (split.length==1) return {oldid:split[0], diff: 'prev'}; else if (split.length==2) return {oldid: split[0], diff: split[1]}; } var ret={}; if (url.indexOf('?')==-1) { return ret; } url = url.split('#')[0]; var s=url.split('?').slice(1).join(); var t=s.split('&'); for (var i=0; i<t.length; ++i) { var z=t[i].split('='); z.push(null); ret[z[0]]=z[1]; } //Diff revision with no oldid is interpreted as a diff to the previous revision by MediaWiki if (ret.diff && typeof(ret.oldid)==='undefined') { ret.oldid = "prev"; } //Documentation seems to say something different, but oldid can also accept prev/next, and Echo is emitting such URLs. Simple fixup during parameter decoding: if (ret.oldid && (ret.oldid==='prev' || ret.oldid==='next' || ret.oldid==='cur')) { var helper = ret.diff; ret.diff = ret.oldid; ret.oldid = helper; } return ret; } // (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup) // (b) change spaces to underscores // (c) encodeURI (just the straight one, no pg.re.urlNoPopup) function myDecodeURI (str) { var ret; // FIXME decodeURIComponent?? try { ret=decodeURI(str.toString()); } catch (summat) { return str; } for (var i=0; i<pg.misc.decodeExtras.length; ++i) { var from=pg.misc.decodeExtras[i].from; var to=pg.misc.decodeExtras[i].to; ret=ret.split(from).join(to); } return ret; } function safeDecodeURI(str) { var ret=myDecodeURI(str); return ret || str; } /////////// // TESTS // /////////// //<NOLITE> function isDisambig(data, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; } return ! article.isTalkPage() && pg.re.disambig.test(data); } function stubCount(data, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; } var sectStub=0; var realStub=0; if (pg.re.stub.test(data)) { var s=data.parenSplit(pg.re.stub); for (var i=1; i<s.length; i=i+2) { if (s[i]) { ++sectStub; } else { ++realStub; } } } return { real: realStub, sect: sectStub }; } function isValidImageName(str){ // extend as needed... return ( str.indexOf('{') == -1 ); } function isInStrippableNamespace(article) { // Does the namespace allow subpages // Note, would be better if we had access to wgNamespacesWithSubpages return ( article.namespaceId() !== 0 ); } function isInMainNamespace(article) { return article.namespaceId() === 0; } function anchorContainsImage(a) { // iterate over children of anchor a // see if any are images if (a === null) { return false; } var kids=a.childNodes; for (var i=0; i<kids.length; ++i) { if (kids[i].nodeName=='IMG') { return true; } } return false; } //</NOLITE> function isPopupLink(a) { // NB for performance reasons, TOC links generally return true // they should be stripped out later if (!markNopopupSpanLinks.done) { markNopopupSpanLinks(); } if (a.inNopopupSpan) { return false; } // FIXME is this faster inline? if (a.onmousedown || a.getAttribute('nopopup')) { return false; } var h=a.href; if (h === document.location.href+'#') { return false; } if (!pg.re.basenames.test(h)) { return false; } if (!pg.re.urlNoPopup.test(h)) { return true; } return ( (pg.re.email.test(h) || pg.re.contribs.test(h) || pg.re.backlinks.test(h) || pg.re.specialdiff.test(h)) && h.indexOf('&limit=') == -1 ); } function markNopopupSpanLinks() { if( !getValueOf('popupOnlyArticleLinks')) fixVectorMenuPopups(); var s = $('.nopopups').toArray(); for (var i=0; i<s.length; ++i) { var as=s[i].getElementsByTagName('a'); for (var j=0; j<as.length; ++j) { as[j].inNopopupSpan=true; } } markNopopupSpanLinks.done=true; } function fixVectorMenuPopups() { $('div.vectorMenu h3:first a:first').prop('inNopopupSpan', true); } // ENDFILE: titles.js // STARTFILE: getpage.js ////////////////////////////////////////////////// // Wiki-specific downloading // // Schematic for a getWiki call // // getPageWithCaching // | // false | true // getPage<-[findPictureInCache]->-onComplete(a fake download) // \. // (async)->addPageToCache(download)->-onComplete(download) // check cache to see if page exists function getPageWithCaching(url, onComplete, owner) { log('getPageWithCaching, url='+url); var i=findInPageCache(url); var d; if (i > -1) { d=fakeDownload(url, owner.idNumber, onComplete, pg.cache.pages[i].data, pg.cache.pages[i].lastModified, owner); } else { d=getPage(url, onComplete, owner); if (d && owner && owner.addDownload) { owner.addDownload(d); d.owner=owner; } } } function getPage(url, onComplete, owner) { log('getPage'); var callback= function (d) { if (!d.aborted) {addPageToCache(d); onComplete(d);} }; return startDownload(url, owner.idNumber, callback); } function findInPageCache(url) { for (var i=0; i<pg.cache.pages.length; ++i) { if (url==pg.cache.pages[i].url) { return i; } } return -1; } function addPageToCache(download) { log('addPageToCache '+download.url); var page = {url: download.url, data: download.data, lastModified: download.lastModified}; return pg.cache.pages.push(page); } // ENDFILE: getpage.js // STARTFILE: parensplit.js ////////////////////////////////////////////////// // parenSplit // String.prototype.parenSplit should do what ECMAscript says String.prototype.split does, // interspersing paren matches (regex capturing groups) between the split elements. // i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c'] if (String('abc'.split(/(b)/))!='a,b,c') { // broken String.split, e.g. konq, IE < 10 String.prototype.parenSplit=function (re) { re=nonGlobalRegex(re); var s=this; var m=re.exec(s); var ret=[]; while (m && s) { // without the following loop, we have // 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/) for(var i=0; i<m.length; ++i) { if (typeof m[i]=='undefined') m[i]=''; } ret.push(s.substring(0,m.index)); ret = ret.concat(m.slice(1)); s=s.substring(m.index + m[0].length); m=re.exec(s); } ret.push(s); return ret; }; } else { String.prototype.parenSplit=function (re) { return this.split(re); }; String.prototype.parenSplit.isNative=true; } function nonGlobalRegex(re) { var s=re.toString(); var flags=''; for (var j=s.length; s.charAt(j) != '/'; --j) { if (s.charAt(j) != 'g') { flags += s.charAt(j); } } var t=s.substring(1,j); return RegExp(t,flags); } // ENDFILE: parensplit.js // STARTFILE: tools.js // IE madness with encoding // ======================== // // suppose throughout that the page is in utf8, like wikipedia // // if a is an anchor DOM element and a.href should consist of // // http://host.name.here/wiki/foo?bar=baz // // then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie)) // but IE gives bar=baz correctly as plain utf8 // // --------------------------------- // // IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here. // // --------------------------------- // // summat else // Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm //<NOLITE> function getJsObj(json) { try { var json_ret = JSON.parse(json); if( json_ret.warnings ) { for( var w=0; w < json_ret.warnings.length; w++ ) { if( json_ret.warnings[w]['*'] ) { log( json_ret.warnings[w]['*'] ); } else { log( json_ret.warnings[w]['warnings'] ); } } } else if ( json_ret.error ) { errlog( json_ret.error.code + ': ' + json_ret.error.info ); } return json_ret; } catch (someError) { errlog('Something went wrong with getJsObj, json='+json); return 1; } } function anyChild(obj) { for (var p in obj) { return obj[p]; } return null; } //</NOLITE> function upcaseFirst(str) { if (typeof str != typeof '' || str === '') return ''; return str.charAt(0).toUpperCase() + str.substring(1); } function findInArray(arr, foo) { if (!arr || !arr.length) { return -1; } var len=arr.length; for (var i=0; i<len; ++i) { if (arr[i]==foo) { return i; } } return -1; } /* eslint-disable no-unused-vars */ function nextOne (array, value) { // NB if the array has two consecutive entries equal // then this will loop on successive calls var i=findInArray(array, value); if (i<0) { return null; } return array[i+1]; } /* eslint-enable no-unused-vars */ function literalizeRegex(str){ return mw.util.escapeRegExp(str); } String.prototype.entify=function() { //var shy='&shy;'; return this.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;'/*+shy*/).split('"').join('&quot;'); }; // Array filter function function removeNulls(val) { return val !== null; } function joinPath(list) { return list.filter(removeNulls).join('/'); } function simplePrintf(str, subs) { if (!str || !subs) { return str; } var ret=[]; var s=str.parenSplit(/(%s|\$[0-9]+)/); var i=0; do { ret.push(s.shift()); if ( !s.length ) { break; } var cmd=s.shift(); if (cmd == '%s') { if ( i < subs.length ) { ret.push(subs[i]); } else { ret.push(cmd); } ++i; } else { var j=parseInt( cmd.replace('$', ''), 10 ) - 1; if ( j > -1 && j < subs.length ) { ret.push(subs[j]); } else { ret.push(cmd); } } } while (s.length > 0); return ret.join(''); } /* eslint-disable no-unused-vars */ function isString(x) { return (typeof x === 'string' || x instanceof String); } function isNumber(x) { return (typeof x === 'number' || x instanceof Number); } function isRegExp(x) { return x instanceof RegExp; } function isArray (x) { return x instanceof Array; } function isObject(x) { return x instanceof Object; } function isFunction(x) { return !isRegExp(x) && (typeof x === 'function' || x instanceof Function); } /* eslint-enable no-unused-vars */ function repeatString(s,mult) { var ret=''; for (var i=0; i<mult; ++i) { ret += s; } return ret; } function zeroFill(s, min) { min = min || 2; var t=s.toString(); return repeatString('0', min - t.length) + t; } function map(f, o) { if (isArray(o)) { return map_array(f,o); } return map_object(f,o); } function map_array(f,o) { var ret=[]; for (var i=0; i<o.length; ++i) { ret.push(f(o[i])); } return ret; } function map_object(f,o) { var ret={}; for (var i in o) { ret[o]=f(o[i]); } return ret; } pg.escapeQuotesHTML = function ( text ) { return text .replace(/&/g, "&amp;") .replace(/"/g, "&quot;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;"); }; // ENDFILE: tools.js // STARTFILE: dab.js //<NOLITE> ////////////////////////////////////////////////// // Dab-fixing code // function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) { log('retargetDab: newTarget='+newTarget + ' oldTarget=' + oldTarget); return changeLinkTargetLink( {newTarget: newTarget, text: newTarget.split(' ').join('&nbsp;'), hint: tprintf('disambigHint', [newTarget]), summary: simplePrintf( getValueOf('popupFixDabsSummary'), [friendlyCurrentArticleName, newTarget ]), clickButton: getValueOf('popupDabsAutoClick'), minor: true, oldTarget: oldTarget, watch: getValueOf('popupWatchDisambiggedPages'), title: titleToEdit}); } function listLinks(wikitext, oldTarget, titleToEdit) { // mediawiki strips trailing spaces, so we do the same // testcase: https://en.wikipedia.org/w/index.php?title=Radial&oldid=97365633 var reg=RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi'); var ret=[]; var splitted=wikitext.parenSplit(reg); // ^[a-z]+ should match interwiki links, hopefully (case-insensitive) // and ^[a-z]* should match those and [[:Category...]] style links too var omitRegex=RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory'); var friendlyCurrentArticleName= oldTarget.toString(); var wikPos = getValueOf('popupDabWiktionary'); for (var i=1; i<splitted.length; i=i+3) { if (typeof splitted[i] == typeof 'string' && splitted[i].length>0 && !omitRegex.test(splitted[i])) { ret.push( retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit) ); } /* if */ } /* for loop */ ret = rmDupesFromSortedList(ret.sort()); if (wikPos) { var wikTarget='wiktionary:' + friendlyCurrentArticleName.replace( RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1' ); var meth; if (wikPos.toLowerCase() == 'first') { meth = 'unshift'; } else { meth = 'push'; } ret[meth]( retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) ); } ret.push(changeLinkTargetLink( { newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'), hint: popupString("remove all links to this disambig page from this article"), clickButton: getValueOf('popupDabsAutoClick'), oldTarget: oldTarget, summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]), watch: getValueOf('popupWatchDisambiggedPages'), title: titleToEdit })); return ret; } function rmDupesFromSortedList(list) { var ret=[]; for (var i=0; i<list.length; ++i) { if (ret.length === 0 || list[i]!=ret[ret.length-1]) { ret.push(list[i]); } } return ret; } function makeFixDab(data, navpop) { // grab title from parent popup if there is one; default exists in changeLinkTargetLink var titleToEdit=(navpop.parentPopup && navpop.parentPopup.article.toString()); var list=listLinks(data, navpop.originalArticle, titleToEdit); if (list.length === 0) { log('listLinks returned empty list'); return null; } var html='<hr />' + popupString('Click to disambiguate this link to:') + '<br>'; html+=list.join(', '); return html; } function makeFixDabs(wikiText, navpop) { if (getValueOf('popupFixDabs') && isDisambig(wikiText, navpop.article) && Title.fromURL(location.href).namespaceId() != pg.nsSpecialId && navpop.article.talkPage() ) { setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber); } } function popupRedlinkHTML(article) { return changeLinkTargetLink( { newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'), hint: popupString("remove all links to this page from this article"), clickButton: getValueOf('popupRedlinkAutoClick'), oldTarget: article.toString(), summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()])}); } //</NOLITE> // ENDFILE: dab.js // STARTFILE: htmloutput.js // this has to use a timer loop as we don't know if the DOM element exists when we want to set the text function setPopupHTML (str, elementId, popupId, onSuccess, append) { if (typeof popupId === 'undefined') { //console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100)); popupId = pg.idNumber; } var popupElement=document.getElementById(elementId+popupId); if (popupElement) { if (!append) { popupElement.innerHTML=''; } if (isString(str)) { popupElement.innerHTML+=str; } else { popupElement.appendChild(str); } if (onSuccess) { onSuccess(); } setTimeout(checkPopupPosition, 100); return true; } else { // call this function again in a little while... setTimeout(function(){ setPopupHTML(str,elementId,popupId,onSuccess); }, 600); } return null; } //<NOLITE> function setPopupTrailer(str,id) {return setPopupHTML(str, 'popupData', id);} //</NOLITE> // args.navpopup is mandatory // optional: args.redir, args.redirTarget // FIXME: ye gods, this is ugly stuff function fillEmptySpans(args) { // if redir is present and true then redirTarget is mandatory var redir=true; var rcid; if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) { redir=false; } var a=args.navpopup.parentAnchor; var article, hint=null, oldid=null, params={}; if (redir && typeof args.redirTarget == typeof {}) { article=args.redirTarget; //hint=article.hintValue(); } else { article=(new Title()).fromAnchor(a); hint=a.originalTitle || article.hintValue(); params=parseParams(a.href); oldid=(getValueOf('popupHistoricalLinks')) ? params.oldid : null; rcid=params.rcid; } var x={ article:article, hint: hint, oldid: oldid, rcid: rcid, navpop:args.navpopup, params:params }; var structure=pg.structures[getValueOf('popupStructure')]; if (typeof structure != 'object') { setPopupHTML('popupError', 'Unknown structure (this should never happen): '+ pg.option.popupStructure, args.navpopup.idNumber); return; } var spans=flatten(pg.misc.layout); var numspans = spans.length; var redirs=pg.misc.redirSpans; for (var i=0; i<numspans; ++i) { var found = redirs && (redirs.indexOf( spans[i] ) !== -1); //log('redir='+redir+', found='+found+', spans[i]='+spans[i]); if ( (found && !redir) || (!found && redir) ) { //log('skipping this set of the loop'); continue; } var structurefn=structure[spans[i]]; var setfn = setPopupHTML; if (getValueOf('popupActiveNavlinks') && (spans[i].indexOf('popupTopLinks')===0 || spans[i].indexOf('popupRedirTopLinks')===0) ) { setfn = setPopupTipsAndHTML; } switch (typeof structurefn) { case 'function': log('running '+spans[i]+'({article:'+x.article+', hint:'+x.hint+', oldid: '+x.oldid+'})'); setfn(structurefn(x), spans[i], args.navpopup.idNumber); break; case 'string': setfn(structurefn, spans[i], args.navpopup.idNumber); break; default: errlog('unknown thing with label '+spans[i] + ' (span index was ' + i + ')'); break; } } } // flatten an array function flatten(list, start) { var ret=[]; if (typeof start == 'undefined') { start=0; } for (var i=start; i<list.length; ++i) { if (typeof list[i] == typeof []) { return ret.concat(flatten(list[i])).concat(flatten(list, i+1)); } else { ret.push(list[i]); } } return ret; } // Generate html for whole popup function popupHTML (a) { getValueOf('popupStructure'); var structure=pg.structures[pg.option.popupStructure]; if (typeof structure != 'object') { //return 'Unknown structure: '+pg.option.popupStructure; // override user choice pg.option.popupStructure=pg.optionDefault.popupStructure; return popupHTML(a); } if (typeof structure.popupLayout != 'function') { return 'Bad layout'; } pg.misc.layout=structure.popupLayout(); if (typeof structure.popupRedirSpans === 'function') { pg.misc.redirSpans=structure.popupRedirSpans(); } else { pg.misc.redirSpans=[]; } return makeEmptySpans(pg.misc.layout, a.navpopup); } function makeEmptySpans (list, navpop) { var ret=''; for (var i=0; i<list.length; ++i) { if (typeof list[i] == typeof '') { ret += emptySpanHTML(list[i], navpop.idNumber, 'div'); } else if (typeof list[i] == typeof [] && list[i].length > 0 ) { ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop)); } else if (typeof list[i] == typeof {} && list[i].nodeType ) { ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType); } } return ret; } function emptySpanHTML(name, id, tag, classname) { tag = tag || 'span'; if (!classname) { classname = emptySpanHTML.classAliases[name]; } classname = classname || name; if (name == getValueOf('popupDragHandle')) { classname += ' popupDragHandle'; } return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]); } emptySpanHTML.classAliases={ 'popupSecondPreview': 'popupPreview' }; // generate html for popup image // <a id="popupImageLinkn"><img id="popupImagen"> // where n=idNumber function imageHTML(article, idNumber) { return simplePrintf('<a id="popupImageLink$1">' + '<img align="right" valign="top" id="popupImg$1" style="display: none;"></img>' + '</a>', [ idNumber ]); } function popTipsSoonFn(id, when, popData) { if (!when) { when=250; } var popTips=function(){ setupTooltips(document.getElementById(id), false, true, popData); }; return function() { setTimeout( popTips, when, popData ); }; } function setPopupTipsAndHTML(html, divname, idnumber, popData) { setPopupHTML(html, divname, idnumber, getValueOf('popupSubpopups') ? popTipsSoonFn(divname + idnumber, null, popData) : null); } // ENDFILE: htmloutput.js // STARTFILE: mouseout.js ////////////////////////////////////////////////// // fuzzy checks function fuzzyCursorOffMenus(x,y, fuzz, parent) { if (!parent) { return null; } var uls=parent.getElementsByTagName('ul'); for (var i=0; i<uls.length; ++i) { if (uls[i].className=='popup_menu') { if (uls[i].offsetWidth > 0) return false; } // else {document.title+='.';} } return true; } function checkPopupPosition () { // stop the popup running off the right of the screen // FIXME avoid pg.current.link if (pg.current.link && pg.current.link.navpopup) pg.current.link.navpopup.limitHorizontalPosition(); } function mouseOutWikiLink () { //console ('mouseOutWikiLink'); var a=this; removeModifierKeyHandler(a); if (a.navpopup === null || typeof a.navpopup === 'undefined') return; if ( ! a.navpopup.isVisible() ) { a.navpopup.banish(); return; } restoreTitle(a); Navpopup.tracker.addHook(posCheckerHook(a.navpopup)); } function posCheckerHook(navpop) { return function() { if (!navpop.isVisible()) { return true; /* remove this hook */ } if (Navpopup.tracker.dirty) { return false; } var x=Navpopup.tracker.x, y=Navpopup.tracker.y; var mouseOverNavpop = navpop.isWithin(x,y,navpop.fuzz, navpop.mainDiv) || !fuzzyCursorOffMenus(x,y,navpop.fuzz, navpop.mainDiv); // FIXME it'd be prettier to do this internal to the Navpopup objects var t=getValueOf('popupHideDelay'); if (t) { t = t * 1000; } if (!t) { if(!mouseOverNavpop) { if(navpop.parentAnchor) { restoreTitle( navpop.parentAnchor ); } navpop.banish(); return true; /* remove this hook */ } return false; } // we have a hide delay set var d=+(new Date()); if ( !navpop.mouseLeavingTime ) { navpop.mouseLeavingTime = d; return false; } if ( mouseOverNavpop ) { navpop.mouseLeavingTime=null; return false; } if (d - navpop.mouseLeavingTime > t) { navpop.mouseLeavingTime=null; navpop.banish(); return true; /* remove this hook */ } return false; }; } function runStopPopupTimer(navpop) { // at this point, we should have left the link but remain within the popup // so we call this function again until we leave the popup. if (!navpop.stopPopupTimer) { navpop.stopPopupTimer=setInterval(posCheckerHook(navpop), 500); navpop.addHook(function(){clearInterval(navpop.stopPopupTimer);}, 'hide', 'before'); } } // ENDFILE: mouseout.js // STARTFILE: previewmaker.js /** @fileoverview Defines the {@link Previewmaker} object, which generates short previews from wiki markup. */ /** Creates a new Previewmaker @constructor @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext. @param {String} wikiText The Wikitext source of the page we wish to preview. @param {String} baseUrl The url we should prepend when creating relative urls. @param {Navpopup} owner The navpop associated to this preview generator */ function Previewmaker(wikiText, baseUrl, owner) { /** The wikitext which is manipulated to generate the preview. */ this.originalData=wikiText; this.baseUrl=baseUrl; this.owner=owner; this.maxCharacters=getValueOf('popupMaxPreviewCharacters'); this.maxSentences=getValueOf('popupMaxPreviewSentences'); this.setData(); } Previewmaker.prototype.setData=function() { var maxSize=Math.max(10000, 2*this.maxCharacters); this.data=this.originalData.substring(0,maxSize); }; /** Remove HTML comments @private */ Previewmaker.prototype.killComments = function () { // this also kills one trailing newline, eg [[diamyo]] this.data=this.data.replace(RegExp('^<!--[^$]*?-->\\n|\\n<!--[^$]*?-->(?=\\n)|<!--[^$]*?-->', 'g'), ''); }; /** @private */ Previewmaker.prototype.killDivs = function () { // say goodbye, divs (can be nested, so use * not *?) this.data=this.data.replace(RegExp('< *div[^>]* *>[\\s\\S]*?< */ *div *>', 'gi'), ''); }; /** @private */ Previewmaker.prototype.killGalleries = function () { this.data=this.data.replace(RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>', 'gi'), ''); }; /** @private */ Previewmaker.prototype.kill = function(opening, closing, subopening, subclosing, repl) { var oldk=this.data; var k=this.killStuff(this.data, opening, closing, subopening, subclosing, repl); while (k.length < oldk.length) { oldk=k; k=this.killStuff(k, opening, closing, subopening, subclosing, repl); } this.data=k; }; /** @private */ Previewmaker.prototype.killStuff = function (txt, opening, closing, subopening, subclosing, repl) { var op=this.makeRegexp(opening); var cl=this.makeRegexp(closing, '^'); var sb=subopening ? this.makeRegexp(subopening, '^') : null; var sc=subclosing ? this.makeRegexp(subclosing, '^') : cl; if (!op || !cl) { alert('Navigation Popups error: op or cl is null! something is wrong.'); return; } if (!op.test(txt)) { return txt; } var ret=''; var opResult = op.exec(txt); ret = txt.substring(0,opResult.index); txt=txt.substring(opResult.index+opResult[0].length); var depth = 1; while (txt.length > 0) { var removal=0; if (depth==1 && cl.test(txt)) { depth--; removal=cl.exec(txt)[0].length; } else if (depth > 1 && sc.test(txt)) { depth--; removal=sc.exec(txt)[0].length; }else if (sb && sb.test(txt)) { depth++; removal=sb.exec(txt)[0].length; } if ( !removal ) { removal = 1; } txt=txt.substring(removal); if (depth === 0) { break; } } return ret + (repl || '') + txt; }; /** @private */ Previewmaker.prototype.makeRegexp = function (x, prefix, suffix) { prefix = prefix || ''; suffix = suffix || ''; var reStr=''; var flags=''; if (isString(x)) { reStr=prefix + literalizeRegex(x) + suffix; } else if (isRegExp(x)) { var s=x.toString().substring(1); var sp=s.split('/'); flags=sp[sp.length-1]; sp[sp.length-1]=''; s=sp.join('/'); s=s.substring(0,s.length-1); reStr= prefix + s + suffix; } else { log ('makeRegexp failed'); } log ('makeRegexp: got reStr=' + reStr + ', flags=' + flags); return RegExp(reStr, flags); }; /** @private */ Previewmaker.prototype.killBoxTemplates = function () { // taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general // also, have float_begin, ... float_end this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /[}][}]\s*/, '{{'); // infoboxes etc // from User:Zyxw/popups.js: kill frames too this.kill(RegExp('[{][{][^{}\\s|]*?(infobox|elementbox|frame)[_ ]', 'i'), /[}][}]\s*/, '{{'); }; /** @private */ Previewmaker.prototype.killTemplates = function () { // the plus sign is to avoid transcluding [[Template:', ']] this.kill('{'+'{', '}'+'}', '{', '}', ' '); }; /** @private */ Previewmaker.prototype.killTables = function () { // tables are bad, too // this can be slow, but it's an inprovement over a browser hang // torture test: [[Comparison_of_Intel_Central_Processing_Units]] this.kill('{|', /[|]}\s*/, '{|'); this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i); // remove lines starting with a pipe for the hell of it (?) this.data=this.data.replace(RegExp('^[|].*$', 'mg'), ''); }; /** @private */ Previewmaker.prototype.killImages = function () { var forbiddenNamespaceAliases = []; jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) { if (_namespaceId!=pg.nsImageId && _namespaceId!=pg.nsCategoryId) return; forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments! }); // images and categories are a nono this.kill(RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'), /\]\]\s*/, '[', ']'); }; /** @private */ Previewmaker.prototype.killHTML = function () { // kill <ref ...>...</ref> this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i); // let's also delete entire lines starting with <. it's worth a try. this.data=this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n'); // and those pesky html tags, but not <nowiki> or <blockquote> var splitted=this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/); var len=splitted.length; for (var i=1; i<len; i=i+2) { switch (splitted[i]) { case '<nowiki>': case '</nowiki>': case '<blockquote>': case '</blockquote>': break; default: splitted[i]=''; } } this.data=splitted.join(''); }; /** @private */ Previewmaker.prototype.killChunks = function() { // heuristics alert // chunks of italic text? you crazy, man? var italicChunkRegex=new RegExp ("((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+", 'g'); // keep stuff separated, though, so stick in \n (fixes [[Union Jack]]? this.data=this.data.replace(italicChunkRegex, '\n'); }; /** @private */ Previewmaker.prototype.mopup = function () { // we simply *can't* be doing with horizontal rules right now this.data=this.data.replace(RegExp('^-{4,}','mg'),''); // no indented lines this.data=this.data.replace(RegExp('(^|\\n) *:[^\\n]*','g'), ''); // replace __TOC__, __NOTOC__ and whatever else there is // this'll probably do this.data=this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'),''); }; /** @private */ Previewmaker.prototype.firstBit = function () { // dont't be givin' me no subsequent paragraphs, you hear me? /// first we "normalize" section headings, removing whitespace after, adding before var d=this.data; if (getValueOf('popupPreviewCutHeadings')) { this.data=this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 '); /// then we want to get rid of paragraph breaks whose text ends badly this.data=this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n'); this.data=this.data.replace(RegExp('^[\\s\\n]*'), ''); var stuff=(RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data); if (stuff) { d = stuff[0]; } if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; } /// now put \n\n after sections so that bullets and numbered lists work d=d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n'); } // Split sentences. Superfluous sentences are RIGHT OUT. // note: exactly 1 set of parens here needed to make the slice work d = d.parenSplit(RegExp('([!?.]+["'+"'"+']*\\s)','g')); // leading space is bad, mmkay? d[0]=d[0].replace(RegExp('^\\s*'), ''); var notSentenceEnds=RegExp('([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$', 'i'); d = this.fixSentenceEnds(d, notSentenceEnds); this.fullLength=d.join('').length; var n=this.maxSentences; var dd=this.firstSentences(d,n); do { dd=this.firstSentences(d,n); --n; } while ( dd.length > this.maxCharacters && n !== 0 ); this.data = dd; }; /** @private */ Previewmaker.prototype.fixSentenceEnds = function(strs, reg) { // take an array of strings, strs // join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg for (var i=0; i<strs.length-2; ++i) { if (reg.test(strs[i])) { var a=[]; for (var j=0; j<strs.length; ++j) { if (j<i) a[j]=strs[j]; if (j==i) a[i]=strs[i]+strs[i+1]+strs[i+2]; if (j>i+2) a[j-2]=strs[j]; } return this.fixSentenceEnds(a,reg); } } return strs; }; /** @private */ Previewmaker.prototype.firstSentences = function(strs, howmany) { var t=strs.slice(0, 2*howmany); return t.join(''); }; /** @private */ Previewmaker.prototype.killBadWhitespace = function() { // also cleans up isolated '''', eg [[Suntory Sungoliath]] this.data=this.data.replace(RegExp('^ *\'+ *$', 'gm'), ''); }; /** Runs the various methods to generate the preview. The preview is stored in the <code>html</html> field. @private */ Previewmaker.prototype.makePreview = function() { if (this.owner.article.namespaceId()!=pg.nsTemplateId && this.owner.article.namespaceId()!=pg.nsImageId ) { this.killComments(); this.killDivs(); this.killGalleries(); this.killBoxTemplates(); if (getValueOf('popupPreviewKillTemplates')) { this.killTemplates(); } else { this.killMultilineTemplates(); } this.killTables(); this.killImages(); this.killHTML(); this.killChunks(); this.mopup(); this.firstBit(); this.killBadWhitespace(); } else { this.killHTML(); } this.html=wiki2html(this.data, this.baseUrl); // needs livepreview this.fixHTML(); this.stripLongTemplates(); }; /** @private */ Previewmaker.prototype.esWiki2HtmlPart = function(data) { var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink reLinks.lastIndex = 0; //reset regex var match; var result = ""; var postfixIndex = 0; while ((match = reLinks.exec(data))) //match all wikilinks { //FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases. result += pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) + '<a href="'+Insta.conf.paths.articles+pg.escapeQuotesHTML(match[1])+'">'+pg.escapeQuotesHTML((match[2]?match[2]:match[1])+match[3])+"</a>"; postfixIndex = reLinks.lastIndex; } //append the rest result += pg.escapeQuotesHTML(data.substring(postfixIndex)); return result; }; Previewmaker.prototype.editSummaryPreview=function() { var reAes = /\/\* *(.*?) *\*\//g; //match the first section marker reAes.lastIndex = 0; //reset regex var match; match = reAes.exec(this.data); if (match) { //we have a section link. Split it, process it, combine it. var prefix = this.data.substring(0,match.index-1); var section = match[1]; var postfix = this.data.substring(reAes.lastIndex); var start = "<span class='autocomment'>"; var end = "</span>"; if (prefix.length>0) start = this.esWiki2HtmlPart(prefix) + " " + start + "- "; if (postfix.length>0) end = ": " + end + this.esWiki2HtmlPart(postfix); var t=new Title().fromURL(this.baseUrl); t.anchorFromUtf(section); var sectionLink = Insta.conf.paths.articles + pg.escapeQuotesHTML(t.toString(true)) + '#' + pg.escapeQuotesHTML(t.anchor); return start + '<a href="'+sectionLink+'">&rarr;</a> '+pg.escapeQuotesHTML(section) + end; } //else there's no section link, htmlify the whole thing. return this.esWiki2HtmlPart(this.data); }; //<NOLITE> /** Test function for debugging preview problems one step at a time. */ /*eslint-disable */ function previewSteps(txt) { try { txt=txt || document.editform.wpTextbox1.value; } catch (err) { if (pg.cache.pages.length > 0) { txt=pg.cache.pages[pg.cache.pages.length-1].data; } else { alert('provide text or use an edit page'); } } txt=txt.substring(0,10000); var base=pg.wiki.articlebase + Title.fromURL(document.location.href).urlString(); var p=new Previewmaker(txt, base, pg.current.link.navpopup); if (this.owner.article.namespaceId() != pg.nsTemplateId) { p.killComments(); if (!confirm('done killComments(). Continue?\n---\n' + p.data)) { return; } p.killDivs(); if (!confirm('done killDivs(). Continue?\n---\n' + p.data)) { return; } p.killGalleries(); if (!confirm('done killGalleries(). Continue?\n---\n' + p.data)) { return; } p.killBoxTemplates(); if (!confirm('done killBoxTemplates(). Continue?\n---\n' + p.data)) { return; } if (getValueOf('popupPreviewKillTemplates')) { p.killTemplates(); if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) { return; } } else { p.killMultilineTemplates(); if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) { return; } } p.killTables(); if (!confirm('done killTables(). Continue?\n---\n' + p.data)) { return; } p.killImages(); if (!confirm('done killImages(). Continue?\n---\n' + p.data)) { return; } p.killHTML(); if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) { return; } p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; } p.mopup(); if (!confirm('done mopup(). Continue?\n---\n' + p.data)) { return; } p.firstBit(); if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) { return; } p.killBadWhitespace(); if (!confirm('done killBadWhitespace(). Continue?\n---\n' + p.data)) { return; } } p.html=wiki2html(p.data, base); // needs livepreview p.fixHTML(); if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) { return; } p.stripLongTemplates(); if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) { return; } alert('finished preview - end result follows.\n---\n' + p.html); } /*eslint-enable */ //</NOLITE> /** Works around livepreview bugs. @private */ Previewmaker.prototype.fixHTML = function() { if(!this.html) return; var ret = this.html; // fix question marks in wiki links // maybe this'll break some stuff :-( ret=ret.replace(RegExp('(<a href="' + pg.wiki.articlePath + '/[^"]*)[?](.*?")', 'g'), '$1%3F$2'); ret=ret.replace(RegExp('(<a href=\'' + pg.wiki.articlePath + '/[^\']*)[?](.*?\')', 'g'), '$1%3F$2'); // FIXME fix up % too this.html=ret; }; /** Generates the preview and displays it in the current popup. Does nothing if the generated preview is invalid or consists of whitespace only. Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true. */ Previewmaker.prototype.showPreview = function () { this.makePreview(); if (typeof this.html != typeof '') return; if (RegExp('^\\s*$').test(this.html)) return; setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber); setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, { owner: this.owner }); var more = (this.fullLength > this.data.length) ? this.moreLink() : ''; setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber); }; /** @private */ Previewmaker.prototype.moreLink=function() { var a=document.createElement('a'); a.className='popupMoreLink'; a.innerHTML=popupString('more...'); var savedThis=this; a.onclick=function() { savedThis.maxCharacters+=2000; savedThis.maxSentences+=20; savedThis.setData(); savedThis.showPreview(); }; return a; }; /** @private */ Previewmaker.prototype.stripLongTemplates = function() { // operates on the HTML! this.html=this.html.replace(RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'), ''); this.html=this.html.split('\n').join(' '); // workaround for <pre> templates this.html=this.html.replace(RegExp('[{][{][^}]*<pre>[^}]*[}][}]','gi'), ''); }; /** @private */ Previewmaker.prototype.killMultilineTemplates = function() { this.kill('{{{', '}}}'); this.kill(RegExp('\\s*[{][{][^{}]*\\n'), '}}', '{{'); }; // ENDFILE: previewmaker.js // STARTFILE: querypreview.js function loadAPIPreview(queryType, article, navpop) { var art=new Title(article).urlString(); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&'; var htmlGenerator=function(/*a, d*/){alert('invalid html generator');}; var usernameart = ''; switch (queryType) { case 'history': url += 'titles=' + art + '&prop=revisions&rvlimit=' + getValueOf('popupHistoryPreviewLimit'); htmlGenerator=APIhistoryPreviewHTML; break; case 'category': url += 'list=categorymembers&cmtitle=' + art; htmlGenerator=APIcategoryPreviewHTML; break; case 'userinfo': var username = new Title( article ).userName(); usernameart = encodeURIComponent( username ); if (pg.re.ipUser.test(username)) { url += 'list=blocks&bkprop=range|restrictions&bkip=' + usernameart; } else { url += 'list=users|usercontribs&usprop=blockinfo|groups|editcount|registration|gender&ususers=' + usernameart + "&meta=globaluserinfo&guiprop=groups|unattached&guiuser="+ usernameart + "&uclimit=1&ucprop=timestamp&ucuser=" + usernameart; } htmlGenerator=APIuserInfoPreviewHTML; break; case 'contribs': usernameart = encodeURIComponent( new Title( article ).userName() ); url += 'list=usercontribs&ucuser=' + usernameart + '&uclimit=' + getValueOf('popupContribsPreviewLimit'); htmlGenerator=APIcontribsPreviewHTML; break; case 'imagepagepreview': var trail=''; if (getValueOf('popupImageLinks')) { trail = '&list=imageusage&iutitle=' + art; } url += 'titles=' + art + '&prop=revisions|imageinfo&rvprop=content' + trail; htmlGenerator=APIimagepagePreviewHTML; break; case 'backlinks': url += 'list=backlinks&bltitle=' + art; htmlGenerator=APIbacklinksPreviewHTML; break; case 'revision': if (article.oldid) { url += 'revids=' + article.oldid; } else { url += 'titles=' + article.removeAnchor().urlString(); } url += '&prop=revisions|pageprops|info|images|categories&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max'; htmlGenerator=APIrevisionPreviewHTML; break; } pendingNavpopTask(navpop); var callback=function(d){ log( "callback of API functions was hit" ); showAPIPreview(queryType, htmlGenerator(article,d,navpop), navpop.idNumber, navpop, d); }; var go = function(){ getPageWithCaching(url, callback, navpop); return true; }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_'+queryType+'_QUERY_DATA'); } } function linkList(list) { list.sort(function(x,y) { return (x==y ? 0 : (x<y ? -1 : 1)); }); var buf=[]; for (var i=0; i<list.length; ++i) { buf.push(wikiLink({article: new Title(list[i]), text: list[i].split(' ').join('&nbsp;'), action: 'view'})); } return buf.join(', '); } function getTimeOffset() { var tz = mw.user.options.get('timecorrection'); if(tz) { if( tz.indexOf('|') > -1 ) { // New format return parseInt(tz.split('|')[1],10); } else if ( tz.indexOf(':') > -1 ) { // Old format return( parseInt(tz,10)*60 + parseInt(tz.split(':')[1],10) ); } } return 0; } /* * Creates a HTML table that's shown in the history and user-contribs popups. * @param {Object[]} h - a list of revisions, returned from the API * @param {boolean} reallyContribs - true only if we're displaying user contributions */ function editPreviewTable(article, h, reallyContribs, timeOffset) { var html=['<table>']; var day=null; var curart=article; var page=null; var makeFirstColumnLinks; if(reallyContribs) { // We're showing user contributions, so make (diff | hist) links makeFirstColumnLinks = function(currentRevision) { var result = '('; result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&diff=prev' + '&oldid=' + currentRevision.revid + '">' + popupString('diff') + '</a>'; result += '&nbsp;|&nbsp;'; result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&action=history">' + popupString('hist') + '</a>'; result += ')'; return result; }; } else { // It's a regular history page, so make (cur | last) links var firstRevid = h[0].revid; makeFirstColumnLinks = function(currentRevision) { var result = '('; result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=' + firstRevid + '&oldid=' + currentRevision.revid + '">' + popupString('cur') + '</a>'; result += '&nbsp;|&nbsp;'; result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=prev&oldid=' + currentRevision.revid + '">' + popupString('last') + '</a>'; result += ')'; return result; }; } for (var i=0; i<h.length; ++i) { if (reallyContribs) { page = h[i].title; curart = new Title(page); } var minor = h[i].minor ? '<b>m </b>' : ''; var editDate = adjustDate(getDateFromTimestamp(h[i].timestamp), timeOffset); var thisDay = dayFormat(editDate); var thisTime = timeFormat(editDate); if (thisDay == day) { thisDay = ''; } else { day = thisDay; } if (thisDay) { html.push( '<tr><td colspan=3><span class="popup_history_date">' + thisDay+'</span></td></tr>' ); } html.push('<tr class="popup_history_row_' + ( (i%2) ? 'odd' : 'even') + '">'); html.push('<td>' + makeFirstColumnLinks(h[i]) + '</td>'); html.push('<td>' + '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&oldid=' + h[i].revid + '">' + thisTime + '</a></td>'); var col3url='', col3txt=''; if (!reallyContribs) { var user=h[i].user; if( !h[i].userhidden ) { if( pg.re.ipUser.test(user) ) { col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Contributions&target=' + new Title(user).urlString(); } else { col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(user).urlString(); } col3txt=pg.escapeQuotesHTML(user); } else { col3url=getValueOf('popupRevDelUrl'); col3txt=pg.escapeQuotesHTML( popupString('revdel')); } } else { col3url=pg.wiki.titlebase + curart.urlString(); col3txt=pg.escapeQuotesHTML(page); } html.push('<td>' + (reallyContribs ? minor : '') + '<a href="' + col3url + '">' + col3txt + '</a></td>'); var comment=''; var c=h[i].comment || h[i].content; if (c) { comment=new Previewmaker(c, new Title(curart).toUrl()).editSummaryPreview(); } else if ( h[i].commenthidden ) { comment=popupString('revdel'); } html.push('<td>' + (!reallyContribs ? minor : '') + comment + '</td>'); html.push('</tr>'); html=[html.join('')]; } html.push('</table>'); return html.join(''); } function getDateFromTimestamp(t) { var s=t.split(/[^0-9]/); switch(s.length) { case 0: return null; case 1: return new Date(s[0]); case 2: return new Date(s[0], s[1]-1); case 3: return new Date(s[0], s[1]-1, s[2]); case 4: return new Date(s[0], s[1]-1, s[2], s[3]); case 5: return new Date(s[0], s[1]-1, s[2], s[3], s[4]); case 6: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5]); default: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5], s[6]); } } function adjustDate(d, offset) { // offset is in minutes var o=offset * 60 * 1000; return new Date( +d + o); } function dayFormat(editDate, utc) { if (utc) { return map(zeroFill, [editDate.getUTCFullYear(), editDate.getUTCMonth()+1, editDate.getUTCDate()]).join('-'); } return map(zeroFill, [editDate.getFullYear(), editDate.getMonth()+1, editDate.getDate()]).join('-'); } function timeFormat(editDate, utc) { if (utc) { return map(zeroFill, [editDate.getUTCHours(), editDate.getUTCMinutes(), editDate.getUTCSeconds()]).join(':'); } return map(zeroFill, [editDate.getHours(), editDate.getMinutes(), editDate.getSeconds()]).join(':'); } function showAPIPreview(queryType, html, id, navpop, download) { // DJ: done var target='popupPreview'; completedNavpopTask(navpop); switch (queryType) { case 'imagelinks': case 'category': target='popupPostPreview'; break; case 'userinfo': target='popupUserData'; break; case 'revision': insertPreview(download); return; } setPopupTipsAndHTML(html, target, id); } function APIrevisionPreviewHTML(article, download) { try{ var jsObj=getJsObj(download.data); var page=anyChild(jsObj.query.pages); if( page.missing ) { // TODO we need to fix this proper later on download.owner = null; return; } var content = ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; if( typeof content === 'string' ) { download.data = content; download.lastModified = new Date(page.revisions[0].timestamp); } } catch(someError) { return 'Revision preview failed :('; } } function APIbacklinksPreviewHTML(article, download/*, navpop*/ ) { try { var jsObj=getJsObj(download.data); var list=jsObj.query.backlinks; var html=[]; if (!list) { return popupString('No backlinks found'); } for ( var i=0; i < list.length; i++ ) { var t=new Title(list[i].title); html.push('<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t + '</a>'); } html=html.join(', '); if (jsObj['continue'] && jsObj['continue'].blcontinue) { html += popupString(' and more'); } return html; } catch (someError) { return 'backlinksPreviewHTML went wonky'; } } pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) { log( "APIsharedImagePagePreviewHTML" ); var popupid = obj.requestid; if( obj.query && obj.query.pages ) { var page=anyChild(obj.query.pages ); var content = ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; if( typeof content === 'string' ) { /* Not entirely safe, but the best we can do */ var p=new Previewmaker(content, pg.current.link.navpopup.article, pg.current.link.navpopup); p.makePreview(); setPopupHTML( p.html, "popupSecondPreview", popupid ); } } }; function APIimagepagePreviewHTML(article, download, navpop) { try { var jsObj=getJsObj(download.data); var page=anyChild(jsObj.query.pages); var content= ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; var ret=''; var alt=''; try{alt=navpop.parentAnchor.childNodes[0].alt;} catch(e){} if (alt) { ret = ret + '<hr /><b>' + popupString('Alt text:') + '</b> ' + pg.escapeQuotesHTML(alt); } if (typeof content === 'string') { var p=prepPreviewmaker(content, article, navpop); p.makePreview(); if (p.html) { ret += '<hr />' + p.html; } if (getValueOf('popupSummaryData')) { var info=getPageInfo(content, download); log(info); setPopupTrailer(info, navpop.idNumber); } } if (page && page.imagerepository == "shared" ) { var art=new Title(article); var encart = encodeURIComponent( "File:" + art.stripNamespace() ); var shared_url = pg.wiki.apicommonsbase + '?format=json&formatversion=2' + '&callback=pg.fn.APIsharedImagePagePreviewHTML' + '&requestid=' + navpop.idNumber + '&action=query&prop=revisions&rvprop=content&titles=' + encart; ret = ret +'<hr />' + popupString( 'Image from Commons') + ': <a href="' + pg.wiki.commonsbase + '?title=' + encart + '">' + popupString( 'Description page') + '</a>'; mw.loader.load( shared_url ); } showAPIPreview('imagelinks', APIimagelinksPreviewHTML(article,download), navpop.idNumber, download); return ret; } catch (someError) { return 'API imagepage preview failed :('; } } function APIimagelinksPreviewHTML(article, download) { try { var jsobj=getJsObj(download.data); var list=jsobj.query.imageusage; if (list) { var ret=[]; for (var i=0; i < list.length; i++) { ret.push(list[i].title); } if (ret.length === 0) { return popupString('No image links found'); } return '<h2>' + popupString('File links') + '</h2>' + linkList(ret); } else { return popupString('No image links found'); } } catch(someError) { return 'Image links preview generation failed :('; } } function APIcategoryPreviewHTML(article, download) { try{ var jsobj=getJsObj(download.data); var list=jsobj.query.categorymembers; var ret=[]; for (var p=0; p < list.length; p++) { ret.push(list[p].title); } if (ret.length === 0) { return popupString('Empty category'); } ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' +linkList(ret); if (jsobj['continue'] && jsobj['continue'].cmcontinue) { ret += popupString(' and more'); } return ret; } catch(someError) { return 'Category preview failed :('; } } function APIuserInfoPreviewHTML(article, download) { var ret=[]; var queryobj = {}; try{ queryobj=getJsObj(download.data).query; } catch(someError) { return 'Userinfo preview failed :('; } var user=anyChild(queryobj.users); if (user) { var globaluserinfo=queryobj.globaluserinfo; if (user.invalid === '') { ret.push( popupString( 'Invalid user') ); } else if (user.missing === '') { ret.push( popupString( 'Not a registered username') ); } if( user.blockedby ) { if( user.blockpartial ) { ret.push('<b>' + popupString('Has blocks') + '</b>'); } else { ret.push('<b>' + popupString('BLOCKED') + '</b>'); } } if( globaluserinfo && ( 'locked' in globaluserinfo || 'hidden' in globaluserinfo ) ) { var lockedSulAccountIsAttachedToThis = true; for( var i=0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) { if ( globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname') ) { lockedSulAccountIsAttachedToThis=false; break; } } if (lockedSulAccountIsAttachedToThis) { if ( 'locked' in globaluserinfo ) ret.push('<b><i>' + popupString('LOCKED') + '</i></b>'); if ( 'hidden' in globaluserinfo ) ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>'); } } if( getValueOf('popupShowGender') && user.gender ) { switch( user.gender ) { case "male": ret.push( popupString( "\u2642" ) ); break; case "female": ret.push( popupString( "\u2640" ) ); break; } } if( user.groups ) { for( var j=0; j < user.groups.length; j++) { var currentGroup = user.groups[j]; if( ["*", "user", "autoconfirmed", "extendedconfirmed"].indexOf( currentGroup ) === -1 ) { ret.push( pg.escapeQuotesHTML(user.groups[j]) ); } } } if( globaluserinfo && globaluserinfo.groups ) { for( var k=0; k < globaluserinfo.groups.length; k++) { ret.push( '<i>'+pg.escapeQuotesHTML(globaluserinfo.groups[k])+'</i>' ); } } if( user.registration ) ret.push( pg.escapeQuotesHTML((user.editcount?user.editcount:'0') + popupString(' edits since: ') + (user.registration?dayFormat(getDateFromTimestamp(user.registration)):'')) ); } if (queryobj.usercontribs && queryobj.usercontribs.length) { ret.push( popupString('last edit on ') + dayFormat(getDateFromTimestamp(queryobj.usercontribs[0].timestamp)) ); } if (queryobj.blocks) { ret.push( popupString( 'IP user') ); //we only request list=blocks for IPs for (var l=0; l<queryobj.blocks.length; l++) { console.log(queryobj); var rbstr = queryobj.blocks[l].rangestart === queryobj.blocks[l].rangeend ? 'BLOCK' : 'RANGEBLOCK'; rbstr = (!Array.isArray(queryobj.blocks[l].restrictions) ? 'Has ' + rbstr.toLowerCase() + 's' : rbstr + 'ED'); ret.push('<b>' + popupString(rbstr) + '</b>' ); } } ret = '<hr />' + ret.join( ', ' ); return ret; } function APIcontribsPreviewHTML(article, download, navpop) { return APIhistoryPreviewHTML(article, download, navpop, true); } function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) { try { var jsobj=getJsObj(download.data); var edits = []; if( reallyContribs ) { edits=jsobj.query.usercontribs; } else { edits=anyChild(jsobj.query.pages).revisions; } var ret=editPreviewTable(article, edits, reallyContribs, getTimeOffset()); return ret; } catch (someError) { return 'History preview failed :-('; } } //</NOLITE> // ENDFILE: querypreview.js // STARTFILE: debug.js //////////////////////////////////////////////////////////////////// // Debugging functions //////////////////////////////////////////////////////////////////// function setupDebugging() { //<NOLITE> if (window.popupDebug) { // popupDebug is set from .version window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; }; window.console.log(x); }; window.errlog=function(x) { window.console.error(x); }; log('Initializing logger'); } else { //</NOLITE> window.log = function() {}; window.errlog = function() {}; //<NOLITE> } //</NOLITE> } // ENDFILE: debug.js // STARTFILE: images.js // load image of type Title. function loadImage(image, navpop) { if (typeof image.stripNamespace != 'function') { alert('loadImages bad'); } // API call to retrieve image info. if ( !getValueOf('popupImages') ) return; if ( !isValidImageName(image) ) return false; var art=image.urlString(); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query'; url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge'); url += '&titles=' + art; pendingNavpopTask(navpop); var callback=function(d){ popupsInsertImage(navpop.idNumber, navpop, d); }; var go = function(){ getPageWithCaching(url, callback, navpop); return true; }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA'); } } function popupsInsertImage(id, navpop, download) { log( "popupsInsertImage"); var imageinfo; try { var jsObj=getJsObj(download.data); var imagepage=anyChild(jsObj.query.pages); if (typeof imagepage.imageinfo === 'undefined') return; imageinfo = imagepage.imageinfo[0]; } catch (someError) { log( "popupsInsertImage failed :(" ); return; } var popupImage = document.getElementById("popupImg"+id); if (!popupImage) { log( "could not find insertion point for image"); return; } popupImage.width=getValueOf('popupImageSize'); popupImage.style.display='inline'; // Set the source for the image. if( imageinfo.thumburl ) popupImage.src=imageinfo.thumburl; else if( imageinfo.mime.indexOf("image") === 0 ){ popupImage.src=imageinfo.url; log( "a thumb could not be found, using original image" ); } else log( "fullsize imagethumb, but not sure if it's an image"); var a=document.getElementById("popupImageLink"+id); if (a === null) { return null; } // Determine the action of the surrouding imagelink. switch (getValueOf('popupThumbAction')) { case 'imagepage': if (pg.current.article.namespaceId()!=pg.nsImageId) { a.href=imageinfo.descriptionurl; // FIXME: unreliable pg.idNumber popTipsSoonFn('popupImage' + id)(); break; } /* falls through */ case 'sizetoggle': a.onclick=toggleSize; a.title=popupString('Toggle image size'); return; case 'linkfull': a.href = imageinfo.url; a.title=popupString('Open full-size image'); return; } } // Toggles the image between inline small and navpop fullwidth. // It's the same image, no actual sizechange occurs, only display width. function toggleSize() { var imgContainer=this; if (!imgContainer) { alert('imgContainer is null :/'); return; } var img=imgContainer.firstChild; if (!img) { alert('img is null :/'); return; } if (!img.style.width || img.style.width==='') { img.style.width='100%'; } else { img.style.width=''; } } // Returns one title of an image from wikiText. function getValidImageFromWikiText(wikiText) { // nb in pg.re.image we're interested in the second bracketed expression // this may change if the regex changes :-( //var match=pg.re.image.exec(wikiText); var matched=null; var match; // strip html comments, used by evil bots :-( var t = removeMatchesUnless(wikiText, RegExp('(<!--[\\s\\S]*?-->)'), 1, RegExp('^<!--[^[]*popup', 'i')); while ( ( match = pg.re.image.exec(t) ) ) { // now find a sane image name - exclude templates by seeking { var m = match[2] || match[6]; if ( isValidImageName(m) ) { matched=m; break; } } pg.re.image.lastIndex=0; if (!matched) { return null; } return mw.config.get('wgFormattedNamespaces')[pg.nsImageId]+':'+upcaseFirst(matched); } function removeMatchesUnless(str, re1, parencount, re2) { var split=str.parenSplit(re1); var c=parencount + 1; for (var i=0; i<split.length; ++i) { if ( i%c === 0 || re2.test(split[i]) ) { continue; } split[i]=''; } return split.join(''); } //</NOLITE> // ENDFILE: images.js // STARTFILE: namespaces.js // Set up namespaces and other non-strings.js localization // (currently that means redirs too) function setNamespaces() { pg.nsSpecialId = -1; pg.nsMainspaceId = 0; pg.nsImageId = 6; pg.nsUserId = 2; pg.nsUsertalkId = 3; pg.nsCategoryId = 14; pg.nsTemplateId = 10; } function setRedirs() { var r='redirect'; var R='REDIRECT'; var redirLists={ //<NOLITE> 'ar': [ R, 'تحويل' ], 'be': [ r, 'перанакіраваньне' ], 'bg': [ r, 'пренасочване', 'виж' ], 'bs': [ r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI' ], 'cs': [ R, 'PŘESMĚRUJ' ], 'cy': [ r, 'ail-cyfeirio' ], 'de': [ R, 'WEITERLEITUNG' ], 'el': [ R, 'ΑΝΑΚΑΤΕΥΘΥΝΣΗ'], 'eo': [ R, 'ALIDIREKTU', 'ALIDIREKTI' ], 'es': [ R, 'REDIRECCIÓN' ], 'et': [ r, 'suuna' ], 'ga': [ r, 'athsheoladh' ], 'gl': [ r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'], 'he': [ R, 'הפניה' ], 'hu': [ R, 'ÁTIRÁNYÍTÁS' ], 'is': [ r, 'tilvísun', 'TILVÍSUN' ], 'it': [ R, 'RINVIA', 'Rinvia'], 'ja': [ R, '転送' ], 'mk': [ r, 'пренасочување', 'види' ], 'nds': [ r, 'wiederleiden' ], 'nl': [ R, 'DOORVERWIJZING' ], 'nn': [ r, 'omdiriger' ], 'pl': [ R, 'PATRZ', 'PRZEKIERUJ', 'TAM' ], 'pt': [ R, 'redir' ], 'ru': [ R, 'ПЕРЕНАПРАВЛЕНИЕ', 'ПЕРЕНАПР' ], 'sk': [ r, 'presmeruj' ], 'sr': [ r, 'Преусмери', 'преусмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI' ], 'tt': [ R, 'yünältü', 'перенаправление', 'перенапр' ], 'uk': [ R, 'ПЕРЕНАПРАВЛЕННЯ', 'ПЕРЕНАПР' ], 'vi': [ r, 'đổi' ], 'zh': [ R, '重定向'] // no comma //</NOLITE> }; var redirList=redirLists[ pg.wiki.lang ] || [r, R]; // Mediawiki is very tolerant about what comes after the #redirect at the start pg.re.redirect=RegExp('^\\s*[#](' + redirList.join('|') + ').*?\\[{2}([^\\|\\]]*)(|[^\\]]*)?\\]{2}\\s*(.*)', 'i'); } function setInterwiki() { if (pg.wiki.wikimedia) { // From https://meta.wikimedia.org/wiki/List_of_Wikipedias pg.wiki.interwiki='aa|ab|ace|af|ak|als|am|an|ang|ar|arc|arz|as|ast|av|ay|az|ba|bar|bat-smg|bcl|be|be-x-old|bg|bh|bi|bjn|bm|bn|bo|bpy|br|bs|bug|bxr|ca|cbk-zam|cdo|ce|ceb|ch|cho|chr|chy|ckb|co|cr|crh|cs|csb|cu|cv|cy|da|de|diq|dsb|dv|dz|ee|el|eml|en|eo|es|et|eu|ext|fa|ff|fi|fiu-vro|fj|fo|fr|frp|frr|fur|fy|ga|gag|gan|gd|gl|glk|gn|got|gu|gv|ha|hak|haw|he|hi|hif|ho|hr|hsb|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|ilo|io|is|it|iu|ja|jbo|jv|ka|kaa|kab|kbd|kg|ki|kj|kk|kl|km|kn|ko|koi|kr|krc|ks|ksh|ku|kv|kw|ky|la|lad|lb|lbe|lg|li|lij|lmo|ln|lo|lt|ltg|lv|map-bms|mdf|mg|mh|mhr|mi|mk|ml|mn|mo|mr|mrj|ms|mt|mus|mwl|my|myv|mzn|na|nah|nap|nds|nds-nl|ne|new|ng|nl|nn|no|nov|nrm|nv|ny|oc|om|or|os|pa|pag|pam|pap|pcd|pdc|pfl|pi|pih|pl|pms|pnb|pnt|ps|pt|qu|rm|rmy|rn|ro|roa-rup|roa-tara|ru|rue|rw|sa|sah|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|sn|so|sq|sr|srn|ss|st|stq|su|sv|sw|szl|ta|te|tet|tg|th|ti|tk|tl|tn|to|tpi|tr|ts|tt|tum|tw|ty|udm|ug|uk|ur|uz|ve|vec|vi|vls|vo|wa|war|wo|wuu|xal|xh|yi|yo|za|zea|zh|zh-classical|zh-min-nan|zh-yue|zu'; pg.re.interwiki=RegExp('^'+pg.wiki.interwiki+':'); } else { pg.wiki.interwiki=null; pg.re.interwiki=RegExp('^$'); } } // return a regexp pattern matching all variants to write the given namespace function nsRe(namespaceId) { var imageNamespaceVariants = []; jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) { if (_namespaceId!=namespaceId) return; _localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc); imageNamespaceVariants.push(mw.util.escapeRegExp(_localizedNamespaceLc).split(' ').join('[ _]')); imageNamespaceVariants.push(mw.util.escapeRegExp(encodeURI(_localizedNamespaceLc))); }); return '(?:' + imageNamespaceVariants.join('|') + ')'; } function nsReImage() { return nsRe(pg.nsImageId); } // ENDFILE: namespaces.js // STARTFILE: selpop.js //<NOLITE> function getEditboxSelection() { // see http://www.webgurusforum.com/8/12/0 var editbox; try { editbox=document.editform.wpTextbox1; } catch (dang) { return; } // IE, Opera if (document.selection) { return document.selection.createRange().text; } // Mozilla var selStart = editbox.selectionStart; var selEnd = editbox.selectionEnd; return (editbox.value).substring(selStart, selEnd); } function doSelectionPopup() { // popup if the selection looks like [[foo|anything afterwards at all // or [[foo|bar]]text without ']]' // or [[foo|bar]] var sel=getEditboxSelection(); var open=sel.indexOf('[['); var pipe=sel.indexOf('|'); var close=sel.indexOf(']]'); if (open == -1 || ( pipe == -1 && close == -1) ) { return; } if (pipe != -1 && open > pipe || close != -1 && open > close) { return; } if (getValueOf('popupOnEditSelection')=='boxpreview') { return doSeparateSelectionPopup(sel); } var article=new Title(sel.substring(open+2, (pipe < 0) ? close : pipe)).urlString(); if (close > 0 && sel.substring(close+2).indexOf('[[') >= 0) { return; } var a=document.createElement('a'); a.href=pg.wiki.titlebase + article; mouseOverWikiLink2(a); if (a.navpopup) { a.navpopup.addHook(function(){runStopPopupTimer(a.navpopup);}, 'unhide', 'after'); } } function doSeparateSelectionPopup(str) { var div=document.getElementById('selectionPreview'); if (!div) { div = document.createElement('div'); div.id='selectionPreview'; try { var box=document.editform.wpTextbox1; box.parentNode.insertBefore(div, box); } catch (error) { return; } } div.innerHTML=wiki2html(str); div.ranSetupTooltipsAlready = false; popTipsSoonFn('selectionPreview')(); } //</NOLITE> // ENDFILE: selpop.js // STARTFILE: navpopup.js /** @fileoverview Defines two classes: {@link Navpopup} and {@link Mousetracker}. <code>Navpopup</code> describes popups: when they appear, where, what they look like and so on. <code>Mousetracker</code> "captures" the mouse using <code>document.onmousemove</code>. */ /** Creates a new Mousetracker. @constructor @class The Mousetracker class. This monitors mouse movements and manages associated hooks. */ function Mousetracker() { /** Interval to regularly run the hooks anyway, in milliseconds. @type Integer */ this.loopDelay=400; /** Timer for the loop. @type Timer */ this.timer=null; /** Flag - are we switched on? @type Boolean */ this.active=false; /** Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position? */ this.dirty=true; /** Array of hook functions. @private @type Array */ this.hooks=[]; } /** Adds a hook, to be called when we get events. @param {Function} f A function which is called as <code>f(x,y)</code>. It should return <code>true</code> when it wants to be removed, and <code>false</code> otherwise. */ Mousetracker.prototype.addHook = function (f) { this.hooks.push(f); }; /** Runs hooks, passing them the x and y coords of the mouse. Hook functions that return true are passed to {@link Mousetracker#removeHooks} for removal. @private */ Mousetracker.prototype.runHooks = function () { if (!this.hooks || !this.hooks.length) { return; } //log('Mousetracker.runHooks; we got some hooks to run'); var remove=false; var removeObj={}; // this method gets called a LOT - // pre-cache some variables var x=this.x, y=this.y, len = this.hooks.length; for (var i=0; i<len; ++i) { //~ run the hook function, and remove it if it returns true if (this.hooks[i](x, y)===true) { remove=true; removeObj[i]=true; } } if (remove) { this.removeHooks(removeObj); } }; /** Removes hooks. @private @param {Object} removeObj An object whose keys are the index numbers of functions for removal, with values that evaluate to true */ Mousetracker.prototype.removeHooks = function(removeObj) { var newHooks=[]; var len = this.hooks.length; for (var i=0; i<len; ++i) { if (! removeObj[i]) { newHooks.push(this.hooks[i]); } } this.hooks=newHooks; }; /** Event handler for mouse wiggles. We simply grab the event, set x and y and run the hooks. This makes the cpu all hot and bothered :-( @private @param {Event} e Mousemove event */ Mousetracker.prototype.track=function (e) { //~ Apparently this is needed in IE. e = e || window.event; var x, y; if (e) { if (e.pageX) { x=e.pageX; y=e.pageY; } else if (typeof e.clientX!='undefined') { var left, top, docElt = document.documentElement; if (docElt) { left=docElt.scrollLeft; } left = left || document.body.scrollLeft || document.scrollLeft || 0; if (docElt) { top=docElt.scrollTop; } top = top || document.body.scrollTop || document.scrollTop || 0; x=e.clientX + left; y=e.clientY + top; } else { return; } this.setPosition(x,y); } }; /** Sets the x and y coordinates stored and takes appropriate action, running hooks as appropriate. @param {Integer} x, y Screen coordinates to set */ Mousetracker.prototype.setPosition=function(x,y) { this.x = x; this.y = y; if (this.dirty || this.hooks.length === 0) { this.dirty=false; return; } if (typeof this.lastHook_x != 'number') { this.lastHook_x = -100; this.lastHook_y=-100; } var diff = (this.lastHook_x - x)*(this.lastHook_y - y); diff = (diff >= 0) ? diff : -diff; if ( diff > 1 ) { this.lastHook_x=x; this.lastHook_y=y; if (this.dirty) { this.dirty = false; } else { this.runHooks(); } } }; /** Sets things in motion, unless they are already that is, registering an event handler on <code>document.onmousemove</code>. A half-hearted attempt is made to preserve the old event handler if there is one. */ Mousetracker.prototype.enable = function () { if (this.active) { return; } this.active=true; //~ Save the current handler for mousemove events. This isn't too //~ robust, of course. this.savedHandler=document.onmousemove; //~ Gotta save @tt{this} again for the closure, and use apply for //~ the member function. var savedThis=this; document.onmousemove=function (e) {savedThis.track.apply(savedThis, [e]);}; if (this.loopDelay) { this.timer = setInterval(function() { //log('loop delay in mousetracker is working'); savedThis.runHooks();}, this.loopDelay); } }; /** Disables the tracker, removing the event handler. */ Mousetracker.prototype.disable = function () { if (!this.active) { return; } if (typeof this.savedHandler === 'function') { document.onmousemove=this.savedHandler; } else { delete document.onmousemove; } if (this.timer) { clearInterval(this.timer); } this.active=false; }; /** Creates a new Navpopup. Gets a UID for the popup and @param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable. @constructor @class The Navpopup class. This generates popup hints, and does some management of them. */ function Navpopup(/*init*/) { //alert('new Navpopup(init)'); /** UID for each Navpopup instance. Read-only. @type integer */ this.uid=Navpopup.uid++; /** Read-only flag for current visibility of the popup. @type boolean @private */ this.visible=false; /** Flag to be set when we want to cancel a previous request to show the popup in a little while. @private @type boolean */ this.noshow=false; /** Categorised list of hooks. @see #runHooks @see #addHook @private @type Object */ this.hooks={ 'create': [], 'unhide': [], 'hide': [] }; /** list of unique IDs of hook functions, to avoid duplicates @private */ this.hookIds={}; /** List of downloads associated with the popup. @private @type Array */ this.downloads=[]; /** Number of uncompleted downloads. @type integer */ this.pending=null; /** Tolerance in pixels when detecting whether the mouse has left the popup. @type integer */ this.fuzz=5; /** Flag to toggle running {@link #limitHorizontalPosition} to regulate the popup's position. @type boolean */ this.constrained=true; /** The popup width in pixels. @private @type integer */ this.width=0; /** The popup width in pixels. @private @type integer */ this.height=0; /** The main content DIV element. @type HTMLDivElement */ this.mainDiv=null; this.createMainDiv(); // if (!init || typeof init.popups_draggable=='undefined' || init.popups_draggable) { // this.makeDraggable(true); // } } /** A UID for each Navpopup. This constructor property is just a counter. @type integer @private */ Navpopup.uid=0; /** Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible. @type boolean */ Navpopup.prototype.isVisible=function() { return this.visible; }; /** Repositions popup using CSS style. @private @param {integer} x x-coordinate (px) @param {integer} y y-coordinate (px) @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition} */ Navpopup.prototype.reposition= function (x,y, noLimitHor) { log ('reposition('+x+','+y+','+noLimitHor+')'); if (typeof x != 'undefined' && x !== null) { this.left=x; } if (typeof y != 'undefined' && y !== null) { this.top=y; } if (typeof this.left != 'undefined' && typeof this.top != 'undefined') { this.mainDiv.style.left=this.left + 'px'; this.mainDiv.style.top=this.top + 'px'; } if (!noLimitHor) { this.limitHorizontalPosition(); } //console.log('navpop'+this.uid+' - (left,top)=(' + this.left + ',' + this.top + '), css=(' //+ this.mainDiv.style.left + ',' + this.mainDiv.style.top + ')'); }; /** Prevents popups from being in silly locations. Hopefully. Should not be run if {@link #constrained} is true. @private */ Navpopup.prototype.limitHorizontalPosition=function() { if (!this.constrained || this.tooWide) { return; } this.updateDimensions(); var x=this.left; var w=this.width; var cWidth=document.body.clientWidth; // log('limitHorizontalPosition: x='+x+ // ', this.left=' + this.left + // ', this.width=' + this.width + // ', cWidth=' + cWidth); if ( (x+w) >= cWidth || ( x > 0 && this.maxWidth && this.width < this.maxWidth && this.height > this.width && x > cWidth - this.maxWidth ) ) { // This is a very nasty hack. There has to be a better way! // We find the "natural" width of the div by positioning it at the far left // then reset it so that it should be flush right (well, nearly) this.mainDiv.style.left='-10000px'; this.mainDiv.style.width = this.maxWidth + 'px'; var naturalWidth=parseInt(this.mainDiv.offsetWidth, 10); var newLeft=cWidth - naturalWidth - 1; if (newLeft < 0) { newLeft = 0; this.tooWide=true; } // still unstable for really wide popups? log ('limitHorizontalPosition: moving to ('+newLeft + ','+ this.top+');' + ' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth); this.reposition(newLeft, null, true); } }; /** Counter indicating the z-order of the "highest" popup. We start the z-index at 1000 so that popups are above everything else on the screen. @private @type integer */ Navpopup.highest=1000; /** Brings popup to the top of the z-order. We increment the {@link #highest} property of the contructor here. @private */ Navpopup.prototype.raise = function () { this.mainDiv.style.zIndex=Navpopup.highest + 1; ++Navpopup.highest; }; /** Shows the popup provided {@link #noshow} is not true. Updates the position, brings the popup to the top of the z-order and unhides it. */ Navpopup.prototype.show = function () { //document.title+='s'; if (this.noshow) { return; } //document.title+='t'; this.reposition(); this.raise(); this.unhide(); }; /** Checks to see if the mouse pointer has stabilised (checking every <code>time</code>/2 milliseconds) and runs the {@link #show} method if it has. @param {integer} time The minimum time (ms) before the popup may be shown. */ Navpopup.prototype.showSoonIfStable = function (time) { log ('showSoonIfStable, time='+time); if (this.visible) { return; } this.noshow = false; //~ initialize these variables so that we never run @tt{show} after //~ just half the time this.stable_x = -10000; this.stable_y = -10000; var stableShow = function() { log('stableShow called'); var new_x = Navpopup.tracker.x, new_y = Navpopup.tracker.y; var dx = savedThis.stable_x - new_x, dy = savedThis.stable_y - new_y; var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz; //document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] '; if ( dx * dx <= fuzz2 && dy * dy <= fuzz2 ) { log ('mouse is stable'); clearInterval(savedThis.showSoonStableTimer); savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]); savedThis.show.apply(savedThis, []); savedThis.limitHorizontalPosition.apply(savedThis, []); return; } savedThis.stable_x = new_x; savedThis.stable_y = new_y; }; var savedThis = this; this.showSoonStableTimer = setInterval(stableShow, time/2); }; /** Sets the {@link #noshow} flag and hides the popup. This should be called when the mouse leaves the link before (or after) it's actually been displayed. */ Navpopup.prototype.banish = function () { log ('banish called'); // hide and prevent showing with showSoon in the future this.noshow=true; if (this.showSoonStableTimer) { log('clearing showSoonStableTimer'); clearInterval(this.showSoonStableTimer); } this.hide(); }; /** Runs hooks added with {@link #addHook}. @private @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide' @param {String} when Controls exactly when the hook is run: either 'before' or 'after' */ Navpopup.prototype.runHooks = function (key, when) { if (!this.hooks[key]) { return; } var keyHooks=this.hooks[key]; var len=keyHooks.length; for (var i=0; i< len; ++i) { if (keyHooks[i] && keyHooks[i].when == when) { if (keyHooks[i].hook.apply(this, [])) { // remove the hook if (keyHooks[i].hookId) { delete this.hookIds[keyHooks[i].hookId]; } keyHooks[i]=null; } } } }; /** Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the Navpopup instance, and no arguments. @param {Function} hook The hook function. Functions that return true are deleted. @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide' @param {String} when Controls exactly when the hook is run: either 'before' or 'after' @param {String} uid A truthy string identifying the hook function; if it matches another hook in this position, it won't be added again. */ Navpopup.prototype.addHook = function ( hook, key, when, uid ) { when = when || 'after'; if (!this.hooks[key]) { return; } // if uid is specified, don't add duplicates var hookId=null; if (uid) { hookId=[key,when,uid].join('|'); if (this.hookIds[hookId]) { return; } this.hookIds[hookId]=true; } this.hooks[key].push( {hook: hook, when: when, hookId: hookId} ); }; /** Creates the main DIV element, which contains all the actual popup content. Runs hooks with key 'create'. @private */ Navpopup.prototype.createMainDiv = function () { if (this.mainDiv) { return; } this.runHooks('create', 'before'); var mainDiv=document.createElement('div'); var savedThis=this; mainDiv.onclick=function(e) {savedThis.onclickHandler(e);}; mainDiv.className=(this.className) ? this.className : 'navpopup_maindiv'; mainDiv.id=mainDiv.className + this.uid; mainDiv.style.position='absolute'; mainDiv.style.minWidth = '350px'; mainDiv.style.display='none'; mainDiv.className='navpopup'; // easy access to javascript object through DOM functions mainDiv.navpopup=this; this.mainDiv=mainDiv; document.body.appendChild(mainDiv); this.runHooks('create', 'after'); }; /** Calls the {@link #raise} method. @private */ Navpopup.prototype.onclickHandler=function(/*e*/) { this.raise(); }; /** Makes the popup draggable, using a {@link Drag} object. @private */ Navpopup.prototype.makeDraggable=function(handleName) { if (!this.mainDiv) { this.createMainDiv(); } var drag=new Drag(); if (!handleName) { drag.startCondition=function(e) { try { if (!e.shiftKey) { return false; } } catch (err) { return false; } return true; }; } var dragHandle; if (handleName) dragHandle = document.getElementById(handleName); if (!dragHandle) dragHandle = this.mainDiv; var np=this; drag.endHook=function(x,y) { Navpopup.tracker.dirty=true; np.reposition(x,y); }; drag.init(dragHandle,this.mainDiv); }; /** Hides the popup using CSS. Runs hooks with key 'hide'. Sets {@link #visible} appropriately. {@link #banish} should be called externally instead of this method. @private */ Navpopup.prototype.hide = function () { this.runHooks('hide', 'before'); this.abortDownloads(); if (typeof this.visible != 'undefined' && this.visible) { this.mainDiv.style.display='none'; this.visible=false; } this.runHooks('hide', 'after'); }; /** Shows the popup using CSS. Runs hooks with key 'unhide'. Sets {@link #visible} appropriately. {@link #show} should be called externally instead of this method. @private */ Navpopup.prototype.unhide = function () { this.runHooks('unhide', 'before'); if (typeof this.visible != 'undefined' && !this.visible) { this.mainDiv.style.display='inline'; this.visible=true; } this.runHooks('unhide', 'after'); }; /** Sets the <code>innerHTML</code> attribute of the main div containing the popup content. @param {String} html The HTML to set. */ Navpopup.prototype.setInnerHTML = function (html) { this.mainDiv.innerHTML = html; }; /** Updates the {@link #width} and {@link #height} attributes with the CSS properties. @private */ Navpopup.prototype.updateDimensions = function () { this.width=parseInt(this.mainDiv.offsetWidth, 10); this.height=parseInt(this.mainDiv.offsetHeight, 10); }; /** Checks if the point (x,y) is within {@link #fuzz} of the {@link #mainDiv}. @param {integer} x x-coordinate (px) @param {integer} y y-coordinate (px) @type boolean */ Navpopup.prototype.isWithin = function(x,y) { //~ If we're not even visible, no point should be considered as //~ being within the popup. if (!this.visible) { return false; } this.updateDimensions(); var fuzz=this.fuzz || 0; //~ Use a simple box metric here. return (x+fuzz >= this.left && x-fuzz <= this.left + this.width && y+fuzz >= this.top && y-fuzz <= this.top + this.height); }; /** Adds a download to {@link #downloads}. @param {Downloader} download */ Navpopup.prototype.addDownload=function(download) { if (!download) { return; } this.downloads.push(download); }; /** Aborts the downloads listed in {@link #downloads}. @see Downloader#abort */ Navpopup.prototype.abortDownloads=function() { for(var i=0; i<this.downloads.length; ++i) { var d=this.downloads[i]; if (d && d.abort) { d.abort(); } } this.downloads=[]; }; /** A {@link Mousetracker} instance which is a property of the constructor (pseudo-global). */ Navpopup.tracker=new Mousetracker(); // ENDFILE: navpopup.js // STARTFILE: diff.js //<NOLITE> /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) and [[:en:User:Lupin]] * * More Info: * http://ejohn.org/projects/javascript-diff-algorithm/ */ function delFmt(x) { if (!x.length) { return ''; } return "<del class='popupDiff'>" + x.join('') +"</del>"; } function insFmt(x) { if (!x.length) { return ''; } return "<ins class='popupDiff'>" + x.join('') +"</ins>"; } function countCrossings(a, b, i, eject) { // count the crossings on the edge starting at b[i] if (!b[i].row && b[i].row !== 0) { return -1; } var count=0; for (var j=0; j<a.length; ++j) { if (!a[j].row && a[j].row !== 0) { continue; } if ( (j-b[i].row)*(i-a[j].row) > 0) { if(eject) { return true; } count++; } } return count; } function shortenDiffString(str, context) { var re=RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)'); var splitted=str.parenSplit(re); var ret=['']; for (var i=0; i<splitted.length; i+=2) { if (splitted[i].length < 2*context) { ret[ret.length-1] += splitted[i]; if (i+1<splitted.length) { ret[ret.length-1] += splitted[i+1]; } continue; } else { if (i > 0) { ret[ret.length-1] += splitted[i].substring(0,context); } if (i+1 < splitted.length) { ret.push(splitted[i].substring(splitted[i].length-context) + splitted[i+1]); } } } while (ret.length > 0 && !ret[0]) { ret = ret.slice(1); } return ret; } function diffString( o, n, simpleSplit ) { var splitRe=RegExp('([[]{2}|[\\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\\s|\\b)'); // We need to split the strings o and n first, and entify() the parts // individually, so that the HTML entities are never cut apart. (AxelBoldt) var out, i, oSplitted, nSplitted; if (simpleSplit) { oSplitted=o.split(/\b/); nSplitted=n.split(/\b/); } else { oSplitted=o.parenSplit(splitRe); nSplitted=n.parenSplit(splitRe); } for (i=0; i<oSplitted.length; ++i) {oSplitted[i]=oSplitted[i].entify();} for (i=0; i<nSplitted.length; ++i) {nSplitted[i]=nSplitted[i].entify();} out = diff (oSplitted, nSplitted); var str = ""; var acc=[]; // accumulator for prettier output // crossing pairings -- eg 'A B' vs 'B A' -- cause problems, so let's iron them out // this doesn't always do things optimally but it should be fast enough var maxOutputPair=0; for (i=0; i<out.n.length; ++i) { if ( out.n[i].paired ) { if( maxOutputPair > out.n[i].row ) { // tangle - delete pairing out.o[ out.n[i].row ]=out.o[ out.n[i].row ].text; out.n[i]=out.n[i].text; } if (maxOutputPair < out.n[i].row) { maxOutputPair = out.n[i].row; } } } // output the stuff preceding the first paired old line for (i=0; i<out.o.length && !out.o[i].paired; ++i) { acc.push( out.o[i] ); } str += delFmt(acc); acc=[]; // main loop for ( i = 0; i < out.n.length; ++i ) { // output unpaired new "lines" while ( i < out.n.length && !out.n[i].paired ) { acc.push( out.n[i++] ); } str += insFmt(acc); acc=[]; if ( i < out.n.length ) { // this new "line" is paired with the (out.n[i].row)th old "line" str += out.n[i].text; // output unpaired old rows starting after this new line's partner var m = out.n[i].row + 1; while ( m < out.o.length && !out.o[m].paired ) { acc.push ( out.o[m++] ); } str += delFmt(acc); acc=[]; } } return str; } // see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object // FIXME: use obj.hasOwnProperty instead of this kludge! var jsReservedProperties=RegExp('^(constructor|prototype|__((define|lookup)[GS]etter)__' + '|eval|hasOwnProperty|propertyIsEnumerable' + '|to(Source|String|LocaleString)|(un)?watch|valueOf)$'); function diffBugAlert(word) { if (!diffBugAlert.list[word]) { diffBugAlert.list[word]=1; alert('Bad word: '+word+'\n\nPlease report this bug.'); } } diffBugAlert.list={}; function makeDiffHashtable(src) { var ret={}; for ( var i = 0; i < src.length; i++ ) { if ( jsReservedProperties.test(src[i]) ) { src[i] += '<!-- -->'; } if ( !ret[ src[i] ] ) { ret[ src[i] ] = []; } try { ret[ src[i] ].push( i ); } catch (err) { diffBugAlert(src[i]); } } return ret; } function diff( o, n ) { // pass 1: make hashtable ns with new rows as keys var ns = makeDiffHashtable(n); // pass 2: make hashtable os with old rows as keys var os = makeDiffHashtable(o); // pass 3: pair unique new rows and matching unique old rows var i; for ( i in ns ) { if ( ns[i].length == 1 && os[i] && os[i].length == 1 ) { n[ ns[i][0] ] = { text: n[ ns[i][0] ], row: os[i][0], paired: true }; o[ os[i][0] ] = { text: o[ os[i][0] ], row: ns[i][0], paired: true }; } } // pass 4: pair matching rows immediately following paired rows (not necessarily unique) for ( i = 0; i < n.length - 1; i++ ) { if ( n[i].paired && ! n[i+1].paired && n[i].row + 1 < o.length && ! o[ n[i].row + 1 ].paired && n[i+1] == o[ n[i].row + 1 ] ) { n[i+1] = { text: n[i+1], row: n[i].row + 1, paired: true }; o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1, paired: true }; } } // pass 5: pair matching rows immediately preceding paired rows (not necessarily unique) for ( i = n.length - 1; i > 0; i-- ) { if ( n[i].paired && ! n[i-1].paired && n[i].row > 0 && ! o[ n[i].row - 1 ].paired && n[i-1] == o[ n[i].row - 1 ] ) { n[i-1] = { text: n[i-1], row: n[i].row - 1, paired: true }; o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1, paired: true }; } } return { o: o, n: n }; } //</NOLITE> // ENDFILE: diff.js // STARTFILE: init.js function setSiteInfo() { if (window.popupLocalDebug) { pg.wiki.hostname = 'en.wikipedia.org'; } else { pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?) } pg.wiki.wikimedia=RegExp('(wiki([pm]edia|source|books|news|quote|versity)|wiktionary|mediawiki)[.]org').test(pg.wiki.hostname); pg.wiki.wikia=RegExp('[.]wikia[.]com$', 'i').test(pg.wiki.hostname); pg.wiki.isLocal=RegExp('^localhost').test(pg.wiki.hostname); pg.wiki.commons=( pg.wiki.wikimedia && pg.wiki.hostname != 'commons.wikimedia.org') ? 'commons.wikimedia.org' : null; pg.wiki.lang = mw.config.get('wgContentLanguage'); var port = location.port ? ':' + location.port : ''; pg.wiki.sitebase = pg.wiki.hostname + port; } function setUserInfo() { var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'query', list: 'users', ususers: mw.config.get('wgUserName'), usprop: 'rights' }; pg.user.canReview = false; if (getValueOf('popupReview')) { api.get(params).done(function(data){ var rights = data.query.users[0].rights; pg.user.canReview = rights.indexOf('review') !== -1; // TODO: Should it be a getValueOf('ReviewRight') ? }); } } function setTitleBase() { var protocol = ( window.popupLocalDebug ? 'http:' : location.protocol ); pg.wiki.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, ""); // as in http://some.thing.com/wiki/Article pg.wiki.botInterfacePath = mw.config.get('wgScript'); pg.wiki.APIPath = mw.config.get('wgScriptPath') +"/api.php"; // default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo var titletail = pg.wiki.botInterfacePath + '?title='; //var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?title=']); // other sites may need to add code here to set titletail depending on how their urls work pg.wiki.titlebase = protocol + '//' + pg.wiki.sitebase + titletail; //pg.wiki.titlebase2 = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]); pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath; pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath; pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath; pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.botInterfacePath; pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.APIPath; pg.re.basenames = RegExp( '^(' + map( literalizeRegex, [ pg.wiki.titlebase, //pg.wiki.titlebase2, pg.wiki.articlebase ]).join('|') + ')' ); } ////////////////////////////////////////////////// // Global regexps function setMainRegex() { var reStart='[^:]*://'; var preTitles = literalizeRegex( mw.config.get('wgScriptPath') ) + '/(?:index[.]php|wiki[.]phtml)[?]title='; preTitles += '|' + literalizeRegex( pg.wiki.articlePath + '/' ); var reEnd='(' + preTitles + ')([^&?#]*)[^#]*(?:#(.+))?'; pg.re.main = RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd); } function setRegexps() { // TODO: We shoud use an api call to get the aliases for special pages, now it does not work for non-English wikipedias: // E.g., https://ru.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=specialpagealiases&formatversion=2 setMainRegex(); var sp=nsRe(pg.nsSpecialId); pg.re.urlNoPopup=RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)') ; pg.re.contribs =RegExp('(title=|/)' + sp + '(?:%3A|:)Contributions' + '(&target=|/|/' + nsRe(pg.nsUserId)+':)(.*)') ; pg.re.email =RegExp('(title=|/)' + sp + '(?:%3A|:)EmailUser' + '(&target=|/|/(?:' + nsRe(pg.nsUserId)+':)?)(.*)') ; pg.re.backlinks =RegExp('(title=|/)' + sp + '(?:%3A|:)WhatLinksHere' + '(&target=|/)([^&]*)'); pg.re.specialdiff=RegExp('/' + sp + '(?:%3A|:)Diff/([^?#]*)'); //<NOLITE> var im=nsReImage(); // note: tries to get images in infobox templates too, e.g. movie pages, album pages etc // (^|\[\[)image: *([^|\]]*[^|\] ]) * // (^|\[\[)image: *([^|\]]*[^|\] ])([^0-9\]]*([0-9]+) *px)? // $4 = 120 as in 120px pg.re.image = RegExp('(^|\\[\\[)' + im + ': *([^|\\]]*[^|\\] ])' + '([^0-9\\]]*([0-9]+) *px)?|(?:\\n *[|]?|[|]) *' + '(' + getValueOf('popupImageVarsRegexp') + ')' + ' *= *(?:\\[\\[ *)?(?:' + im + ':)?' + '([^|]*?)(?:\\]\\])? *[|]? *\\n', 'img') ; pg.re.imageBracketCount = 6; pg.re.category = RegExp('\\[\\[' +nsRe(pg.nsCategoryId) + ': *([^|\\]]*[^|\\] ]) *', 'i'); pg.re.categoryBracketCount = 1; pg.re.ipUser=RegExp('^' + // IPv6 '(?::(?::|(?::[0-9A-Fa-f]{1,4}){1,7})|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,6}::|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){7})' + // IPv4 '|(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' + '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$'); pg.re.stub= RegExp(getValueOf('popupStubRegexp'), 'im'); pg.re.disambig=RegExp(getValueOf('popupDabRegexp'), 'im'); //</NOLITE> // FIXME replace with general parameter parsing function, this is daft pg.re.oldid=RegExp('[?&]oldid=([^&]*)'); pg.re.diff=RegExp('[?&]diff=([^&]*)'); } ////////////////////////////////////////////////// // miscellany function setupCache() { // page caching pg.cache.pages = []; } function setMisc() { pg.current.link=null; pg.current.links=[]; pg.current.linksHash={}; setupCache(); pg.timer.checkPopupPosition=null; pg.counter.loop=0; // ids change with each popup: popupImage0, popupImage1 etc pg.idNumber=0; // for myDecodeURI pg.misc.decodeExtras = [ {from: '%2C', to: ',' }, {from: '_', to: ' ' }, {from: '%24', to: '$'}, {from: '%26', to: '&' } // no , ]; pg.misc.userAgent = 'Navigation popups/1.0 (' + mw.config.get( 'wgServerName' ) +')'; } // We need a callback since this might end up asynchronous because of // the mw.loader.using() call. function setupPopups( callback ) { if ( setupPopups.completed ) { if ( typeof callback === 'function' ) { callback(); } return; } // These dependencies are also enforced from the gadget, // but not everyone loads this as a gadget, so double check mw.loader.using( ['mediawiki.util', 'mediawiki.user', 'user.options'] ).then( function() { // NB translatable strings should be set up first (strings.js) // basics setupDebugging(); setSiteInfo(); setTitleBase(); setOptions(); // see options.js setUserInfo(); // namespaces etc setNamespaces(); setInterwiki(); // regexps setRegexps(); setRedirs(); // other stuff setMisc(); setupLivePreview(); // main deal here setupTooltips(); log('In setupPopups(), just called setupTooltips()'); Navpopup.tracker.enable(); setupPopups.completed = true; if ( typeof callback === 'function' ) { callback(); } }); } // ENDFILE: init.js // STARTFILE: navlinks.js //<NOLITE> ////////////////////////////////////////////////// // navlinks... let the fun begin // function defaultNavlinkSpec() { var str=''; str += '<b><<mainlink|shortcut= >></b>'; if (getValueOf('popupLastEditLink')) { str += '*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}'; } // user links // contribs - log - count - email - block // count only if applicable; block only if popupAdminLinks str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>'; str+='if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}'; // editing links // talkpage -> edit|new - history - un|watch - article|edit // other page -> edit - history - un|watch - talk|edit|new var editstr='<<edit|shortcut=e>>'; var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'; var historystr='<<history|shortcut=h>>|<<editors|shortcut=E|>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; str+='<br>if(talk){' + editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + // not a talk page editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}'; // misc links str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>'; // admin links str += 'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}'; return str; } function navLinksHTML (article, hint, params) { //oldid, rcid) { var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>'; // BAM return navlinkStringToHTML(str, article, params); } function expandConditionalNavlinkString(s,article,z,recursionCount) { var oldid=z.oldid, rcid=z.rcid, diff=z.diff; // nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out) if (typeof recursionCount!=typeof 0) { recursionCount=0; } var conditionalSplitRegex=RegExp( //(1 if \\( (2 2) \\) {(3 3)} (4 else {(5 5)} 4)1) '(;?\\s*if\\s*\\(\\s*([\\w]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))', 'i'); var splitted=s.parenSplit(conditionalSplitRegex); // $1: whole conditional // $2: test condition // $3: true expansion // $4: else clause (possibly empty) // $5: false expansion (possibly null) var numParens=5; var ret = splitted[0]; for (var i=1; i<splitted.length; i=i+numParens+1) { var testString=splitted[i+2-1]; var trueString=splitted[i+3-1]; var falseString=splitted[i+5-1]; if (typeof falseString=='undefined' || !falseString) { falseString=''; } var testResult=null; switch (testString) { case 'user': testResult=(article.userName())?true:false; break; case 'talk': testResult=(article.talkPage())?false:true; // talkPage converts _articles_ to talkPages break; case 'admin': testResult=getValueOf('popupAdminLinks')?true:false; break; case 'oldid': testResult=(typeof oldid != 'undefined' && oldid)?true:false; break; case 'rcid': testResult=(typeof rcid != 'undefined' && rcid)?true:false; break; case 'ipuser': testResult=(article.isIpUser())?true:false; break; case 'mainspace_en': testResult=isInMainNamespace(article) && pg.wiki.hostname=='en.wikipedia.org'; break; case 'wikimedia': testResult=(pg.wiki.wikimedia) ? true : false; break; case 'diff': testResult=(typeof diff != 'undefined' && diff)?true:false; break; } switch(testResult) { case null: ret+=splitted[i]; break; case true: ret+=trueString; break; case false: ret+=falseString; break; } // append non-conditional string ret += splitted[i+numParens]; } if (conditionalSplitRegex.test(ret) && recursionCount < 10) { return expandConditionalNavlinkString(ret,article,z,recursionCount+1); } return ret; } function navlinkStringToArray(s, article, params) { s=expandConditionalNavlinkString(s,article,params); var splitted=s.parenSplit(RegExp('<<(.*?)>>')); var ret=[]; for (var i=0; i<splitted.length; ++i) { if (i%2) { // i odd, so s is a tag var t=new navlinkTag(); var ss=splitted[i].split('|'); t.id=ss[0]; for (var j=1; j<ss.length; ++j) { var sss=ss[j].split('='); if (sss.length>1) { t[sss[0]]=sss[1]; } else { // no assignment (no "="), so treat this as a title (overwriting the last one) t.text=popupString(sss[0]); } } t.article=article; var oldid=params.oldid, rcid=params.rcid, diff=params.diff; if (typeof oldid !== 'undefined' && oldid !== null) { t.oldid=oldid; } if (typeof rcid !== 'undefined' && rcid !== null) { t.rcid=rcid; } if (typeof diff !== 'undefined' && diff !== null) { t.diff=diff; } if (!t.text && t.id !== 'mainlink') { t.text=popupString(t.id); } ret.push(t); } else { // plain HTML ret.push(splitted[i]); } } return ret; } function navlinkSubstituteHTML(s) { return s.split('*').join(getValueOf('popupNavLinkSeparator')) .split('<menurow>').join('<li class="popup_menu_row">') .split('</menurow>').join('</li>') .split('<menu>').join('<ul class="popup_menu">') .split('</menu>').join('</ul>'); } function navlinkDepth(magic,s) { return s.split('<' + magic + '>').length - s.split('</' + magic + '>').length; } // navlinkString: * becomes the separator // <<foo|bar=baz|fubar>> becomes a foo-link with attribute bar='baz' // and visible text 'fubar' // if(test){...} and if(test){...}else{...} work too (nested ok) function navlinkStringToHTML(s,article,params) { //limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article)); var p=navlinkStringToArray(s,article,params); var html=''; var menudepth = 0; // nested menus not currently allowed, but doesn't do any harm to code for it var menurowdepth = 0; for (var i=0; i<p.length; ++i) { if (typeof p[i] == typeof '') { html+=navlinkSubstituteHTML(p[i]); menudepth += navlinkDepth('menu', p[i]); menurowdepth += navlinkDepth('menurow', p[i]); // if (menudepth === 0) { // tagType='span'; // } else if (menurowdepth === 0) { // tagType='li'; // } else { // tagType = null; // } } else if (typeof p[i].type != 'undefined' && p[i].type=='navlinkTag') { if (menudepth > 0 && menurowdepth === 0) { html += '<li class="popup_menu_item">' + p[i].html() + '</li>'; } else { html+=p[i].html(); } } } return html; } function navlinkTag() { this.type='navlinkTag'; } navlinkTag.prototype.html=function () { this.getNewWin(); this.getPrintFunction(); var html=''; var opening, closing; var tagType='span'; if (!tagType) { opening = ''; closing = ''; } else { opening = '<' + tagType + ' class="popup_' + this.id + '">'; closing = '</' + tagType + '>'; } if (typeof this.print!='function') { errlog ('Oh dear - invalid print function for a navlinkTag, id='+this.id); } else { html=this.print(this); if (typeof html != typeof '') {html='';} else if (typeof this.shortcut!='undefined') html=addPopupShortcut(html, this.shortcut); } return opening + html + closing; }; navlinkTag.prototype.getNewWin=function() { getValueOf('popupLinksNewWindow'); if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') { this.newWin=null; } this.newWin=pg.option.popupLinksNewWindow[this.id]; }; navlinkTag.prototype.getPrintFunction=function() { //think about this some more // this.id and this.article should already be defined if (typeof this.id!=typeof '' || typeof this.article!=typeof {} ) { return; } this.noPopup=1; switch (this.id) { case 'contribs': case 'history': case 'whatLinksHere': case 'userPage': case 'monobook': case 'userTalk': case 'talk': case 'article': case 'lastEdit': this.noPopup=null; } switch (this.id) { case 'email': case 'contribs': case 'block': case 'unblock': case 'userlog': case 'userSpace': case 'deletedContribs': this.article=this.article.userName(); } switch (this.id) { case 'userTalk': case 'newUserTalk': case 'editUserTalk': case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog': this.article=this.article.userName(true); /* fall through */ case 'pagelog': case 'deletelog': case 'protectlog': delete this.oldid; } if (this.id=='editMonobook' || this.id=='monobook') { this.article.append('/monobook.js'); } if (this.id != 'mainlink') { // FIXME anchor handling should be done differently with Title object this.article=this.article.removeAnchor(); // if (typeof this.text=='undefined') this.text=popupString(this.id); } switch (this.id) { case 'undelete': this.print=specialLink; this.specialpage='Undelete'; this.sep='/'; break; case 'whatLinksHere': this.print=specialLink; this.specialpage='Whatlinkshere'; break; case 'relatedChanges': this.print=specialLink; this.specialpage='Recentchangeslinked'; break; case 'move': this.print=specialLink; this.specialpage='Movepage'; break; case 'contribs': this.print=specialLink; this.specialpage='Contributions'; break; case 'deletedContribs':this.print=specialLink; this.specialpage='Deletedcontributions'; break; case 'email': this.print=specialLink; this.specialpage='EmailUser'; this.sep='/'; break; case 'block': this.print=specialLink; this.specialpage='Blockip'; this.sep='&ip='; break; case 'unblock': this.print=specialLink; this.specialpage='Ipblocklist'; this.sep='&action=unblock&ip='; break; case 'userlog': this.print=specialLink; this.specialpage='Log'; this.sep='&user='; break; case 'blocklog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=block&page='; break; case 'pagelog': this.print=specialLink; this.specialpage='Log'; this.sep='&page='; break; case 'protectlog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=protect&page='; break; case 'deletelog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=delete&page='; break; case 'userSpace': this.print=specialLink; this.specialpage='PrefixIndex'; this.sep='&namespace=2&prefix='; break; case 'search': this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break; case 'thank': this.print=specialLink; this.specialpage='Thanks'; this.sep='/'; this.article.value = this.diff; break; case 'unwatch': case 'watch': this.print=magicWatchLink; this.action=this.id+'&autowatchlist=1&autoimpl=' + popupString('autoedit_version') + '&actoken='+autoClickToken(); break; case 'history': case 'historyfeed': case 'unprotect': case 'protect': this.print=wikiLink; this.action=this.id; break; case 'delete': this.print=wikiLink; this.action='delete'; if (this.article.namespaceId()==pg.nsImageId) { var img=this.article.stripNamespace(); this.action+='&image='+img; } break; case 'markpatrolled': case 'edit': // editOld should keep the oldid, but edit should not. delete this.oldid; /* fall through */ case 'view': case 'purge': case 'render': this.print=wikiLink; this.action=this.id; break; case 'raw': this.print=wikiLink; this.action='raw'; break; case 'new': this.print=wikiLink; this.action='edit&section=new'; break; case 'mainlink': if (typeof this.text=='undefined') { this.text=this.article.toString().entify(); } if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) { // only show the /subpage part of the title text var s=this.text.split('/'); this.text=s[s.length-1]; if (this.text==='' && s.length > 1) { this.text=s[s.length-2]; } } this.print=titledWikiLink; if (typeof this.title==='undefined' && pg.current.link && typeof pg.current.link.href !== 'undefined') { this.title=safeDecodeURI((pg.current.link.originalTitle)?pg.current.link.originalTitle:this.article); if (typeof this.oldid !== 'undefined' && this.oldid) { this.title=tprintf('Revision %s of %s', [this.oldid, this.title]); } } this.action='view'; break; case 'userPage': case 'article': case 'monobook': case 'editMonobook': case 'editArticle': delete this.oldid; //alert(this.id+'\n'+this.article + '\n'+ typeof this.article); this.article=this.article.articleFromTalkOrArticle(); //alert(this.id+'\n'+this.article + '\n'+ typeof this.article); this.print=wikiLink; if (this.id.indexOf('edit')===0) { this.action='edit'; } else { this.action='view';} break; case 'userTalk': case 'talk': this.article=this.article.talkPage(); delete this.oldid; this.print=wikiLink; this.action='view'; break; case 'arin': this.print=arinLink; break; case 'count': this.print=editCounterLink; break; case 'google': this.print=googleLink; break; case 'editors': this.print=editorListLink; break; case 'globalsearch': this.print=globalSearchLink; break; case 'lastEdit': this.print=titledDiffLink; this.title=popupString('Show the last edit'); this.from='prev'; this.to='cur'; break; case 'oldEdit': this.print=titledDiffLink; this.title=popupString('Show the edit made to get revision') + ' ' + this.oldid; this.from='prev'; this.to=this.oldid; break; case 'editOld': this.print=wikiLink; this.action='edit'; break; case 'undo': this.print=wikiLink; this.action='edit&undo='; break; case 'revert': this.print=wikiLink; this.action='revert'; break; case 'nullEdit': this.print=wikiLink; this.action='nullEdit'; break; case 'diffCur': this.print=titledDiffLink; this.title=tprintf('Show changes since revision %s', [this.oldid]); this.from=this.oldid; this.to='cur'; break; case 'editUserTalk': case 'editTalk': delete this.oldid; this.article=this.article.talkPage(); this.action='edit'; this.print=wikiLink; break; case 'newUserTalk': case 'newTalk': this.article=this.article.talkPage(); this.action='edit&section=new'; this.print=wikiLink; break; case 'lastContrib': case 'sinceMe': this.print=magicHistoryLink; break; case 'togglePreviews': this.text=popupString(pg.option.simplePopups ? 'enable previews' : 'disable previews'); /* fall through */ case 'disablePopups': case 'purgePopups': this.print=popupMenuLink; break; default: this.print=function () {return 'Unknown navlink type: '+this.id+'';}; } }; // // end navlinks ////////////////////////////////////////////////// //</NOLITE> // ENDFILE: navlinks.js // STARTFILE: shortcutkeys.js //<NOLITE> function popupHandleKeypress(evt) { var keyCode = window.event ? window.event.keyCode : ( evt.keyCode ? evt.keyCode : evt.which); if (!keyCode || !pg.current.link || !pg.current.link.navpopup) { return; } if (keyCode==27) { // escape killPopup(); return false; // swallow keypress } var letter=String.fromCharCode(keyCode); var links=pg.current.link.navpopup.mainDiv.getElementsByTagName('A'); var startLink=0; var i,j; if (popupHandleKeypress.lastPopupLinkSelected) { for (i=0; i<links.length; ++i) { if (links[i]==popupHandleKeypress.lastPopupLinkSelected) { startLink=i; } } } for (j=0; j<links.length; ++j) { i=(startLink + j + 1) % links.length; if (links[i].getAttribute('popupkey')==letter) { if (evt && evt.preventDefault) evt.preventDefault(); links[i].focus(); popupHandleKeypress.lastPopupLinkSelected=links[i]; return false; // swallow keypress } } // pass keypress on if (document.oldPopupOnkeypress) { return document.oldPopupOnkeypress(evt); } return true; } function addPopupShortcuts() { if (document.onkeypress!=popupHandleKeypress) { document.oldPopupOnkeypress=document.onkeypress; } document.onkeypress=popupHandleKeypress; } function rmPopupShortcuts() { popupHandleKeypress.lastPopupLinkSelected=null; try { if (document.oldPopupOnkeypress && document.oldPopupOnkeypress==popupHandleKeypress) { // panic document.onkeypress=null; //function () {}; return; } document.onkeypress=document.oldPopupOnkeypress; } catch (nasties) { /* IE goes here */ } } function addLinkProperty(html, property) { // take "<a href=...>...</a> and add a property // not sophisticated at all, easily broken var i=html.indexOf('>'); if (i<0) { return html; } return html.substring(0,i) + ' ' + property + html.substring(i); } function addPopupShortcut(html, key) { if (!getValueOf('popupShortcutKeys')) { return html; } var ret= addLinkProperty(html, 'popupkey="'+key+'"'); if (key==' ') { key=popupString('spacebar'); } return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'),'$1$2$3 ['+key+']$4'); } //</NOLITE> // ENDFILE: shortcutkeys.js // STARTFILE: diffpreview.js //<NOLITE> //lets jump through hoops to find the rev ids we need to retrieve function loadDiff(article, oldid, diff, navpop) { navpop.diffData={ oldRev: {}, newRev: {} }; mw.loader.using( 'mediawiki.api' ).then( function() { var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'compare', prop: 'ids|title' }; if(article.title){ params.fromtitle = article.title; } switch (diff) { case 'cur': switch ( oldid ) { case null: case '': case 'prev': // this can only work if we have the title // cur -> prev params.torelative = 'prev'; break; default: params.fromrev = oldid; params.torelative = 'cur'; break; } break; case 'prev': if( oldid ) { params.fromrev = oldid; } else { params.fromtitle; } params.torelative = 'prev'; break; case 'next': params.fromrev = oldid || 0; params.torelative = 'next'; break; default: params.fromrev = oldid || 0; params.torev = diff || 0; break; } api.get( params ).then( function( data ) { navpop.diffData.oldRev.revid = data.compare.fromrevid; navpop.diffData.newRev.revid = data.compare.torevid; addReviewLink(navpop, 'popupMiscTools'); var go = function() { pendingNavpopTask(navpop); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&'; url += 'revids=' + navpop.diffData.oldRev.revid + '|' + navpop.diffData.newRev.revid; url += '&prop=revisions&rvprop=ids|timestamp|content'; getPageWithCaching(url, doneDiff, navpop); return true; // remove hook once run }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS'); } } ); } ); } // Put a "mark patrolled" link to an element target // TODO: Allow patrol a revision, as well as a diff function addReviewLink (navpop, target) { if (! pg.user.canReview) return; // If 'newRev' is older than 'oldRev' than it could be confusing, so we do not show the review link. if (navpop.diffData.newRev.revid <= navpop.diffData.oldRev.revid) return; var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'query', prop: 'info|flagged', revids: navpop.diffData.oldRev.revid, formatversion : 2 }; api.get (params).then(function(data){ var stable_revid = data.query.pages[0].flagged && data.query.pages[0].flagged.stable_revid || 0; // The diff can be reviewed if the old version is the last reviewed version // TODO: Other possible conditions that we may want to implement instead of this one: // * old version is patrolled and the new version is not patrolled // * old version is patrolled and the new version is more recent than the last reviewed version if (stable_revid == navpop.diffData.oldRev.revid) { var a = document.createElement('a'); a.innerHTML = popupString('mark patrolled'); a.title=popupString('markpatrolledHint'); a.onclick = function() { var params = { action: 'review', revid: navpop.diffData.newRev.revid, comment: tprintf('defaultpopupReviewedSummary', [navpop.diffData.oldRev.revid, navpop.diffData.newRev.revid]) }; api.postWithToken('csrf',params).done(function(){ a.style.display = "none"; // TODO: Update current page and other already constructed popups } ).fail(function(){ alert(popupString('Could not marked this edit as patrolled')); }); }; setPopupHTML(a, target, navpop.idNumber,null,true); } }); } function doneDiff(download) { if (!download.owner || !download.owner.diffData) { return; } var navpop=download.owner; completedNavpopTask(navpop); var pages, revisions=[]; try{ // Process the downloads pages = getJsObj(download.data).query.pages; for(var i=0; i < pages.length; i++ ) { revisions = revisions.concat(pages[i].revisions); } for(i=0; i< revisions.length; i++){ if(revisions[i].revid == navpop.diffData.oldRev.revid) { navpop.diffData.oldRev.revision = revisions[i]; } else if (revisions[i].revid == navpop.diffData.newRev.revid) { navpop.diffData.newRev.revision = revisions[i]; } } } catch(someError) { errlog( 'Could not get diff' ); } insertDiff(navpop); } function rmBoringLines(a,b,context) { if (typeof context == 'undefined') { context=2; } // this is fairly slow... i think it's quicker than doing a word-based diff from the off, though var aa=[], aaa=[]; var bb=[], bbb=[]; var i, j; // first, gather all disconnected nodes in a and all crossing nodes in a and b for (i=0; i<a.length; ++i ) { if(!a[i].paired) { aa[i]=1; } else if (countCrossings(b,a,i, true)) { aa[i]=1; bb[ a[i].row ] = 1; } } // pick up remaining disconnected nodes in b for (i=0; i<b.length; ++i ) { if (bb[i]==1) { continue; } if(!b[i].paired) { bb[i]=1; } } // another pass to gather context: we want the neighbours of included nodes which are not yet included // we have to add in partners of these nodes, but we don't want to add context for *those* nodes in the next pass for (i=0; i<b.length; ++i) { if ( bb[i] == 1 ) { for (j=Math.max(0,i-context); j < Math.min(b.length, i+context); ++j) { if ( !bb[j] ) { bb[j] = 1; aa[ b[j].row ] = 0.5; } } } } for (i=0; i<a.length; ++i) { if ( aa[i] == 1 ) { for (j=Math.max(0,i-context); j < Math.min(a.length, i+context); ++j) { if ( !aa[j] ) { aa[j] = 1; bb[ a[j].row ] = 0.5; } } } } for (i=0; i<bb.length; ++i) { if (bb[i] > 0) { // it's a row we need if (b[i].paired) { bbb.push(b[i].text); } // joined; partner should be in aa else { bbb.push(b[i]); } } } for (i=0; i<aa.length; ++i) { if (aa[i] > 0) { // it's a row we need if (a[i].paired) { aaa.push(a[i].text); } // joined; partner should be in aa else { aaa.push(a[i]); } } } return { a: aaa, b: bbb}; } function stripOuterCommonLines(a,b,context) { var i=0; while (i<a.length && i < b.length && a[i]==b[i]) { ++i; } var j=a.length-1; var k=b.length-1; while ( j>=0 && k>=0 && a[j]==b[k] ) { --j; --k; } return { a: a.slice(Math.max(0,i - 1 - context), Math.min(a.length+1, j + context+1)), b: b.slice(Math.max(0,i - 1 - context), Math.min(b.length+1, k + context+1)) }; } function insertDiff(navpop) { // for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff // FIXME: sometimes this gives misleading diffs as distant chunks are squashed together var oldlines = navpop.diffData.oldRev.revision.content.split('\n'); var newlines = navpop.diffData.newRev.revision.content.split('\n'); var inner=stripOuterCommonLines(oldlines,newlines,getValueOf('popupDiffContextLines')); oldlines=inner.a; newlines=inner.b; var truncated=false; getValueOf('popupDiffMaxLines'); if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) { // truncate truncated=true; inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines), newlines.slice(0,pg.option.popupDiffMaxLines), pg.option.popupDiffContextLines); oldlines=inner.a; newlines=inner.b; } var lineDiff=diff(oldlines, newlines); var lines2=rmBoringLines(lineDiff.o, lineDiff.n); var oldlines2=lines2.a; var newlines2=lines2.b; var simpleSplit = !String.prototype.parenSplit.isNative; var html='<hr />'; if (getValueOf('popupDiffDates')) { html += diffDatesTable(navpop); html += '<hr />'; } html += shortenDiffString( diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit), getValueOf('popupDiffContextCharacters') ).join('<hr />'); setPopupTipsAndHTML(html.split('\n').join('<br>') + (truncated ? '<hr /><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') , 'popupPreview', navpop.idNumber); } function diffDatesTable( navpop ) { var html='<table class="popup_diff_dates">'; html += diffDatesTableRow( navpop.diffData.newRev.revision, tprintf('New revision')); html += diffDatesTableRow( navpop.diffData.oldRev.revision, tprintf('Old revision')); html += '</table>'; return html; } function diffDatesTableRow( revision, label ) { var txt=''; var lastModifiedDate = new Date(revision.timestamp); var datePrint=getValueOf('popupDiffDatePrinter'); if (typeof lastModifiedDate[datePrint] == 'function') { var d2 = adjustDate(lastModifiedDate, getTimeOffset()); txt = dayFormat(d2, true) + ' ' + timeFormat(d2, true); } else { txt = tprintf('Invalid %s %s', ['popupDiffDatePrinter', datePrint]); } var revlink = generalLink({url: mw.config.get('wgScript') + '?oldid='+revision.revid, text: label, title: label}); return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [ revlink, txt ]); } //</NOLITE> // ENDFILE: diffpreview.js // STARTFILE: links.js //<NOLITE> ///////////////////// // LINK GENERATION // ///////////////////// // titledDiffLink --> titledWikiLink --> generalLink // wikiLink --> titledWikiLink --> generalLink // editCounterLink --> generalLink // TODO Make these functions return Element objects, not just raw HTML strings. function titledDiffLink(l) { // article, text, title, from, to) { return titledWikiLink({article: l.article, action: l.to + '&oldid=' + l.from, newWin: l.newWin, noPopup: l.noPopup, text: l.text, title: l.title, /* hack: no oldid here */ actionName: 'diff'}); } function wikiLink(l) { //{article:article, action:action, text:text, oldid, newid}) { if (! (typeof l.article == typeof {} && typeof l.action == typeof '' && typeof l.text==typeof '')) return null; if (typeof l.oldid == 'undefined') { l.oldid=null; } var savedOldid = l.oldid; if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) { l.oldid=null; } var hint=popupString(l.action + 'Hint'); // revertHint etc etc etc var oldidData=[l.oldid, safeDecodeURI(l.article)]; var revisionString = tprintf('revision %s of %s', oldidData); log('revisionString='+revisionString); switch (l.action) { case 'edit&section=new': hint = popupString('newSectionHint'); break; case 'edit&undo=': if (l.diff && l.diff != 'prev' && savedOldid ) { l.action += l.diff + '&undoafter=' + savedOldid; } else if (savedOldid) { l.action += savedOldid; } hint = popupString('undoHint'); break; case 'raw&ctype=text/css': hint=popupString('rawHint'); break; case 'revert': var p=parseParams(pg.current.link.href); l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=' + revertSummary(l.oldid, p.diff); if (p.diff=='prev') { l.action += '&direction=prev'; revisionString = tprintf('the revision prior to revision %s of %s', oldidData); } if (getValueOf('popupRevertSummaryPrompt')) { l.action += '&autosummaryprompt=true'; } if (getValueOf('popupMinorReverts')) { l.action += '&autominor=true'; } log('revisionString is now '+revisionString); break; case 'nullEdit': l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=null'; break; case 'historyfeed': l.action='history&feed=rss'; break; case 'markpatrolled': l.action='markpatrolled&rcid='+l.rcid; } if (hint) { if (l.oldid) { hint = simplePrintf(hint, [revisionString]); } else { hint = simplePrintf(hint, [safeDecodeURI(l.article)]); } } else { hint = safeDecodeURI(l.article + '&action=' + l.action) + (l.oldid) ? '&oldid='+l.oldid : ''; } return titledWikiLink({article: l.article, action: l.action, text: l.text, newWin:l.newWin, title: hint, oldid: l.oldid, noPopup: l.noPopup, onclick: l.onclick}); } function revertSummary(oldid, diff) { var ret=''; if (diff == 'prev') { ret=getValueOf('popupQueriedRevertToPreviousSummary'); } else { ret = getValueOf('popupQueriedRevertSummary'); } return ret + '&autorv=' + oldid; } function titledWikiLink(l) { // possible properties of argument: // article, action, text, title, oldid, actionName, className, noPopup // oldid = null is fine here // article and action are mandatory args if (typeof l.article == 'undefined' || typeof l.action=='undefined') { errlog('got undefined article or action in titledWikiLink'); return null; } var base = pg.wiki.titlebase + l.article.urlString(); var url=base; if (typeof l.actionName=='undefined' || !l.actionName) { l.actionName='action'; } // no need to add &action=view, and this confuses anchors if (l.action != 'view') { url = base + '&' + l.actionName + '=' + l.action; } if (typeof l.oldid!='undefined' && l.oldid) { url+='&oldid='+l.oldid; } var cssClass=pg.misc.defaultNavlinkClassname; if (typeof l.className!='undefined' && l.className) { cssClass=l.className; } return generalNavLink({url: url, newWin: l.newWin, title: (typeof l.title != 'undefined') ? l.title : null, text: (typeof l.text!='undefined')?l.text:null, className: cssClass, noPopup:l.noPopup, onclick:l.onclick}); } pg.fn.getLastContrib = function getLastContrib(wikipage, newWin) { getHistoryInfo(wikipage, function(x) { processLastContribInfo(x, {page: wikipage, newWin: newWin}); }); }; function processLastContribInfo(info, stuff) { if(!info.edits || !info.edits.length) { alert('Popups: an odd thing happened. Please retry.'); return; } if(!info.firstNewEditor) { alert(tprintf('Only found one editor: %s made %s edits', [info.edits[0].editor,info.edits.length])); return; } var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+info.firstNewEditor.oldid; displayUrl(newUrl, stuff.newWin); } pg.fn.getDiffSinceMyEdit = function getDiffSinceMyEdit(wikipage, newWin) { getHistoryInfo(wikipage, function(x){ processDiffSinceMyEdit(x, {page: wikipage, newWin: newWin}); }); }; function processDiffSinceMyEdit(info, stuff) { if(!info.edits || !info.edits.length) { alert('Popups: something fishy happened. Please try again.'); return; } var friendlyName=stuff.page.split('_').join(' '); if(!info.myLastEdit) { alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s', [info.userName, getValueOf('popupHistoryLimit'), friendlyName])); return; } if(info.myLastEdit.index === 0) { alert(tprintf("%s seems to be the last editor to the page %s", [info.userName, friendlyName])); return; } var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+ info.myLastEdit.oldid; displayUrl(newUrl, stuff.newWin); } function displayUrl(url, newWin){ if(newWin) { window.open(url); } else { document.location=url; } } pg.fn.purgePopups = function purgePopups() { processAllPopups(true); setupCache(); // deletes all cached items (not browser cached, though...) pg.option={}; abortAllDownloads(); }; function processAllPopups(nullify, banish) { for (var i=0; pg.current.links && i<pg.current.links.length; ++i) { if (!pg.current.links[i].navpopup) { continue; } if (nullify || banish) pg.current.links[i].navpopup.banish(); pg.current.links[i].simpleNoMore=false; if (nullify) pg.current.links[i].navpopup=null; } } pg.fn.disablePopups = function disablePopups(){ processAllPopups(false, true); setupTooltips(null, true); }; pg.fn.togglePreviews = function togglePreviews() { processAllPopups(true, true); pg.option.simplePopups=!pg.option.simplePopups; abortAllDownloads(); }; function magicWatchLink(l) { //Yuck!! Would require a thorough redesign to add this as a click event though ... l.onclick = simplePrintf( 'pg.fn.modifyWatchlist(\'%s\',\'%s\');return false;', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), this.id] ); return wikiLink(l); } pg.fn.modifyWatchlist = function modifyWatchlist(title, action) { var reqData = { 'action': 'watch', 'formatversion': 2, 'titles': title, 'uselang': mw.config.get('wgUserLanguage') }; if ( action === 'unwatch' ) reqData.unwatch = true; var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); // Load the Addedwatchtext or Removedwatchtext message and show it var mwTitle = mw.Title.newFromText( title ); var messageName; if ( mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1 ) { messageName = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk'; } else { messageName = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext'; } $.when( api.postWithToken( 'watch', reqData ), mw.loader.using( [ 'mediawiki.api', 'mediawiki.jqueryMsg' ] ).then( function () { return api.loadMessagesIfMissing( [ messageName ] ); } ) ).done( function () { mw.notify( mw.message( messageName, title ).parseDom() ); } ); }; function magicHistoryLink(l) { // FIXME use onclick change href trick to sort this out instead of window.open var jsUrl='', title='', onClick=''; switch(l.id) { case 'lastContrib': onClick=simplePrintf('pg.fn.getLastContrib(\'%s\',%s)', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]); title=popupString('lastContribHint'); break; case 'sinceMe': onClick=simplePrintf('pg.fn.getDiffSinceMyEdit(\'%s\',%s)', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]); title=popupString('sinceMeHint'); break; } jsUrl = 'javascript:' + onClick; // jshint ignore:line onClick += ';return false;'; return generalNavLink({url: jsUrl, newWin: false, // can't have new windows with JS links, I think title: title, text: l.text, noPopup: l.noPopup, onclick: onClick }); } function popupMenuLink(l) { var jsUrl=simplePrintf('javascript:pg.fn.%s()', [l.id]); // jshint ignore:line var title=popupString(simplePrintf('%sHint', [l.id])); var onClick=simplePrintf('pg.fn.%s();return false;', [l.id]); return generalNavLink({url: jsUrl, newWin:false, title:title, text:l.text, noPopup:l.noPopup, onclick: onClick}); } function specialLink(l) { // properties: article, specialpage, text, sep if (typeof l.specialpage=='undefined'||!l.specialpage) return null; var base = pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId]+':'+l.specialpage; if (typeof l.sep == 'undefined' || l.sep === null) l.sep='&target='; var article=l.article.urlString({keepSpaces: l.specialpage=='Search'}); var hint=popupString(l.specialpage+'Hint'); switch (l.specialpage) { case 'Log': switch (l.sep) { case '&user=': hint=popupString('userLogHint'); break; case '&type=block&page=': hint=popupString('blockLogHint'); break; case '&page=': hint=popupString('pageLogHint'); break; case '&type=protect&page=': hint=popupString('protectLogHint'); break; case '&type=delete&page=': hint=popupString('deleteLogHint'); break; default: log('Unknown log type, sep=' + l.sep); hint='Missing hint (FIXME)'; } break; case 'PrefixIndex': article += '/'; break; } if (hint) hint = simplePrintf(hint, [safeDecodeURI(l.article)]); else hint = safeDecodeURI(l.specialpage+':'+l.article) ; var url = base + l.sep + article; return generalNavLink({url: url, title: hint, text: l.text, newWin:l.newWin, noPopup:l.noPopup}); } function generalLink(l) { // l.url, l.text, l.title, l.newWin, l.className, l.noPopup, l.onclick if (typeof l.url=='undefined') return null; // only quotation marks in the url can screw us up now... I think var url=l.url.split('"').join('%22'); var ret='<a href="' + url + '"'; if (typeof l.title!='undefined' && l.title) { ret += ' title="' + pg.escapeQuotesHTML(l.title) + '"'; } if (typeof l.onclick!='undefined' && l.onclick) { ret += ' onclick="' + pg.escapeQuotesHTML(l.onclick) + '"'; } if (l.noPopup) { ret += ' noPopup=1'; } var newWin; if (typeof l.newWin=='undefined' || l.newWin === null) { newWin=getValueOf('popupNewWindows'); } else { newWin=l.newWin; } if (newWin) { ret += ' target="_blank"'; } if (typeof l.className!='undefined'&&l.className) { ret+=' class="'+l.className+'"'; } ret += '>'; if (typeof l.text==typeof '') { ret+= l.text; } ret +='</a>'; return ret; } function appendParamsToLink(linkstr, params) { var sp=linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i')); if (sp.length<2) return null; var ret=sp.shift() + sp.shift(); ret += '&' + params + '"'; ret += sp.join(''); return ret; } function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor, title (optional), alsoChangeLabel { if (x.newTarget) { log ('changeLinkTargetLink: newTarget=' + x.newTarget); } if (x.oldTarget !== decodeURIComponent( x.oldTarget ) ) { log ('This might be an input problem: ' + x.oldTarget ); } // FIXME: first character of page title as well as namespace should be case insensitive // eg [[category:X1]] and [[Category:X1]] are equivalent // this'll break if charAt(0) is nasty var cA = mw.util.escapeRegExp(x.oldTarget); var chs = cA.charAt(0).toUpperCase(); chs='['+chs + chs.toLowerCase()+']'; var currentArticleRegexBit=chs+cA.substring(1); currentArticleRegexBit=currentArticleRegexBit .split(RegExp('(?:[_ ]+|%20)', 'g')).join('(?:[_ ]+|%20)') .split('\\(').join('(?:%28|\\()') .split('\\)').join('(?:%29|\\))'); // why does this need to match encoded strings ? links in the document ? // leading and trailing space should be ignored, and anchor bits optional: currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + '(?:#[^\\[\\|]*)?)\\s*'; // e.g. Computer (archaic) -> \s*([Cc]omputer[_ ](?:%2528|\()archaic(?:%2528|\)))\s* // autoedit=s~\[\[([Cc]ad)\]\]~[[Computer-aided%20design|$1]]~g;s~\[\[([Cc]AD)[|]~[[Computer-aided%20design|~g var title=x.title || mw.config.get('wgPageName').split('_').join(' '); var lk=titledWikiLink({article: new Title(title), newWin:x.newWin, action: 'edit', text: x.text, title: x.hint, className: 'popup_change_title_link' }); var cmd=''; if (x.newTarget) { // escape '&' and other nasties var t = x.newTarget; var s = mw.util.escapeRegExp(x.newTarget); if (x.alsoChangeLabel) { cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + ']]~g;'; cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;'; cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g'; } else { cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + '|$1]]~g;'; cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;'; cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g'; } } else { cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~$1~g;'; cmd += 's~\\[\\['+currentArticleRegexBit+'[|](.*?)\\]\\]~$2~g'; } // Build query cmd = 'autoedit=' + encodeURIComponent ( cmd ); cmd += '&autoclick='+ encodeURIComponent( x.clickButton ) + '&actoken=' + encodeURIComponent( autoClickToken() ); cmd += ( x.minor === null ) ? '' : '&autominor='+ encodeURIComponent( x.minor ); cmd += ( x.watch === null ) ? '' : '&autowatch='+ encodeURIComponent( x.watch ); cmd += '&autosummary='+encodeURIComponent(x.summary); cmd += '&autoimpl='+encodeURIComponent( popupString('autoedit_version') ); return appendParamsToLink(lk, cmd); } function redirLink(redirMatch, article) { // NB redirMatch is in wikiText var ret=''; if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) { ret += '<hr />'; if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) { ret += popupString('Redirects to: (Fix '); log('redirLink: newTarget=' + redirMatch); ret += addPopupShortcut(changeLinkTargetLink({ newTarget: redirMatch, text: popupString('target'), hint: popupString('Fix this redirect, changing just the link target'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]), oldTarget: article.toString(), clickButton: getValueOf('popupRedirAutoClick'), minor: true, watch: getValueOf('popupWatchRedirredPages') }), 'R'); ret += popupString(' or '); ret += addPopupShortcut(changeLinkTargetLink({ newTarget: redirMatch, text: popupString('target & label'), hint: popupString('Fix this redirect, changing the link target and label'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]), oldTarget: article.toString(), clickButton: getValueOf('popupRedirAutoClick'), minor: true, watch: getValueOf('popupWatchRedirredPages'), alsoChangeLabel: true }), 'R'); ret += popupString(')'); } else ret += popupString('Redirects') + popupString(' to '); return ret; } else return '<br> ' + popupString('Redirects') + popupString(' to ') + titledWikiLink({article: new Title().fromWikiText(redirMatch), action: 'view', /* FIXME: newWin */ text: safeDecodeURI(redirMatch), title: popupString('Bypass redirect')}); } function arinLink(l) { if (!saneLinkCheck(l)) { return null; } if ( ! l.article.isIpUser() || ! pg.wiki.wikimedia) return null; var uN=l.article.userName(); return generalNavLink({url:'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + encodeURIComponent(uN), newWin:l.newWin, title: tprintf('Look up %s in ARIN whois database', [uN]), text: l.text, noPopup:1}); } function toolDbName(cookieStyle) { var ret = mw.config.get('wgDBname'); if (!cookieStyle) { ret+= '_p'; } return ret; } function saneLinkCheck(l) { if (typeof l.article != typeof {} || typeof l.text != typeof '') { return false; } return true; } function editCounterLink(l) { if(!saneLinkCheck(l)) return null; if (! pg.wiki.wikimedia) return null; var uN=l.article.userName(); var tool=getValueOf('popupEditCounterTool'); var url; var defaultToolUrl='//tools.wmflabs.org/supercount/index.php?user=$1&project=$2.$3'; switch(tool) { case 'custom': url=simplePrintf(getValueOf('popupEditCounterUrl'), [ encodeURIComponent(uN), toolDbName() ]); break; case 'soxred': // no longer available case 'kate': // no longer available case 'interiot':// no longer available /* fall through */ case 'supercount': default: var theWiki=pg.wiki.hostname.split('.'); url=simplePrintf(defaultToolUrl, [ encodeURIComponent(uN), theWiki[0], theWiki[1] ]); } return generalNavLink({url:url, title: tprintf('editCounterLinkHint', [uN]), newWin:l.newWin, text: l.text, noPopup:1}); } function globalSearchLink(l) { if(!saneLinkCheck(l)) return null; var base='http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search='; var article=l.article.urlString({keepSpaces:true}); return generalNavLink({url:base + article, newWin:l.newWin, title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]), text: l.text, noPopup:1}); } function googleLink(l) { if(!saneLinkCheck(l)) return null; var base='https://www.google.com/search?q='; var article=l.article.urlString({keepSpaces:true}); return generalNavLink({url:base + '%22' + article + '%22', newWin:l.newWin, title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]), text: l.text, noPopup:1}); } function editorListLink(l) { if(!saneLinkCheck(l)) return null; var article= l.article.articleFromTalkPage() || l.article; var url='https://xtools.wmflabs.org/articleinfo/' + encodeURI( pg.wiki.hostname ) + '/' + article.urlString() + '?uselang=' + mw.config.get('wgUserLanguage'); return generalNavLink({url:url, title: tprintf('editorListHint', [article]), newWin:l.newWin, text: l.text, noPopup:1}); } function generalNavLink(l) { l.className = (l.className === null) ? 'popupNavLink' : l.className; return generalLink(l); } ////////////////////////////////////////////////// // magic history links // function getHistoryInfo(wikipage, whatNext) { log('getHistoryInfo'); getHistory(wikipage, whatNext ? function(d){whatNext(processHistory(d));} : processHistory); } // FIXME eliminate pg.idNumber ... how? :-( function getHistory(wikipage, onComplete) { log('getHistory'); var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&prop=revisions&titles=' + new Title(wikipage).urlString() + '&rvlimit=' + getValueOf('popupHistoryLimit'); log('getHistory: url='+url); return startDownload(url, pg.idNumber+'history', onComplete); } function processHistory(download) { var jsobj = getJsObj(download.data); try { var revisions = anyChild(jsobj.query.pages).revisions; var edits=[]; for (var i=0; i<revisions.length; ++i) { edits.push({ oldid: revisions[i].revid, editor: revisions[i].user }); } log('processed ' + edits.length + ' edits'); return finishProcessHistory( edits, mw.config.get('wgUserName') ); } catch (someError) { log('Something went wrong with JSON business'); return finishProcessHistory([]); } } function finishProcessHistory(edits, userName) { var histInfo={}; histInfo.edits=edits; histInfo.userName=userName; for (var i=0; i<edits.length; ++i) { if (typeof histInfo.myLastEdit === 'undefined' && userName && edits[i].editor==userName) { histInfo.myLastEdit={index: i, oldid: edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)}; } if (typeof histInfo.firstNewEditor === 'undefined' && edits[i].editor != edits[0].editor) { histInfo.firstNewEditor={index:i, oldid:edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)}; } } //pg.misc.historyInfo=histInfo; return histInfo; } //</NOLITE> // ENDFILE: links.js // STARTFILE: options.js ////////////////////////////////////////////////// // options // check for existing value, else use default function defaultize(x) { if (pg.option[x]===null || typeof pg.option[x]=='undefined') { if (typeof window[x] != 'undefined' ) pg.option[x]=window[x]; else pg.option[x]=pg.optionDefault[x]; } } function newOption(x, def) { pg.optionDefault[x]=def; } function setDefault(x, def) { return newOption(x, def); } function getValueOf(varName) { defaultize(varName); return pg.option[varName]; } /*eslint-disable */ function useDefaultOptions() { // for testing for (var p in pg.optionDefault) { pg.option[p]=pg.optionDefault[p]; if (typeof window[p]!='undefined') { delete window[p]; } } } /*eslint-enable */ function setOptions() { // user-settable parameters and defaults var userIsSysop = false; if ( mw.config.get('wgUserGroups') ) { for ( var g = 0; g < mw.config.get('wgUserGroups').length; ++g ) { if ( mw.config.get('wgUserGroups')[g] == "bảo quản viên" ) userIsSysop = true; } } // Basic options newOption('popupDelay', 0.5); newOption('popupHideDelay', 0.5); newOption('simplePopups', false); newOption('popupStructure', 'shortmenus'); // see later - default for popupStructure is 'original' if simplePopups is true newOption('popupActionsMenu', true); newOption('popupSetupMenu', true); newOption('popupAdminLinks', userIsSysop); newOption('popupShortcutKeys', false); newOption('popupHistoricalLinks', true); newOption('popupOnlyArticleLinks', true); newOption('removeTitles', true); newOption('popupMaxWidth', 350); newOption('popupSimplifyMainLink', true); newOption('popupAppendRedirNavLinks', true); newOption('popupTocLinks', false); newOption('popupSubpopups', true); newOption('popupDragHandle', false /* 'popupTopLinks'*/); newOption('popupLazyPreviews', true); newOption('popupLazyDownloads', true); newOption('popupAllDabsStubs', false); newOption('popupDebugging', false); newOption('popupActiveNavlinks', true); newOption('popupModifier', false); // ctrl, shift, alt or meta newOption('popupModifierAction', 'enable'); // or 'disable' newOption('popupDraggable', true); newOption('popupReview', false); //<NOLITE> // images newOption('popupImages', true); newOption('imagePopupsForImages', true); newOption('popupNeverGetThumbs', false); //newOption('popupImagesToggleSize', true); newOption('popupThumbAction', 'imagepage'); //'sizetoggle'); newOption('popupImageSize', 60); newOption('popupImageSizeLarge', 200); // redirs, dabs, reversion newOption('popupFixRedirs', false); newOption('popupRedirAutoClick', 'wpDiff'); newOption('popupFixDabs', false); newOption('popupDabsAutoClick', 'wpDiff'); newOption('popupRevertSummaryPrompt', false); newOption('popupMinorReverts', false); newOption('popupRedlinkRemoval', false); newOption('popupRedlinkAutoClick', 'wpDiff'); newOption('popupWatchDisambiggedPages', null); newOption('popupWatchRedirredPages', null); newOption('popupDabWiktionary', 'last'); // navlinks newOption('popupNavLinks', true); newOption('popupNavLinkSeparator', ' &sdot; '); newOption('popupLastEditLink', true); newOption('popupEditCounterTool', 'supercount'); newOption('popupEditCounterUrl', ''); //</NOLITE> // previews etc newOption('popupPreviews', true); newOption('popupSummaryData', true); newOption('popupMaxPreviewSentences', 9); newOption('popupMaxPreviewCharacters', 900); newOption('popupLastModified', true); newOption('popupPreviewKillTemplates', true); newOption('popupPreviewRawTemplates', true); newOption('popupPreviewFirstParOnly', false); newOption('popupPreviewCutHeadings', false); newOption('popupPreviewButton', false); newOption('popupPreviewButtonEvent', 'click'); //<NOLITE> // diffs newOption('popupPreviewDiffs', true); newOption('popupDiffMaxLines', 100); newOption('popupDiffContextLines', 2); newOption('popupDiffContextCharacters', 40); newOption('popupDiffDates', true); newOption('popupDiffDatePrinter', 'toLocaleString'); // edit summaries. God, these are ugly. newOption('popupReviewedSummary', popupString('defaultpopupReviewedSummary') ); newOption('popupFixDabsSummary', popupString('defaultpopupFixDabsSummary') ); newOption('popupExtendedRevertSummary', popupString('defaultpopupExtendedRevertSummary') ); newOption('popupRevertSummary', popupString('defaultpopupRevertSummary') ); newOption('popupRevertToPreviousSummary', popupString('defaultpopupRevertToPreviousSummary') ); newOption('popupQueriedRevertSummary', popupString('defaultpopupQueriedRevertSummary') ); newOption('popupQueriedRevertToPreviousSummary', popupString('defaultpopupQueriedRevertToPreviousSummary') ); newOption('popupFixRedirsSummary', popupString('defaultpopupFixRedirsSummary') ); newOption('popupRedlinkSummary', popupString('defaultpopupRedlinkSummary') ); newOption('popupRmDabLinkSummary', popupString('defaultpopupRmDabLinkSummary') ); //</NOLITE> // misc newOption('popupHistoryLimit', 50); //<NOLITE> newOption('popupFilters', [popupFilterStubDetect, popupFilterDisambigDetect, popupFilterPageSize, popupFilterCountLinks, popupFilterCountImages, popupFilterCountCategories, popupFilterLastModified]); newOption('extraPopupFilters', []); newOption('popupOnEditSelection', 'cursor'); newOption('popupPreviewHistory', true); newOption('popupImageLinks', true); newOption('popupCategoryMembers', true); newOption('popupUserInfo', true); newOption('popupHistoryPreviewLimit', 25); newOption('popupContribsPreviewLimit',25); newOption('popupRevDelUrl', '//en.wikipedia.org/wiki/Wikipedia:Revision_deletion'); newOption('popupShowGender', true); //</NOLITE> // new windows newOption('popupNewWindows', false); newOption('popupLinksNewWindow', {'lastContrib': true, 'sinceMe': true}); // regexps newOption('popupDabRegexp', '(\\{\\{\\s*disambig(?!uation needed)|disambig(uation|)\\s*\\}\\}|disamb\\s*\\}\\}|dab\\s*\\}\\})|\\{\\{\\s*(((geo|hn|road?|school|number)dis)|[234][lc][acw]|(road|ship)index)(\\s*[|][^}]*)?\\s*[}][}]|is a .*disambiguation.*page'); newOption('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub'); newOption('popupImageVarsRegexp', 'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo'); } // ENDFILE: options.js // STARTFILE: strings.js //<NOLITE> ////////////////////////////////////////////////// // Translatable strings ////////////////////////////////////////////////// // // See instructions at // https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation pg.string = { ///////////////////////////////////// // summary data, searching etc. ///////////////////////////////////// 'article': 'article', 'category': 'category', 'categories': 'categories', 'image': 'image', 'images': 'images', 'stub': 'stub', 'section stub': 'section stub', 'Empty page': 'Empty page', 'kB': 'kB', 'bytes': 'bytes', 'day': 'day', 'days': 'days', 'hour': 'hour', 'hours': 'hours', 'minute': 'minute', 'minutes': 'minutes', 'second': 'second', 'seconds': 'seconds', 'week': 'week', 'weeks': 'weeks', 'search': 'search', 'SearchHint': 'Find English Wikipedia articles containing %s', 'web': 'web', 'global': 'global', 'globalSearchHint': 'Search across Wikipedias in different languages for %s', 'googleSearchHint': 'Google for %s', ///////////////////////////////////// // article-related actions and info // (some actions also apply to user pages) ///////////////////////////////////// 'actions': 'actions', ///// view articles and view talk 'popupsMenu': 'popups', 'togglePreviewsHint': 'Toggle preview generation in popups on this page', 'enable previews': 'enable previews', 'disable previews': 'disable previews', 'toggle previews': 'toggle previews', 'show preview': 'show preview', 'reset': 'reset', 'more...': 'more...', 'disable': 'disable popups', 'disablePopupsHint': 'Disable popups on this page. Reload page to re-enable.', 'historyfeedHint': 'RSS feed of recent changes to this page', 'purgePopupsHint': 'Reset popups, clearing all cached popup data.', 'PopupsHint': 'Reset popups, clearing all cached popup data.', 'spacebar': 'space', 'view': 'view', 'view article': 'view article', 'viewHint': 'Go to %s', 'talk': 'talk', 'talk page': 'talk page', 'this&nbsp;revision': 'this&nbsp;revision', 'revision %s of %s': 'revision %s of %s', 'Revision %s of %s': 'Revision %s of %s', 'the revision prior to revision %s of %s': 'the revision prior to revision %s of %s', 'Toggle image size': 'Click to toggle image size', 'del': 'del', ///// delete, protect, move 'delete': 'delete', 'deleteHint': 'Delete %s', 'undeleteShort': 'un', 'UndeleteHint': 'Show the deletion history for %s', 'protect': 'protect', 'protectHint': 'Restrict editing rights to %s', 'unprotectShort': 'un', 'unprotectHint': 'Allow %s to be edited by anyone again', 'send thanks': 'send thanks', 'ThanksHint': 'Send a thank you notification to this user', 'move': 'move', 'move page': 'move page', 'MovepageHint': 'Change the title of %s', 'edit': 'edit', ///// edit articles and talk 'edit article': 'edit article', 'editHint': 'Change the content of %s', 'edit talk': 'edit talk', 'new': 'new', 'new topic': 'new topic', 'newSectionHint': 'Start a new section on %s', 'null edit': 'null edit', 'nullEditHint': 'Submit an edit to %s, making no changes ', 'hist': 'hist', ///// history, diffs, editors, related 'history': 'history', 'historyHint': 'List the changes made to %s', 'last': 'prev', // For labelling the previous revision in history pages; the key is "last" for backwards compatibility 'lastEdit': 'lastEdit', 'mark patrolled': 'mark patrolled', 'markpatrolledHint': 'Mark this edit as patrolled', 'Could not marked this edit as patrolled': 'Could not marked this edit as patrolled', 'show last edit': 'most recent edit', 'Show the last edit': 'Show the effects of the most recent change', 'lastContrib': 'lastContrib', 'last set of edits': 'latest edits', 'lastContribHint': 'Show the net effect of changes made by the last editor', 'cur': 'cur', 'diffCur': 'diffCur', 'Show changes since revision %s': 'Show changes since revision %s', '%s old': '%s old', // as in 4 weeks old 'oldEdit': 'oldEdit', 'purge': 'purge', 'purgeHint': 'Demand a fresh copy of %s', 'raw': 'source', 'rawHint': 'Download the source of %s', 'render': 'simple', 'renderHint': 'Show a plain HTML version of %s', 'Show the edit made to get revision': 'Show the edit made to get revision', 'sinceMe': 'sinceMe', 'changes since mine': 'diff my edit', 'sinceMeHint': 'Show changes since my last edit', 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s': 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s', 'eds': 'eds', 'editors': 'editors', 'editorListHint': 'List the users who have edited %s', 'related': 'related', 'relatedChanges': 'relatedChanges', 'related changes': 'related changes', 'RecentchangeslinkedHint': 'Show changes in articles related to %s', 'editOld': 'editOld', ///// edit old version, or revert 'rv': 'rv', 'revert': 'revert', 'revertHint': 'Revert to %s', 'defaultpopupReviewedSummary': 'Accepted by reviewing the [[Special:diff/%s/%s|difference]] between this version and previously accepted version using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRedlinkSummary': 'Removing link to empty page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupFixDabsSummary': 'Disambiguate [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupFixRedirsSummary': 'Redirect bypass from [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupExtendedRevertSummary': 'Revert to revision dated %s by %s, oldid %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRevertToPreviousSummary': 'Revert to the revision prior to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRevertSummary': 'Revert to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupQueriedRevertToPreviousSummary': 'Revert to the revision prior to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupQueriedRevertSummary': 'Revert to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRmDabLinkSummary': 'Remove link to dab page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'Redirects': 'Redirects', // as in Redirects to ... ' to ': ' to ', // as in Redirects to ... 'Bypass redirect': 'Bypass redirect', 'Fix this redirect': 'Fix this redirect', 'disambig': 'disambig', ///// add or remove dab etc. 'disambigHint': 'Disambiguate this link to [[%s]]', 'Click to disambiguate this link to:': 'Click to disambiguate this link to:', 'remove this link': 'remove this link', 'remove all links to this page from this article': 'remove all links to this page from this article', 'remove all links to this disambig page from this article': 'remove all links to this disambig page from this article', 'mainlink': 'mainlink', ///// links, watch, unwatch 'wikiLink': 'wikiLink', 'wikiLinks': 'wikiLinks', 'links here': 'links here', 'whatLinksHere': 'whatLinksHere', 'what links here': 'what links here', 'WhatlinkshereHint': 'List the pages that are hyperlinked to %s', 'unwatchShort': 'un', 'watchThingy': 'watch', // called watchThingy because {}.watch is a function 'watchHint': 'Add %s to my watchlist', 'unwatchHint': 'Remove %s from my watchlist', 'Only found one editor: %s made %s edits': 'Only found one editor: %s made %s edits', '%s seems to be the last editor to the page %s': '%s seems to be the last editor to the page %s', 'rss': 'rss', ///////////////////////////////////// // diff previews ///////////////////////////////////// 'Diff truncated for performance reasons': 'Diff truncated for performance reasons', 'Old revision': 'Old revision', 'New revision': 'New revision', 'Something went wrong :-(': 'Something went wrong :-(', 'Empty revision, maybe non-existent': 'Empty revision, maybe non-existent', 'Unknown date': 'Unknown date', ///////////////////////////////////// // other special previews ///////////////////////////////////// 'Empty category': 'Empty category', 'Category members (%s shown)': 'Category members (%s shown)', 'No image links found': 'No image links found', 'File links': 'File links', 'No image found': 'No image found', 'Image from Commons': 'Image from Commons', 'Description page': 'Description page', 'Alt text:': 'Alt text:', 'revdel':'Hidden revision', ///////////////////////////////////// // user-related actions and info ///////////////////////////////////// 'user': 'user', ///// user page, talk, email, space 'user&nbsp;page': 'user&nbsp;page', 'user talk': 'user talk', 'edit user talk': 'edit user talk', 'leave comment': 'leave comment', 'email': 'email', 'email user': 'email user', 'EmailuserHint': 'Send an email to %s', 'space': 'space', // short form for userSpace link 'PrefixIndexHint': 'Show pages in the userspace of %s', 'count': 'count', ///// contributions, log 'edit counter': 'edit counter', 'editCounterLinkHint': 'Count the contributions made by %s', 'contribs': 'contribs', 'contributions': 'contributions', 'deletedContribs': 'deleted contributions', 'DeletedcontributionsHint': 'List deleted edits made by %s', 'ContributionsHint': 'List the contributions made by %s', 'log': 'log', 'user log': 'user log', 'userLogHint': 'Show %s\'s user log', 'arin': 'ARIN lookup', ///// ARIN lookup, block user or IP 'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database', 'unblockShort': 'un', 'block': 'block', 'block user': 'block user', 'IpblocklistHint': 'Unblock %s', 'BlockipHint': 'Prevent %s from editing', 'block log': 'block log', 'blockLogHint': 'Show the block log for %s', 'protectLogHint': 'Show the protection log for %s', 'pageLogHint': 'Show the page log for %s', 'deleteLogHint': 'Show the deletion log for %s', 'Invalid %s %s': 'The option %s is invalid: %s', 'No backlinks found': 'No backlinks found', ' and more': ' and more', 'undo': 'undo', 'undoHint': 'undo this edit', 'Download preview data': 'Download preview data', 'Invalid or IP user': 'Invalid or IP user', 'Not a registered username': 'Not a registered username', 'BLOCKED': 'BLOCKED', 'Has blocks': 'Has blocks', ' edits since: ': ' edits since: ', 'last edit on ': 'last edit on ', ///////////////////////////////////// // Autoediting ///////////////////////////////////// 'Enter a non-empty edit summary or press cancel to abort': 'Enter a non-empty edit summary or press cancel to abort', 'Failed to get revision information, please edit manually.\n\n': 'Failed to get revision information, please edit manually.\n\n', 'The %s button has been automatically clicked. Please wait for the next page to load.': 'The %s button has been automatically clicked. Please wait for the next page to load.', 'Could not find button %s. Please check the settings in your javascript file.': 'Could not find button %s. Please check the settings in your javascript file.', ///////////////////////////////////// // Popups setup ///////////////////////////////////// 'Open full-size image': 'Open full-size image', 'zxy': 'zxy', 'autoedit_version': 'np20140416' }; function popupString(str) { if (typeof popupStrings != 'undefined' && popupStrings && popupStrings[str]) { return popupStrings[str]; } if (pg.string[str]) { return pg.string[str]; } return str; } function tprintf(str,subs) { if (typeof subs != typeof []) { subs = [subs]; } return simplePrintf(popupString(str), subs); } //</NOLITE> // ENDFILE: strings.js // STARTFILE: run.js //////////////////////////////////////////////////////////////////// // Run things //////////////////////////////////////////////////////////////////// // For some reason popups requires a fully loaded page jQuery.ready(...) causes problems for some. // The old addOnloadHook did something similar to the below if (document.readyState=="complete") autoEdit(); //will setup popups else $( window ).on( 'load', autoEdit ); // Support for MediaWiki's live preview, VisualEditor's saves and Echo's flyout. ( function () { var once = true; function dynamicContentHandler( $content ) { // Try to detect the hook fired on initial page load and disregard // it, we already hook to onload (possibly to different parts of // page - it's configurable) and running twice might be bad. Ugly… if ( $content.attr( 'id' ) == 'mw-content-text' ) { if ( once ) { once = false; return; } } function registerHooksForVisibleNavpops () { for (var i=0; pg.current.links && i<pg.current.links.length; ++i) { var navpop = pg.current.links[i].navpopup; if (!navpop || !navpop.isVisible()) { continue; } Navpopup.tracker.addHook(posCheckerHook(navpop)); } } function doIt () { registerHooksForVisibleNavpops(); $content.each( function () { this.ranSetupTooltipsAlready = false; setupTooltips( this ); } ); } setupPopups( doIt ); } // This hook is also fired after page load. mw.hook( 'wikipage.content' ).add( dynamicContentHandler ); mw.hook( 'ext.echo.overlay.beforeShowingOverlay' ).add( function($overlay){ dynamicContentHandler( $overlay.find(".mw-echo-state") ); }); } )(); // Forked from User:Connel MacKenzie/mess-with-popups.js Previewmaker.prototype.firstBit = function() { try{ if (mw.config.get("wgNamespaceNumber") !== 0){ // dont't be givin' me no subsequent paragraphs, you hear me? /// first we "normalize" section headings, removing whitespace after, adding before var d = this.data; if (getValueOf('popupPreviewCutHeadings')) { this.data = this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 '); /// then we want to get rid of paragraph breaks whose text ends badly this.data = this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n'); this.data = this.data.replace(RegExp('^[\\s\\n]*'), ''); stuff = (RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data); if (stuff) { d = stuff[0]; } if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; } /// now put \n\n after sections so that bullets and numbered lists work d = d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n'); } // superfluous sentences are RIGHT OUT. // note: exactly 1 set of parens here needed to make the slice work d = d.parenSplit(RegExp('([!?.]+["' + "'" + ']*\\s)', 'g')); // leading space is bad, mmkay? d[0] = d[0].replace(RegExp('^\\s*'), ''); var notSentenceEnds = RegExp('([^.][a-z][.][a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|\\[[^\\]]*|\\s[A-Zvclm])$', 'i'); d = this.fixSentenceEnds(d, notSentenceEnds); var maxChars=getValueOf('popupMaxPreviewCharacters'); var n = getValueOf('popupMaxPreviewSentences'); var dd; do { dd = this.firstSentences(d, n); --n; } while (dd.length > maxChars && n > 0); this.data = dd; } else { //TODO: Limit this override to urls that start with http://*.wiktionary.org/ //TODO: extend the above section to also preview Wikipedia links var charsWikt = 0; var linesWikt = this.data.split('\n'); for (jWikt = 0; jWikt < linesWikt.length; jWikt++) { linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{[in]transitive}}/g, ''); linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{[un]countable}}/g, ''); linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{cattag\|(.*?)}}/g, ""); linesWikt[jWikt] = linesWikt[jWikt].replace(/[\{\}]/g, "'"); if (linesWikt[jWikt].search(/^[#]/) == -1) { if (linesWikt[jWikt].search(/^==[A-Z]/) == -1) { linesWikt[jWikt] = ''; } } charsWikt += linesWikt[jWikt].length; } if (charsWikt < 2) { linesWikt[0] = "No properly formatted content"; } var maxChars = getValueOf('popupMaxPreviewCharacters'); var n = getValueOf('popupMaxPreviewSentences'); var dd = ''; for (jWikt = 0; jWikt < linesWikt.length; jWikt++) { if (linesWikt[jWikt] !== '') { dd += linesWikt[jWikt] + '\n'; } if (dd.length < maxChars) { this.data = dd; } } } } catch (e) { console.log("error from mess with popups: ", e); } }; }); cjzg9n7b3b5x4fcokxhd6enhhmtvy05 2513949 2513948 2026-08-14T04:47:22Z Hiyuune 50834 2513949 javascript text/javascript // {{documentation}} // _________________________________________________________________________________________ // | | // | === WARNING: GLOBAL GADGET FILE === | // | Changes to this page affect many users. | // | Please discuss changes on the [[Wiktionary:Grease Pit]] before editing. | // |_________________________________________________________________________________________| mw.loader.load('//en.wikipedia.org/w/index.php?action=raw&title=MediaWiki:Gadget-navpop.css&ctype=text/css', "text/css"); // Forked from [[:w:MediaWiki:Gadget-popups.js]] $(function () { ////////////////////////////////////////////////// // Globals // // Trying to shove as many of these as possible into the pg (popup globals) object var pg = { re: {}, // regexps ns: {}, // namespaces string: {}, // translatable strings wiki: {}, // local site info user: {}, // current user info misc: {}, // YUCK PHOOEY option: {}, // options, see newOption etc optionDefault: {}, // default option values flag: {}, // misc flags cache: {}, // page and image cache structures: {}, // navlink structures timer: {}, // all sorts of timers (too damn many) counter: {}, // .. and all sorts of counters current: {}, // state info fn: {}, // functions endoflist: null }; /* Bail if the gadget/script is being loaded twice */ if( window.pg ) { return; } /* Export to global context */ window.pg = pg; /// Local Variables: /// /// mode:c /// /// End: /// // ENDFILE: main.js // STARTFILE: actions.js function setupTooltips(container, remove, force, popData) { log('setupTooltips, container='+container+', remove='+remove); if (!container) { //<NOLITE> // the main initial call if (getValueOf('popupOnEditSelection') && document && document.editform && document.editform.wpTextbox1) { document.editform.wpTextbox1.onmouseup=doSelectionPopup; } //</NOLITE> // article/content is a structure-dependent thing container = defaultPopupsContainer(); } if (!remove && !force && container.ranSetupTooltipsAlready) { return; } container.ranSetupTooltipsAlready = !remove; var anchors; anchors=container.getElementsByTagName('A'); setupTooltipsLoop(anchors, 0, 250, 100, remove, popData); } function defaultPopupsContainer() { if (getValueOf('popupOnlyArticleLinks')) { return document.getElementById('mw_content') || document.getElementById('content') || document.getElementById('article') || document; } return document; } function setupTooltipsLoop(anchors,begin,howmany,sleep, remove, popData) { log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments)); var finish=begin+howmany; var loopend = Math.min(finish, anchors.length); var j=loopend - begin; log ('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin + ', howmany=' + howmany + ', loopend=' + loopend + ', remove=' + remove); var doTooltip= remove ? removeTooltip : addTooltip; // try a faster (?) loop construct if (j > 0) { do { var a=anchors[loopend - j]; if (typeof a==='undefined' || !a || !a.href) { log('got null anchor at index ' + loopend - j); continue; } doTooltip(a, popData); } while (--j); } if (finish < anchors.length) { setTimeout(function() { setupTooltipsLoop(anchors,finish,howmany,sleep,remove,popData);}, sleep); } else { if ( !remove && ! getValueOf('popupTocLinks')) { rmTocTooltips(); } pg.flag.finishedLoading=true; } } // eliminate popups from the TOC // This also kills any onclick stuff that used to be going on in the toc function rmTocTooltips() { var toc=document.getElementById('toc'); if (toc) { var tocLinks=toc.getElementsByTagName('A'); var tocLen = tocLinks.length; for (var j=0; j<tocLen; ++j) { removeTooltip(tocLinks[j], true); } } } function addTooltip(a, popData) { if ( !isPopupLink(a) ) { return; } a.onmouseover=mouseOverWikiLink; a.onmouseout= mouseOutWikiLink; a.onmousedown = killPopup; a.hasPopup = true; a.popData = popData; } function removeTooltip(a) { if ( !a.hasPopup ) { return; } a.onmouseover = null; a.onmouseout = null; if (a.originalTitle) { a.title = a.originalTitle; } a.hasPopup=false; } function removeTitle(a) { if (!a.originalTitle) { a.originalTitle=a.title; } a.title=''; } function restoreTitle(a) { if ( a.title || !a.originalTitle ) { return; } a.title = a.originalTitle; } function registerHooks(np) { var popupMaxWidth=getValueOf('popupMaxWidth'); if (typeof popupMaxWidth === 'number') { var setMaxWidth = function () { np.mainDiv.style.maxWidth = popupMaxWidth + 'px'; np.maxWidth = popupMaxWidth; }; np.addHook(setMaxWidth, 'unhide', 'before'); } //<NOLITE> np.addHook(addPopupShortcuts, 'unhide', 'after'); np.addHook(rmPopupShortcuts, 'hide', 'before'); //</NOLITE> } function removeModifierKeyHandler(a) { //remove listeners for modifier key if any that were added in mouseOverWikiLink document.removeEventListener('keydown', a.modifierKeyHandler, false); document.removeEventListener('keyup', a.modifierKeyHandler, false); } function mouseOverWikiLink(evt) { if (!evt && window.event) {evt=window.event;} // if the modifier is needed, listen for it, // we will remove the listener when we mouseout of this link or kill popup. if (getValueOf('popupModifier')) { // if popupModifierAction = enable, we should popup when the modifier is pressed // if popupModifierAction = disable, we should popup unless the modifier is pressed var action = getValueOf('popupModifierAction'); var key = action=='disable' ? 'keyup' : 'keydown'; var a = this; a.modifierKeyHandler = function(evt) { mouseOverWikiLink2(a, evt); }; document.addEventListener(key, a.modifierKeyHandler, false); } return mouseOverWikiLink2(this, evt); } /** * Gets the references list item that the provided footnote link targets. This * is typically a li element within the ol.references element inside the reflist. * @param {Element} a - A footnote link. * @returns {Element|boolean} The targeted element, or false if one can't be found. */ function footnoteTarget(a) { var aTitle=Title.fromAnchor(a); // We want ".3A" rather than "%3A" or "?" here, so use the anchor property directly var anch = aTitle.anchor; if ( ! /^(cite_note-|_note-|endnote)/.test(anch) ) { return false; } var lTitle=Title.fromURL(location.href); if ( lTitle.toString(true) !== aTitle.toString(true) ) { return false; } var el=document.getElementById(anch); while ( el && typeof el.nodeName === 'string') { var nt = el.nodeName.toLowerCase(); if ( nt === 'li' ) { return el; } else if ( nt === 'body' ) { return false; } else if ( el.parentNode ) { el=el.parentNode; } else { return false; } } return false; } function footnotePreview(x, navpop) { setPopupHTML('<hr />' + x.innerHTML, 'popupPreview', navpop.idNumber); } function modifierPressed(evt) { var mod=getValueOf('popupModifier'); if (!mod) { return false; } if (!evt && window.event) {evt=window.event;} return ( evt && mod && evt[mod.toLowerCase() + 'Key'] ); } // Checks if the correct modifier pressed/unpressed if needed function isCorrectModifier(a,evt) { if (!getValueOf('popupModifier')) { return true; } // if popupModifierAction = enable, we should popup when the modifier is pressed // if popupModifierAction = disable, we should popup unless the modifier is pressed var action = getValueOf('popupModifierAction'); return ( action == 'enable' && modifierPressed(evt) || action == 'disable' && !modifierPressed(evt) ); } function mouseOverWikiLink2(a, evt) { if (!isCorrectModifier(a,evt)) { return; } if ( getValueOf('removeTitles') ) { removeTitle(a); } if ( a==pg.current.link && a.navpopup && a.navpopup.isVisible() ) { return; } pg.current.link=a; if (getValueOf('simplePopups') && !pg.option.popupStructure) { // reset *default value* of popupStructure setDefault('popupStructure', 'original'); } var article=(new Title()).fromAnchor(a); // set global variable (ugh) to hold article (wikipage) pg.current.article = article; if (!a.navpopup) { a.navpopup=newNavpopup(a, article); pg.current.linksHash[a.href] = a.navpopup; pg.current.links.push(a); } if (a.navpopup.pending === null || a.navpopup.pending !== 0) { // either fresh popups or those with unfinshed business are redone from scratch simplePopupContent(a, article); } a.navpopup.showSoonIfStable(a.navpopup.delay); clearInterval(pg.timer.checkPopupPosition); pg.timer.checkPopupPosition=setInterval(checkPopupPosition, 600); if(getValueOf('simplePopups')) { if (getValueOf('popupPreviewButton') && !a.simpleNoMore) { var d=document.createElement('div'); d.className='popupPreviewButtonDiv'; var s=document.createElement('span'); d.appendChild(s); s.className='popupPreviewButton'; s['on' + getValueOf('popupPreviewButtonEvent')] = function() { a.simpleNoMore=true; d.style.display = "none"; nonsimplePopupContent(a,article); }; s.innerHTML=popupString('show preview'); setPopupHTML(d, 'popupPreview', a.navpopup.idNumber); } } if (a.navpopup.pending !== 0 ) { nonsimplePopupContent(a, article); } } // simplePopupContent: the content that do not require additional download // (it is shown even when simplePopups is true) function simplePopupContent(a, article) { /* FIXME hack */ a.navpopup.hasPopupMenu=false; a.navpopup.setInnerHTML(popupHTML(a)); fillEmptySpans({navpopup:a.navpopup}); if (getValueOf('popupDraggable')) { var dragHandle = getValueOf('popupDragHandle') || null; if (dragHandle && dragHandle != 'all') { dragHandle += a.navpopup.idNumber; } setTimeout(function(){a.navpopup.makeDraggable(dragHandle);}, 150); } //<NOLITE> if (getValueOf('popupRedlinkRemoval') && a.className=='new') { setPopupHTML('<br>'+popupRedlinkHTML(article), 'popupRedlink', a.navpopup.idNumber); } //</NOLITE> } function debugData(navpopup) { if(getValueOf('popupDebugging') && navpopup.idNumber) { setPopupHTML('idNumber='+navpopup.idNumber + ', pending=' + navpopup.pending, 'popupError', navpopup.idNumber); } } function newNavpopup(a, article) { var navpopup = new Navpopup(); navpopup.fuzz=5; navpopup.delay=getValueOf('popupDelay')*1000; // increment global counter now navpopup.idNumber = ++pg.idNumber; navpopup.parentAnchor = a; navpopup.parentPopup = (a.popData && a.popData.owner); navpopup.article = article; registerHooks(navpopup); return navpopup; } // Should we show nonsimple context? // If simplePopups is set to true, then we do not show nonsimple context, // but if a bottom "show preview" was clicked we do show nonsimple context function shouldShowNonSimple(a) { return !getValueOf('simplePopups') || a.simpleNoMore; } // Should we show nonsimple context govern by the option (e.g. popupUserInfo)? // If the user explicitly asked for nonsimple context by setting the option to true, // then we show it even in nonsimple mode. function shouldShow(a,option) { if (shouldShowNonSimple(a)) { return getValueOf(option); } else { return (typeof window[option] != 'undefined' ) && window[option]; } } function nonsimplePopupContent(a, article) { var diff=null, history=null; var params=parseParams(a.href); var oldid=(typeof params.oldid=='undefined' ? null : params.oldid); //<NOLITE> if(shouldShow(a,'popupPreviewDiffs')) { diff=params.diff; } if(shouldShow(a,'popupPreviewHistory')) { history=(params.action=='history'); } //</NOLITE> a.navpopup.pending=0; var referenceElement = footnoteTarget(a); if (referenceElement) { footnotePreview(referenceElement, a.navpopup); //<NOLITE> } else if ( diff || diff === 0 ) { loadDiff(article, oldid, diff, a.navpopup); } else if ( history ) { loadAPIPreview('history', article, a.navpopup); } else if ( shouldShowNonSimple(a) && pg.re.contribs.test(a.href) ) { loadAPIPreview('contribs', article, a.navpopup); } else if ( shouldShowNonSimple(a) && pg.re.backlinks.test(a.href) ) { loadAPIPreview('backlinks', article, a.navpopup); } else if ( // FIXME should be able to get all preview combinations with options article.namespaceId()==pg.nsImageId && ( shouldShow(a,'imagePopupsForImages') || ! anchorContainsImage(a) ) ) { loadAPIPreview('imagepagepreview', article, a.navpopup); loadImage(article, a.navpopup); //</NOLITE> } else { if (article.namespaceId() == pg.nsCategoryId && shouldShow(a,'popupCategoryMembers')) { loadAPIPreview('category', article, a.navpopup); } else if ((article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) && shouldShow(a,'popupUserInfo')) { loadAPIPreview('userinfo', article, a.navpopup); } if (shouldShowNonSimple(a)) startArticlePreview(article, oldid, a.navpopup); } } function pendingNavpopTask(navpop) { if (navpop && navpop.pending === null) { navpop.pending=0; } ++navpop.pending; debugData(navpop); } function completedNavpopTask(navpop) { if (navpop && navpop.pending) { --navpop.pending; } debugData(navpop); } function startArticlePreview(article, oldid, navpop) { navpop.redir=0; loadPreview(article, oldid, navpop); } function loadPreview(article, oldid, navpop) { if (!navpop.redir) { navpop.originalArticle=article; } article.oldid = oldid; loadAPIPreview('revision', article, navpop); } function loadPreviewFromRedir(redirMatch, navpop) { // redirMatch is a regex match var target = new Title().fromWikiText(redirMatch[2]); // overwrite (or add) anchor from original target // mediawiki does overwrite; eg User:Lupin/foo3#Done if ( navpop.article.anchor ) { target.anchor = navpop.article.anchor; } navpop.redir++; navpop.redirTarget=target; //<NOLITE> var warnRedir = redirLink(target, navpop.article); setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber); //</NOLITE> navpop.article=target; fillEmptySpans({redir: true, redirTarget: target, navpopup:navpop}); return loadPreview(target, null, navpop); } function insertPreview(download) { if (!download.owner) { return; } var redirMatch = pg.re.redirect.exec(download.data); if (download.owner.redir === 0 && redirMatch) { loadPreviewFromRedir(redirMatch, download.owner); return; } if (download.owner.visible || !getValueOf('popupLazyPreviews')) { insertPreviewNow(download); } else { var id=(download.owner.redir) ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK'; download.owner.addHook( function(){insertPreviewNow(download); return true;}, 'unhide', 'after', id ); } } function insertPreviewNow(download) { if (!download.owner) { return; } var wikiText=download.data; var navpop=download.owner; var art=navpop.redirTarget || navpop.originalArticle; //<NOLITE> makeFixDabs(wikiText, navpop); if (getValueOf('popupSummaryData')) { getPageInfo(wikiText, download); setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber); } var imagePage=''; if (art.namespaceId()==pg.nsImageId) { imagePage=art.toString(); } else { imagePage=getValidImageFromWikiText(wikiText); } if(imagePage) { loadImage(Title.fromWikiText(imagePage), navpop); } //</NOLITE> if (getValueOf('popupPreviews')) { insertArticlePreview(download, art, navpop); } } function insertArticlePreview(download, art, navpop) { if (download && typeof download.data == typeof ''){ if (art.namespaceId()==pg.nsTemplateId && getValueOf('popupPreviewRawTemplates')) { // FIXME compare/consolidate with diff escaping code for wikitext var h='<hr /><span style="font-family: monospace;">' + download.data.entify().split('\\n').join('<br />\\n') + '</span>'; setPopupHTML(h, 'popupPreview', navpop.idNumber); } else { var p=prepPreviewmaker(download.data, art, navpop); p.showPreview(); } } } function prepPreviewmaker(data, article, navpop) { // deal with tricksy anchors var d=anchorize(data, article.anchorString()); var urlBase=joinPath([pg.wiki.articlebase, article.urlString()]); var p=new Previewmaker(d, urlBase, navpop); return p; } // Try to imitate the way mediawiki generates HTML anchors from section titles function anchorize(d, anch) { if (!anch) { return d; } var anchRe=RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*'+getValueOf('popupAnchorRegexp')+'\\s*(?:\\|[^|}]*)*?\\s*'+literalizeRegex(anch)+'\\s*(?:\\|[^}]*)?}})'); var match=d.match(anchRe); if(match && match.length > 0 && match[0]) { return d.substring(d.indexOf(match[0])); } // now try to deal with == foo [[bar|baz]] boom == -> #foo_baz_boom var lines=d.split('\n'); for (var i=0; i<lines.length; ++i) { lines[i]=lines[i].replace(RegExp('[[]{2}([^|\\]]*?[|])?(.*?)[\\]]{2}', 'g'), '$2') .replace(/'''([^'])/g, '$1').replace(RegExp("''([^'])", 'g'), '$1'); if (lines[i].match(anchRe)) { return d.split('\n').slice(i).join('\n').replace(RegExp('^[^=]*'), ''); } } return d; } function killPopup() { removeModifierKeyHandler(this); if (getValueOf('popupShortcutKeys')) { rmPopupShortcuts(); } if (!pg) { return; } if (pg.current.link && pg.current.link.navpopup) { pg.current.link.navpopup.banish(); } pg.current.link=null; abortAllDownloads(); if (pg.timer.checkPopupPosition) { clearInterval(pg.timer.checkPopupPosition); pg.timer.checkPopupPosition=null; } return true; // preserve default action } // ENDFILE: actions.js // STARTFILE: domdrag.js /** @fileoverview The {@link Drag} object, which enables objects to be dragged around. <pre> ************************************************* dom-drag.js 09.25.2001 www.youngpup.net ************************************************** 10.28.2001 - fixed minor bug where events sometimes fired off the handle, not the root. ************************************************* Pared down, some hooks added by User:Lupin Copyright Aaron Boodman. Saying stupid things daily since March 2001. </pre> */ /** Creates a new Drag object. This is used to make various DOM elements draggable. @constructor */ function Drag () { /** Condition to determine whether or not to drag. This function should take one parameter, an Event. To disable this, set it to <code>null</code>. @type Function */ this.startCondition = null; /** Hook to be run when the drag finishes. This is passed the final coordinates of the dragged object (two integers, x and y). To disables this, set it to <code>null</code>. @type Function */ this.endHook = null; } /** Gets an event in a cross-browser manner. @param {Event} e @private */ Drag.prototype.fixE = function(e) { if (typeof e == 'undefined') { e = window.event; } if (typeof e.layerX == 'undefined') { e.layerX = e.offsetX; } if (typeof e.layerY == 'undefined') { e.layerY = e.offsetY; } return e; }; /** Initialises the Drag instance by telling it which object you want to be draggable, and what you want to drag it by. @param {DOMElement} o The "handle" by which <code>oRoot</code> is dragged. @param {DOMElement} oRoot The object which moves when <code>o</code> is dragged, or <code>o</code> if omitted. */ Drag.prototype.init = function(o, oRoot) { var dragObj = this; this.obj = o; o.onmousedown = function(e) { dragObj.start.apply( dragObj, [e]); }; o.dragging = false; o.popups_draggable = true; o.hmode = true; o.vmode = true; o.root = oRoot ? oRoot : o ; if (isNaN(parseInt(o.root.style.left, 10))) { o.root.style.left = "0px"; } if (isNaN(parseInt(o.root.style.top, 10))) { o.root.style.top = "0px"; } o.root.onthisStart = function(){}; o.root.onthisEnd = function(){}; o.root.onthis = function(){}; }; /** Starts the drag. @private @param {Event} e */ Drag.prototype.start = function(e) { var o = this.obj; // = this; e = this.fixE(e); if (this.startCondition && !this.startCondition(e)) { return; } var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10); o.root.onthisStart(x, y); o.lastMouseX = e.clientX; o.lastMouseY = e.clientY; var dragObj = this; o.onmousemoveDefault = document.onmousemove; o.dragging = true; document.onmousemove = function(e) { dragObj.drag.apply( dragObj, [e] ); }; document.onmouseup = function(e) { dragObj.end.apply( dragObj, [e] ); }; return false; }; /** Does the drag. @param {Event} e @private */ Drag.prototype.drag = function(e) { e = this.fixE(e); var o = this.obj; var ey = e.clientY; var ex = e.clientX; var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10 ); var nx, ny; nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1)); ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1)); this.obj.root.style[o.hmode ? "left" : "right"] = nx + "px"; this.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px"; this.obj.lastMouseX = ex; this.obj.lastMouseY = ey; this.obj.root.onthis(nx, ny); return false; }; /** Ends the drag. @private */ Drag.prototype.end = function() { document.onmousemove=this.obj.onmousemoveDefault; document.onmouseup = null; this.obj.dragging = false; if (this.endHook) { this.endHook( parseInt(this.obj.root.style[this.obj.hmode ? "left" : "right"], 10), parseInt(this.obj.root.style[this.obj.vmode ? "top" : "bottom"], 10)); } }; // ENDFILE: domdrag.js // STARTFILE: structures.js //<NOLITE> pg.structures.original={}; pg.structures.original.popupLayout=function () { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupUserData', 'popupData', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; pg.structures.original.popupRedirSpans=function () { return ['popupRedir', 'popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks']; }; pg.structures.original.popupTitle=function (x) { log ('defaultstructure.popupTitle'); if (!getValueOf('popupNavLinks')) { return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params); } return ''; }; pg.structures.original.popupTopLinks=function (x) { log ('defaultstructure.popupTopLinks'); if (getValueOf('popupNavLinks')) { return navLinksHTML(x.article, x.hint, x.params); } return ''; }; pg.structures.original.popupImage=function(x) { log ('original.popupImage, x.article='+x.article+', x.navpop.idNumber='+x.navpop.idNumber); return imageHTML(x.article, x.navpop.idNumber); }; pg.structures.original.popupRedirTitle=pg.structures.original.popupTitle; pg.structures.original.popupRedirTopLinks=pg.structures.original.popupTopLinks; function copyStructure(oldStructure, newStructure) { pg.structures[newStructure]={}; for (var prop in pg.structures[oldStructure]) { pg.structures[newStructure][prop]=pg.structures[oldStructure][prop]; } } copyStructure('original', 'nostalgia'); pg.structures.nostalgia.popupTopLinks=function(x) { var str=''; str += '<b><<mainlink|shortcut= >></b>'; // user links // contribs - log - count - email - block // count only if applicable; block only if popupAdminLinks str += 'if(user){<br><<contribs|shortcut=c>>'; str+='if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}'; // editing links // talkpage -> edit|new - history - un|watch - article|edit // other page -> edit - history - un|watch - talk|edit|new var editstr='<<edit|shortcut=e>>'; var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'; var historystr='<<history|shortcut=h>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; str += '<br>if(talk){' + editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + // not a talk page editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}'; // misc links str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>'; str += 'if(admin){<br>}else{*}<<move|shortcut=m>>'; // admin links str += 'if(admin){*<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>}'; return navlinkStringToHTML(str, x.article, x.params); }; pg.structures.nostalgia.popupRedirTopLinks=pg.structures.nostalgia.popupTopLinks; /** -- fancy -- **/ copyStructure('original', 'fancy'); pg.structures.fancy.popupTitle=function (x) { return navlinkStringToHTML('<font size=+0><<mainlink>></font>',x.article,x.params); }; pg.structures.fancy.popupTopLinks=function(x) { var hist='<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>|<<editors|shortcut=E|eds>>'; var watch='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; var move='<<move|shortcut=m|move>>'; return navlinkStringToHTML('if(talk){' + '<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' + hist + '*' + '<<article|shortcut=a>>|<<editArticle|edit>>' + '*' + watch + '*' + move + '}else{<<edit|shortcut=e>>*' + hist + '*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' + '*' + watch + '*' + move+'}<br>', x.article, x.params); }; pg.structures.fancy.popupOtherLinks=function(x) { var admin='<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*<<undelete|undeleteShort>>|<<delete|shortcut=d|del>>'; var user='<<contribs|shortcut=c>>if(wikimedia){|<<count|shortcut=#|#>>}'; user+='if(ipuser){|<<arin>>}else{*<<email|shortcut=E|'+ popupString('email')+'>>}if(admin){*<<block|shortcut=b>>}'; var normal='<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>'; return navlinkStringToHTML('<br>if(user){' + user + '*}if(admin){'+admin+'if(user){<br>}else{*}}' + normal, x.article, x.params); }; pg.structures.fancy.popupRedirTitle=pg.structures.fancy.popupTitle; pg.structures.fancy.popupRedirTopLinks=pg.structures.fancy.popupTopLinks; pg.structures.fancy.popupRedirOtherLinks=pg.structures.fancy.popupOtherLinks; /** -- fancy2 -- **/ // hack for User:MacGyverMagic copyStructure('fancy', 'fancy2'); pg.structures.fancy2.popupTopLinks=function(x) { // hack out the <br> at the end and put one at the beginning return '<br>'+pg.structures.fancy.popupTopLinks(x).replace(RegExp('<br>$','i'),''); }; pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title return ['popupError', 'popupImage', 'popupTitle', 'popupUserData', 'popupData', 'popupTopLinks', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; /** -- menus -- **/ copyStructure('original', 'menus'); pg.structures.menus.popupLayout=function () { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupUserData', 'popupData', 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; pg.structures.menus.popupTopLinks = function (x, shorter) { // FIXME maybe this stuff should be cached var s=[]; var dropdiv='<div class="popup_drop">'; var enddiv='</div>'; var hist='<<history|shortcut=h>>'; if (!shorter) { hist = '<menurow>' + hist + '|<<historyfeed|rss>>|<<editors|shortcut=E>></menurow>'; } var lastedit='<<lastEdit|shortcut=/|show last edit>>'; var thank='if(diff){<<thank|send thanks>>}'; var jsHistory='<<lastContrib|last set of edits>><<sinceMe|changes since mine>>'; var linkshere='<<whatLinksHere|shortcut=l|what links here>>'; var related='<<relatedChanges|shortcut=r|related changes>>'; var search='<menurow><<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}' + '|<<google|shortcut=G|web>></menurow>'; var watch='<menurow><<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>></menurow>'; var protect='<menurow><<unprotect|unprotectShort>>|' + '<<protect|shortcut=p>>|<<protectlog|log>></menurow>'; var del='<menurow><<undelete|undeleteShort>>|<<delete|shortcut=d>>|' + '<<deletelog|log>></menurow>'; var move='<<move|shortcut=m|move page>>'; var nullPurge='<menurow><<nullEdit|shortcut=n|null edit>>|<<purge|shortcut=P>></menurow>'; var viewOptions='<menurow><<view|shortcut=v>>|<<render|shortcut=S>>|<<raw>></menurow>'; var editRow='if(oldid){' + '<menurow><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></menurow>' + '<menurow><<revert|shortcut=v>>|<<undo>></menurow>' + '}else{<<edit|shortcut=e>>}'; var markPatrolled='if(rcid){<<markpatrolled|mark patrolled>>}'; var newTopic='if(talk){<<new|shortcut=+|new topic>>}'; var protectDelete='if(admin){' + protect + del + '}'; if (getValueOf('popupActionsMenu')) { s.push( '<<mainlink>>*' + dropdiv + menuTitle('actions')); } else { s.push( dropdiv + '<<mainlink>>'); } s.push( '<menu>'); s.push( editRow + markPatrolled + newTopic + hist + lastedit + thank ); if (!shorter) { s.push(jsHistory); } s.push( move + linkshere + related); if (!shorter) { s.push(nullPurge + search); } if (!shorter) { s.push(viewOptions); } s.push('<hr />' + watch + protectDelete); s.push('<hr />' + 'if(talk){<<article|shortcut=a|view article>><<editArticle|edit article>>}' + 'else{<<talk|shortcut=t|talk page>><<editTalk|edit talk>>' + '<<newTalk|shortcut=+|new topic>>}</menu>' + enddiv); // user menu starts here var email='<<email|shortcut=E|email user>>'; var contribs= 'if(wikimedia){<menurow>}<<contribs|shortcut=c|contributions>>if(wikimedia){</menurow>}' + 'if(admin){<menurow><<deletedContribs>></menurow>}'; s.push('if(user){*' + dropdiv + menuTitle('user')); s.push('<menu>'); s.push('<menurow><<userPage|shortcut=u|user&nbsp;page>>|<<userSpace|space>></menurow>'); s.push('<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' + '<<newUserTalk|shortcut=+|leave comment>>'); if(!shorter) { s.push( 'if(ipuser){<<arin>>}else{' + email + '}' ); } else { s.push( 'if(ipuser){}else{' + email + '}' ); } s.push('<hr />' + contribs + '<<userlog|shortcut=L|user log>>'); s.push('if(wikimedia){<<count|shortcut=#|edit counter>>}'); s.push('if(admin){<menurow><<unblock|unblockShort>>|<<block|shortcut=b|block user>></menurow>}'); s.push('<<blocklog|shortcut=B|block log>>'); s.push('</menu>' + enddiv + '}'); // popups menu starts here if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */) { x.navpop.hasPopupMenu=true; s.push('*' + dropdiv + menuTitle('popupsMenu') + '<menu>'); s.push('<<togglePreviews|toggle previews>>'); s.push('<<purgePopups|reset>>'); s.push('<<disablePopups|disable>>'); s.push('</menu>'+enddiv); } return navlinkStringToHTML(s.join(''), x.article, x.params); }; function menuTitle(s) { return '<a href="#" noPopup=1>' + popupString(s) + '</a>'; } pg.structures.menus.popupRedirTitle=pg.structures.menus.popupTitle; pg.structures.menus.popupRedirTopLinks=pg.structures.menus.popupTopLinks; copyStructure('menus', 'shortmenus'); pg.structures.shortmenus.popupTopLinks=function(x) { return pg.structures.menus.popupTopLinks(x,true); }; pg.structures.shortmenus.popupRedirTopLinks=pg.structures.shortmenus.popupTopLinks; //</NOLITE> pg.structures.lite={}; pg.structures.lite.popupLayout=function () { return ['popupTitle', 'popupPreview' ]; }; pg.structures.lite.popupTitle=function (x) { log (x.article + ': structures.lite.popupTitle'); //return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params); return '<div><span class="popup_mainlink"><b>' + x.article.toString() + '</b></span></div>'; }; // ENDFILE: structures.js // STARTFILE: autoedit.js //<NOLITE> function substitute(data,cmdBody) { // alert('sub\nfrom: '+cmdBody.from+'\nto: '+cmdBody.to+'\nflags: '+cmdBody.flags); var fromRe=RegExp(cmdBody.from, cmdBody.flags); return data.replace(fromRe, cmdBody.to); } function execCmds(data, cmdList) { for (var i=0; i<cmdList.length; ++i) { data=cmdList[i].action(data, cmdList[i]); } return data; } function parseCmd(str) { // returns a list of commands if (!str.length) { return []; } var p=false; switch (str.charAt(0)) { case 's': p=parseSubstitute(str); break; default: return false; } if (p) { return [p].concat(parseCmd(p.remainder)); } return false; } function unEscape(str, sep) { return str.split('\\\\').join('\\').split('\\'+sep).join(sep).split('\\n').join('\n'); } function parseSubstitute(str) { // takes a string like s/a/b/flags;othercmds and parses it var from,to,flags,tmp; if (str.length<4) { return false; } var sep=str.charAt(1); str=str.substring(2); tmp=skipOver(str,sep); if (tmp) { from=tmp.segment; str=tmp.remainder; } else { return false; } tmp=skipOver(str,sep); if (tmp) { to=tmp.segment; str=tmp.remainder; } else { return false; } flags=''; if (str.length) { tmp=skipOver(str,';') || skipToEnd(str, ';'); if (tmp) {flags=tmp.segment; str=tmp.remainder; } } return {action: substitute, from: from, to: to, flags: flags, remainder: str}; } function skipOver(str,sep) { var endSegment=findNext(str,sep); if (endSegment<0) { return false; } var segment=unEscape(str.substring(0,endSegment), sep); return {segment: segment, remainder: str.substring(endSegment+1)}; } /*eslint-disable*/ function skipToEnd(str,sep) { return {segment: str, remainder: ''}; } /*eslint-enable */ function findNext(str, ch) { for (var i=0; i<str.length; ++i) { if (str.charAt(i)=='\\') { i+=2; } if (str.charAt(i)==ch) { return i; } } return -1; } function setCheckbox(param, box) { var val=mw.util.getParamValue(param); if (val) { switch (val) { case '1': case 'yes': case 'true': box.checked=true; break; case '0': case 'no': case 'false': box.checked=false; } } } function autoEdit() { setupPopups( function () { if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version') ) { return false; } if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken')===autoClickToken()) { pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action')); } if (!document.editform) { return false; } if (autoEdit.alreadyRan) { return false; } autoEdit.alreadyRan=true; var cmdString=mw.util.getParamValue('autoedit'); if (cmdString) { try { var editbox=document.editform.wpTextbox1; var cmdList=parseCmd(cmdString); var input=editbox.value; var output=execCmds(input, cmdList); editbox.value=output; } catch (dang) { return; } // wikEd user script compatibility if (typeof(wikEdUseWikEd) != 'undefined') { if (wikEdUseWikEd === true) { WikEdUpdateFrame(); } } } setCheckbox('autominor', document.editform.wpMinoredit); setCheckbox('autowatch', document.editform.wpWatchthis); var rvid = mw.util.getParamValue('autorv'); if (rvid) { var url=pg.wiki.apiwikibase + '?action=query&format=json&formatversion=2&prop=revisions&revids='+rvid; startDownload(url, null, autoEdit2); } else { autoEdit2(); } } ); } function autoEdit2(d) { var summary=mw.util.getParamValue('autosummary'); var summaryprompt=mw.util.getParamValue('autosummaryprompt'); var summarynotice=''; if (d && d.data && mw.util.getParamValue('autorv')) { var s = getRvSummary(summary, d.data); if (s === false) { summaryprompt=true; summarynotice=popupString('Failed to get revision information, please edit manually.\n\n'); summary = simplePrintf(summary, [mw.util.getParamValue('autorv'), '(unknown)', '(unknown)']); } else { summary = s; } } if (summaryprompt) { var txt= summarynotice + popupString('Enter a non-empty edit summary or press cancel to abort'); var response=prompt(txt, summary); if (response) { summary=response; } else { return; } } if (summary) { document.editform.wpSummary.value=summary; } // Attempt to avoid possible premature clicking of the save button // (maybe delays in updates to the DOM are to blame?? or a red herring) setTimeout(autoEdit3, 100); } function autoClickToken() { return mw.user.sessionId(); } function autoEdit3() { if( mw.util.getParamValue('actoken') != autoClickToken()) { return; } var btn=mw.util.getParamValue('autoclick'); if (btn) { if (document.editform && document.editform[btn]) { var button=document.editform[btn]; var msg=tprintf('The %s button has been automatically clicked. Please wait for the next page to load.', [ button.value ]); bannerMessage(msg); document.title='('+document.title+')'; button.click(); } else { alert(tprintf('Could not find button %s. Please check the settings in your javascript file.', [ btn ])); } } } function bannerMessage(s) { var headings=document.getElementsByTagName('h1'); if (headings) { var div=document.createElement('div'); div.innerHTML='<font size=+1><b>' + s + '</b></font>'; headings[0].parentNode.insertBefore(div, headings[0]); } } function getRvSummary(template, json) { try { var o=getJsObj(json); var edit = anyChild(o.query.pages).revisions[0]; var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, ''); return simplePrintf(template, [edit.revid, timestamp, edit.userhidden ? '(hidden)' : edit.user ]); } catch (badness) { return false; } } //</NOLITE> // ENDFILE: autoedit.js // STARTFILE: downloader.js /** @fileoverview {@link Downloader}, a xmlhttprequest wrapper, and helper functions. */ /** Creates a new Downloader @constructor @class The Downloader class. Create a new instance of this class to download stuff. @param {String} url The url to download. This can be omitted and supplied later. */ function Downloader(url) { if (typeof XMLHttpRequest!='undefined') { this.http = new XMLHttpRequest(); } /** The url to download @type String */ this.url = url; /** A universally unique ID number @type integer */ this.id=null; /** Modification date, to be culled from the incoming headers @type Date @private */ this.lastModified = null; /** What to do when the download completes successfully @type Function @private */ this.callbackFunction = null; /** What to do on failure @type Function @private */ this.onFailure = null; /** Flag set on <code>abort</code> @type boolean */ this.aborted = false; /** HTTP method. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for possibilities. @type String */ this.method='GET'; /** Async flag. @type boolean */ this.async=true; } new Downloader(); /** Submits the http request. */ Downloader.prototype.send = function (x) { if (!this.http) { return null; } return this.http.send(x); }; /** Aborts the download, setting the <code>aborted</code> field to true. */ Downloader.prototype.abort = function () { if (!this.http) { return null; } this.aborted=true; return this.http.abort(); }; /** Returns the downloaded data. */ Downloader.prototype.getData = function () {if (!this.http) { return null; } return this.http.responseText;}; /** Prepares the download. */ Downloader.prototype.setTarget = function () { if (!this.http) { return null; } this.http.open(this.method, this.url, this.async); this.http.setRequestHeader( 'Api-User-Agent', pg.misc.userAgent ); }; /** Gets the state of the download. */ Downloader.prototype.getReadyState=function () {if (!this.http) { return null; } return this.http.readyState;}; pg.misc.downloadsInProgress = { }; /** Starts the download. Note that setTarget {@link Downloader#setTarget} must be run first */ Downloader.prototype.start=function () { if (!this.http) { return; } pg.misc.downloadsInProgress[this.id] = this; this.http.send(null); }; /** Gets the 'Last-Modified' date from the download headers. Should be run after the download completes. Returns <code>null</code> on failure. @return {Date} */ Downloader.prototype.getLastModifiedDate=function () { if(!this.http) { return null; } var lastmod=null; try { lastmod=this.http.getResponseHeader('Last-Modified'); } catch (err) {} if (lastmod) { return new Date(lastmod); } return null; }; /** Sets the callback function. @param {Function} f callback function, called as <code>f(this)</code> on success */ Downloader.prototype.setCallback = function (f) { if(!this.http) { return; } this.http.onreadystatechange = f; }; Downloader.prototype.getStatus = function() { if (!this.http) { return null; } return this.http.status; }; ////////////////////////////////////////////////// // helper functions /** Creates a new {@link Downloader} and prepares it for action. @param {String} url The url to download @param {integer} id The ID of the {@link Downloader} object @param {Function} callback The callback function invoked on success @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser */ function newDownload(url, id, callback, onfailure) { var d=new Downloader(url); if (!d.http) { return 'ohdear'; } d.id=id; d.setTarget(); if (!onfailure) { onfailure=2; } var f = function () { if (d.getReadyState() == 4) { delete pg.misc.downloadsInProgress[this.id]; try { if ( d.getStatus() == 200 ) { d.data=d.getData(); d.lastModified=d.getLastModifiedDate(); callback(d); } else if (typeof onfailure == typeof 1) { if (onfailure > 0) { // retry newDownload(url, id, callback, onfailure - 1); } } else if (typeof onfailure === 'function') { onfailure(d,url,id,callback); } } catch (somerr) { /* ignore it */ } } }; d.setCallback(f); return d; } /** Simulates a download from cached data. The supplied data is put into a {@link Downloader} as if it had downloaded it. @param {String} url The url. @param {integer} id The ID. @param {Function} callback The callback, which is invoked immediately as <code>callback(d)</code>, where <code>d</code> is the new {@link Downloader}. @param {String} data The (cached) data. @param {Date} lastModified The (cached) last modified date. */ function fakeDownload(url, id, callback, data, lastModified, owner) { var d=newDownload(url,callback); d.owner=owner; d.id=id; d.data=data; d.lastModified=lastModified; return callback(d); } /** Starts a download. @param {String} url The url to download @param {integer} id The ID of the {@link Downloader} object @param {Function} callback The callback function invoked on success @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser */ function startDownload(url, id, callback) { var d=newDownload(url, id, callback); if (typeof d == typeof '' ) { return d; } d.start(); return d; } /** Aborts all downloads which have been started. */ function abortAllDownloads() { for ( var x in pg.misc.downloadsInProgress ) { try { pg.misc.downloadsInProgress[x].aborted=true; pg.misc.downloadsInProgress[x].abort(); delete pg.misc.downloadsInProgress[x]; } catch (e) {} } } // ENDFILE: downloader.js // STARTFILE: livepreview.js // TODO: location is often not correct (eg relative links in previews) // NOTE: removed md5 and image and math parsing. was broken, lots of bytes. /** * InstaView - a Mediawiki to HTML converter in JavaScript * Version 0.6.1 * Copyright (C) Pedro Fayolle 2005-2006 * https://en.wikipedia.org/wiki/User:Pilaf * Distributed under the BSD license * * Changelog: * * 0.6.1 * - Fixed problem caused by \r characters * - Improved inline formatting parser * * 0.6 * - Changed name to InstaView * - Some major code reorganizations and factored out some common functions * - Handled conversion of relative links (i.e. [[/foo]]) * - Fixed misrendering of adjacent definition list items * - Fixed bug in table headings handling * - Changed date format in signatures to reflect Mediawiki's * - Fixed handling of [[:Image:...]] * - Updated MD5 function (hopefully it will work with UTF-8) * - Fixed bug in handling of links inside images * * To do: * - Better support for math tags * - Full support for <nowiki> * - Parser-based (as opposed to RegExp-based) inline wikicode handling (make it one-pass and bullet-proof) * - Support for templates (through AJAX) * - Support for coloured links (AJAX) */ var Insta = {}; function setupLivePreview() { // options Insta.conf = { baseUrl: '', user: {}, wiki: { lang: pg.wiki.lang, interwiki: pg.wiki.interwiki, default_thumb_width: 180 }, paths: { articles: pg.wiki.articlePath + '/', // Only used for Insta previews with images. (not in popups) math: '/math/', images: '//upload.wikimedia.org/wikipedia/en/', // FIXME getImageUrlStart(pg.wiki.hostname), images_fallback: '//upload.wikimedia.org/wikipedia/commons/', }, locale: { user: mw.config.get('wgFormattedNamespaces')[pg.nsUserId], image: mw.config.get('wgFormattedNamespaces')[pg.nsImageId], category: mw.config.get('wgFormattedNamespaces')[pg.nsCategoryId], // shouldn't be used in popup previews, i think months: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'] } }; // options with default values or backreferences Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian'; Insta.conf.user.signature = '[['+Insta.conf.locale.user+':'+Insta.conf.user.name+'|'+Insta.conf.user.name+']]'; //Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/'; // define constants Insta.BLOCK_IMAGE = new RegExp('^\\[\\[(?:File|Image|'+Insta.conf.locale.image+ '):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)', 'i'); } Insta.dump = function(from, to) { if (typeof from == 'string') { from = document.getElementById(from); } if (typeof to == 'string') { to = document.getElementById(to); } to.innerHTML = this.convert(from.value); }; Insta.convert = function(wiki) { var ll = (typeof wiki == 'string')? wiki.replace(/\r/g,'').split(/\n/): wiki, // lines of wikicode o = '', // output p = 0, // para flag $r; // result of passing a regexp to $() // some shorthands function remain() { return ll.length; } function sh() { return ll.shift(); } // shift function ps(s) { o += s; } // push // similar to C's printf, uses ? as placeholders, ?? to escape question marks function f() { var i=1, a=arguments, f=a[0], o='', c, p; for (; i<a.length; i++) { if ((p=f.indexOf('?'))+1) { // allow character escaping i -= c = f.charAt(p+1)=='?' ? 1 : 0; o += f.substring(0,p) + (c ? '?' : a[i]); f = f.substr(p+1+c); } else { break; } } return o+f; } function html_entities(s) { return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); } // Wiki text parsing to html is a nightmare. // The below functions deliberately don't escape the ampersand since this would make it more difficult, // and we don't absolutely need to for how we need it. // This means that any unescaped ampersands in wikitext will remain unescaped and can cause invalid HTML. // Browsers should all be able to handle it though. // We also escape significant wikimarkup characters to prevent further matching on the processed text function htmlescape_text(s) { return s.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/:/g,"&#58;").replace(/\[/g,"&#91;").replace(/]/g,"&#93;"); } function htmlescape_attr(s) { return htmlescape_text(s).replace(/'/g,"&#39;").replace(/"/g,"&quot;"); } // return the first non matching character position between two strings function str_imatch(a, b) { for (var i=0, l=Math.min(a.length, b.length); i<l; i++) { if (a.charAt(i)!=b.charAt(i)) { break; } } return i; } // compare current line against a string or regexp // if passed a string it will compare only the first string.length characters // if passed a regexp the result is stored in $r function $(c) { return (typeof c == 'string') ? (ll[0].substr(0,c.length)==c) : ($r = ll[0].match(c)); } function $$(c) { return ll[0]==c; } // compare current line against a string function _(p) { return ll[0].charAt(p); } // return char at pos p function endl(s) { ps(s); sh(); } function parse_list() { var prev=''; while (remain() && $(/^([*#:;]+)(.*)$/)) { var l_match = $r; sh(); var ipos = str_imatch(prev, l_match[1]); // close uncontinued lists for (var prevPos=prev.length-1; prevPos >= ipos; prevPos--) { var pi = prev.charAt(prevPos); if (pi=='*') { ps('</ul>'); } else if (pi=='#') { ps('</ol>'); } // close a dl only if the new item is not a dl item (:, ; or empty) else if($.inArray(l_match[1].charAt(prevPos), ['','*','#'])) { ps('</dl>'); } } // open new lists for (var matchPos=ipos; matchPos<l_match[1].length; matchPos++) { var li = l_match[1].charAt(matchPos); if (li=='*') { ps('<ul>'); } else if (li=='#') { ps('<ol>'); } // open a new dl only if the prev item is not a dl item (:, ; or empty) else if ($.inArray(prev.charAt(matchPos), ['','*','#'])) { ps('<dl>'); } } switch (l_match[1].charAt(l_match[1].length-1)) { case '*': case '#': ps('<li>' + parse_inline_nowiki(l_match[2])); break; case ';': ps('<dt>'); var dt_match = l_match[2].match(/(.*?)(:.*?)$/); // handle ;dt :dd format if (dt_match) { ps(parse_inline_nowiki(dt_match[1])); ll.unshift(dt_match[2]); } else ps(parse_inline_nowiki(l_match[2])); break; case ':': ps('<dd>' + parse_inline_nowiki(l_match[2])); } prev=l_match[1]; } // close remaining lists for (var i=prev.length-1; i>=0; i--) { ps(f('</?>', (prev.charAt(i)=='*')? 'ul': ((prev.charAt(i)=='#')? 'ol': 'dl'))); } } function parse_table() { endl(f('<table>', $(/^\{\|( .*)$/)? $r[1]: '')); for (;remain();) if ($('|')) switch (_(1)) { case '}': endl('</table>'); return; case '-': endl(f('<tr>', $(/\|-*(.*)/)[1])); break; default: parse_table_data(); } else if ($('!')) { parse_table_data(); } else { sh(); } } function parse_table_data() { var td_line, match_i; // 1: "|+", '|' or '+' // 2: ?? // 3: attributes ?? // TODO: finish commenting this regexp var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/); if (td_match[1] == '|+') ps('<caption'); else ps('<t' + ((td_match[1]=='|')?'d':'h')); if (typeof td_match[3] != 'undefined') { //ps(' ' + td_match[3]) match_i = 4; } else match_i = 2; ps('>'); if (td_match[1] != '|+') { // use || or !! as a cell separator depending on context // NOTE: when split() is passed a regexp make sure to use non-capturing brackets td_line = td_match[match_i].split((td_match[1] == '|')? '||': /(?:\|\||!!)/); ps(parse_inline_nowiki(td_line.shift())); while (td_line.length) ll.unshift(td_match[1] + td_line.pop()); } else ps(td_match[match_i]); var tc = 0, td = []; while (remain()) { td.push(sh()); if ($('|')) { if (!tc) break; // we're at the outer-most level (no nested tables), skip to td parse else if (_(1)=='}') tc--; } else if (!tc && $('!')) break; else if ($('{|')) tc++; } if (td.length) ps(Insta.convert(td)); } function parse_pre() { ps('<pre>'); do { endl(parse_inline_nowiki(ll[0].substring(1)) + "\n"); } while (remain() && $(' ')); ps('</pre>'); } function parse_block_image() { ps(parse_image(sh())); } function parse_image(str) { //<NOLITE> // get what's in between "[[Image:" and "]]" var tag = str.substring(str.indexOf(':') + 1, str.length - 2); /* eslint-disable no-unused-vars */ var width; var attr = [], filename, caption = ''; var thumb=0, frame=0, center=0; var align=''; /* eslint-enable no-unused-vars */ if (tag.match(/\|/)) { // manage nested links var nesting = 0; var last_attr; for (var i = tag.length-1; i > 0; i--) { if (tag.charAt(i) == '|' && !nesting) { last_attr = tag.substr(i+1); tag = tag.substring(0, i); break; } else switch (tag.substr(i-1, 2)) { case ']]': nesting++; i--; break; case '[[': nesting--; i--; } } attr = tag.split(/\s*\|\s*/); attr.push(last_attr); filename = attr.shift(); var w_match; for (;attr.length; attr.shift()) { w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/); if (w_match) width = w_match[1]; else switch(attr[0]) { case 'thumb': case 'thumbnail': thumb=true; frame=true; break; case 'frame': frame=true; break; case 'none': case 'right': case 'left': center=false; align=attr[0]; break; case 'center': center=true; align='none'; break; default: if (attr.length == 1) caption = attr[0]; } } } else filename = tag; return ''; //</NOLITE> } function parse_inline_nowiki(str) { var start, lastend=0; var substart=0, nestlev=0, open, close, subloop; var html=''; while (-1 != (start = str.indexOf('<nowiki>', substart))) { html += parse_inline_wiki(str.substring(lastend, start)); start += 8; substart = start; subloop = true; do { open = str.indexOf('<nowiki>', substart); close = str.indexOf('</nowiki>', substart); if (close<=open || open==-1) { if (close==-1) { return html + html_entities(str.substr(start)); } substart = close+9; if (nestlev) { nestlev--; } else { lastend = substart; html += html_entities(str.substring(start, lastend-9)); subloop = false; } } else { substart = open+8; nestlev++; } } while (subloop); } return html + parse_inline_wiki(str.substr(lastend)); } function parse_inline_images(str) { //<NOLITE> var start, substart=0, nestlev=0; var loop, close, open, wiki, html; while (-1 != (start=str.indexOf('[[', substart))) { if(str.substr(start+2).match(RegExp('^(Image|File|' + Insta.conf.locale.image + '):','i'))) { loop=true; substart=start; do { substart+=2; close=str.indexOf(']]',substart); open=str.indexOf('[[',substart); if (close<=open||open==-1) { if (close==-1) return str; substart=close; if (nestlev) { nestlev--; } else { wiki=str.substring(start,close+2); html=parse_image(wiki); str=str.replace(wiki,html); substart=start+html.length; loop=false; } } else { substart=open; nestlev++; } } while (loop); } else break; } //</NOLITE> return str; } // the output of this function doesn't respect the FILO structure of HTML // but since most browsers can handle it I'll save myself the hassle function parse_inline_formatting(str) { var em,st,i,li,o=''; while ((i=str.indexOf("''",li))+1) { o += str.substring(li,i); li=i+2; if (str.charAt(i+2)=="'") { li++; st=!st; o+=st?'<strong>':'</strong>'; } else { em=!em; o+=em?'<em>':'</em>'; } } return o+str.substr(li); } function parse_inline_wiki(str) { str = parse_inline_images(str); str = parse_inline_formatting(str); // math str = str.replace(/<(?:)math>(.*?)<\/math>/ig, ''); // Build a Mediawiki-formatted date string var date = new Date(); var minutes = date.getUTCMinutes(); if (minutes < 10) minutes = '0' + minutes; date = f("?:?, ? ? ? (UTC)", date.getUTCHours(), minutes, date.getUTCDate(), Insta.conf.locale.months[date.getUTCMonth()], date.getUTCFullYear()); // text formatting return str. // signatures replace(/~{5}(?!~)/g, date). replace(/~{4}(?!~)/g, Insta.conf.user.name+' '+date). replace(/~{3}(?!~)/g, Insta.conf.user.name). // [[:Category:...]], [[:Image:...]], etc... replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):[^|]*?)\\]\\](\\w*)','gi'), function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2));}). // remove straight category and interwiki tags replace(RegExp('\\[\\[(?:'+Insta.conf.locale.category+'|'+Insta.conf.wiki.interwiki+'):.*?\\]\\]','gi'),''). // [[:Category:...|Links]], [[:Image:...|Links]], etc... replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):.*?)\\|([^\\]]+?)\\]\\](\\w*)','gi'), function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));}). // [[/Relative links]] replace(/\[\[(\/[^|]*?)\]\]/g, function($0,$1){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($1)); }). // [[/Replaced|Relative links]] replace(/\[\[(\/.*?)\|(.+?)\]\]/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($2)); }). // [[Common links]] replace(/\[\[([^[|]*?)\]\](\w*)/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2)); }). // [[Replaced|Links]] replace(/\[\[([^[]*?)\|([^\]]+?)\]\](\w*)/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3)); }). // [[Stripped:Namespace|Namespace]] replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1) + htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($2)); }). // External links replace(/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g, function($0,$1,$2,$3,$4){return f("<a class='external' href='?:?'>?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($4)); }). replace(/\[http:\/\/(.*?)\]/g, function($0,$1){return f("<a class='external' href='http://?'>[#]</a>", htmlescape_attr($1)); }). replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, function($0,$1,$2,$3){return f("<a class='external' href='?:?'>?:?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($1), htmlescape_text($2) + htmlescape_text($3)); }). replace(/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*[^.,!?;: $])/g, function($0,$1,$2,$3,$4){return f("?<a class='external' href='?:?'>?:?</a>", htmlescape_text($1), htmlescape_attr($2), htmlescape_attr($3) + htmlescape_attr($4), htmlescape_text($2), htmlescape_text($3) + htmlescape_text($4)); }). replace('__NOTOC__',''). replace('__NOEDITSECTION__',''); } // begin parsing for (;remain();) if ($(/^(={1,6})(.*)\1(.*)$/)) { p=0; endl(f('<h?>?</h?>?', $r[1].length, parse_inline_nowiki($r[2]), $r[1].length, $r[3])); } else if ($(/^[*#:;]/)) { p=0; parse_list(); } else if ($(' ')) { p=0; parse_pre(); } else if ($('{|')) { p=0; parse_table(); } else if ($(/^----+$/)) { p=0; endl('<hr />'); } else if ($(Insta.BLOCK_IMAGE)) { p=0; parse_block_image(); } else { // handle paragraphs if ($$('')) { p = (remain()>1 && ll[1]===('')); if (p) endl('<p><br>'); } else { if(!p) { ps('<p>'); p=1; } ps(parse_inline_nowiki(ll[0]) + ' '); } sh(); } return o; }; function wiki2html(txt,baseurl) { Insta.conf.baseUrl=baseurl; return Insta.convert(txt); } // ENDFILE: livepreview.js // STARTFILE: pageinfo.js //<NOLITE> function popupFilterPageSize(data) { return formatBytes(data.length); } function popupFilterCountLinks(data) { var num=countLinks(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('wikiLinks'):popupString('wikiLink')); } function popupFilterCountImages(data) { var num=countImages(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('images'):popupString('image')); } function popupFilterCountCategories(data) { var num=countCategories(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('categories'):popupString('category')); } function popupFilterLastModified(data,download) { var lastmod=download.lastModified; var now=new Date(); var age=now-lastmod; if (lastmod && getValueOf('popupLastModified')) { return (tprintf('%s old', [formatAge(age)])).replace(RegExp(' ','g'), '&nbsp;'); } return ''; } function formatAge(age) { // coerce into a number var a=0+age, aa=a; var seclen = 1000; var minlen = 60*seclen; var hourlen = 60*minlen; var daylen = 24*hourlen; var weeklen = 7*daylen; var numweeks = (a-a%weeklen)/weeklen; a = a-numweeks*weeklen; var sweeks = addunit(numweeks, 'week'); var numdays = (a-a%daylen)/daylen; a = a-numdays*daylen; var sdays = addunit(numdays, 'day'); var numhours = (a-a%hourlen)/hourlen; a = a-numhours*hourlen; var shours = addunit(numhours,'hour'); var nummins = (a-a%minlen)/minlen; a = a-nummins*minlen; var smins = addunit(nummins, 'minute'); var numsecs = (a-a%seclen)/seclen; a = a-numsecs*seclen; var ssecs = addunit(numsecs, 'second'); if (aa > 4*weeklen) { return sweeks; } if (aa > weeklen) { return sweeks + ' ' + sdays; } if (aa > daylen) { return sdays + ' ' + shours; } if (aa > 6*hourlen) { return shours; } if (aa > hourlen) { return shours + ' ' + smins; } if (aa > 10*minlen) { return smins; } if (aa > minlen) { return smins + ' ' + ssecs; } return ssecs; } function addunit(num,str) { return '' + num + ' ' + ((num!=1) ? popupString(str+'s') : popupString(str)) ;} function runPopupFilters(list, data, download) { var ret=[]; for (var i=0; i<list.length; ++i) { if (list[i] && typeof list[i] == 'function') { var s=list[i](data, download, download.owner.article); if (s) { ret.push(s); } } } return ret; } function getPageInfo(data, download) { if (!data || data.length === 0) { return popupString('Empty page'); } var popupFilters=getValueOf('popupFilters') || []; var extraPopupFilters = getValueOf('extraPopupFilters') || []; var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download); var pageInfo=pageInfoArray.join(', '); if (pageInfo !== '' ) { pageInfo = upcaseFirst(pageInfo); } return pageInfo; } // this could be improved! function countLinks(wikiText) { return wikiText.split('[[').length - 1; } // if N = # matches, n = # brackets, then // String.parenSplit(regex) intersperses the N+1 split elements // with Nn other elements. So total length is // L= N+1 + Nn = N(n+1)+1. So N=(L-1)/(n+1). function countImages(wikiText) { return (wikiText.parenSplit(pg.re.image).length - 1) / (pg.re.imageBracketCount + 1); } function countCategories(wikiText) { return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1); } function popupFilterStubDetect(data, download, article) { var counts=stubCount(data, article); if (counts.real) { return popupString('stub'); } if (counts.sect) { return popupString('section stub'); } return ''; } function popupFilterDisambigDetect(data, download, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return ''; } return (isDisambig(data, article)) ? popupString('disambig') : ''; } function formatBytes(num) { return (num > 949) ? (Math.round(num/100)/10+popupString('kB')) : (num +'&nbsp;' + popupString('bytes')) ; } //</NOLITE> // ENDFILE: pageinfo.js // STARTFILE: titles.js /** @fileoverview Defines the {@link Title} class, and associated crufty functions. <code>Title</code> deals with article titles and their various forms. {@link Stringwrapper} is the parent class of <code>Title</code>, which exists simply to make things a little neater. */ /** Creates a new Stringwrapper. @constructor @class the Stringwrapper class. This base class is not really useful on its own; it just wraps various common string operations. */ function Stringwrapper() { /** Wrapper for this.toString().indexOf() @param {String} x @type integer */ this.indexOf=function(x){return this.toString().indexOf(x);}; /** Returns this.value. @type String */ this.toString=function(){return this.value;}; /** Wrapper for {@link String#parenSplit} applied to this.toString() @param {RegExp} x @type Array */ this.parenSplit=function(x){return this.toString().parenSplit(x);}; /** Wrapper for this.toString().substring() @param {String} x @param {String} y (optional) @type String */ this.substring=function(x,y){ if (typeof y=='undefined') { return this.toString().substring(x); } return this.toString().substring(x,y); }; /** Wrapper for this.toString().split() @param {String} x @type Array */ this.split=function(x){return this.toString().split(x);}; /** Wrapper for this.toString().replace() @param {String} x @param {String} y @type String */ this.replace=function(x,y){ return this.toString().replace(x,y); }; } /** Creates a new <code>Title</code>. @constructor @class The Title class. Holds article titles and converts them into various forms. Also deals with anchors, by which we mean the bits of the article URL after a # character, representing locations within an article. @param {String} value The initial value to assign to the article. This must be the canonical title (see {@link Title#value}. Omit this in the constructor and use another function to set the title if this is unavailable. */ function Title(val) { /** The canonical article title. This must be in UTF-8 with no entities, escaping or nasties. Also, underscores should be replaced with spaces. @type String @private */ this.value=null; /** The canonical form of the anchor. This should be exactly as it appears in the URL, i.e. with the .C3.0A bits in. @type String */ this.anchor=''; this.setUtf(val); } Title.prototype=new Stringwrapper(); /** Returns the canonical representation of the article title, optionally without anchor. @param {boolean} omitAnchor @fixme Decide specs for anchor @return String The article title and the anchor. */ Title.prototype.toString=function(omitAnchor) { return this.value + ( (!omitAnchor && this.anchor) ? '#' + this.anchorString() : '' ); }; Title.prototype.anchorString=function() { if (!this.anchor) { return ''; } var split=this.anchor.parenSplit(/((?:[.][0-9A-F]{2})+)/); var len=split.length; for (var j=1; j<len; j+=2) { // FIXME s/decodeURI/decodeURIComponent/g ? split[j]=decodeURIComponent(split[j].split('.').join('%')).split('_').join(' '); } return split.join(''); }; Title.prototype.urlAnchor=function() { var split=this.anchor.parenSplit('/((?:[%][0-9A-F]{2})+)/'); var len=split.length; for (var j=1; j<len; j+=2) { split[j]=split[j].split('%').join('.'); } return split.join(''); }; Title.prototype.anchorFromUtf=function(str) { this.anchor=encodeURIComponent(str.split(' ').join('_')) .split('%3A').join(':').split("'").join('%27').split('%').join('.'); }; Title.fromURL=function(h) { return new Title().fromURL(h); }; Title.prototype.fromURL=function(h) { if (typeof h != 'string') { this.value=null; return this; } // NOTE : playing with decodeURI, encodeURI, escape, unescape, // we seem to be able to replicate the IE borked encoding // IE doesn't do this new-fangled utf-8 thing. // and it's worse than that. // IE seems to treat the query string differently to the rest of the url // the query is treated as bona-fide utf8, but the first bit of the url is pissed around with // we fix up & for all browsers, just in case. var splitted=h.split('?'); splitted[0]=splitted[0].split('&').join('%26'); h=splitted.join('?'); var contribs=pg.re.contribs.exec(h); if (contribs) { if (contribs[1]=='title=') { contribs[3]=contribs[3].split('+').join(' '); } var u=new Title(contribs[3]); this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + u.stripNamespace())); return this; } var email=pg.re.email.exec(h); if (email) { this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(email[3]).stripNamespace())); return this; } var backlinks=pg.re.backlinks.exec(h); if (backlinks) { this.setUtf(this.decodeNasties(new Title(backlinks[3]))); return this; } //A dummy title object for a Special:Diff link. var specialdiff=pg.re.specialdiff.exec(h); if (specialdiff) { this.setUtf(this.decodeNasties(new Title(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Diff'))); return this; } // no more special cases to check -- // hopefully it's not a disguised user-related or specially treated special page var m=pg.re.main.exec(h); if(m === null) { this.value=null; } else { var fromBotInterface = /[?](.+[&])?title=/.test(h); if (fromBotInterface) { m[2]=m[2].split('+').join('_'); } var extracted = m[2] + (m[3] ? '#' + m[3] : ''); if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) { // Fix Safari issue // Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3. this.setUtf(decodeURIComponent(unescape(extracted))); } else { this.setUtf(this.decodeNasties(extracted)); } } return this; }; Title.prototype.decodeNasties=function(txt) { var ret= this.decodeEscapes(decodeURI(txt)); ret = ret.replace(/[_ ]*$/, ''); return ret; }; Title.prototype.decodeEscapes=function(txt) { var split=txt.parenSplit(/((?:[%][0-9A-Fa-f]{2})+)/); var len=split.length; for (var i=1; i<len; i=i+2) { // FIXME is decodeURIComponent better? split[i]=unescape(split[i]); } return split.join(''); }; Title.fromAnchor=function(a) { return new Title().fromAnchor(a); }; Title.prototype.fromAnchor=function(a) { if (!a) { this.value=null; return this; } return this.fromURL(a.href); }; Title.fromWikiText=function(txt) { return new Title().fromWikiText(txt); }; Title.prototype.fromWikiText=function(txt) { // FIXME - testing needed txt=myDecodeURI(txt); this.setUtf(txt); return this; }; Title.prototype.hintValue=function(){ if(!this.value) { return ''; } return safeDecodeURI(this.value); }; //<NOLITE> Title.prototype.toUserName=function(withNs) { if (this.namespaceId() != pg.nsUserId && this.namespaceId() != pg.nsUsertalkId) { this.value=null; return; } this.value = (withNs ? mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' : '') + this.stripNamespace().split('/')[0]; }; Title.prototype.userName=function(withNs) { var t=(new Title(this.value)); t.toUserName(withNs); if (t.value) { return t; } return null; }; Title.prototype.toTalkPage=function() { // convert article to a talk page, or if we can't, return null // In other words: return null if this ALREADY IS a talk page // and return the corresponding talk page otherwise // // Per https://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces // * All discussion namespaces have odd-integer indices // * The discussion namespace index for a specific namespace with index n is n + 1 if (this.value === null) { return null; } var namespaceId = this.namespaceId(); if (namespaceId>=0 && namespaceId % 2 === 0) //non-special and subject namespace { var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId+1]; if (typeof localizedNamespace!=='undefined') { if (localizedNamespace === '') { this.value = this.stripNamespace(); } else { this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace(); } return this.value; } } this.value=null; return null; }; //</NOLITE> // Return canonical, localized namespace Title.prototype.namespace=function() { return mw.config.get('wgFormattedNamespaces')[this.namespaceId()]; }; Title.prototype.namespaceId=function() { var n=this.value.indexOf(':'); if (n<0) { return 0; } //mainspace var namespaceId = mw.config.get('wgNamespaceIds')[this.value.substring(0,n).split(' ').join('_').toLowerCase()]; if (typeof namespaceId=='undefined') return 0; //mainspace return namespaceId; }; //<NOLITE> Title.prototype.talkPage=function() { var t=new Title(this.value); t.toTalkPage(); if (t.value) { return t; } return null; }; Title.prototype.isTalkPage=function() { if (this.talkPage()===null) { return true; } return false; }; Title.prototype.toArticleFromTalkPage=function() { //largely copy/paste from toTalkPage above. if (this.value === null) { return null; } var namespaceId = this.namespaceId(); if (namespaceId >= 0 && namespaceId % 2 == 1) //non-special and talk namespace { var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId-1]; if (typeof localizedNamespace!=='undefined') { if (localizedNamespace === '') { this.value = this.stripNamespace(); } else { this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace(); } return this.value; } } this.value=null; return null; }; Title.prototype.articleFromTalkPage=function() { var t=new Title(this.value); t.toArticleFromTalkPage(); if (t.value) { return t; } return null; }; Title.prototype.articleFromTalkOrArticle=function() { var t=new Title(this.value); if ( t.toArticleFromTalkPage() ) { return t; } return this; }; Title.prototype.isIpUser=function() { return pg.re.ipUser.test(this.userName()); }; //</NOLITE> Title.prototype.stripNamespace=function(){ // returns a string, not a Title var n=this.value.indexOf(':'); if (n<0) { return this.value; } var namespaceId = this.namespaceId(); if (namespaceId === pg.nsMainspaceId) return this.value; return this.value.substring(n+1); }; Title.prototype.setUtf=function(value){ if (!value) { this.value=''; return; } var anch=value.indexOf('#'); if(anch < 0) { this.value=value.split('_').join(' '); this.anchor=''; return; } this.value=value.substring(0,anch).split('_').join(' '); this.anchor=value.substring(anch+1); this.ns=null; // wait until namespace() is called }; Title.prototype.setUrl=function(urlfrag) { var anch=urlfrag.indexOf('#'); this.value=safeDecodeURI(urlfrag.substring(0,anch)); this.anchor=this.value.substring(anch+1); }; Title.prototype.append=function(x){ this.setUtf(this.value + x); }; Title.prototype.urlString=function(x) { if(!x) { x={}; } var v=this.toString(true); if (!x.omitAnchor && this.anchor) { v+= '#' + this.urlAnchor(); } if (!x.keepSpaces) { v=v.split(' ').join('_'); } return encodeURI(v).split('&').join('%26').split('?').join('%3F').split('+').join('%2B'); }; Title.prototype.removeAnchor=function() { return new Title(this.toString(true)); }; Title.prototype.toUrl=function() { return pg.wiki.titlebase + this.urlString(); }; function parseParams(url) { var specialDiff = pg.re.specialdiff.exec(url); if (specialDiff) { var split= specialDiff[1].split('/'); if (split.length==1) return {oldid:split[0], diff: 'prev'}; else if (split.length==2) return {oldid: split[0], diff: split[1]}; } var ret={}; if (url.indexOf('?')==-1) { return ret; } url = url.split('#')[0]; var s=url.split('?').slice(1).join(); var t=s.split('&'); for (var i=0; i<t.length; ++i) { var z=t[i].split('='); z.push(null); ret[z[0]]=z[1]; } //Diff revision with no oldid is interpreted as a diff to the previous revision by MediaWiki if (ret.diff && typeof(ret.oldid)==='undefined') { ret.oldid = "prev"; } //Documentation seems to say something different, but oldid can also accept prev/next, and Echo is emitting such URLs. Simple fixup during parameter decoding: if (ret.oldid && (ret.oldid==='prev' || ret.oldid==='next' || ret.oldid==='cur')) { var helper = ret.diff; ret.diff = ret.oldid; ret.oldid = helper; } return ret; } // (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup) // (b) change spaces to underscores // (c) encodeURI (just the straight one, no pg.re.urlNoPopup) function myDecodeURI (str) { var ret; // FIXME decodeURIComponent?? try { ret=decodeURI(str.toString()); } catch (summat) { return str; } for (var i=0; i<pg.misc.decodeExtras.length; ++i) { var from=pg.misc.decodeExtras[i].from; var to=pg.misc.decodeExtras[i].to; ret=ret.split(from).join(to); } return ret; } function safeDecodeURI(str) { var ret=myDecodeURI(str); return ret || str; } /////////// // TESTS // /////////// //<NOLITE> function isDisambig(data, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; } return ! article.isTalkPage() && pg.re.disambig.test(data); } function stubCount(data, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; } var sectStub=0; var realStub=0; if (pg.re.stub.test(data)) { var s=data.parenSplit(pg.re.stub); for (var i=1; i<s.length; i=i+2) { if (s[i]) { ++sectStub; } else { ++realStub; } } } return { real: realStub, sect: sectStub }; } function isValidImageName(str){ // extend as needed... return ( str.indexOf('{') == -1 ); } function isInStrippableNamespace(article) { // Does the namespace allow subpages // Note, would be better if we had access to wgNamespacesWithSubpages return ( article.namespaceId() !== 0 ); } function isInMainNamespace(article) { return article.namespaceId() === 0; } function anchorContainsImage(a) { // iterate over children of anchor a // see if any are images if (a === null) { return false; } var kids=a.childNodes; for (var i=0; i<kids.length; ++i) { if (kids[i].nodeName=='IMG') { return true; } } return false; } //</NOLITE> function isPopupLink(a) { // NB for performance reasons, TOC links generally return true // they should be stripped out later if (!markNopopupSpanLinks.done) { markNopopupSpanLinks(); } if (a.inNopopupSpan) { return false; } // FIXME is this faster inline? if (a.onmousedown || a.getAttribute('nopopup')) { return false; } var h=a.href; if (h === document.location.href+'#') { return false; } if (!pg.re.basenames.test(h)) { return false; } if (!pg.re.urlNoPopup.test(h)) { return true; } return ( (pg.re.email.test(h) || pg.re.contribs.test(h) || pg.re.backlinks.test(h) || pg.re.specialdiff.test(h)) && h.indexOf('&limit=') == -1 ); } function markNopopupSpanLinks() { if( !getValueOf('popupOnlyArticleLinks')) fixVectorMenuPopups(); var s = $('.nopopups').toArray(); for (var i=0; i<s.length; ++i) { var as=s[i].getElementsByTagName('a'); for (var j=0; j<as.length; ++j) { as[j].inNopopupSpan=true; } } markNopopupSpanLinks.done=true; } function fixVectorMenuPopups() { $('div.vectorMenu h3:first a:first').prop('inNopopupSpan', true); } // ENDFILE: titles.js // STARTFILE: getpage.js ////////////////////////////////////////////////// // Wiki-specific downloading // // Schematic for a getWiki call // // getPageWithCaching // | // false | true // getPage<-[findPictureInCache]->-onComplete(a fake download) // \. // (async)->addPageToCache(download)->-onComplete(download) // check cache to see if page exists function getPageWithCaching(url, onComplete, owner) { log('getPageWithCaching, url='+url); var i=findInPageCache(url); var d; if (i > -1) { d=fakeDownload(url, owner.idNumber, onComplete, pg.cache.pages[i].data, pg.cache.pages[i].lastModified, owner); } else { d=getPage(url, onComplete, owner); if (d && owner && owner.addDownload) { owner.addDownload(d); d.owner=owner; } } } function getPage(url, onComplete, owner) { log('getPage'); var callback= function (d) { if (!d.aborted) {addPageToCache(d); onComplete(d);} }; return startDownload(url, owner.idNumber, callback); } function findInPageCache(url) { for (var i=0; i<pg.cache.pages.length; ++i) { if (url==pg.cache.pages[i].url) { return i; } } return -1; } function addPageToCache(download) { log('addPageToCache '+download.url); var page = {url: download.url, data: download.data, lastModified: download.lastModified}; return pg.cache.pages.push(page); } // ENDFILE: getpage.js // STARTFILE: parensplit.js ////////////////////////////////////////////////// // parenSplit // String.prototype.parenSplit should do what ECMAscript says String.prototype.split does, // interspersing paren matches (regex capturing groups) between the split elements. // i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c'] if (String('abc'.split(/(b)/))!='a,b,c') { // broken String.split, e.g. konq, IE < 10 String.prototype.parenSplit=function (re) { re=nonGlobalRegex(re); var s=this; var m=re.exec(s); var ret=[]; while (m && s) { // without the following loop, we have // 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/) for(var i=0; i<m.length; ++i) { if (typeof m[i]=='undefined') m[i]=''; } ret.push(s.substring(0,m.index)); ret = ret.concat(m.slice(1)); s=s.substring(m.index + m[0].length); m=re.exec(s); } ret.push(s); return ret; }; } else { String.prototype.parenSplit=function (re) { return this.split(re); }; String.prototype.parenSplit.isNative=true; } function nonGlobalRegex(re) { var s=re.toString(); var flags=''; for (var j=s.length; s.charAt(j) != '/'; --j) { if (s.charAt(j) != 'g') { flags += s.charAt(j); } } var t=s.substring(1,j); return RegExp(t,flags); } // ENDFILE: parensplit.js // STARTFILE: tools.js // IE madness with encoding // ======================== // // suppose throughout that the page is in utf8, like wikipedia // // if a is an anchor DOM element and a.href should consist of // // http://host.name.here/wiki/foo?bar=baz // // then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie)) // but IE gives bar=baz correctly as plain utf8 // // --------------------------------- // // IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here. // // --------------------------------- // // summat else // Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm //<NOLITE> function getJsObj(json) { try { var json_ret = JSON.parse(json); if( json_ret.warnings ) { for( var w=0; w < json_ret.warnings.length; w++ ) { if( json_ret.warnings[w]['*'] ) { log( json_ret.warnings[w]['*'] ); } else { log( json_ret.warnings[w]['warnings'] ); } } } else if ( json_ret.error ) { errlog( json_ret.error.code + ': ' + json_ret.error.info ); } return json_ret; } catch (someError) { errlog('Something went wrong with getJsObj, json='+json); return 1; } } function anyChild(obj) { for (var p in obj) { return obj[p]; } return null; } //</NOLITE> function upcaseFirst(str) { if (typeof str != typeof '' || str === '') return ''; return str.charAt(0).toUpperCase() + str.substring(1); } function findInArray(arr, foo) { if (!arr || !arr.length) { return -1; } var len=arr.length; for (var i=0; i<len; ++i) { if (arr[i]==foo) { return i; } } return -1; } /* eslint-disable no-unused-vars */ function nextOne (array, value) { // NB if the array has two consecutive entries equal // then this will loop on successive calls var i=findInArray(array, value); if (i<0) { return null; } return array[i+1]; } /* eslint-enable no-unused-vars */ function literalizeRegex(str){ return mw.util.escapeRegExp(str); } String.prototype.entify=function() { //var shy='&shy;'; return this.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;'/*+shy*/).split('"').join('&quot;'); }; // Array filter function function removeNulls(val) { return val !== null; } function joinPath(list) { return list.filter(removeNulls).join('/'); } function simplePrintf(str, subs) { if (!str || !subs) { return str; } var ret=[]; var s=str.parenSplit(/(%s|\$[0-9]+)/); var i=0; do { ret.push(s.shift()); if ( !s.length ) { break; } var cmd=s.shift(); if (cmd == '%s') { if ( i < subs.length ) { ret.push(subs[i]); } else { ret.push(cmd); } ++i; } else { var j=parseInt( cmd.replace('$', ''), 10 ) - 1; if ( j > -1 && j < subs.length ) { ret.push(subs[j]); } else { ret.push(cmd); } } } while (s.length > 0); return ret.join(''); } /* eslint-disable no-unused-vars */ function isString(x) { return (typeof x === 'string' || x instanceof String); } function isNumber(x) { return (typeof x === 'number' || x instanceof Number); } function isRegExp(x) { return x instanceof RegExp; } function isArray (x) { return x instanceof Array; } function isObject(x) { return x instanceof Object; } function isFunction(x) { return !isRegExp(x) && (typeof x === 'function' || x instanceof Function); } /* eslint-enable no-unused-vars */ function repeatString(s,mult) { var ret=''; for (var i=0; i<mult; ++i) { ret += s; } return ret; } function zeroFill(s, min) { min = min || 2; var t=s.toString(); return repeatString('0', min - t.length) + t; } function map(f, o) { if (isArray(o)) { return map_array(f,o); } return map_object(f,o); } function map_array(f,o) { var ret=[]; for (var i=0; i<o.length; ++i) { ret.push(f(o[i])); } return ret; } function map_object(f,o) { var ret={}; for (var i in o) { ret[o]=f(o[i]); } return ret; } pg.escapeQuotesHTML = function ( text ) { return text .replace(/&/g, "&amp;") .replace(/"/g, "&quot;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;"); }; // ENDFILE: tools.js // STARTFILE: dab.js //<NOLITE> ////////////////////////////////////////////////// // Dab-fixing code // function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) { log('retargetDab: newTarget='+newTarget + ' oldTarget=' + oldTarget); return changeLinkTargetLink( {newTarget: newTarget, text: newTarget.split(' ').join('&nbsp;'), hint: tprintf('disambigHint', [newTarget]), summary: simplePrintf( getValueOf('popupFixDabsSummary'), [friendlyCurrentArticleName, newTarget ]), clickButton: getValueOf('popupDabsAutoClick'), minor: true, oldTarget: oldTarget, watch: getValueOf('popupWatchDisambiggedPages'), title: titleToEdit}); } function listLinks(wikitext, oldTarget, titleToEdit) { // mediawiki strips trailing spaces, so we do the same // testcase: https://en.wikipedia.org/w/index.php?title=Radial&oldid=97365633 var reg=RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi'); var ret=[]; var splitted=wikitext.parenSplit(reg); // ^[a-z]+ should match interwiki links, hopefully (case-insensitive) // and ^[a-z]* should match those and [[:Category...]] style links too var omitRegex=RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory'); var friendlyCurrentArticleName= oldTarget.toString(); var wikPos = getValueOf('popupDabWiktionary'); for (var i=1; i<splitted.length; i=i+3) { if (typeof splitted[i] == typeof 'string' && splitted[i].length>0 && !omitRegex.test(splitted[i])) { ret.push( retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit) ); } /* if */ } /* for loop */ ret = rmDupesFromSortedList(ret.sort()); if (wikPos) { var wikTarget='wiktionary:' + friendlyCurrentArticleName.replace( RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1' ); var meth; if (wikPos.toLowerCase() == 'first') { meth = 'unshift'; } else { meth = 'push'; } ret[meth]( retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) ); } ret.push(changeLinkTargetLink( { newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'), hint: popupString("remove all links to this disambig page from this article"), clickButton: getValueOf('popupDabsAutoClick'), oldTarget: oldTarget, summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]), watch: getValueOf('popupWatchDisambiggedPages'), title: titleToEdit })); return ret; } function rmDupesFromSortedList(list) { var ret=[]; for (var i=0; i<list.length; ++i) { if (ret.length === 0 || list[i]!=ret[ret.length-1]) { ret.push(list[i]); } } return ret; } function makeFixDab(data, navpop) { // grab title from parent popup if there is one; default exists in changeLinkTargetLink var titleToEdit=(navpop.parentPopup && navpop.parentPopup.article.toString()); var list=listLinks(data, navpop.originalArticle, titleToEdit); if (list.length === 0) { log('listLinks returned empty list'); return null; } var html='<hr />' + popupString('Click to disambiguate this link to:') + '<br>'; html+=list.join(', '); return html; } function makeFixDabs(wikiText, navpop) { if (getValueOf('popupFixDabs') && isDisambig(wikiText, navpop.article) && Title.fromURL(location.href).namespaceId() != pg.nsSpecialId && navpop.article.talkPage() ) { setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber); } } function popupRedlinkHTML(article) { return changeLinkTargetLink( { newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'), hint: popupString("remove all links to this page from this article"), clickButton: getValueOf('popupRedlinkAutoClick'), oldTarget: article.toString(), summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()])}); } //</NOLITE> // ENDFILE: dab.js // STARTFILE: htmloutput.js // this has to use a timer loop as we don't know if the DOM element exists when we want to set the text function setPopupHTML (str, elementId, popupId, onSuccess, append) { if (typeof popupId === 'undefined') { //console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100)); popupId = pg.idNumber; } var popupElement=document.getElementById(elementId+popupId); if (popupElement) { if (!append) { popupElement.innerHTML=''; } if (isString(str)) { popupElement.innerHTML+=str; } else { popupElement.appendChild(str); } if (onSuccess) { onSuccess(); } setTimeout(checkPopupPosition, 100); return true; } else { // call this function again in a little while... setTimeout(function(){ setPopupHTML(str,elementId,popupId,onSuccess); }, 600); } return null; } //<NOLITE> function setPopupTrailer(str,id) {return setPopupHTML(str, 'popupData', id);} //</NOLITE> // args.navpopup is mandatory // optional: args.redir, args.redirTarget // FIXME: ye gods, this is ugly stuff function fillEmptySpans(args) { // if redir is present and true then redirTarget is mandatory var redir=true; var rcid; if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) { redir=false; } var a=args.navpopup.parentAnchor; var article, hint=null, oldid=null, params={}; if (redir && typeof args.redirTarget == typeof {}) { article=args.redirTarget; //hint=article.hintValue(); } else { article=(new Title()).fromAnchor(a); hint=a.originalTitle || article.hintValue(); params=parseParams(a.href); oldid=(getValueOf('popupHistoricalLinks')) ? params.oldid : null; rcid=params.rcid; } var x={ article:article, hint: hint, oldid: oldid, rcid: rcid, navpop:args.navpopup, params:params }; var structure=pg.structures[getValueOf('popupStructure')]; if (typeof structure != 'object') { setPopupHTML('popupError', 'Unknown structure (this should never happen): '+ pg.option.popupStructure, args.navpopup.idNumber); return; } var spans=flatten(pg.misc.layout); var numspans = spans.length; var redirs=pg.misc.redirSpans; for (var i=0; i<numspans; ++i) { var found = redirs && (redirs.indexOf( spans[i] ) !== -1); //log('redir='+redir+', found='+found+', spans[i]='+spans[i]); if ( (found && !redir) || (!found && redir) ) { //log('skipping this set of the loop'); continue; } var structurefn=structure[spans[i]]; var setfn = setPopupHTML; if (getValueOf('popupActiveNavlinks') && (spans[i].indexOf('popupTopLinks')===0 || spans[i].indexOf('popupRedirTopLinks')===0) ) { setfn = setPopupTipsAndHTML; } switch (typeof structurefn) { case 'function': log('running '+spans[i]+'({article:'+x.article+', hint:'+x.hint+', oldid: '+x.oldid+'})'); setfn(structurefn(x), spans[i], args.navpopup.idNumber); break; case 'string': setfn(structurefn, spans[i], args.navpopup.idNumber); break; default: errlog('unknown thing with label '+spans[i] + ' (span index was ' + i + ')'); break; } } } // flatten an array function flatten(list, start) { var ret=[]; if (typeof start == 'undefined') { start=0; } for (var i=start; i<list.length; ++i) { if (typeof list[i] == typeof []) { return ret.concat(flatten(list[i])).concat(flatten(list, i+1)); } else { ret.push(list[i]); } } return ret; } // Generate html for whole popup function popupHTML (a) { getValueOf('popupStructure'); var structure=pg.structures[pg.option.popupStructure]; if (typeof structure != 'object') { //return 'Unknown structure: '+pg.option.popupStructure; // override user choice pg.option.popupStructure=pg.optionDefault.popupStructure; return popupHTML(a); } if (typeof structure.popupLayout != 'function') { return 'Bad layout'; } pg.misc.layout=structure.popupLayout(); if (typeof structure.popupRedirSpans === 'function') { pg.misc.redirSpans=structure.popupRedirSpans(); } else { pg.misc.redirSpans=[]; } return makeEmptySpans(pg.misc.layout, a.navpopup); } function makeEmptySpans (list, navpop) { var ret=''; for (var i=0; i<list.length; ++i) { if (typeof list[i] == typeof '') { ret += emptySpanHTML(list[i], navpop.idNumber, 'div'); } else if (typeof list[i] == typeof [] && list[i].length > 0 ) { ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop)); } else if (typeof list[i] == typeof {} && list[i].nodeType ) { ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType); } } return ret; } function emptySpanHTML(name, id, tag, classname) { tag = tag || 'span'; if (!classname) { classname = emptySpanHTML.classAliases[name]; } classname = classname || name; if (name == getValueOf('popupDragHandle')) { classname += ' popupDragHandle'; } return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]); } emptySpanHTML.classAliases={ 'popupSecondPreview': 'popupPreview' }; // generate html for popup image // <a id="popupImageLinkn"><img id="popupImagen"> // where n=idNumber function imageHTML(article, idNumber) { return simplePrintf('<a id="popupImageLink$1">' + '<img align="right" valign="top" id="popupImg$1" style="display: none;"></img>' + '</a>', [ idNumber ]); } function popTipsSoonFn(id, when, popData) { if (!when) { when=250; } var popTips=function(){ setupTooltips(document.getElementById(id), false, true, popData); }; return function() { setTimeout( popTips, when, popData ); }; } function setPopupTipsAndHTML(html, divname, idnumber, popData) { setPopupHTML(html, divname, idnumber, getValueOf('popupSubpopups') ? popTipsSoonFn(divname + idnumber, null, popData) : null); } // ENDFILE: htmloutput.js // STARTFILE: mouseout.js ////////////////////////////////////////////////// // fuzzy checks function fuzzyCursorOffMenus(x,y, fuzz, parent) { if (!parent) { return null; } var uls=parent.getElementsByTagName('ul'); for (var i=0; i<uls.length; ++i) { if (uls[i].className=='popup_menu') { if (uls[i].offsetWidth > 0) return false; } // else {document.title+='.';} } return true; } function checkPopupPosition () { // stop the popup running off the right of the screen // FIXME avoid pg.current.link if (pg.current.link && pg.current.link.navpopup) pg.current.link.navpopup.limitHorizontalPosition(); } function mouseOutWikiLink () { //console ('mouseOutWikiLink'); var a=this; removeModifierKeyHandler(a); if (a.navpopup === null || typeof a.navpopup === 'undefined') return; if ( ! a.navpopup.isVisible() ) { a.navpopup.banish(); return; } restoreTitle(a); Navpopup.tracker.addHook(posCheckerHook(a.navpopup)); } function posCheckerHook(navpop) { return function() { if (!navpop.isVisible()) { return true; /* remove this hook */ } if (Navpopup.tracker.dirty) { return false; } var x=Navpopup.tracker.x, y=Navpopup.tracker.y; var mouseOverNavpop = navpop.isWithin(x,y,navpop.fuzz, navpop.mainDiv) || !fuzzyCursorOffMenus(x,y,navpop.fuzz, navpop.mainDiv); // FIXME it'd be prettier to do this internal to the Navpopup objects var t=getValueOf('popupHideDelay'); if (t) { t = t * 1000; } if (!t) { if(!mouseOverNavpop) { if(navpop.parentAnchor) { restoreTitle( navpop.parentAnchor ); } navpop.banish(); return true; /* remove this hook */ } return false; } // we have a hide delay set var d=+(new Date()); if ( !navpop.mouseLeavingTime ) { navpop.mouseLeavingTime = d; return false; } if ( mouseOverNavpop ) { navpop.mouseLeavingTime=null; return false; } if (d - navpop.mouseLeavingTime > t) { navpop.mouseLeavingTime=null; navpop.banish(); return true; /* remove this hook */ } return false; }; } function runStopPopupTimer(navpop) { // at this point, we should have left the link but remain within the popup // so we call this function again until we leave the popup. if (!navpop.stopPopupTimer) { navpop.stopPopupTimer=setInterval(posCheckerHook(navpop), 500); navpop.addHook(function(){clearInterval(navpop.stopPopupTimer);}, 'hide', 'before'); } } // ENDFILE: mouseout.js // STARTFILE: previewmaker.js /** @fileoverview Defines the {@link Previewmaker} object, which generates short previews from wiki markup. */ /** Creates a new Previewmaker @constructor @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext. @param {String} wikiText The Wikitext source of the page we wish to preview. @param {String} baseUrl The url we should prepend when creating relative urls. @param {Navpopup} owner The navpop associated to this preview generator */ function Previewmaker(wikiText, baseUrl, owner) { /** The wikitext which is manipulated to generate the preview. */ this.originalData=wikiText; this.baseUrl=baseUrl; this.owner=owner; this.maxCharacters=getValueOf('popupMaxPreviewCharacters'); this.maxSentences=getValueOf('popupMaxPreviewSentences'); this.setData(); } Previewmaker.prototype.setData=function() { var maxSize=Math.max(10000, 2*this.maxCharacters); this.data=this.originalData.substring(0,maxSize); }; /** Remove HTML comments @private */ Previewmaker.prototype.killComments = function () { // this also kills one trailing newline, eg [[diamyo]] this.data=this.data.replace(RegExp('^<!--[^$]*?-->\\n|\\n<!--[^$]*?-->(?=\\n)|<!--[^$]*?-->', 'g'), ''); }; /** @private */ Previewmaker.prototype.killDivs = function () { // say goodbye, divs (can be nested, so use * not *?) this.data=this.data.replace(RegExp('< *div[^>]* *>[\\s\\S]*?< */ *div *>', 'gi'), ''); }; /** @private */ Previewmaker.prototype.killGalleries = function () { this.data=this.data.replace(RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>', 'gi'), ''); }; /** @private */ Previewmaker.prototype.kill = function(opening, closing, subopening, subclosing, repl) { var oldk=this.data; var k=this.killStuff(this.data, opening, closing, subopening, subclosing, repl); while (k.length < oldk.length) { oldk=k; k=this.killStuff(k, opening, closing, subopening, subclosing, repl); } this.data=k; }; /** @private */ Previewmaker.prototype.killStuff = function (txt, opening, closing, subopening, subclosing, repl) { var op=this.makeRegexp(opening); var cl=this.makeRegexp(closing, '^'); var sb=subopening ? this.makeRegexp(subopening, '^') : null; var sc=subclosing ? this.makeRegexp(subclosing, '^') : cl; if (!op || !cl) { alert('Navigation Popups error: op or cl is null! something is wrong.'); return; } if (!op.test(txt)) { return txt; } var ret=''; var opResult = op.exec(txt); ret = txt.substring(0,opResult.index); txt=txt.substring(opResult.index+opResult[0].length); var depth = 1; while (txt.length > 0) { var removal=0; if (depth==1 && cl.test(txt)) { depth--; removal=cl.exec(txt)[0].length; } else if (depth > 1 && sc.test(txt)) { depth--; removal=sc.exec(txt)[0].length; }else if (sb && sb.test(txt)) { depth++; removal=sb.exec(txt)[0].length; } if ( !removal ) { removal = 1; } txt=txt.substring(removal); if (depth === 0) { break; } } return ret + (repl || '') + txt; }; /** @private */ Previewmaker.prototype.makeRegexp = function (x, prefix, suffix) { prefix = prefix || ''; suffix = suffix || ''; var reStr=''; var flags=''; if (isString(x)) { reStr=prefix + literalizeRegex(x) + suffix; } else if (isRegExp(x)) { var s=x.toString().substring(1); var sp=s.split('/'); flags=sp[sp.length-1]; sp[sp.length-1]=''; s=sp.join('/'); s=s.substring(0,s.length-1); reStr= prefix + s + suffix; } else { log ('makeRegexp failed'); } log ('makeRegexp: got reStr=' + reStr + ', flags=' + flags); return RegExp(reStr, flags); }; /** @private */ Previewmaker.prototype.killBoxTemplates = function () { // taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general // also, have float_begin, ... float_end this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /[}][}]\s*/, '{{'); // infoboxes etc // from User:Zyxw/popups.js: kill frames too this.kill(RegExp('[{][{][^{}\\s|]*?(infobox|elementbox|frame)[_ ]', 'i'), /[}][}]\s*/, '{{'); }; /** @private */ Previewmaker.prototype.killTemplates = function () { // the plus sign is to avoid transcluding [[Template:', ']] this.kill('{'+'{', '}'+'}', '{', '}', ' '); }; /** @private */ Previewmaker.prototype.killTables = function () { // tables are bad, too // this can be slow, but it's an inprovement over a browser hang // torture test: [[Comparison_of_Intel_Central_Processing_Units]] this.kill('{|', /[|]}\s*/, '{|'); this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i); // remove lines starting with a pipe for the hell of it (?) this.data=this.data.replace(RegExp('^[|].*$', 'mg'), ''); }; /** @private */ Previewmaker.prototype.killImages = function () { var forbiddenNamespaceAliases = []; jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) { if (_namespaceId!=pg.nsImageId && _namespaceId!=pg.nsCategoryId) return; forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments! }); // images and categories are a nono this.kill(RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'), /\]\]\s*/, '[', ']'); }; /** @private */ Previewmaker.prototype.killHTML = function () { // kill <ref ...>...</ref> this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i); // let's also delete entire lines starting with <. it's worth a try. this.data=this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n'); // and those pesky html tags, but not <nowiki> or <blockquote> var splitted=this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/); var len=splitted.length; for (var i=1; i<len; i=i+2) { switch (splitted[i]) { case '<nowiki>': case '</nowiki>': case '<blockquote>': case '</blockquote>': break; default: splitted[i]=''; } } this.data=splitted.join(''); }; /** @private */ Previewmaker.prototype.killChunks = function() { // heuristics alert // chunks of italic text? you crazy, man? var italicChunkRegex=new RegExp ("((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+", 'g'); // keep stuff separated, though, so stick in \n (fixes [[Union Jack]]? this.data=this.data.replace(italicChunkRegex, '\n'); }; /** @private */ Previewmaker.prototype.mopup = function () { // we simply *can't* be doing with horizontal rules right now this.data=this.data.replace(RegExp('^-{4,}','mg'),''); // no indented lines this.data=this.data.replace(RegExp('(^|\\n) *:[^\\n]*','g'), ''); // replace __TOC__, __NOTOC__ and whatever else there is // this'll probably do this.data=this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'),''); }; /** @private */ Previewmaker.prototype.firstBit = function () { // dont't be givin' me no subsequent paragraphs, you hear me? /// first we "normalize" section headings, removing whitespace after, adding before var d=this.data; if (getValueOf('popupPreviewCutHeadings')) { this.data=this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 '); /// then we want to get rid of paragraph breaks whose text ends badly this.data=this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n'); this.data=this.data.replace(RegExp('^[\\s\\n]*'), ''); var stuff=(RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data); if (stuff) { d = stuff[0]; } if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; } /// now put \n\n after sections so that bullets and numbered lists work d=d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n'); } // Split sentences. Superfluous sentences are RIGHT OUT. // note: exactly 1 set of parens here needed to make the slice work d = d.parenSplit(RegExp('([!?.]+["'+"'"+']*\\s)','g')); // leading space is bad, mmkay? d[0]=d[0].replace(RegExp('^\\s*'), ''); var notSentenceEnds=RegExp('([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$', 'i'); d = this.fixSentenceEnds(d, notSentenceEnds); this.fullLength=d.join('').length; var n=this.maxSentences; var dd=this.firstSentences(d,n); do { dd=this.firstSentences(d,n); --n; } while ( dd.length > this.maxCharacters && n !== 0 ); this.data = dd; }; /** @private */ Previewmaker.prototype.fixSentenceEnds = function(strs, reg) { // take an array of strings, strs // join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg for (var i=0; i<strs.length-2; ++i) { if (reg.test(strs[i])) { var a=[]; for (var j=0; j<strs.length; ++j) { if (j<i) a[j]=strs[j]; if (j==i) a[i]=strs[i]+strs[i+1]+strs[i+2]; if (j>i+2) a[j-2]=strs[j]; } return this.fixSentenceEnds(a,reg); } } return strs; }; /** @private */ Previewmaker.prototype.firstSentences = function(strs, howmany) { var t=strs.slice(0, 2*howmany); return t.join(''); }; /** @private */ Previewmaker.prototype.killBadWhitespace = function() { // also cleans up isolated '''', eg [[Suntory Sungoliath]] this.data=this.data.replace(RegExp('^ *\'+ *$', 'gm'), ''); }; /** Runs the various methods to generate the preview. The preview is stored in the <code>html</html> field. @private */ Previewmaker.prototype.makePreview = function() { if (this.owner.article.namespaceId()!=pg.nsTemplateId && this.owner.article.namespaceId()!=pg.nsImageId ) { this.killComments(); this.killDivs(); this.killGalleries(); this.killBoxTemplates(); if (getValueOf('popupPreviewKillTemplates')) { this.killTemplates(); } else { this.killMultilineTemplates(); } this.killTables(); this.killImages(); this.killHTML(); this.killChunks(); this.mopup(); this.firstBit(); this.killBadWhitespace(); } else { this.killHTML(); } this.html=wiki2html(this.data, this.baseUrl); // needs livepreview this.fixHTML(); this.stripLongTemplates(); }; /** @private */ Previewmaker.prototype.esWiki2HtmlPart = function(data) { var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink reLinks.lastIndex = 0; //reset regex var match; var result = ""; var postfixIndex = 0; while ((match = reLinks.exec(data))) //match all wikilinks { //FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases. result += pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) + '<a href="'+Insta.conf.paths.articles+pg.escapeQuotesHTML(match[1])+'">'+pg.escapeQuotesHTML((match[2]?match[2]:match[1])+match[3])+"</a>"; postfixIndex = reLinks.lastIndex; } //append the rest result += pg.escapeQuotesHTML(data.substring(postfixIndex)); return result; }; Previewmaker.prototype.editSummaryPreview=function() { var reAes = /\/\* *(.*?) *\*\//g; //match the first section marker reAes.lastIndex = 0; //reset regex var match; match = reAes.exec(this.data); if (match) { //we have a section link. Split it, process it, combine it. var prefix = this.data.substring(0,match.index-1); var section = match[1]; var postfix = this.data.substring(reAes.lastIndex); var start = "<span class='autocomment'>"; var end = "</span>"; if (prefix.length>0) start = this.esWiki2HtmlPart(prefix) + " " + start + "- "; if (postfix.length>0) end = ": " + end + this.esWiki2HtmlPart(postfix); var t=new Title().fromURL(this.baseUrl); t.anchorFromUtf(section); var sectionLink = Insta.conf.paths.articles + pg.escapeQuotesHTML(t.toString(true)) + '#' + pg.escapeQuotesHTML(t.anchor); return start + '<a href="'+sectionLink+'">&rarr;</a> '+pg.escapeQuotesHTML(section) + end; } //else there's no section link, htmlify the whole thing. return this.esWiki2HtmlPart(this.data); }; //<NOLITE> /** Test function for debugging preview problems one step at a time. */ /*eslint-disable */ function previewSteps(txt) { try { txt=txt || document.editform.wpTextbox1.value; } catch (err) { if (pg.cache.pages.length > 0) { txt=pg.cache.pages[pg.cache.pages.length-1].data; } else { alert('provide text or use an edit page'); } } txt=txt.substring(0,10000); var base=pg.wiki.articlebase + Title.fromURL(document.location.href).urlString(); var p=new Previewmaker(txt, base, pg.current.link.navpopup); if (this.owner.article.namespaceId() != pg.nsTemplateId) { p.killComments(); if (!confirm('done killComments(). Continue?\n---\n' + p.data)) { return; } p.killDivs(); if (!confirm('done killDivs(). Continue?\n---\n' + p.data)) { return; } p.killGalleries(); if (!confirm('done killGalleries(). Continue?\n---\n' + p.data)) { return; } p.killBoxTemplates(); if (!confirm('done killBoxTemplates(). Continue?\n---\n' + p.data)) { return; } if (getValueOf('popupPreviewKillTemplates')) { p.killTemplates(); if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) { return; } } else { p.killMultilineTemplates(); if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) { return; } } p.killTables(); if (!confirm('done killTables(). Continue?\n---\n' + p.data)) { return; } p.killImages(); if (!confirm('done killImages(). Continue?\n---\n' + p.data)) { return; } p.killHTML(); if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) { return; } p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; } p.mopup(); if (!confirm('done mopup(). Continue?\n---\n' + p.data)) { return; } p.firstBit(); if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) { return; } p.killBadWhitespace(); if (!confirm('done killBadWhitespace(). Continue?\n---\n' + p.data)) { return; } } p.html=wiki2html(p.data, base); // needs livepreview p.fixHTML(); if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) { return; } p.stripLongTemplates(); if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) { return; } alert('finished preview - end result follows.\n---\n' + p.html); } /*eslint-enable */ //</NOLITE> /** Works around livepreview bugs. @private */ Previewmaker.prototype.fixHTML = function() { if(!this.html) return; var ret = this.html; // fix question marks in wiki links // maybe this'll break some stuff :-( ret=ret.replace(RegExp('(<a href="' + pg.wiki.articlePath + '/[^"]*)[?](.*?")', 'g'), '$1%3F$2'); ret=ret.replace(RegExp('(<a href=\'' + pg.wiki.articlePath + '/[^\']*)[?](.*?\')', 'g'), '$1%3F$2'); // FIXME fix up % too this.html=ret; }; /** Generates the preview and displays it in the current popup. Does nothing if the generated preview is invalid or consists of whitespace only. Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true. */ Previewmaker.prototype.showPreview = function () { this.makePreview(); if (typeof this.html != typeof '') return; if (RegExp('^\\s*$').test(this.html)) return; setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber); setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, { owner: this.owner }); var more = (this.fullLength > this.data.length) ? this.moreLink() : ''; setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber); }; /** @private */ Previewmaker.prototype.moreLink=function() { var a=document.createElement('a'); a.className='popupMoreLink'; a.innerHTML=popupString('more...'); var savedThis=this; a.onclick=function() { savedThis.maxCharacters+=2000; savedThis.maxSentences+=20; savedThis.setData(); savedThis.showPreview(); }; return a; }; /** @private */ Previewmaker.prototype.stripLongTemplates = function() { // operates on the HTML! this.html=this.html.replace(RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'), ''); this.html=this.html.split('\n').join(' '); // workaround for <pre> templates this.html=this.html.replace(RegExp('[{][{][^}]*<pre>[^}]*[}][}]','gi'), ''); }; /** @private */ Previewmaker.prototype.killMultilineTemplates = function() { this.kill('{{{', '}}}'); this.kill(RegExp('\\s*[{][{][^{}]*\\n'), '}}', '{{'); }; // ENDFILE: previewmaker.js // STARTFILE: querypreview.js function loadAPIPreview(queryType, article, navpop) { var art=new Title(article).urlString(); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&'; var htmlGenerator=function(/*a, d*/){alert('invalid html generator');}; var usernameart = ''; switch (queryType) { case 'history': url += 'titles=' + art + '&prop=revisions&rvlimit=' + getValueOf('popupHistoryPreviewLimit'); htmlGenerator=APIhistoryPreviewHTML; break; case 'category': url += 'list=categorymembers&cmtitle=' + art; htmlGenerator=APIcategoryPreviewHTML; break; case 'userinfo': var username = new Title( article ).userName(); usernameart = encodeURIComponent( username ); if (pg.re.ipUser.test(username)) { url += 'list=blocks&bkprop=range|restrictions&bkip=' + usernameart; } else { url += 'list=users|usercontribs&usprop=blockinfo|groups|editcount|registration|gender&ususers=' + usernameart + "&meta=globaluserinfo&guiprop=groups|unattached&guiuser="+ usernameart + "&uclimit=1&ucprop=timestamp&ucuser=" + usernameart; } htmlGenerator=APIuserInfoPreviewHTML; break; case 'contribs': usernameart = encodeURIComponent( new Title( article ).userName() ); url += 'list=usercontribs&ucuser=' + usernameart + '&uclimit=' + getValueOf('popupContribsPreviewLimit'); htmlGenerator=APIcontribsPreviewHTML; break; case 'imagepagepreview': var trail=''; if (getValueOf('popupImageLinks')) { trail = '&list=imageusage&iutitle=' + art; } url += 'titles=' + art + '&prop=revisions|imageinfo&rvprop=content' + trail; htmlGenerator=APIimagepagePreviewHTML; break; case 'backlinks': url += 'list=backlinks&bltitle=' + art; htmlGenerator=APIbacklinksPreviewHTML; break; case 'revision': if (article.oldid) { url += 'revids=' + article.oldid; } else { url += 'titles=' + article.removeAnchor().urlString(); } url += '&prop=revisions|pageprops|info|images|categories&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max'; htmlGenerator=APIrevisionPreviewHTML; break; } pendingNavpopTask(navpop); var callback=function(d){ log( "callback of API functions was hit" ); showAPIPreview(queryType, htmlGenerator(article,d,navpop), navpop.idNumber, navpop, d); }; var go = function(){ getPageWithCaching(url, callback, navpop); return true; }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_'+queryType+'_QUERY_DATA'); } } function linkList(list) { list.sort(function(x,y) { return (x==y ? 0 : (x<y ? -1 : 1)); }); var buf=[]; for (var i=0; i<list.length; ++i) { buf.push(wikiLink({article: new Title(list[i]), text: list[i].split(' ').join('&nbsp;'), action: 'view'})); } return buf.join(', '); } function getTimeOffset() { var tz = mw.user.options.get('timecorrection'); if(tz) { if( tz.indexOf('|') > -1 ) { // New format return parseInt(tz.split('|')[1],10); } else if ( tz.indexOf(':') > -1 ) { // Old format return( parseInt(tz,10)*60 + parseInt(tz.split(':')[1],10) ); } } return 0; } /* * Creates a HTML table that's shown in the history and user-contribs popups. * @param {Object[]} h - a list of revisions, returned from the API * @param {boolean} reallyContribs - true only if we're displaying user contributions */ function editPreviewTable(article, h, reallyContribs, timeOffset) { var html=['<table>']; var day=null; var curart=article; var page=null; var makeFirstColumnLinks; if(reallyContribs) { // We're showing user contributions, so make (diff | hist) links makeFirstColumnLinks = function(currentRevision) { var result = '('; result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&diff=prev' + '&oldid=' + currentRevision.revid + '">' + popupString('diff') + '</a>'; result += '&nbsp;|&nbsp;'; result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&action=history">' + popupString('hist') + '</a>'; result += ')'; return result; }; } else { // It's a regular history page, so make (cur | last) links var firstRevid = h[0].revid; makeFirstColumnLinks = function(currentRevision) { var result = '('; result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=' + firstRevid + '&oldid=' + currentRevision.revid + '">' + popupString('cur') + '</a>'; result += '&nbsp;|&nbsp;'; result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=prev&oldid=' + currentRevision.revid + '">' + popupString('last') + '</a>'; result += ')'; return result; }; } for (var i=0; i<h.length; ++i) { if (reallyContribs) { page = h[i].title; curart = new Title(page); } var minor = h[i].minor ? '<b>m </b>' : ''; var editDate = adjustDate(getDateFromTimestamp(h[i].timestamp), timeOffset); var thisDay = dayFormat(editDate); var thisTime = timeFormat(editDate); if (thisDay == day) { thisDay = ''; } else { day = thisDay; } if (thisDay) { html.push( '<tr><td colspan=3><span class="popup_history_date">' + thisDay+'</span></td></tr>' ); } html.push('<tr class="popup_history_row_' + ( (i%2) ? 'odd' : 'even') + '">'); html.push('<td>' + makeFirstColumnLinks(h[i]) + '</td>'); html.push('<td>' + '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&oldid=' + h[i].revid + '">' + thisTime + '</a></td>'); var col3url='', col3txt=''; if (!reallyContribs) { var user=h[i].user; if( !h[i].userhidden ) { if( pg.re.ipUser.test(user) ) { col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Contributions&target=' + new Title(user).urlString(); } else { col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(user).urlString(); } col3txt=pg.escapeQuotesHTML(user); } else { col3url=getValueOf('popupRevDelUrl'); col3txt=pg.escapeQuotesHTML( popupString('revdel')); } } else { col3url=pg.wiki.titlebase + curart.urlString(); col3txt=pg.escapeQuotesHTML(page); } html.push('<td>' + (reallyContribs ? minor : '') + '<a href="' + col3url + '">' + col3txt + '</a></td>'); var comment=''; var c=h[i].comment || h[i].content; if (c) { comment=new Previewmaker(c, new Title(curart).toUrl()).editSummaryPreview(); } else if ( h[i].commenthidden ) { comment=popupString('revdel'); } html.push('<td>' + (!reallyContribs ? minor : '') + comment + '</td>'); html.push('</tr>'); html=[html.join('')]; } html.push('</table>'); return html.join(''); } function getDateFromTimestamp(t) { var s=t.split(/[^0-9]/); switch(s.length) { case 0: return null; case 1: return new Date(s[0]); case 2: return new Date(s[0], s[1]-1); case 3: return new Date(s[0], s[1]-1, s[2]); case 4: return new Date(s[0], s[1]-1, s[2], s[3]); case 5: return new Date(s[0], s[1]-1, s[2], s[3], s[4]); case 6: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5]); default: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5], s[6]); } } function adjustDate(d, offset) { // offset is in minutes var o=offset * 60 * 1000; return new Date( +d + o); } function dayFormat(editDate, utc) { if (utc) { return map(zeroFill, [editDate.getUTCFullYear(), editDate.getUTCMonth()+1, editDate.getUTCDate()]).join('-'); } return map(zeroFill, [editDate.getFullYear(), editDate.getMonth()+1, editDate.getDate()]).join('-'); } function timeFormat(editDate, utc) { if (utc) { return map(zeroFill, [editDate.getUTCHours(), editDate.getUTCMinutes(), editDate.getUTCSeconds()]).join(':'); } return map(zeroFill, [editDate.getHours(), editDate.getMinutes(), editDate.getSeconds()]).join(':'); } function showAPIPreview(queryType, html, id, navpop, download) { // DJ: done var target='popupPreview'; completedNavpopTask(navpop); switch (queryType) { case 'imagelinks': case 'category': target='popupPostPreview'; break; case 'userinfo': target='popupUserData'; break; case 'revision': insertPreview(download); return; } setPopupTipsAndHTML(html, target, id); } function APIrevisionPreviewHTML(article, download) { try{ var jsObj=getJsObj(download.data); var page=anyChild(jsObj.query.pages); if( page.missing ) { // TODO we need to fix this proper later on download.owner = null; return; } var content = ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; if( typeof content === 'string' ) { download.data = content; download.lastModified = new Date(page.revisions[0].timestamp); } } catch(someError) { return 'Revision preview failed :('; } } function APIbacklinksPreviewHTML(article, download/*, navpop*/ ) { try { var jsObj=getJsObj(download.data); var list=jsObj.query.backlinks; var html=[]; if (!list) { return popupString('No backlinks found'); } for ( var i=0; i < list.length; i++ ) { var t=new Title(list[i].title); html.push('<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t + '</a>'); } html=html.join(', '); if (jsObj['continue'] && jsObj['continue'].blcontinue) { html += popupString(' and more'); } return html; } catch (someError) { return 'backlinksPreviewHTML went wonky'; } } pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) { log( "APIsharedImagePagePreviewHTML" ); var popupid = obj.requestid; if( obj.query && obj.query.pages ) { var page=anyChild(obj.query.pages ); var content = ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; if( typeof content === 'string' ) { /* Not entirely safe, but the best we can do */ var p=new Previewmaker(content, pg.current.link.navpopup.article, pg.current.link.navpopup); p.makePreview(); setPopupHTML( p.html, "popupSecondPreview", popupid ); } } }; function APIimagepagePreviewHTML(article, download, navpop) { try { var jsObj=getJsObj(download.data); var page=anyChild(jsObj.query.pages); var content= ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; var ret=''; var alt=''; try{alt=navpop.parentAnchor.childNodes[0].alt;} catch(e){} if (alt) { ret = ret + '<hr /><b>' + popupString('Alt text:') + '</b> ' + pg.escapeQuotesHTML(alt); } if (typeof content === 'string') { var p=prepPreviewmaker(content, article, navpop); p.makePreview(); if (p.html) { ret += '<hr />' + p.html; } if (getValueOf('popupSummaryData')) { var info=getPageInfo(content, download); log(info); setPopupTrailer(info, navpop.idNumber); } } if (page && page.imagerepository == "shared" ) { var art=new Title(article); var encart = encodeURIComponent( "File:" + art.stripNamespace() ); var shared_url = pg.wiki.apicommonsbase + '?format=json&formatversion=2' + '&callback=pg.fn.APIsharedImagePagePreviewHTML' + '&requestid=' + navpop.idNumber + '&action=query&prop=revisions&rvprop=content&titles=' + encart; ret = ret +'<hr />' + popupString( 'Image from Commons') + ': <a href="' + pg.wiki.commonsbase + '?title=' + encart + '">' + popupString( 'Description page') + '</a>'; mw.loader.load( shared_url ); } showAPIPreview('imagelinks', APIimagelinksPreviewHTML(article,download), navpop.idNumber, download); return ret; } catch (someError) { return 'API imagepage preview failed :('; } } function APIimagelinksPreviewHTML(article, download) { try { var jsobj=getJsObj(download.data); var list=jsobj.query.imageusage; if (list) { var ret=[]; for (var i=0; i < list.length; i++) { ret.push(list[i].title); } if (ret.length === 0) { return popupString('No image links found'); } return '<h2>' + popupString('File links') + '</h2>' + linkList(ret); } else { return popupString('No image links found'); } } catch(someError) { return 'Image links preview generation failed :('; } } function APIcategoryPreviewHTML(article, download) { try{ var jsobj=getJsObj(download.data); var list=jsobj.query.categorymembers; var ret=[]; for (var p=0; p < list.length; p++) { ret.push(list[p].title); } if (ret.length === 0) { return popupString('Empty category'); } ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' +linkList(ret); if (jsobj['continue'] && jsobj['continue'].cmcontinue) { ret += popupString(' and more'); } return ret; } catch(someError) { return 'Category preview failed :('; } } function APIuserInfoPreviewHTML(article, download) { var ret=[]; var queryobj = {}; try{ queryobj=getJsObj(download.data).query; } catch(someError) { return 'Userinfo preview failed :('; } var user=anyChild(queryobj.users); if (user) { var globaluserinfo=queryobj.globaluserinfo; if (user.invalid === '') { ret.push( popupString( 'Invalid user') ); } else if (user.missing === '') { ret.push( popupString( 'Not a registered username') ); } if( user.blockedby ) { if( user.blockpartial ) { ret.push('<b>' + popupString('Has blocks') + '</b>'); } else { ret.push('<b>' + popupString('CẤM') + '</b>'); } } if( globaluserinfo && ( 'locked' in globaluserinfo || 'hidden' in globaluserinfo ) ) { var lockedSulAccountIsAttachedToThis = true; for( var i=0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) { if ( globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname') ) { lockedSulAccountIsAttachedToThis=false; break; } } if (lockedSulAccountIsAttachedToThis) { if ( 'locked' in globaluserinfo ) ret.push('<b><i>' + popupString('LOCKED') + '</i></b>'); if ( 'hidden' in globaluserinfo ) ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>'); } } if( getValueOf('popupShowGender') && user.gender ) { switch( user.gender ) { case "male": ret.push( popupString( "\u2642" ) ); break; case "female": ret.push( popupString( "\u2640" ) ); break; } } if( user.groups ) { for( var j=0; j < user.groups.length; j++) { var currentGroup = user.groups[j]; if( ["*", "user", "autoconfirmed", "extendedconfirmed"].indexOf( currentGroup ) === -1 ) { ret.push( pg.escapeQuotesHTML(user.groups[j]) ); } } } if( globaluserinfo && globaluserinfo.groups ) { for( var k=0; k < globaluserinfo.groups.length; k++) { ret.push( '<i>'+pg.escapeQuotesHTML(globaluserinfo.groups[k])+'</i>' ); } } if( user.registration ) ret.push( pg.escapeQuotesHTML((user.editcount?user.editcount:'0') + popupString(' sửa đổi từ: ') + (user.registration?dayFormat(getDateFromTimestamp(user.registration)):'')) ); } if (queryobj.usercontribs && queryobj.usercontribs.length) { ret.push( popupString('last edit on ') + dayFormat(getDateFromTimestamp(queryobj.usercontribs[0].timestamp)) ); } if (queryobj.blocks) { ret.push( popupString( 'IP user') ); //we only request list=blocks for IPs for (var l=0; l<queryobj.blocks.length; l++) { console.log(queryobj); var rbstr = queryobj.blocks[l].rangestart === queryobj.blocks[l].rangeend ? 'BLOCK' : 'RANGEBLOCK'; rbstr = (!Array.isArray(queryobj.blocks[l].restrictions) ? 'Has ' + rbstr.toLowerCase() + 's' : rbstr + 'ED'); ret.push('<b>' + popupString(rbstr) + '</b>' ); } } ret = '<hr />' + ret.join( ', ' ); return ret; } function APIcontribsPreviewHTML(article, download, navpop) { return APIhistoryPreviewHTML(article, download, navpop, true); } function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) { try { var jsobj=getJsObj(download.data); var edits = []; if( reallyContribs ) { edits=jsobj.query.usercontribs; } else { edits=anyChild(jsobj.query.pages).revisions; } var ret=editPreviewTable(article, edits, reallyContribs, getTimeOffset()); return ret; } catch (someError) { return 'History preview failed :-('; } } //</NOLITE> // ENDFILE: querypreview.js // STARTFILE: debug.js //////////////////////////////////////////////////////////////////// // Debugging functions //////////////////////////////////////////////////////////////////// function setupDebugging() { //<NOLITE> if (window.popupDebug) { // popupDebug is set from .version window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; }; window.console.log(x); }; window.errlog=function(x) { window.console.error(x); }; log('Initializing logger'); } else { //</NOLITE> window.log = function() {}; window.errlog = function() {}; //<NOLITE> } //</NOLITE> } // ENDFILE: debug.js // STARTFILE: images.js // load image of type Title. function loadImage(image, navpop) { if (typeof image.stripNamespace != 'function') { alert('loadImages bad'); } // API call to retrieve image info. if ( !getValueOf('popupImages') ) return; if ( !isValidImageName(image) ) return false; var art=image.urlString(); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query'; url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge'); url += '&titles=' + art; pendingNavpopTask(navpop); var callback=function(d){ popupsInsertImage(navpop.idNumber, navpop, d); }; var go = function(){ getPageWithCaching(url, callback, navpop); return true; }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA'); } } function popupsInsertImage(id, navpop, download) { log( "popupsInsertImage"); var imageinfo; try { var jsObj=getJsObj(download.data); var imagepage=anyChild(jsObj.query.pages); if (typeof imagepage.imageinfo === 'undefined') return; imageinfo = imagepage.imageinfo[0]; } catch (someError) { log( "popupsInsertImage failed :(" ); return; } var popupImage = document.getElementById("popupImg"+id); if (!popupImage) { log( "could not find insertion point for image"); return; } popupImage.width=getValueOf('popupImageSize'); popupImage.style.display='inline'; // Set the source for the image. if( imageinfo.thumburl ) popupImage.src=imageinfo.thumburl; else if( imageinfo.mime.indexOf("image") === 0 ){ popupImage.src=imageinfo.url; log( "a thumb could not be found, using original image" ); } else log( "fullsize imagethumb, but not sure if it's an image"); var a=document.getElementById("popupImageLink"+id); if (a === null) { return null; } // Determine the action of the surrouding imagelink. switch (getValueOf('popupThumbAction')) { case 'imagepage': if (pg.current.article.namespaceId()!=pg.nsImageId) { a.href=imageinfo.descriptionurl; // FIXME: unreliable pg.idNumber popTipsSoonFn('popupImage' + id)(); break; } /* falls through */ case 'sizetoggle': a.onclick=toggleSize; a.title=popupString('Toggle image size'); return; case 'linkfull': a.href = imageinfo.url; a.title=popupString('Open full-size image'); return; } } // Toggles the image between inline small and navpop fullwidth. // It's the same image, no actual sizechange occurs, only display width. function toggleSize() { var imgContainer=this; if (!imgContainer) { alert('imgContainer is null :/'); return; } var img=imgContainer.firstChild; if (!img) { alert('img is null :/'); return; } if (!img.style.width || img.style.width==='') { img.style.width='100%'; } else { img.style.width=''; } } // Returns one title of an image from wikiText. function getValidImageFromWikiText(wikiText) { // nb in pg.re.image we're interested in the second bracketed expression // this may change if the regex changes :-( //var match=pg.re.image.exec(wikiText); var matched=null; var match; // strip html comments, used by evil bots :-( var t = removeMatchesUnless(wikiText, RegExp('(<!--[\\s\\S]*?-->)'), 1, RegExp('^<!--[^[]*popup', 'i')); while ( ( match = pg.re.image.exec(t) ) ) { // now find a sane image name - exclude templates by seeking { var m = match[2] || match[6]; if ( isValidImageName(m) ) { matched=m; break; } } pg.re.image.lastIndex=0; if (!matched) { return null; } return mw.config.get('wgFormattedNamespaces')[pg.nsImageId]+':'+upcaseFirst(matched); } function removeMatchesUnless(str, re1, parencount, re2) { var split=str.parenSplit(re1); var c=parencount + 1; for (var i=0; i<split.length; ++i) { if ( i%c === 0 || re2.test(split[i]) ) { continue; } split[i]=''; } return split.join(''); } //</NOLITE> // ENDFILE: images.js // STARTFILE: namespaces.js // Set up namespaces and other non-strings.js localization // (currently that means redirs too) function setNamespaces() { pg.nsSpecialId = -1; pg.nsMainspaceId = 0; pg.nsImageId = 6; pg.nsUserId = 2; pg.nsUsertalkId = 3; pg.nsCategoryId = 14; pg.nsTemplateId = 10; } function setRedirs() { var r='redirect'; var R='REDIRECT'; var redirLists={ //<NOLITE> 'ar': [ R, 'تحويل' ], 'be': [ r, 'перанакіраваньне' ], 'bg': [ r, 'пренасочване', 'виж' ], 'bs': [ r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI' ], 'cs': [ R, 'PŘESMĚRUJ' ], 'cy': [ r, 'ail-cyfeirio' ], 'de': [ R, 'WEITERLEITUNG' ], 'el': [ R, 'ΑΝΑΚΑΤΕΥΘΥΝΣΗ'], 'eo': [ R, 'ALIDIREKTU', 'ALIDIREKTI' ], 'es': [ R, 'REDIRECCIÓN' ], 'et': [ r, 'suuna' ], 'ga': [ r, 'athsheoladh' ], 'gl': [ r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'], 'he': [ R, 'הפניה' ], 'hu': [ R, 'ÁTIRÁNYÍTÁS' ], 'is': [ r, 'tilvísun', 'TILVÍSUN' ], 'it': [ R, 'RINVIA', 'Rinvia'], 'ja': [ R, '転送' ], 'mk': [ r, 'пренасочување', 'види' ], 'nds': [ r, 'wiederleiden' ], 'nl': [ R, 'DOORVERWIJZING' ], 'nn': [ r, 'omdiriger' ], 'pl': [ R, 'PATRZ', 'PRZEKIERUJ', 'TAM' ], 'pt': [ R, 'redir' ], 'ru': [ R, 'ПЕРЕНАПРАВЛЕНИЕ', 'ПЕРЕНАПР' ], 'sk': [ r, 'presmeruj' ], 'sr': [ r, 'Преусмери', 'преусмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI' ], 'tt': [ R, 'yünältü', 'перенаправление', 'перенапр' ], 'uk': [ R, 'ПЕРЕНАПРАВЛЕННЯ', 'ПЕРЕНАПР' ], 'vi': [ r, 'đổi' ], 'zh': [ R, '重定向'] // no comma //</NOLITE> }; var redirList=redirLists[ pg.wiki.lang ] || [r, R]; // Mediawiki is very tolerant about what comes after the #redirect at the start pg.re.redirect=RegExp('^\\s*[#](' + redirList.join('|') + ').*?\\[{2}([^\\|\\]]*)(|[^\\]]*)?\\]{2}\\s*(.*)', 'i'); } function setInterwiki() { if (pg.wiki.wikimedia) { // From https://meta.wikimedia.org/wiki/List_of_Wikipedias pg.wiki.interwiki='aa|ab|ace|af|ak|als|am|an|ang|ar|arc|arz|as|ast|av|ay|az|ba|bar|bat-smg|bcl|be|be-x-old|bg|bh|bi|bjn|bm|bn|bo|bpy|br|bs|bug|bxr|ca|cbk-zam|cdo|ce|ceb|ch|cho|chr|chy|ckb|co|cr|crh|cs|csb|cu|cv|cy|da|de|diq|dsb|dv|dz|ee|el|eml|en|eo|es|et|eu|ext|fa|ff|fi|fiu-vro|fj|fo|fr|frp|frr|fur|fy|ga|gag|gan|gd|gl|glk|gn|got|gu|gv|ha|hak|haw|he|hi|hif|ho|hr|hsb|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|ilo|io|is|it|iu|ja|jbo|jv|ka|kaa|kab|kbd|kg|ki|kj|kk|kl|km|kn|ko|koi|kr|krc|ks|ksh|ku|kv|kw|ky|la|lad|lb|lbe|lg|li|lij|lmo|ln|lo|lt|ltg|lv|map-bms|mdf|mg|mh|mhr|mi|mk|ml|mn|mo|mr|mrj|ms|mt|mus|mwl|my|myv|mzn|na|nah|nap|nds|nds-nl|ne|new|ng|nl|nn|no|nov|nrm|nv|ny|oc|om|or|os|pa|pag|pam|pap|pcd|pdc|pfl|pi|pih|pl|pms|pnb|pnt|ps|pt|qu|rm|rmy|rn|ro|roa-rup|roa-tara|ru|rue|rw|sa|sah|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|sn|so|sq|sr|srn|ss|st|stq|su|sv|sw|szl|ta|te|tet|tg|th|ti|tk|tl|tn|to|tpi|tr|ts|tt|tum|tw|ty|udm|ug|uk|ur|uz|ve|vec|vi|vls|vo|wa|war|wo|wuu|xal|xh|yi|yo|za|zea|zh|zh-classical|zh-min-nan|zh-yue|zu'; pg.re.interwiki=RegExp('^'+pg.wiki.interwiki+':'); } else { pg.wiki.interwiki=null; pg.re.interwiki=RegExp('^$'); } } // return a regexp pattern matching all variants to write the given namespace function nsRe(namespaceId) { var imageNamespaceVariants = []; jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) { if (_namespaceId!=namespaceId) return; _localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc); imageNamespaceVariants.push(mw.util.escapeRegExp(_localizedNamespaceLc).split(' ').join('[ _]')); imageNamespaceVariants.push(mw.util.escapeRegExp(encodeURI(_localizedNamespaceLc))); }); return '(?:' + imageNamespaceVariants.join('|') + ')'; } function nsReImage() { return nsRe(pg.nsImageId); } // ENDFILE: namespaces.js // STARTFILE: selpop.js //<NOLITE> function getEditboxSelection() { // see http://www.webgurusforum.com/8/12/0 var editbox; try { editbox=document.editform.wpTextbox1; } catch (dang) { return; } // IE, Opera if (document.selection) { return document.selection.createRange().text; } // Mozilla var selStart = editbox.selectionStart; var selEnd = editbox.selectionEnd; return (editbox.value).substring(selStart, selEnd); } function doSelectionPopup() { // popup if the selection looks like [[foo|anything afterwards at all // or [[foo|bar]]text without ']]' // or [[foo|bar]] var sel=getEditboxSelection(); var open=sel.indexOf('[['); var pipe=sel.indexOf('|'); var close=sel.indexOf(']]'); if (open == -1 || ( pipe == -1 && close == -1) ) { return; } if (pipe != -1 && open > pipe || close != -1 && open > close) { return; } if (getValueOf('popupOnEditSelection')=='boxpreview') { return doSeparateSelectionPopup(sel); } var article=new Title(sel.substring(open+2, (pipe < 0) ? close : pipe)).urlString(); if (close > 0 && sel.substring(close+2).indexOf('[[') >= 0) { return; } var a=document.createElement('a'); a.href=pg.wiki.titlebase + article; mouseOverWikiLink2(a); if (a.navpopup) { a.navpopup.addHook(function(){runStopPopupTimer(a.navpopup);}, 'unhide', 'after'); } } function doSeparateSelectionPopup(str) { var div=document.getElementById('selectionPreview'); if (!div) { div = document.createElement('div'); div.id='selectionPreview'; try { var box=document.editform.wpTextbox1; box.parentNode.insertBefore(div, box); } catch (error) { return; } } div.innerHTML=wiki2html(str); div.ranSetupTooltipsAlready = false; popTipsSoonFn('selectionPreview')(); } //</NOLITE> // ENDFILE: selpop.js // STARTFILE: navpopup.js /** @fileoverview Defines two classes: {@link Navpopup} and {@link Mousetracker}. <code>Navpopup</code> describes popups: when they appear, where, what they look like and so on. <code>Mousetracker</code> "captures" the mouse using <code>document.onmousemove</code>. */ /** Creates a new Mousetracker. @constructor @class The Mousetracker class. This monitors mouse movements and manages associated hooks. */ function Mousetracker() { /** Interval to regularly run the hooks anyway, in milliseconds. @type Integer */ this.loopDelay=400; /** Timer for the loop. @type Timer */ this.timer=null; /** Flag - are we switched on? @type Boolean */ this.active=false; /** Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position? */ this.dirty=true; /** Array of hook functions. @private @type Array */ this.hooks=[]; } /** Adds a hook, to be called when we get events. @param {Function} f A function which is called as <code>f(x,y)</code>. It should return <code>true</code> when it wants to be removed, and <code>false</code> otherwise. */ Mousetracker.prototype.addHook = function (f) { this.hooks.push(f); }; /** Runs hooks, passing them the x and y coords of the mouse. Hook functions that return true are passed to {@link Mousetracker#removeHooks} for removal. @private */ Mousetracker.prototype.runHooks = function () { if (!this.hooks || !this.hooks.length) { return; } //log('Mousetracker.runHooks; we got some hooks to run'); var remove=false; var removeObj={}; // this method gets called a LOT - // pre-cache some variables var x=this.x, y=this.y, len = this.hooks.length; for (var i=0; i<len; ++i) { //~ run the hook function, and remove it if it returns true if (this.hooks[i](x, y)===true) { remove=true; removeObj[i]=true; } } if (remove) { this.removeHooks(removeObj); } }; /** Removes hooks. @private @param {Object} removeObj An object whose keys are the index numbers of functions for removal, with values that evaluate to true */ Mousetracker.prototype.removeHooks = function(removeObj) { var newHooks=[]; var len = this.hooks.length; for (var i=0; i<len; ++i) { if (! removeObj[i]) { newHooks.push(this.hooks[i]); } } this.hooks=newHooks; }; /** Event handler for mouse wiggles. We simply grab the event, set x and y and run the hooks. This makes the cpu all hot and bothered :-( @private @param {Event} e Mousemove event */ Mousetracker.prototype.track=function (e) { //~ Apparently this is needed in IE. e = e || window.event; var x, y; if (e) { if (e.pageX) { x=e.pageX; y=e.pageY; } else if (typeof e.clientX!='undefined') { var left, top, docElt = document.documentElement; if (docElt) { left=docElt.scrollLeft; } left = left || document.body.scrollLeft || document.scrollLeft || 0; if (docElt) { top=docElt.scrollTop; } top = top || document.body.scrollTop || document.scrollTop || 0; x=e.clientX + left; y=e.clientY + top; } else { return; } this.setPosition(x,y); } }; /** Sets the x and y coordinates stored and takes appropriate action, running hooks as appropriate. @param {Integer} x, y Screen coordinates to set */ Mousetracker.prototype.setPosition=function(x,y) { this.x = x; this.y = y; if (this.dirty || this.hooks.length === 0) { this.dirty=false; return; } if (typeof this.lastHook_x != 'number') { this.lastHook_x = -100; this.lastHook_y=-100; } var diff = (this.lastHook_x - x)*(this.lastHook_y - y); diff = (diff >= 0) ? diff : -diff; if ( diff > 1 ) { this.lastHook_x=x; this.lastHook_y=y; if (this.dirty) { this.dirty = false; } else { this.runHooks(); } } }; /** Sets things in motion, unless they are already that is, registering an event handler on <code>document.onmousemove</code>. A half-hearted attempt is made to preserve the old event handler if there is one. */ Mousetracker.prototype.enable = function () { if (this.active) { return; } this.active=true; //~ Save the current handler for mousemove events. This isn't too //~ robust, of course. this.savedHandler=document.onmousemove; //~ Gotta save @tt{this} again for the closure, and use apply for //~ the member function. var savedThis=this; document.onmousemove=function (e) {savedThis.track.apply(savedThis, [e]);}; if (this.loopDelay) { this.timer = setInterval(function() { //log('loop delay in mousetracker is working'); savedThis.runHooks();}, this.loopDelay); } }; /** Disables the tracker, removing the event handler. */ Mousetracker.prototype.disable = function () { if (!this.active) { return; } if (typeof this.savedHandler === 'function') { document.onmousemove=this.savedHandler; } else { delete document.onmousemove; } if (this.timer) { clearInterval(this.timer); } this.active=false; }; /** Creates a new Navpopup. Gets a UID for the popup and @param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable. @constructor @class The Navpopup class. This generates popup hints, and does some management of them. */ function Navpopup(/*init*/) { //alert('new Navpopup(init)'); /** UID for each Navpopup instance. Read-only. @type integer */ this.uid=Navpopup.uid++; /** Read-only flag for current visibility of the popup. @type boolean @private */ this.visible=false; /** Flag to be set when we want to cancel a previous request to show the popup in a little while. @private @type boolean */ this.noshow=false; /** Categorised list of hooks. @see #runHooks @see #addHook @private @type Object */ this.hooks={ 'create': [], 'unhide': [], 'hide': [] }; /** list of unique IDs of hook functions, to avoid duplicates @private */ this.hookIds={}; /** List of downloads associated with the popup. @private @type Array */ this.downloads=[]; /** Number of uncompleted downloads. @type integer */ this.pending=null; /** Tolerance in pixels when detecting whether the mouse has left the popup. @type integer */ this.fuzz=5; /** Flag to toggle running {@link #limitHorizontalPosition} to regulate the popup's position. @type boolean */ this.constrained=true; /** The popup width in pixels. @private @type integer */ this.width=0; /** The popup width in pixels. @private @type integer */ this.height=0; /** The main content DIV element. @type HTMLDivElement */ this.mainDiv=null; this.createMainDiv(); // if (!init || typeof init.popups_draggable=='undefined' || init.popups_draggable) { // this.makeDraggable(true); // } } /** A UID for each Navpopup. This constructor property is just a counter. @type integer @private */ Navpopup.uid=0; /** Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible. @type boolean */ Navpopup.prototype.isVisible=function() { return this.visible; }; /** Repositions popup using CSS style. @private @param {integer} x x-coordinate (px) @param {integer} y y-coordinate (px) @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition} */ Navpopup.prototype.reposition= function (x,y, noLimitHor) { log ('reposition('+x+','+y+','+noLimitHor+')'); if (typeof x != 'undefined' && x !== null) { this.left=x; } if (typeof y != 'undefined' && y !== null) { this.top=y; } if (typeof this.left != 'undefined' && typeof this.top != 'undefined') { this.mainDiv.style.left=this.left + 'px'; this.mainDiv.style.top=this.top + 'px'; } if (!noLimitHor) { this.limitHorizontalPosition(); } //console.log('navpop'+this.uid+' - (left,top)=(' + this.left + ',' + this.top + '), css=(' //+ this.mainDiv.style.left + ',' + this.mainDiv.style.top + ')'); }; /** Prevents popups from being in silly locations. Hopefully. Should not be run if {@link #constrained} is true. @private */ Navpopup.prototype.limitHorizontalPosition=function() { if (!this.constrained || this.tooWide) { return; } this.updateDimensions(); var x=this.left; var w=this.width; var cWidth=document.body.clientWidth; // log('limitHorizontalPosition: x='+x+ // ', this.left=' + this.left + // ', this.width=' + this.width + // ', cWidth=' + cWidth); if ( (x+w) >= cWidth || ( x > 0 && this.maxWidth && this.width < this.maxWidth && this.height > this.width && x > cWidth - this.maxWidth ) ) { // This is a very nasty hack. There has to be a better way! // We find the "natural" width of the div by positioning it at the far left // then reset it so that it should be flush right (well, nearly) this.mainDiv.style.left='-10000px'; this.mainDiv.style.width = this.maxWidth + 'px'; var naturalWidth=parseInt(this.mainDiv.offsetWidth, 10); var newLeft=cWidth - naturalWidth - 1; if (newLeft < 0) { newLeft = 0; this.tooWide=true; } // still unstable for really wide popups? log ('limitHorizontalPosition: moving to ('+newLeft + ','+ this.top+');' + ' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth); this.reposition(newLeft, null, true); } }; /** Counter indicating the z-order of the "highest" popup. We start the z-index at 1000 so that popups are above everything else on the screen. @private @type integer */ Navpopup.highest=1000; /** Brings popup to the top of the z-order. We increment the {@link #highest} property of the contructor here. @private */ Navpopup.prototype.raise = function () { this.mainDiv.style.zIndex=Navpopup.highest + 1; ++Navpopup.highest; }; /** Shows the popup provided {@link #noshow} is not true. Updates the position, brings the popup to the top of the z-order and unhides it. */ Navpopup.prototype.show = function () { //document.title+='s'; if (this.noshow) { return; } //document.title+='t'; this.reposition(); this.raise(); this.unhide(); }; /** Checks to see if the mouse pointer has stabilised (checking every <code>time</code>/2 milliseconds) and runs the {@link #show} method if it has. @param {integer} time The minimum time (ms) before the popup may be shown. */ Navpopup.prototype.showSoonIfStable = function (time) { log ('showSoonIfStable, time='+time); if (this.visible) { return; } this.noshow = false; //~ initialize these variables so that we never run @tt{show} after //~ just half the time this.stable_x = -10000; this.stable_y = -10000; var stableShow = function() { log('stableShow called'); var new_x = Navpopup.tracker.x, new_y = Navpopup.tracker.y; var dx = savedThis.stable_x - new_x, dy = savedThis.stable_y - new_y; var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz; //document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] '; if ( dx * dx <= fuzz2 && dy * dy <= fuzz2 ) { log ('mouse is stable'); clearInterval(savedThis.showSoonStableTimer); savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]); savedThis.show.apply(savedThis, []); savedThis.limitHorizontalPosition.apply(savedThis, []); return; } savedThis.stable_x = new_x; savedThis.stable_y = new_y; }; var savedThis = this; this.showSoonStableTimer = setInterval(stableShow, time/2); }; /** Sets the {@link #noshow} flag and hides the popup. This should be called when the mouse leaves the link before (or after) it's actually been displayed. */ Navpopup.prototype.banish = function () { log ('banish called'); // hide and prevent showing with showSoon in the future this.noshow=true; if (this.showSoonStableTimer) { log('clearing showSoonStableTimer'); clearInterval(this.showSoonStableTimer); } this.hide(); }; /** Runs hooks added with {@link #addHook}. @private @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide' @param {String} when Controls exactly when the hook is run: either 'before' or 'after' */ Navpopup.prototype.runHooks = function (key, when) { if (!this.hooks[key]) { return; } var keyHooks=this.hooks[key]; var len=keyHooks.length; for (var i=0; i< len; ++i) { if (keyHooks[i] && keyHooks[i].when == when) { if (keyHooks[i].hook.apply(this, [])) { // remove the hook if (keyHooks[i].hookId) { delete this.hookIds[keyHooks[i].hookId]; } keyHooks[i]=null; } } } }; /** Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the Navpopup instance, and no arguments. @param {Function} hook The hook function. Functions that return true are deleted. @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide' @param {String} when Controls exactly when the hook is run: either 'before' or 'after' @param {String} uid A truthy string identifying the hook function; if it matches another hook in this position, it won't be added again. */ Navpopup.prototype.addHook = function ( hook, key, when, uid ) { when = when || 'after'; if (!this.hooks[key]) { return; } // if uid is specified, don't add duplicates var hookId=null; if (uid) { hookId=[key,when,uid].join('|'); if (this.hookIds[hookId]) { return; } this.hookIds[hookId]=true; } this.hooks[key].push( {hook: hook, when: when, hookId: hookId} ); }; /** Creates the main DIV element, which contains all the actual popup content. Runs hooks with key 'create'. @private */ Navpopup.prototype.createMainDiv = function () { if (this.mainDiv) { return; } this.runHooks('create', 'before'); var mainDiv=document.createElement('div'); var savedThis=this; mainDiv.onclick=function(e) {savedThis.onclickHandler(e);}; mainDiv.className=(this.className) ? this.className : 'navpopup_maindiv'; mainDiv.id=mainDiv.className + this.uid; mainDiv.style.position='absolute'; mainDiv.style.minWidth = '350px'; mainDiv.style.display='none'; mainDiv.className='navpopup'; // easy access to javascript object through DOM functions mainDiv.navpopup=this; this.mainDiv=mainDiv; document.body.appendChild(mainDiv); this.runHooks('create', 'after'); }; /** Calls the {@link #raise} method. @private */ Navpopup.prototype.onclickHandler=function(/*e*/) { this.raise(); }; /** Makes the popup draggable, using a {@link Drag} object. @private */ Navpopup.prototype.makeDraggable=function(handleName) { if (!this.mainDiv) { this.createMainDiv(); } var drag=new Drag(); if (!handleName) { drag.startCondition=function(e) { try { if (!e.shiftKey) { return false; } } catch (err) { return false; } return true; }; } var dragHandle; if (handleName) dragHandle = document.getElementById(handleName); if (!dragHandle) dragHandle = this.mainDiv; var np=this; drag.endHook=function(x,y) { Navpopup.tracker.dirty=true; np.reposition(x,y); }; drag.init(dragHandle,this.mainDiv); }; /** Hides the popup using CSS. Runs hooks with key 'hide'. Sets {@link #visible} appropriately. {@link #banish} should be called externally instead of this method. @private */ Navpopup.prototype.hide = function () { this.runHooks('hide', 'before'); this.abortDownloads(); if (typeof this.visible != 'undefined' && this.visible) { this.mainDiv.style.display='none'; this.visible=false; } this.runHooks('hide', 'after'); }; /** Shows the popup using CSS. Runs hooks with key 'unhide'. Sets {@link #visible} appropriately. {@link #show} should be called externally instead of this method. @private */ Navpopup.prototype.unhide = function () { this.runHooks('unhide', 'before'); if (typeof this.visible != 'undefined' && !this.visible) { this.mainDiv.style.display='inline'; this.visible=true; } this.runHooks('unhide', 'after'); }; /** Sets the <code>innerHTML</code> attribute of the main div containing the popup content. @param {String} html The HTML to set. */ Navpopup.prototype.setInnerHTML = function (html) { this.mainDiv.innerHTML = html; }; /** Updates the {@link #width} and {@link #height} attributes with the CSS properties. @private */ Navpopup.prototype.updateDimensions = function () { this.width=parseInt(this.mainDiv.offsetWidth, 10); this.height=parseInt(this.mainDiv.offsetHeight, 10); }; /** Checks if the point (x,y) is within {@link #fuzz} of the {@link #mainDiv}. @param {integer} x x-coordinate (px) @param {integer} y y-coordinate (px) @type boolean */ Navpopup.prototype.isWithin = function(x,y) { //~ If we're not even visible, no point should be considered as //~ being within the popup. if (!this.visible) { return false; } this.updateDimensions(); var fuzz=this.fuzz || 0; //~ Use a simple box metric here. return (x+fuzz >= this.left && x-fuzz <= this.left + this.width && y+fuzz >= this.top && y-fuzz <= this.top + this.height); }; /** Adds a download to {@link #downloads}. @param {Downloader} download */ Navpopup.prototype.addDownload=function(download) { if (!download) { return; } this.downloads.push(download); }; /** Aborts the downloads listed in {@link #downloads}. @see Downloader#abort */ Navpopup.prototype.abortDownloads=function() { for(var i=0; i<this.downloads.length; ++i) { var d=this.downloads[i]; if (d && d.abort) { d.abort(); } } this.downloads=[]; }; /** A {@link Mousetracker} instance which is a property of the constructor (pseudo-global). */ Navpopup.tracker=new Mousetracker(); // ENDFILE: navpopup.js // STARTFILE: diff.js //<NOLITE> /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) and [[:en:User:Lupin]] * * More Info: * http://ejohn.org/projects/javascript-diff-algorithm/ */ function delFmt(x) { if (!x.length) { return ''; } return "<del class='popupDiff'>" + x.join('') +"</del>"; } function insFmt(x) { if (!x.length) { return ''; } return "<ins class='popupDiff'>" + x.join('') +"</ins>"; } function countCrossings(a, b, i, eject) { // count the crossings on the edge starting at b[i] if (!b[i].row && b[i].row !== 0) { return -1; } var count=0; for (var j=0; j<a.length; ++j) { if (!a[j].row && a[j].row !== 0) { continue; } if ( (j-b[i].row)*(i-a[j].row) > 0) { if(eject) { return true; } count++; } } return count; } function shortenDiffString(str, context) { var re=RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)'); var splitted=str.parenSplit(re); var ret=['']; for (var i=0; i<splitted.length; i+=2) { if (splitted[i].length < 2*context) { ret[ret.length-1] += splitted[i]; if (i+1<splitted.length) { ret[ret.length-1] += splitted[i+1]; } continue; } else { if (i > 0) { ret[ret.length-1] += splitted[i].substring(0,context); } if (i+1 < splitted.length) { ret.push(splitted[i].substring(splitted[i].length-context) + splitted[i+1]); } } } while (ret.length > 0 && !ret[0]) { ret = ret.slice(1); } return ret; } function diffString( o, n, simpleSplit ) { var splitRe=RegExp('([[]{2}|[\\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\\s|\\b)'); // We need to split the strings o and n first, and entify() the parts // individually, so that the HTML entities are never cut apart. (AxelBoldt) var out, i, oSplitted, nSplitted; if (simpleSplit) { oSplitted=o.split(/\b/); nSplitted=n.split(/\b/); } else { oSplitted=o.parenSplit(splitRe); nSplitted=n.parenSplit(splitRe); } for (i=0; i<oSplitted.length; ++i) {oSplitted[i]=oSplitted[i].entify();} for (i=0; i<nSplitted.length; ++i) {nSplitted[i]=nSplitted[i].entify();} out = diff (oSplitted, nSplitted); var str = ""; var acc=[]; // accumulator for prettier output // crossing pairings -- eg 'A B' vs 'B A' -- cause problems, so let's iron them out // this doesn't always do things optimally but it should be fast enough var maxOutputPair=0; for (i=0; i<out.n.length; ++i) { if ( out.n[i].paired ) { if( maxOutputPair > out.n[i].row ) { // tangle - delete pairing out.o[ out.n[i].row ]=out.o[ out.n[i].row ].text; out.n[i]=out.n[i].text; } if (maxOutputPair < out.n[i].row) { maxOutputPair = out.n[i].row; } } } // output the stuff preceding the first paired old line for (i=0; i<out.o.length && !out.o[i].paired; ++i) { acc.push( out.o[i] ); } str += delFmt(acc); acc=[]; // main loop for ( i = 0; i < out.n.length; ++i ) { // output unpaired new "lines" while ( i < out.n.length && !out.n[i].paired ) { acc.push( out.n[i++] ); } str += insFmt(acc); acc=[]; if ( i < out.n.length ) { // this new "line" is paired with the (out.n[i].row)th old "line" str += out.n[i].text; // output unpaired old rows starting after this new line's partner var m = out.n[i].row + 1; while ( m < out.o.length && !out.o[m].paired ) { acc.push ( out.o[m++] ); } str += delFmt(acc); acc=[]; } } return str; } // see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object // FIXME: use obj.hasOwnProperty instead of this kludge! var jsReservedProperties=RegExp('^(constructor|prototype|__((define|lookup)[GS]etter)__' + '|eval|hasOwnProperty|propertyIsEnumerable' + '|to(Source|String|LocaleString)|(un)?watch|valueOf)$'); function diffBugAlert(word) { if (!diffBugAlert.list[word]) { diffBugAlert.list[word]=1; alert('Bad word: '+word+'\n\nPlease report this bug.'); } } diffBugAlert.list={}; function makeDiffHashtable(src) { var ret={}; for ( var i = 0; i < src.length; i++ ) { if ( jsReservedProperties.test(src[i]) ) { src[i] += '<!-- -->'; } if ( !ret[ src[i] ] ) { ret[ src[i] ] = []; } try { ret[ src[i] ].push( i ); } catch (err) { diffBugAlert(src[i]); } } return ret; } function diff( o, n ) { // pass 1: make hashtable ns with new rows as keys var ns = makeDiffHashtable(n); // pass 2: make hashtable os with old rows as keys var os = makeDiffHashtable(o); // pass 3: pair unique new rows and matching unique old rows var i; for ( i in ns ) { if ( ns[i].length == 1 && os[i] && os[i].length == 1 ) { n[ ns[i][0] ] = { text: n[ ns[i][0] ], row: os[i][0], paired: true }; o[ os[i][0] ] = { text: o[ os[i][0] ], row: ns[i][0], paired: true }; } } // pass 4: pair matching rows immediately following paired rows (not necessarily unique) for ( i = 0; i < n.length - 1; i++ ) { if ( n[i].paired && ! n[i+1].paired && n[i].row + 1 < o.length && ! o[ n[i].row + 1 ].paired && n[i+1] == o[ n[i].row + 1 ] ) { n[i+1] = { text: n[i+1], row: n[i].row + 1, paired: true }; o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1, paired: true }; } } // pass 5: pair matching rows immediately preceding paired rows (not necessarily unique) for ( i = n.length - 1; i > 0; i-- ) { if ( n[i].paired && ! n[i-1].paired && n[i].row > 0 && ! o[ n[i].row - 1 ].paired && n[i-1] == o[ n[i].row - 1 ] ) { n[i-1] = { text: n[i-1], row: n[i].row - 1, paired: true }; o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1, paired: true }; } } return { o: o, n: n }; } //</NOLITE> // ENDFILE: diff.js // STARTFILE: init.js function setSiteInfo() { if (window.popupLocalDebug) { pg.wiki.hostname = 'en.wikipedia.org'; } else { pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?) } pg.wiki.wikimedia=RegExp('(wiki([pm]edia|source|books|news|quote|versity)|wiktionary|mediawiki)[.]org').test(pg.wiki.hostname); pg.wiki.wikia=RegExp('[.]wikia[.]com$', 'i').test(pg.wiki.hostname); pg.wiki.isLocal=RegExp('^localhost').test(pg.wiki.hostname); pg.wiki.commons=( pg.wiki.wikimedia && pg.wiki.hostname != 'commons.wikimedia.org') ? 'commons.wikimedia.org' : null; pg.wiki.lang = mw.config.get('wgContentLanguage'); var port = location.port ? ':' + location.port : ''; pg.wiki.sitebase = pg.wiki.hostname + port; } function setUserInfo() { var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'query', list: 'users', ususers: mw.config.get('wgUserName'), usprop: 'rights' }; pg.user.canReview = false; if (getValueOf('popupReview')) { api.get(params).done(function(data){ var rights = data.query.users[0].rights; pg.user.canReview = rights.indexOf('review') !== -1; // TODO: Should it be a getValueOf('ReviewRight') ? }); } } function setTitleBase() { var protocol = ( window.popupLocalDebug ? 'http:' : location.protocol ); pg.wiki.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, ""); // as in http://some.thing.com/wiki/Article pg.wiki.botInterfacePath = mw.config.get('wgScript'); pg.wiki.APIPath = mw.config.get('wgScriptPath') +"/api.php"; // default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo var titletail = pg.wiki.botInterfacePath + '?title='; //var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?title=']); // other sites may need to add code here to set titletail depending on how their urls work pg.wiki.titlebase = protocol + '//' + pg.wiki.sitebase + titletail; //pg.wiki.titlebase2 = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]); pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath; pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath; pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath; pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.botInterfacePath; pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.APIPath; pg.re.basenames = RegExp( '^(' + map( literalizeRegex, [ pg.wiki.titlebase, //pg.wiki.titlebase2, pg.wiki.articlebase ]).join('|') + ')' ); } ////////////////////////////////////////////////// // Global regexps function setMainRegex() { var reStart='[^:]*://'; var preTitles = literalizeRegex( mw.config.get('wgScriptPath') ) + '/(?:index[.]php|wiki[.]phtml)[?]title='; preTitles += '|' + literalizeRegex( pg.wiki.articlePath + '/' ); var reEnd='(' + preTitles + ')([^&?#]*)[^#]*(?:#(.+))?'; pg.re.main = RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd); } function setRegexps() { // TODO: We shoud use an api call to get the aliases for special pages, now it does not work for non-English wikipedias: // E.g., https://ru.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=specialpagealiases&formatversion=2 setMainRegex(); var sp=nsRe(pg.nsSpecialId); pg.re.urlNoPopup=RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)') ; pg.re.contribs =RegExp('(title=|/)' + sp + '(?:%3A|:)Contributions' + '(&target=|/|/' + nsRe(pg.nsUserId)+':)(.*)') ; pg.re.email =RegExp('(title=|/)' + sp + '(?:%3A|:)EmailUser' + '(&target=|/|/(?:' + nsRe(pg.nsUserId)+':)?)(.*)') ; pg.re.backlinks =RegExp('(title=|/)' + sp + '(?:%3A|:)WhatLinksHere' + '(&target=|/)([^&]*)'); pg.re.specialdiff=RegExp('/' + sp + '(?:%3A|:)Diff/([^?#]*)'); //<NOLITE> var im=nsReImage(); // note: tries to get images in infobox templates too, e.g. movie pages, album pages etc // (^|\[\[)image: *([^|\]]*[^|\] ]) * // (^|\[\[)image: *([^|\]]*[^|\] ])([^0-9\]]*([0-9]+) *px)? // $4 = 120 as in 120px pg.re.image = RegExp('(^|\\[\\[)' + im + ': *([^|\\]]*[^|\\] ])' + '([^0-9\\]]*([0-9]+) *px)?|(?:\\n *[|]?|[|]) *' + '(' + getValueOf('popupImageVarsRegexp') + ')' + ' *= *(?:\\[\\[ *)?(?:' + im + ':)?' + '([^|]*?)(?:\\]\\])? *[|]? *\\n', 'img') ; pg.re.imageBracketCount = 6; pg.re.category = RegExp('\\[\\[' +nsRe(pg.nsCategoryId) + ': *([^|\\]]*[^|\\] ]) *', 'i'); pg.re.categoryBracketCount = 1; pg.re.ipUser=RegExp('^' + // IPv6 '(?::(?::|(?::[0-9A-Fa-f]{1,4}){1,7})|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,6}::|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){7})' + // IPv4 '|(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' + '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$'); pg.re.stub= RegExp(getValueOf('popupStubRegexp'), 'im'); pg.re.disambig=RegExp(getValueOf('popupDabRegexp'), 'im'); //</NOLITE> // FIXME replace with general parameter parsing function, this is daft pg.re.oldid=RegExp('[?&]oldid=([^&]*)'); pg.re.diff=RegExp('[?&]diff=([^&]*)'); } ////////////////////////////////////////////////// // miscellany function setupCache() { // page caching pg.cache.pages = []; } function setMisc() { pg.current.link=null; pg.current.links=[]; pg.current.linksHash={}; setupCache(); pg.timer.checkPopupPosition=null; pg.counter.loop=0; // ids change with each popup: popupImage0, popupImage1 etc pg.idNumber=0; // for myDecodeURI pg.misc.decodeExtras = [ {from: '%2C', to: ',' }, {from: '_', to: ' ' }, {from: '%24', to: '$'}, {from: '%26', to: '&' } // no , ]; pg.misc.userAgent = 'Navigation popups/1.0 (' + mw.config.get( 'wgServerName' ) +')'; } // We need a callback since this might end up asynchronous because of // the mw.loader.using() call. function setupPopups( callback ) { if ( setupPopups.completed ) { if ( typeof callback === 'function' ) { callback(); } return; } // These dependencies are also enforced from the gadget, // but not everyone loads this as a gadget, so double check mw.loader.using( ['mediawiki.util', 'mediawiki.user', 'user.options'] ).then( function() { // NB translatable strings should be set up first (strings.js) // basics setupDebugging(); setSiteInfo(); setTitleBase(); setOptions(); // see options.js setUserInfo(); // namespaces etc setNamespaces(); setInterwiki(); // regexps setRegexps(); setRedirs(); // other stuff setMisc(); setupLivePreview(); // main deal here setupTooltips(); log('In setupPopups(), just called setupTooltips()'); Navpopup.tracker.enable(); setupPopups.completed = true; if ( typeof callback === 'function' ) { callback(); } }); } // ENDFILE: init.js // STARTFILE: navlinks.js //<NOLITE> ////////////////////////////////////////////////// // navlinks... let the fun begin // function defaultNavlinkSpec() { var str=''; str += '<b><<mainlink|shortcut= >></b>'; if (getValueOf('popupLastEditLink')) { str += '*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}'; } // user links // contribs - log - count - email - block // count only if applicable; block only if popupAdminLinks str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>'; str+='if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}'; // editing links // talkpage -> edit|new - history - un|watch - article|edit // other page -> edit - history - un|watch - talk|edit|new var editstr='<<edit|shortcut=e>>'; var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'; var historystr='<<history|shortcut=h>>|<<editors|shortcut=E|>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; str+='<br>if(talk){' + editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + // not a talk page editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}'; // misc links str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>'; // admin links str += 'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}'; return str; } function navLinksHTML (article, hint, params) { //oldid, rcid) { var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>'; // BAM return navlinkStringToHTML(str, article, params); } function expandConditionalNavlinkString(s,article,z,recursionCount) { var oldid=z.oldid, rcid=z.rcid, diff=z.diff; // nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out) if (typeof recursionCount!=typeof 0) { recursionCount=0; } var conditionalSplitRegex=RegExp( //(1 if \\( (2 2) \\) {(3 3)} (4 else {(5 5)} 4)1) '(;?\\s*if\\s*\\(\\s*([\\w]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))', 'i'); var splitted=s.parenSplit(conditionalSplitRegex); // $1: whole conditional // $2: test condition // $3: true expansion // $4: else clause (possibly empty) // $5: false expansion (possibly null) var numParens=5; var ret = splitted[0]; for (var i=1; i<splitted.length; i=i+numParens+1) { var testString=splitted[i+2-1]; var trueString=splitted[i+3-1]; var falseString=splitted[i+5-1]; if (typeof falseString=='undefined' || !falseString) { falseString=''; } var testResult=null; switch (testString) { case 'user': testResult=(article.userName())?true:false; break; case 'talk': testResult=(article.talkPage())?false:true; // talkPage converts _articles_ to talkPages break; case 'admin': testResult=getValueOf('popupAdminLinks')?true:false; break; case 'oldid': testResult=(typeof oldid != 'undefined' && oldid)?true:false; break; case 'rcid': testResult=(typeof rcid != 'undefined' && rcid)?true:false; break; case 'ipuser': testResult=(article.isIpUser())?true:false; break; case 'mainspace_en': testResult=isInMainNamespace(article) && pg.wiki.hostname=='en.wikipedia.org'; break; case 'wikimedia': testResult=(pg.wiki.wikimedia) ? true : false; break; case 'diff': testResult=(typeof diff != 'undefined' && diff)?true:false; break; } switch(testResult) { case null: ret+=splitted[i]; break; case true: ret+=trueString; break; case false: ret+=falseString; break; } // append non-conditional string ret += splitted[i+numParens]; } if (conditionalSplitRegex.test(ret) && recursionCount < 10) { return expandConditionalNavlinkString(ret,article,z,recursionCount+1); } return ret; } function navlinkStringToArray(s, article, params) { s=expandConditionalNavlinkString(s,article,params); var splitted=s.parenSplit(RegExp('<<(.*?)>>')); var ret=[]; for (var i=0; i<splitted.length; ++i) { if (i%2) { // i odd, so s is a tag var t=new navlinkTag(); var ss=splitted[i].split('|'); t.id=ss[0]; for (var j=1; j<ss.length; ++j) { var sss=ss[j].split('='); if (sss.length>1) { t[sss[0]]=sss[1]; } else { // no assignment (no "="), so treat this as a title (overwriting the last one) t.text=popupString(sss[0]); } } t.article=article; var oldid=params.oldid, rcid=params.rcid, diff=params.diff; if (typeof oldid !== 'undefined' && oldid !== null) { t.oldid=oldid; } if (typeof rcid !== 'undefined' && rcid !== null) { t.rcid=rcid; } if (typeof diff !== 'undefined' && diff !== null) { t.diff=diff; } if (!t.text && t.id !== 'mainlink') { t.text=popupString(t.id); } ret.push(t); } else { // plain HTML ret.push(splitted[i]); } } return ret; } function navlinkSubstituteHTML(s) { return s.split('*').join(getValueOf('popupNavLinkSeparator')) .split('<menurow>').join('<li class="popup_menu_row">') .split('</menurow>').join('</li>') .split('<menu>').join('<ul class="popup_menu">') .split('</menu>').join('</ul>'); } function navlinkDepth(magic,s) { return s.split('<' + magic + '>').length - s.split('</' + magic + '>').length; } // navlinkString: * becomes the separator // <<foo|bar=baz|fubar>> becomes a foo-link with attribute bar='baz' // and visible text 'fubar' // if(test){...} and if(test){...}else{...} work too (nested ok) function navlinkStringToHTML(s,article,params) { //limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article)); var p=navlinkStringToArray(s,article,params); var html=''; var menudepth = 0; // nested menus not currently allowed, but doesn't do any harm to code for it var menurowdepth = 0; for (var i=0; i<p.length; ++i) { if (typeof p[i] == typeof '') { html+=navlinkSubstituteHTML(p[i]); menudepth += navlinkDepth('menu', p[i]); menurowdepth += navlinkDepth('menurow', p[i]); // if (menudepth === 0) { // tagType='span'; // } else if (menurowdepth === 0) { // tagType='li'; // } else { // tagType = null; // } } else if (typeof p[i].type != 'undefined' && p[i].type=='navlinkTag') { if (menudepth > 0 && menurowdepth === 0) { html += '<li class="popup_menu_item">' + p[i].html() + '</li>'; } else { html+=p[i].html(); } } } return html; } function navlinkTag() { this.type='navlinkTag'; } navlinkTag.prototype.html=function () { this.getNewWin(); this.getPrintFunction(); var html=''; var opening, closing; var tagType='span'; if (!tagType) { opening = ''; closing = ''; } else { opening = '<' + tagType + ' class="popup_' + this.id + '">'; closing = '</' + tagType + '>'; } if (typeof this.print!='function') { errlog ('Oh dear - invalid print function for a navlinkTag, id='+this.id); } else { html=this.print(this); if (typeof html != typeof '') {html='';} else if (typeof this.shortcut!='undefined') html=addPopupShortcut(html, this.shortcut); } return opening + html + closing; }; navlinkTag.prototype.getNewWin=function() { getValueOf('popupLinksNewWindow'); if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') { this.newWin=null; } this.newWin=pg.option.popupLinksNewWindow[this.id]; }; navlinkTag.prototype.getPrintFunction=function() { //think about this some more // this.id and this.article should already be defined if (typeof this.id!=typeof '' || typeof this.article!=typeof {} ) { return; } this.noPopup=1; switch (this.id) { case 'contribs': case 'history': case 'whatLinksHere': case 'userPage': case 'monobook': case 'userTalk': case 'talk': case 'article': case 'lastEdit': this.noPopup=null; } switch (this.id) { case 'email': case 'contribs': case 'block': case 'unblock': case 'userlog': case 'userSpace': case 'deletedContribs': this.article=this.article.userName(); } switch (this.id) { case 'userTalk': case 'newUserTalk': case 'editUserTalk': case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog': this.article=this.article.userName(true); /* fall through */ case 'pagelog': case 'deletelog': case 'protectlog': delete this.oldid; } if (this.id=='editMonobook' || this.id=='monobook') { this.article.append('/monobook.js'); } if (this.id != 'mainlink') { // FIXME anchor handling should be done differently with Title object this.article=this.article.removeAnchor(); // if (typeof this.text=='undefined') this.text=popupString(this.id); } switch (this.id) { case 'undelete': this.print=specialLink; this.specialpage='Undelete'; this.sep='/'; break; case 'whatLinksHere': this.print=specialLink; this.specialpage='Whatlinkshere'; break; case 'relatedChanges': this.print=specialLink; this.specialpage='Recentchangeslinked'; break; case 'move': this.print=specialLink; this.specialpage='Movepage'; break; case 'contribs': this.print=specialLink; this.specialpage='Contributions'; break; case 'deletedContribs':this.print=specialLink; this.specialpage='Deletedcontributions'; break; case 'email': this.print=specialLink; this.specialpage='EmailUser'; this.sep='/'; break; case 'block': this.print=specialLink; this.specialpage='Blockip'; this.sep='&ip='; break; case 'unblock': this.print=specialLink; this.specialpage='Ipblocklist'; this.sep='&action=unblock&ip='; break; case 'userlog': this.print=specialLink; this.specialpage='Log'; this.sep='&user='; break; case 'blocklog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=block&page='; break; case 'pagelog': this.print=specialLink; this.specialpage='Log'; this.sep='&page='; break; case 'protectlog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=protect&page='; break; case 'deletelog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=delete&page='; break; case 'userSpace': this.print=specialLink; this.specialpage='PrefixIndex'; this.sep='&namespace=2&prefix='; break; case 'search': this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break; case 'thank': this.print=specialLink; this.specialpage='Thanks'; this.sep='/'; this.article.value = this.diff; break; case 'unwatch': case 'watch': this.print=magicWatchLink; this.action=this.id+'&autowatchlist=1&autoimpl=' + popupString('autoedit_version') + '&actoken='+autoClickToken(); break; case 'history': case 'historyfeed': case 'unprotect': case 'protect': this.print=wikiLink; this.action=this.id; break; case 'delete': this.print=wikiLink; this.action='delete'; if (this.article.namespaceId()==pg.nsImageId) { var img=this.article.stripNamespace(); this.action+='&image='+img; } break; case 'markpatrolled': case 'edit': // editOld should keep the oldid, but edit should not. delete this.oldid; /* fall through */ case 'view': case 'purge': case 'render': this.print=wikiLink; this.action=this.id; break; case 'raw': this.print=wikiLink; this.action='raw'; break; case 'new': this.print=wikiLink; this.action='edit&section=new'; break; case 'mainlink': if (typeof this.text=='undefined') { this.text=this.article.toString().entify(); } if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) { // only show the /subpage part of the title text var s=this.text.split('/'); this.text=s[s.length-1]; if (this.text==='' && s.length > 1) { this.text=s[s.length-2]; } } this.print=titledWikiLink; if (typeof this.title==='undefined' && pg.current.link && typeof pg.current.link.href !== 'undefined') { this.title=safeDecodeURI((pg.current.link.originalTitle)?pg.current.link.originalTitle:this.article); if (typeof this.oldid !== 'undefined' && this.oldid) { this.title=tprintf('Revision %s of %s', [this.oldid, this.title]); } } this.action='view'; break; case 'userPage': case 'article': case 'monobook': case 'editMonobook': case 'editArticle': delete this.oldid; //alert(this.id+'\n'+this.article + '\n'+ typeof this.article); this.article=this.article.articleFromTalkOrArticle(); //alert(this.id+'\n'+this.article + '\n'+ typeof this.article); this.print=wikiLink; if (this.id.indexOf('edit')===0) { this.action='edit'; } else { this.action='view';} break; case 'userTalk': case 'talk': this.article=this.article.talkPage(); delete this.oldid; this.print=wikiLink; this.action='view'; break; case 'arin': this.print=arinLink; break; case 'count': this.print=editCounterLink; break; case 'google': this.print=googleLink; break; case 'editors': this.print=editorListLink; break; case 'globalsearch': this.print=globalSearchLink; break; case 'lastEdit': this.print=titledDiffLink; this.title=popupString('Show the last edit'); this.from='prev'; this.to='cur'; break; case 'oldEdit': this.print=titledDiffLink; this.title=popupString('Show the edit made to get revision') + ' ' + this.oldid; this.from='prev'; this.to=this.oldid; break; case 'editOld': this.print=wikiLink; this.action='edit'; break; case 'undo': this.print=wikiLink; this.action='edit&undo='; break; case 'revert': this.print=wikiLink; this.action='revert'; break; case 'nullEdit': this.print=wikiLink; this.action='nullEdit'; break; case 'diffCur': this.print=titledDiffLink; this.title=tprintf('Show changes since revision %s', [this.oldid]); this.from=this.oldid; this.to='cur'; break; case 'editUserTalk': case 'editTalk': delete this.oldid; this.article=this.article.talkPage(); this.action='edit'; this.print=wikiLink; break; case 'newUserTalk': case 'newTalk': this.article=this.article.talkPage(); this.action='edit&section=new'; this.print=wikiLink; break; case 'lastContrib': case 'sinceMe': this.print=magicHistoryLink; break; case 'togglePreviews': this.text=popupString(pg.option.simplePopups ? 'enable previews' : 'disable previews'); /* fall through */ case 'disablePopups': case 'purgePopups': this.print=popupMenuLink; break; default: this.print=function () {return 'Unknown navlink type: '+this.id+'';}; } }; // // end navlinks ////////////////////////////////////////////////// //</NOLITE> // ENDFILE: navlinks.js // STARTFILE: shortcutkeys.js //<NOLITE> function popupHandleKeypress(evt) { var keyCode = window.event ? window.event.keyCode : ( evt.keyCode ? evt.keyCode : evt.which); if (!keyCode || !pg.current.link || !pg.current.link.navpopup) { return; } if (keyCode==27) { // escape killPopup(); return false; // swallow keypress } var letter=String.fromCharCode(keyCode); var links=pg.current.link.navpopup.mainDiv.getElementsByTagName('A'); var startLink=0; var i,j; if (popupHandleKeypress.lastPopupLinkSelected) { for (i=0; i<links.length; ++i) { if (links[i]==popupHandleKeypress.lastPopupLinkSelected) { startLink=i; } } } for (j=0; j<links.length; ++j) { i=(startLink + j + 1) % links.length; if (links[i].getAttribute('popupkey')==letter) { if (evt && evt.preventDefault) evt.preventDefault(); links[i].focus(); popupHandleKeypress.lastPopupLinkSelected=links[i]; return false; // swallow keypress } } // pass keypress on if (document.oldPopupOnkeypress) { return document.oldPopupOnkeypress(evt); } return true; } function addPopupShortcuts() { if (document.onkeypress!=popupHandleKeypress) { document.oldPopupOnkeypress=document.onkeypress; } document.onkeypress=popupHandleKeypress; } function rmPopupShortcuts() { popupHandleKeypress.lastPopupLinkSelected=null; try { if (document.oldPopupOnkeypress && document.oldPopupOnkeypress==popupHandleKeypress) { // panic document.onkeypress=null; //function () {}; return; } document.onkeypress=document.oldPopupOnkeypress; } catch (nasties) { /* IE goes here */ } } function addLinkProperty(html, property) { // take "<a href=...>...</a> and add a property // not sophisticated at all, easily broken var i=html.indexOf('>'); if (i<0) { return html; } return html.substring(0,i) + ' ' + property + html.substring(i); } function addPopupShortcut(html, key) { if (!getValueOf('popupShortcutKeys')) { return html; } var ret= addLinkProperty(html, 'popupkey="'+key+'"'); if (key==' ') { key=popupString('spacebar'); } return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'),'$1$2$3 ['+key+']$4'); } //</NOLITE> // ENDFILE: shortcutkeys.js // STARTFILE: diffpreview.js //<NOLITE> //lets jump through hoops to find the rev ids we need to retrieve function loadDiff(article, oldid, diff, navpop) { navpop.diffData={ oldRev: {}, newRev: {} }; mw.loader.using( 'mediawiki.api' ).then( function() { var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'compare', prop: 'ids|title' }; if(article.title){ params.fromtitle = article.title; } switch (diff) { case 'cur': switch ( oldid ) { case null: case '': case 'prev': // this can only work if we have the title // cur -> prev params.torelative = 'prev'; break; default: params.fromrev = oldid; params.torelative = 'cur'; break; } break; case 'prev': if( oldid ) { params.fromrev = oldid; } else { params.fromtitle; } params.torelative = 'prev'; break; case 'next': params.fromrev = oldid || 0; params.torelative = 'next'; break; default: params.fromrev = oldid || 0; params.torev = diff || 0; break; } api.get( params ).then( function( data ) { navpop.diffData.oldRev.revid = data.compare.fromrevid; navpop.diffData.newRev.revid = data.compare.torevid; addReviewLink(navpop, 'popupMiscTools'); var go = function() { pendingNavpopTask(navpop); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&'; url += 'revids=' + navpop.diffData.oldRev.revid + '|' + navpop.diffData.newRev.revid; url += '&prop=revisions&rvprop=ids|timestamp|content'; getPageWithCaching(url, doneDiff, navpop); return true; // remove hook once run }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS'); } } ); } ); } // Put a "mark patrolled" link to an element target // TODO: Allow patrol a revision, as well as a diff function addReviewLink (navpop, target) { if (! pg.user.canReview) return; // If 'newRev' is older than 'oldRev' than it could be confusing, so we do not show the review link. if (navpop.diffData.newRev.revid <= navpop.diffData.oldRev.revid) return; var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'query', prop: 'info|flagged', revids: navpop.diffData.oldRev.revid, formatversion : 2 }; api.get (params).then(function(data){ var stable_revid = data.query.pages[0].flagged && data.query.pages[0].flagged.stable_revid || 0; // The diff can be reviewed if the old version is the last reviewed version // TODO: Other possible conditions that we may want to implement instead of this one: // * old version is patrolled and the new version is not patrolled // * old version is patrolled and the new version is more recent than the last reviewed version if (stable_revid == navpop.diffData.oldRev.revid) { var a = document.createElement('a'); a.innerHTML = popupString('mark patrolled'); a.title=popupString('markpatrolledHint'); a.onclick = function() { var params = { action: 'review', revid: navpop.diffData.newRev.revid, comment: tprintf('defaultpopupReviewedSummary', [navpop.diffData.oldRev.revid, navpop.diffData.newRev.revid]) }; api.postWithToken('csrf',params).done(function(){ a.style.display = "none"; // TODO: Update current page and other already constructed popups } ).fail(function(){ alert(popupString('Could not marked this edit as patrolled')); }); }; setPopupHTML(a, target, navpop.idNumber,null,true); } }); } function doneDiff(download) { if (!download.owner || !download.owner.diffData) { return; } var navpop=download.owner; completedNavpopTask(navpop); var pages, revisions=[]; try{ // Process the downloads pages = getJsObj(download.data).query.pages; for(var i=0; i < pages.length; i++ ) { revisions = revisions.concat(pages[i].revisions); } for(i=0; i< revisions.length; i++){ if(revisions[i].revid == navpop.diffData.oldRev.revid) { navpop.diffData.oldRev.revision = revisions[i]; } else if (revisions[i].revid == navpop.diffData.newRev.revid) { navpop.diffData.newRev.revision = revisions[i]; } } } catch(someError) { errlog( 'Could not get diff' ); } insertDiff(navpop); } function rmBoringLines(a,b,context) { if (typeof context == 'undefined') { context=2; } // this is fairly slow... i think it's quicker than doing a word-based diff from the off, though var aa=[], aaa=[]; var bb=[], bbb=[]; var i, j; // first, gather all disconnected nodes in a and all crossing nodes in a and b for (i=0; i<a.length; ++i ) { if(!a[i].paired) { aa[i]=1; } else if (countCrossings(b,a,i, true)) { aa[i]=1; bb[ a[i].row ] = 1; } } // pick up remaining disconnected nodes in b for (i=0; i<b.length; ++i ) { if (bb[i]==1) { continue; } if(!b[i].paired) { bb[i]=1; } } // another pass to gather context: we want the neighbours of included nodes which are not yet included // we have to add in partners of these nodes, but we don't want to add context for *those* nodes in the next pass for (i=0; i<b.length; ++i) { if ( bb[i] == 1 ) { for (j=Math.max(0,i-context); j < Math.min(b.length, i+context); ++j) { if ( !bb[j] ) { bb[j] = 1; aa[ b[j].row ] = 0.5; } } } } for (i=0; i<a.length; ++i) { if ( aa[i] == 1 ) { for (j=Math.max(0,i-context); j < Math.min(a.length, i+context); ++j) { if ( !aa[j] ) { aa[j] = 1; bb[ a[j].row ] = 0.5; } } } } for (i=0; i<bb.length; ++i) { if (bb[i] > 0) { // it's a row we need if (b[i].paired) { bbb.push(b[i].text); } // joined; partner should be in aa else { bbb.push(b[i]); } } } for (i=0; i<aa.length; ++i) { if (aa[i] > 0) { // it's a row we need if (a[i].paired) { aaa.push(a[i].text); } // joined; partner should be in aa else { aaa.push(a[i]); } } } return { a: aaa, b: bbb}; } function stripOuterCommonLines(a,b,context) { var i=0; while (i<a.length && i < b.length && a[i]==b[i]) { ++i; } var j=a.length-1; var k=b.length-1; while ( j>=0 && k>=0 && a[j]==b[k] ) { --j; --k; } return { a: a.slice(Math.max(0,i - 1 - context), Math.min(a.length+1, j + context+1)), b: b.slice(Math.max(0,i - 1 - context), Math.min(b.length+1, k + context+1)) }; } function insertDiff(navpop) { // for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff // FIXME: sometimes this gives misleading diffs as distant chunks are squashed together var oldlines = navpop.diffData.oldRev.revision.content.split('\n'); var newlines = navpop.diffData.newRev.revision.content.split('\n'); var inner=stripOuterCommonLines(oldlines,newlines,getValueOf('popupDiffContextLines')); oldlines=inner.a; newlines=inner.b; var truncated=false; getValueOf('popupDiffMaxLines'); if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) { // truncate truncated=true; inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines), newlines.slice(0,pg.option.popupDiffMaxLines), pg.option.popupDiffContextLines); oldlines=inner.a; newlines=inner.b; } var lineDiff=diff(oldlines, newlines); var lines2=rmBoringLines(lineDiff.o, lineDiff.n); var oldlines2=lines2.a; var newlines2=lines2.b; var simpleSplit = !String.prototype.parenSplit.isNative; var html='<hr />'; if (getValueOf('popupDiffDates')) { html += diffDatesTable(navpop); html += '<hr />'; } html += shortenDiffString( diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit), getValueOf('popupDiffContextCharacters') ).join('<hr />'); setPopupTipsAndHTML(html.split('\n').join('<br>') + (truncated ? '<hr /><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') , 'popupPreview', navpop.idNumber); } function diffDatesTable( navpop ) { var html='<table class="popup_diff_dates">'; html += diffDatesTableRow( navpop.diffData.newRev.revision, tprintf('New revision')); html += diffDatesTableRow( navpop.diffData.oldRev.revision, tprintf('Old revision')); html += '</table>'; return html; } function diffDatesTableRow( revision, label ) { var txt=''; var lastModifiedDate = new Date(revision.timestamp); var datePrint=getValueOf('popupDiffDatePrinter'); if (typeof lastModifiedDate[datePrint] == 'function') { var d2 = adjustDate(lastModifiedDate, getTimeOffset()); txt = dayFormat(d2, true) + ' ' + timeFormat(d2, true); } else { txt = tprintf('Invalid %s %s', ['popupDiffDatePrinter', datePrint]); } var revlink = generalLink({url: mw.config.get('wgScript') + '?oldid='+revision.revid, text: label, title: label}); return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [ revlink, txt ]); } //</NOLITE> // ENDFILE: diffpreview.js // STARTFILE: links.js //<NOLITE> ///////////////////// // LINK GENERATION // ///////////////////// // titledDiffLink --> titledWikiLink --> generalLink // wikiLink --> titledWikiLink --> generalLink // editCounterLink --> generalLink // TODO Make these functions return Element objects, not just raw HTML strings. function titledDiffLink(l) { // article, text, title, from, to) { return titledWikiLink({article: l.article, action: l.to + '&oldid=' + l.from, newWin: l.newWin, noPopup: l.noPopup, text: l.text, title: l.title, /* hack: no oldid here */ actionName: 'diff'}); } function wikiLink(l) { //{article:article, action:action, text:text, oldid, newid}) { if (! (typeof l.article == typeof {} && typeof l.action == typeof '' && typeof l.text==typeof '')) return null; if (typeof l.oldid == 'undefined') { l.oldid=null; } var savedOldid = l.oldid; if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) { l.oldid=null; } var hint=popupString(l.action + 'Hint'); // revertHint etc etc etc var oldidData=[l.oldid, safeDecodeURI(l.article)]; var revisionString = tprintf('revision %s of %s', oldidData); log('revisionString='+revisionString); switch (l.action) { case 'edit&section=new': hint = popupString('newSectionHint'); break; case 'edit&undo=': if (l.diff && l.diff != 'prev' && savedOldid ) { l.action += l.diff + '&undoafter=' + savedOldid; } else if (savedOldid) { l.action += savedOldid; } hint = popupString('undoHint'); break; case 'raw&ctype=text/css': hint=popupString('rawHint'); break; case 'revert': var p=parseParams(pg.current.link.href); l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=' + revertSummary(l.oldid, p.diff); if (p.diff=='prev') { l.action += '&direction=prev'; revisionString = tprintf('the revision prior to revision %s of %s', oldidData); } if (getValueOf('popupRevertSummaryPrompt')) { l.action += '&autosummaryprompt=true'; } if (getValueOf('popupMinorReverts')) { l.action += '&autominor=true'; } log('revisionString is now '+revisionString); break; case 'nullEdit': l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=null'; break; case 'historyfeed': l.action='history&feed=rss'; break; case 'markpatrolled': l.action='markpatrolled&rcid='+l.rcid; } if (hint) { if (l.oldid) { hint = simplePrintf(hint, [revisionString]); } else { hint = simplePrintf(hint, [safeDecodeURI(l.article)]); } } else { hint = safeDecodeURI(l.article + '&action=' + l.action) + (l.oldid) ? '&oldid='+l.oldid : ''; } return titledWikiLink({article: l.article, action: l.action, text: l.text, newWin:l.newWin, title: hint, oldid: l.oldid, noPopup: l.noPopup, onclick: l.onclick}); } function revertSummary(oldid, diff) { var ret=''; if (diff == 'prev') { ret=getValueOf('popupQueriedRevertToPreviousSummary'); } else { ret = getValueOf('popupQueriedRevertSummary'); } return ret + '&autorv=' + oldid; } function titledWikiLink(l) { // possible properties of argument: // article, action, text, title, oldid, actionName, className, noPopup // oldid = null is fine here // article and action are mandatory args if (typeof l.article == 'undefined' || typeof l.action=='undefined') { errlog('got undefined article or action in titledWikiLink'); return null; } var base = pg.wiki.titlebase + l.article.urlString(); var url=base; if (typeof l.actionName=='undefined' || !l.actionName) { l.actionName='action'; } // no need to add &action=view, and this confuses anchors if (l.action != 'view') { url = base + '&' + l.actionName + '=' + l.action; } if (typeof l.oldid!='undefined' && l.oldid) { url+='&oldid='+l.oldid; } var cssClass=pg.misc.defaultNavlinkClassname; if (typeof l.className!='undefined' && l.className) { cssClass=l.className; } return generalNavLink({url: url, newWin: l.newWin, title: (typeof l.title != 'undefined') ? l.title : null, text: (typeof l.text!='undefined')?l.text:null, className: cssClass, noPopup:l.noPopup, onclick:l.onclick}); } pg.fn.getLastContrib = function getLastContrib(wikipage, newWin) { getHistoryInfo(wikipage, function(x) { processLastContribInfo(x, {page: wikipage, newWin: newWin}); }); }; function processLastContribInfo(info, stuff) { if(!info.edits || !info.edits.length) { alert('Popups: an odd thing happened. Please retry.'); return; } if(!info.firstNewEditor) { alert(tprintf('Only found one editor: %s made %s edits', [info.edits[0].editor,info.edits.length])); return; } var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+info.firstNewEditor.oldid; displayUrl(newUrl, stuff.newWin); } pg.fn.getDiffSinceMyEdit = function getDiffSinceMyEdit(wikipage, newWin) { getHistoryInfo(wikipage, function(x){ processDiffSinceMyEdit(x, {page: wikipage, newWin: newWin}); }); }; function processDiffSinceMyEdit(info, stuff) { if(!info.edits || !info.edits.length) { alert('Popups: something fishy happened. Please try again.'); return; } var friendlyName=stuff.page.split('_').join(' '); if(!info.myLastEdit) { alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s', [info.userName, getValueOf('popupHistoryLimit'), friendlyName])); return; } if(info.myLastEdit.index === 0) { alert(tprintf("%s seems to be the last editor to the page %s", [info.userName, friendlyName])); return; } var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+ info.myLastEdit.oldid; displayUrl(newUrl, stuff.newWin); } function displayUrl(url, newWin){ if(newWin) { window.open(url); } else { document.location=url; } } pg.fn.purgePopups = function purgePopups() { processAllPopups(true); setupCache(); // deletes all cached items (not browser cached, though...) pg.option={}; abortAllDownloads(); }; function processAllPopups(nullify, banish) { for (var i=0; pg.current.links && i<pg.current.links.length; ++i) { if (!pg.current.links[i].navpopup) { continue; } if (nullify || banish) pg.current.links[i].navpopup.banish(); pg.current.links[i].simpleNoMore=false; if (nullify) pg.current.links[i].navpopup=null; } } pg.fn.disablePopups = function disablePopups(){ processAllPopups(false, true); setupTooltips(null, true); }; pg.fn.togglePreviews = function togglePreviews() { processAllPopups(true, true); pg.option.simplePopups=!pg.option.simplePopups; abortAllDownloads(); }; function magicWatchLink(l) { //Yuck!! Would require a thorough redesign to add this as a click event though ... l.onclick = simplePrintf( 'pg.fn.modifyWatchlist(\'%s\',\'%s\');return false;', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), this.id] ); return wikiLink(l); } pg.fn.modifyWatchlist = function modifyWatchlist(title, action) { var reqData = { 'action': 'watch', 'formatversion': 2, 'titles': title, 'uselang': mw.config.get('wgUserLanguage') }; if ( action === 'unwatch' ) reqData.unwatch = true; var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); // Load the Addedwatchtext or Removedwatchtext message and show it var mwTitle = mw.Title.newFromText( title ); var messageName; if ( mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1 ) { messageName = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk'; } else { messageName = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext'; } $.when( api.postWithToken( 'watch', reqData ), mw.loader.using( [ 'mediawiki.api', 'mediawiki.jqueryMsg' ] ).then( function () { return api.loadMessagesIfMissing( [ messageName ] ); } ) ).done( function () { mw.notify( mw.message( messageName, title ).parseDom() ); } ); }; function magicHistoryLink(l) { // FIXME use onclick change href trick to sort this out instead of window.open var jsUrl='', title='', onClick=''; switch(l.id) { case 'lastContrib': onClick=simplePrintf('pg.fn.getLastContrib(\'%s\',%s)', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]); title=popupString('lastContribHint'); break; case 'sinceMe': onClick=simplePrintf('pg.fn.getDiffSinceMyEdit(\'%s\',%s)', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]); title=popupString('sinceMeHint'); break; } jsUrl = 'javascript:' + onClick; // jshint ignore:line onClick += ';return false;'; return generalNavLink({url: jsUrl, newWin: false, // can't have new windows with JS links, I think title: title, text: l.text, noPopup: l.noPopup, onclick: onClick }); } function popupMenuLink(l) { var jsUrl=simplePrintf('javascript:pg.fn.%s()', [l.id]); // jshint ignore:line var title=popupString(simplePrintf('%sHint', [l.id])); var onClick=simplePrintf('pg.fn.%s();return false;', [l.id]); return generalNavLink({url: jsUrl, newWin:false, title:title, text:l.text, noPopup:l.noPopup, onclick: onClick}); } function specialLink(l) { // properties: article, specialpage, text, sep if (typeof l.specialpage=='undefined'||!l.specialpage) return null; var base = pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId]+':'+l.specialpage; if (typeof l.sep == 'undefined' || l.sep === null) l.sep='&target='; var article=l.article.urlString({keepSpaces: l.specialpage=='Search'}); var hint=popupString(l.specialpage+'Hint'); switch (l.specialpage) { case 'Log': switch (l.sep) { case '&user=': hint=popupString('userLogHint'); break; case '&type=block&page=': hint=popupString('blockLogHint'); break; case '&page=': hint=popupString('pageLogHint'); break; case '&type=protect&page=': hint=popupString('protectLogHint'); break; case '&type=delete&page=': hint=popupString('deleteLogHint'); break; default: log('Unknown log type, sep=' + l.sep); hint='Missing hint (FIXME)'; } break; case 'PrefixIndex': article += '/'; break; } if (hint) hint = simplePrintf(hint, [safeDecodeURI(l.article)]); else hint = safeDecodeURI(l.specialpage+':'+l.article) ; var url = base + l.sep + article; return generalNavLink({url: url, title: hint, text: l.text, newWin:l.newWin, noPopup:l.noPopup}); } function generalLink(l) { // l.url, l.text, l.title, l.newWin, l.className, l.noPopup, l.onclick if (typeof l.url=='undefined') return null; // only quotation marks in the url can screw us up now... I think var url=l.url.split('"').join('%22'); var ret='<a href="' + url + '"'; if (typeof l.title!='undefined' && l.title) { ret += ' title="' + pg.escapeQuotesHTML(l.title) + '"'; } if (typeof l.onclick!='undefined' && l.onclick) { ret += ' onclick="' + pg.escapeQuotesHTML(l.onclick) + '"'; } if (l.noPopup) { ret += ' noPopup=1'; } var newWin; if (typeof l.newWin=='undefined' || l.newWin === null) { newWin=getValueOf('popupNewWindows'); } else { newWin=l.newWin; } if (newWin) { ret += ' target="_blank"'; } if (typeof l.className!='undefined'&&l.className) { ret+=' class="'+l.className+'"'; } ret += '>'; if (typeof l.text==typeof '') { ret+= l.text; } ret +='</a>'; return ret; } function appendParamsToLink(linkstr, params) { var sp=linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i')); if (sp.length<2) return null; var ret=sp.shift() + sp.shift(); ret += '&' + params + '"'; ret += sp.join(''); return ret; } function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor, title (optional), alsoChangeLabel { if (x.newTarget) { log ('changeLinkTargetLink: newTarget=' + x.newTarget); } if (x.oldTarget !== decodeURIComponent( x.oldTarget ) ) { log ('This might be an input problem: ' + x.oldTarget ); } // FIXME: first character of page title as well as namespace should be case insensitive // eg [[category:X1]] and [[Category:X1]] are equivalent // this'll break if charAt(0) is nasty var cA = mw.util.escapeRegExp(x.oldTarget); var chs = cA.charAt(0).toUpperCase(); chs='['+chs + chs.toLowerCase()+']'; var currentArticleRegexBit=chs+cA.substring(1); currentArticleRegexBit=currentArticleRegexBit .split(RegExp('(?:[_ ]+|%20)', 'g')).join('(?:[_ ]+|%20)') .split('\\(').join('(?:%28|\\()') .split('\\)').join('(?:%29|\\))'); // why does this need to match encoded strings ? links in the document ? // leading and trailing space should be ignored, and anchor bits optional: currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + '(?:#[^\\[\\|]*)?)\\s*'; // e.g. Computer (archaic) -> \s*([Cc]omputer[_ ](?:%2528|\()archaic(?:%2528|\)))\s* // autoedit=s~\[\[([Cc]ad)\]\]~[[Computer-aided%20design|$1]]~g;s~\[\[([Cc]AD)[|]~[[Computer-aided%20design|~g var title=x.title || mw.config.get('wgPageName').split('_').join(' '); var lk=titledWikiLink({article: new Title(title), newWin:x.newWin, action: 'edit', text: x.text, title: x.hint, className: 'popup_change_title_link' }); var cmd=''; if (x.newTarget) { // escape '&' and other nasties var t = x.newTarget; var s = mw.util.escapeRegExp(x.newTarget); if (x.alsoChangeLabel) { cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + ']]~g;'; cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;'; cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g'; } else { cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + '|$1]]~g;'; cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;'; cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g'; } } else { cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~$1~g;'; cmd += 's~\\[\\['+currentArticleRegexBit+'[|](.*?)\\]\\]~$2~g'; } // Build query cmd = 'autoedit=' + encodeURIComponent ( cmd ); cmd += '&autoclick='+ encodeURIComponent( x.clickButton ) + '&actoken=' + encodeURIComponent( autoClickToken() ); cmd += ( x.minor === null ) ? '' : '&autominor='+ encodeURIComponent( x.minor ); cmd += ( x.watch === null ) ? '' : '&autowatch='+ encodeURIComponent( x.watch ); cmd += '&autosummary='+encodeURIComponent(x.summary); cmd += '&autoimpl='+encodeURIComponent( popupString('autoedit_version') ); return appendParamsToLink(lk, cmd); } function redirLink(redirMatch, article) { // NB redirMatch is in wikiText var ret=''; if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) { ret += '<hr />'; if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) { ret += popupString('Redirects to: (Fix '); log('redirLink: newTarget=' + redirMatch); ret += addPopupShortcut(changeLinkTargetLink({ newTarget: redirMatch, text: popupString('target'), hint: popupString('Fix this redirect, changing just the link target'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]), oldTarget: article.toString(), clickButton: getValueOf('popupRedirAutoClick'), minor: true, watch: getValueOf('popupWatchRedirredPages') }), 'R'); ret += popupString(' or '); ret += addPopupShortcut(changeLinkTargetLink({ newTarget: redirMatch, text: popupString('target & label'), hint: popupString('Fix this redirect, changing the link target and label'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]), oldTarget: article.toString(), clickButton: getValueOf('popupRedirAutoClick'), minor: true, watch: getValueOf('popupWatchRedirredPages'), alsoChangeLabel: true }), 'R'); ret += popupString(')'); } else ret += popupString('Redirects') + popupString(' to '); return ret; } else return '<br> ' + popupString('Redirects') + popupString(' to ') + titledWikiLink({article: new Title().fromWikiText(redirMatch), action: 'view', /* FIXME: newWin */ text: safeDecodeURI(redirMatch), title: popupString('Bypass redirect')}); } function arinLink(l) { if (!saneLinkCheck(l)) { return null; } if ( ! l.article.isIpUser() || ! pg.wiki.wikimedia) return null; var uN=l.article.userName(); return generalNavLink({url:'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + encodeURIComponent(uN), newWin:l.newWin, title: tprintf('Look up %s in ARIN whois database', [uN]), text: l.text, noPopup:1}); } function toolDbName(cookieStyle) { var ret = mw.config.get('wgDBname'); if (!cookieStyle) { ret+= '_p'; } return ret; } function saneLinkCheck(l) { if (typeof l.article != typeof {} || typeof l.text != typeof '') { return false; } return true; } function editCounterLink(l) { if(!saneLinkCheck(l)) return null; if (! pg.wiki.wikimedia) return null; var uN=l.article.userName(); var tool=getValueOf('popupEditCounterTool'); var url; var defaultToolUrl='//tools.wmflabs.org/supercount/index.php?user=$1&project=$2.$3'; switch(tool) { case 'custom': url=simplePrintf(getValueOf('popupEditCounterUrl'), [ encodeURIComponent(uN), toolDbName() ]); break; case 'soxred': // no longer available case 'kate': // no longer available case 'interiot':// no longer available /* fall through */ case 'supercount': default: var theWiki=pg.wiki.hostname.split('.'); url=simplePrintf(defaultToolUrl, [ encodeURIComponent(uN), theWiki[0], theWiki[1] ]); } return generalNavLink({url:url, title: tprintf('editCounterLinkHint', [uN]), newWin:l.newWin, text: l.text, noPopup:1}); } function globalSearchLink(l) { if(!saneLinkCheck(l)) return null; var base='http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search='; var article=l.article.urlString({keepSpaces:true}); return generalNavLink({url:base + article, newWin:l.newWin, title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]), text: l.text, noPopup:1}); } function googleLink(l) { if(!saneLinkCheck(l)) return null; var base='https://www.google.com/search?q='; var article=l.article.urlString({keepSpaces:true}); return generalNavLink({url:base + '%22' + article + '%22', newWin:l.newWin, title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]), text: l.text, noPopup:1}); } function editorListLink(l) { if(!saneLinkCheck(l)) return null; var article= l.article.articleFromTalkPage() || l.article; var url='https://xtools.wmflabs.org/articleinfo/' + encodeURI( pg.wiki.hostname ) + '/' + article.urlString() + '?uselang=' + mw.config.get('wgUserLanguage'); return generalNavLink({url:url, title: tprintf('editorListHint', [article]), newWin:l.newWin, text: l.text, noPopup:1}); } function generalNavLink(l) { l.className = (l.className === null) ? 'popupNavLink' : l.className; return generalLink(l); } ////////////////////////////////////////////////// // magic history links // function getHistoryInfo(wikipage, whatNext) { log('getHistoryInfo'); getHistory(wikipage, whatNext ? function(d){whatNext(processHistory(d));} : processHistory); } // FIXME eliminate pg.idNumber ... how? :-( function getHistory(wikipage, onComplete) { log('getHistory'); var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&prop=revisions&titles=' + new Title(wikipage).urlString() + '&rvlimit=' + getValueOf('popupHistoryLimit'); log('getHistory: url='+url); return startDownload(url, pg.idNumber+'history', onComplete); } function processHistory(download) { var jsobj = getJsObj(download.data); try { var revisions = anyChild(jsobj.query.pages).revisions; var edits=[]; for (var i=0; i<revisions.length; ++i) { edits.push({ oldid: revisions[i].revid, editor: revisions[i].user }); } log('processed ' + edits.length + ' edits'); return finishProcessHistory( edits, mw.config.get('wgUserName') ); } catch (someError) { log('Something went wrong with JSON business'); return finishProcessHistory([]); } } function finishProcessHistory(edits, userName) { var histInfo={}; histInfo.edits=edits; histInfo.userName=userName; for (var i=0; i<edits.length; ++i) { if (typeof histInfo.myLastEdit === 'undefined' && userName && edits[i].editor==userName) { histInfo.myLastEdit={index: i, oldid: edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)}; } if (typeof histInfo.firstNewEditor === 'undefined' && edits[i].editor != edits[0].editor) { histInfo.firstNewEditor={index:i, oldid:edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)}; } } //pg.misc.historyInfo=histInfo; return histInfo; } //</NOLITE> // ENDFILE: links.js // STARTFILE: options.js ////////////////////////////////////////////////// // options // check for existing value, else use default function defaultize(x) { if (pg.option[x]===null || typeof pg.option[x]=='undefined') { if (typeof window[x] != 'undefined' ) pg.option[x]=window[x]; else pg.option[x]=pg.optionDefault[x]; } } function newOption(x, def) { pg.optionDefault[x]=def; } function setDefault(x, def) { return newOption(x, def); } function getValueOf(varName) { defaultize(varName); return pg.option[varName]; } /*eslint-disable */ function useDefaultOptions() { // for testing for (var p in pg.optionDefault) { pg.option[p]=pg.optionDefault[p]; if (typeof window[p]!='undefined') { delete window[p]; } } } /*eslint-enable */ function setOptions() { // user-settable parameters and defaults var userIsSysop = false; if ( mw.config.get('wgUserGroups') ) { for ( var g = 0; g < mw.config.get('wgUserGroups').length; ++g ) { if ( mw.config.get('wgUserGroups')[g] == "sysop" ) userIsSysop = true; } } // Basic options newOption('popupDelay', 0.5); newOption('popupHideDelay', 0.5); newOption('simplePopups', false); newOption('popupStructure', 'shortmenus'); // see later - default for popupStructure is 'original' if simplePopups is true newOption('popupActionsMenu', true); newOption('popupSetupMenu', true); newOption('popupAdminLinks', userIsSysop); newOption('popupShortcutKeys', false); newOption('popupHistoricalLinks', true); newOption('popupOnlyArticleLinks', true); newOption('removeTitles', true); newOption('popupMaxWidth', 350); newOption('popupSimplifyMainLink', true); newOption('popupAppendRedirNavLinks', true); newOption('popupTocLinks', false); newOption('popupSubpopups', true); newOption('popupDragHandle', false /* 'popupTopLinks'*/); newOption('popupLazyPreviews', true); newOption('popupLazyDownloads', true); newOption('popupAllDabsStubs', false); newOption('popupDebugging', false); newOption('popupActiveNavlinks', true); newOption('popupModifier', false); // ctrl, shift, alt or meta newOption('popupModifierAction', 'enable'); // or 'disable' newOption('popupDraggable', true); newOption('popupReview', false); //<NOLITE> // images newOption('popupImages', true); newOption('imagePopupsForImages', true); newOption('popupNeverGetThumbs', false); //newOption('popupImagesToggleSize', true); newOption('popupThumbAction', 'imagepage'); //'sizetoggle'); newOption('popupImageSize', 60); newOption('popupImageSizeLarge', 200); // redirs, dabs, reversion newOption('popupFixRedirs', false); newOption('popupRedirAutoClick', 'wpDiff'); newOption('popupFixDabs', false); newOption('popupDabsAutoClick', 'wpDiff'); newOption('popupRevertSummaryPrompt', false); newOption('popupMinorReverts', false); newOption('popupRedlinkRemoval', false); newOption('popupRedlinkAutoClick', 'wpDiff'); newOption('popupWatchDisambiggedPages', null); newOption('popupWatchRedirredPages', null); newOption('popupDabWiktionary', 'last'); // navlinks newOption('popupNavLinks', true); newOption('popupNavLinkSeparator', ' &sdot; '); newOption('popupLastEditLink', true); newOption('popupEditCounterTool', 'supercount'); newOption('popupEditCounterUrl', ''); //</NOLITE> // previews etc newOption('popupPreviews', true); newOption('popupSummaryData', true); newOption('popupMaxPreviewSentences', 9); newOption('popupMaxPreviewCharacters', 900); newOption('popupLastModified', true); newOption('popupPreviewKillTemplates', true); newOption('popupPreviewRawTemplates', true); newOption('popupPreviewFirstParOnly', false); newOption('popupPreviewCutHeadings', false); newOption('popupPreviewButton', false); newOption('popupPreviewButtonEvent', 'click'); //<NOLITE> // diffs newOption('popupPreviewDiffs', true); newOption('popupDiffMaxLines', 100); newOption('popupDiffContextLines', 2); newOption('popupDiffContextCharacters', 40); newOption('popupDiffDates', true); newOption('popupDiffDatePrinter', 'toLocaleString'); // edit summaries. God, these are ugly. newOption('popupReviewedSummary', popupString('defaultpopupReviewedSummary') ); newOption('popupFixDabsSummary', popupString('defaultpopupFixDabsSummary') ); newOption('popupExtendedRevertSummary', popupString('defaultpopupExtendedRevertSummary') ); newOption('popupRevertSummary', popupString('defaultpopupRevertSummary') ); newOption('popupRevertToPreviousSummary', popupString('defaultpopupRevertToPreviousSummary') ); newOption('popupQueriedRevertSummary', popupString('defaultpopupQueriedRevertSummary') ); newOption('popupQueriedRevertToPreviousSummary', popupString('defaultpopupQueriedRevertToPreviousSummary') ); newOption('popupFixRedirsSummary', popupString('defaultpopupFixRedirsSummary') ); newOption('popupRedlinkSummary', popupString('defaultpopupRedlinkSummary') ); newOption('popupRmDabLinkSummary', popupString('defaultpopupRmDabLinkSummary') ); //</NOLITE> // misc newOption('popupHistoryLimit', 50); //<NOLITE> newOption('popupFilters', [popupFilterStubDetect, popupFilterDisambigDetect, popupFilterPageSize, popupFilterCountLinks, popupFilterCountImages, popupFilterCountCategories, popupFilterLastModified]); newOption('extraPopupFilters', []); newOption('popupOnEditSelection', 'cursor'); newOption('popupPreviewHistory', true); newOption('popupImageLinks', true); newOption('popupCategoryMembers', true); newOption('popupUserInfo', true); newOption('popupHistoryPreviewLimit', 25); newOption('popupContribsPreviewLimit',25); newOption('popupRevDelUrl', '//en.wikipedia.org/wiki/Wikipedia:Revision_deletion'); newOption('popupShowGender', true); //</NOLITE> // new windows newOption('popupNewWindows', false); newOption('popupLinksNewWindow', {'lastContrib': true, 'sinceMe': true}); // regexps newOption('popupDabRegexp', '(\\{\\{\\s*disambig(?!uation needed)|disambig(uation|)\\s*\\}\\}|disamb\\s*\\}\\}|dab\\s*\\}\\})|\\{\\{\\s*(((geo|hn|road?|school|number)dis)|[234][lc][acw]|(road|ship)index)(\\s*[|][^}]*)?\\s*[}][}]|is a .*disambiguation.*page'); newOption('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub'); newOption('popupImageVarsRegexp', 'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo'); } // ENDFILE: options.js // STARTFILE: strings.js //<NOLITE> ////////////////////////////////////////////////// // Translatable strings ////////////////////////////////////////////////// // // See instructions at // https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation pg.string = { ///////////////////////////////////// // summary data, searching etc. ///////////////////////////////////// 'article': 'article', 'category': 'category', 'categories': 'categories', 'image': 'image', 'images': 'images', 'stub': 'stub', 'section stub': 'section stub', 'Empty page': 'Empty page', 'kB': 'kB', 'bytes': 'bytes', 'day': 'day', 'days': 'days', 'hour': 'hour', 'hours': 'hours', 'minute': 'minute', 'minutes': 'minutes', 'second': 'second', 'seconds': 'seconds', 'week': 'week', 'weeks': 'weeks', 'search': 'search', 'SearchHint': 'Find English Wikipedia articles containing %s', 'web': 'web', 'global': 'global', 'globalSearchHint': 'Search across Wikipedias in different languages for %s', 'googleSearchHint': 'Google for %s', ///////////////////////////////////// // article-related actions and info // (some actions also apply to user pages) ///////////////////////////////////// 'actions': 'actions', ///// view articles and view talk 'popupsMenu': 'popups', 'togglePreviewsHint': 'Toggle preview generation in popups on this page', 'enable previews': 'enable previews', 'disable previews': 'disable previews', 'toggle previews': 'toggle previews', 'show preview': 'show preview', 'reset': 'reset', 'more...': 'more...', 'disable': 'disable popups', 'disablePopupsHint': 'Disable popups on this page. Reload page to re-enable.', 'historyfeedHint': 'RSS feed of recent changes to this page', 'purgePopupsHint': 'Reset popups, clearing all cached popup data.', 'PopupsHint': 'Reset popups, clearing all cached popup data.', 'spacebar': 'space', 'view': 'view', 'view article': 'view article', 'viewHint': 'Go to %s', 'talk': 'talk', 'talk page': 'talk page', 'this&nbsp;revision': 'this&nbsp;revision', 'revision %s of %s': 'revision %s of %s', 'Revision %s of %s': 'Revision %s of %s', 'the revision prior to revision %s of %s': 'the revision prior to revision %s of %s', 'Toggle image size': 'Click to toggle image size', 'del': 'del', ///// delete, protect, move 'delete': 'delete', 'deleteHint': 'Delete %s', 'undeleteShort': 'un', 'UndeleteHint': 'Show the deletion history for %s', 'protect': 'protect', 'protectHint': 'Restrict editing rights to %s', 'unprotectShort': 'un', 'unprotectHint': 'Allow %s to be edited by anyone again', 'send thanks': 'send thanks', 'ThanksHint': 'Send a thank you notification to this user', 'move': 'move', 'move page': 'move page', 'MovepageHint': 'Change the title of %s', 'edit': 'edit', ///// edit articles and talk 'edit article': 'edit article', 'editHint': 'Change the content of %s', 'edit talk': 'edit talk', 'new': 'new', 'new topic': 'new topic', 'newSectionHint': 'Start a new section on %s', 'null edit': 'null edit', 'nullEditHint': 'Submit an edit to %s, making no changes ', 'hist': 'hist', ///// history, diffs, editors, related 'history': 'history', 'historyHint': 'List the changes made to %s', 'last': 'prev', // For labelling the previous revision in history pages; the key is "last" for backwards compatibility 'lastEdit': 'lastEdit', 'mark patrolled': 'mark patrolled', 'markpatrolledHint': 'Mark this edit as patrolled', 'Could not marked this edit as patrolled': 'Could not marked this edit as patrolled', 'show last edit': 'most recent edit', 'Show the last edit': 'Show the effects of the most recent change', 'lastContrib': 'lastContrib', 'last set of edits': 'latest edits', 'lastContribHint': 'Show the net effect of changes made by the last editor', 'cur': 'cur', 'diffCur': 'diffCur', 'Show changes since revision %s': 'Show changes since revision %s', '%s old': '%s old', // as in 4 weeks old 'oldEdit': 'oldEdit', 'purge': 'purge', 'purgeHint': 'Demand a fresh copy of %s', 'raw': 'source', 'rawHint': 'Download the source of %s', 'render': 'simple', 'renderHint': 'Show a plain HTML version of %s', 'Show the edit made to get revision': 'Show the edit made to get revision', 'sinceMe': 'sinceMe', 'changes since mine': 'diff my edit', 'sinceMeHint': 'Show changes since my last edit', 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s': 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s', 'eds': 'eds', 'editors': 'editors', 'editorListHint': 'List the users who have edited %s', 'related': 'related', 'relatedChanges': 'relatedChanges', 'related changes': 'related changes', 'RecentchangeslinkedHint': 'Show changes in articles related to %s', 'editOld': 'editOld', ///// edit old version, or revert 'rv': 'rv', 'revert': 'revert', 'revertHint': 'Revert to %s', 'defaultpopupReviewedSummary': 'Accepted by reviewing the [[Special:diff/%s/%s|difference]] between this version and previously accepted version using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRedlinkSummary': 'Removing link to empty page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupFixDabsSummary': 'Disambiguate [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupFixRedirsSummary': 'Redirect bypass from [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupExtendedRevertSummary': 'Revert to revision dated %s by %s, oldid %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRevertToPreviousSummary': 'Revert to the revision prior to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRevertSummary': 'Revert to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupQueriedRevertToPreviousSummary': 'Revert to the revision prior to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupQueriedRevertSummary': 'Revert to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRmDabLinkSummary': 'Remove link to dab page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'Redirects': 'Redirects', // as in Redirects to ... ' to ': ' to ', // as in Redirects to ... 'Bypass redirect': 'Bypass redirect', 'Fix this redirect': 'Fix this redirect', 'disambig': 'disambig', ///// add or remove dab etc. 'disambigHint': 'Disambiguate this link to [[%s]]', 'Click to disambiguate this link to:': 'Click to disambiguate this link to:', 'remove this link': 'remove this link', 'remove all links to this page from this article': 'remove all links to this page from this article', 'remove all links to this disambig page from this article': 'remove all links to this disambig page from this article', 'mainlink': 'mainlink', ///// links, watch, unwatch 'wikiLink': 'wikiLink', 'wikiLinks': 'wikiLinks', 'links here': 'links here', 'whatLinksHere': 'whatLinksHere', 'what links here': 'what links here', 'WhatlinkshereHint': 'List the pages that are hyperlinked to %s', 'unwatchShort': 'un', 'watchThingy': 'watch', // called watchThingy because {}.watch is a function 'watchHint': 'Add %s to my watchlist', 'unwatchHint': 'Remove %s from my watchlist', 'Only found one editor: %s made %s edits': 'Only found one editor: %s made %s edits', '%s seems to be the last editor to the page %s': '%s seems to be the last editor to the page %s', 'rss': 'rss', ///////////////////////////////////// // diff previews ///////////////////////////////////// 'Diff truncated for performance reasons': 'Diff truncated for performance reasons', 'Old revision': 'Old revision', 'New revision': 'New revision', 'Something went wrong :-(': 'Something went wrong :-(', 'Empty revision, maybe non-existent': 'Empty revision, maybe non-existent', 'Unknown date': 'Unknown date', ///////////////////////////////////// // other special previews ///////////////////////////////////// 'Empty category': 'Empty category', 'Category members (%s shown)': 'Category members (%s shown)', 'No image links found': 'No image links found', 'File links': 'File links', 'No image found': 'No image found', 'Image from Commons': 'Image from Commons', 'Description page': 'Description page', 'Alt text:': 'Alt text:', 'revdel':'Hidden revision', ///////////////////////////////////// // user-related actions and info ///////////////////////////////////// 'user': 'user', ///// user page, talk, email, space 'user&nbsp;page': 'user&nbsp;page', 'user talk': 'user talk', 'edit user talk': 'edit user talk', 'leave comment': 'leave comment', 'email': 'email', 'email user': 'email user', 'EmailuserHint': 'Send an email to %s', 'space': 'space', // short form for userSpace link 'PrefixIndexHint': 'Show pages in the userspace of %s', 'count': 'count', ///// contributions, log 'edit counter': 'edit counter', 'editCounterLinkHint': 'Count the contributions made by %s', 'contribs': 'contribs', 'contributions': 'contributions', 'deletedContribs': 'deleted contributions', 'DeletedcontributionsHint': 'List deleted edits made by %s', 'ContributionsHint': 'List the contributions made by %s', 'log': 'log', 'user log': 'user log', 'userLogHint': 'Show %s\'s user log', 'arin': 'ARIN lookup', ///// ARIN lookup, block user or IP 'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database', 'unblockShort': 'un', 'block': 'block', 'block user': 'block user', 'IpblocklistHint': 'Unblock %s', 'BlockipHint': 'Prevent %s from editing', 'block log': 'block log', 'blockLogHint': 'Show the block log for %s', 'protectLogHint': 'Show the protection log for %s', 'pageLogHint': 'Show the page log for %s', 'deleteLogHint': 'Show the deletion log for %s', 'Invalid %s %s': 'The option %s is invalid: %s', 'No backlinks found': 'No backlinks found', ' and more': ' and more', 'undo': 'undo', 'undoHint': 'undo this edit', 'Download preview data': 'Download preview data', 'Invalid or IP user': 'Invalid or IP user', 'Not a registered username': 'Not a registered username', 'CẤM': 'CẤM', 'Has blocks': 'Has blocks', ' sửa đổi từ: ': ' sửa đổi từ: ', 'last edit on ': 'last edit on ', ///////////////////////////////////// // Autoediting ///////////////////////////////////// 'Enter a non-empty edit summary or press cancel to abort': 'Enter a non-empty edit summary or press cancel to abort', 'Failed to get revision information, please edit manually.\n\n': 'Failed to get revision information, please edit manually.\n\n', 'The %s button has been automatically clicked. Please wait for the next page to load.': 'The %s button has been automatically clicked. Please wait for the next page to load.', 'Could not find button %s. Please check the settings in your javascript file.': 'Could not find button %s. Please check the settings in your javascript file.', ///////////////////////////////////// // Popups setup ///////////////////////////////////// 'Open full-size image': 'Open full-size image', 'zxy': 'zxy', 'autoedit_version': 'np20140416' }; function popupString(str) { if (typeof popupStrings != 'undefined' && popupStrings && popupStrings[str]) { return popupStrings[str]; } if (pg.string[str]) { return pg.string[str]; } return str; } function tprintf(str,subs) { if (typeof subs != typeof []) { subs = [subs]; } return simplePrintf(popupString(str), subs); } //</NOLITE> // ENDFILE: strings.js // STARTFILE: run.js //////////////////////////////////////////////////////////////////// // Run things //////////////////////////////////////////////////////////////////// // For some reason popups requires a fully loaded page jQuery.ready(...) causes problems for some. // The old addOnloadHook did something similar to the below if (document.readyState=="complete") autoEdit(); //will setup popups else $( window ).on( 'load', autoEdit ); // Support for MediaWiki's live preview, VisualEditor's saves and Echo's flyout. ( function () { var once = true; function dynamicContentHandler( $content ) { // Try to detect the hook fired on initial page load and disregard // it, we already hook to onload (possibly to different parts of // page - it's configurable) and running twice might be bad. Ugly… if ( $content.attr( 'id' ) == 'mw-content-text' ) { if ( once ) { once = false; return; } } function registerHooksForVisibleNavpops () { for (var i=0; pg.current.links && i<pg.current.links.length; ++i) { var navpop = pg.current.links[i].navpopup; if (!navpop || !navpop.isVisible()) { continue; } Navpopup.tracker.addHook(posCheckerHook(navpop)); } } function doIt () { registerHooksForVisibleNavpops(); $content.each( function () { this.ranSetupTooltipsAlready = false; setupTooltips( this ); } ); } setupPopups( doIt ); } // This hook is also fired after page load. mw.hook( 'wikipage.content' ).add( dynamicContentHandler ); mw.hook( 'ext.echo.overlay.beforeShowingOverlay' ).add( function($overlay){ dynamicContentHandler( $overlay.find(".mw-echo-state") ); }); } )(); // Forked from User:Connel MacKenzie/mess-with-popups.js Previewmaker.prototype.firstBit = function() { try{ if (mw.config.get("wgNamespaceNumber") !== 0){ // dont't be givin' me no subsequent paragraphs, you hear me? /// first we "normalize" section headings, removing whitespace after, adding before var d = this.data; if (getValueOf('popupPreviewCutHeadings')) { this.data = this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 '); /// then we want to get rid of paragraph breaks whose text ends badly this.data = this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n'); this.data = this.data.replace(RegExp('^[\\s\\n]*'), ''); stuff = (RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data); if (stuff) { d = stuff[0]; } if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; } /// now put \n\n after sections so that bullets and numbered lists work d = d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n'); } // superfluous sentences are RIGHT OUT. // note: exactly 1 set of parens here needed to make the slice work d = d.parenSplit(RegExp('([!?.]+["' + "'" + ']*\\s)', 'g')); // leading space is bad, mmkay? d[0] = d[0].replace(RegExp('^\\s*'), ''); var notSentenceEnds = RegExp('([^.][a-z][.][a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|\\[[^\\]]*|\\s[A-Zvclm])$', 'i'); d = this.fixSentenceEnds(d, notSentenceEnds); var maxChars=getValueOf('popupMaxPreviewCharacters'); var n = getValueOf('popupMaxPreviewSentences'); var dd; do { dd = this.firstSentences(d, n); --n; } while (dd.length > maxChars && n > 0); this.data = dd; } else { //TODO: Limit this override to urls that start with http://*.wiktionary.org/ //TODO: extend the above section to also preview Wikipedia links var charsWikt = 0; var linesWikt = this.data.split('\n'); for (jWikt = 0; jWikt < linesWikt.length; jWikt++) { linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{[in]transitive}}/g, ''); linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{[un]countable}}/g, ''); linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{cattag\|(.*?)}}/g, ""); linesWikt[jWikt] = linesWikt[jWikt].replace(/[\{\}]/g, "'"); if (linesWikt[jWikt].search(/^[#]/) == -1) { if (linesWikt[jWikt].search(/^==[A-Z]/) == -1) { linesWikt[jWikt] = ''; } } charsWikt += linesWikt[jWikt].length; } if (charsWikt < 2) { linesWikt[0] = "No properly formatted content"; } var maxChars = getValueOf('popupMaxPreviewCharacters'); var n = getValueOf('popupMaxPreviewSentences'); var dd = ''; for (jWikt = 0; jWikt < linesWikt.length; jWikt++) { if (linesWikt[jWikt] !== '') { dd += linesWikt[jWikt] + '\n'; } if (dd.length < maxChars) { this.data = dd; } } } } catch (e) { console.log("error from mess with popups: ", e); } }; }); qwdgm5plp41md6gs93lqsroj7mbz15m 2513951 2513949 2026-08-14T04:48:50Z Hiyuune 50834 2513951 javascript text/javascript // {{documentation}} // _________________________________________________________________________________________ // | | // | === WARNING: GLOBAL GADGET FILE === | // | Changes to this page affect many users. | // | Please discuss changes on the [[Wiktionary:Grease Pit]] before editing. | // |_________________________________________________________________________________________| mw.loader.load('//en.wikipedia.org/w/index.php?action=raw&title=MediaWiki:Gadget-navpop.css&ctype=text/css', "text/css"); // Forked from [[:w:MediaWiki:Gadget-popups.js]] $(function () { ////////////////////////////////////////////////// // Globals // // Trying to shove as many of these as possible into the pg (popup globals) object var pg = { re: {}, // regexps ns: {}, // namespaces string: {}, // translatable strings wiki: {}, // local site info user: {}, // current user info misc: {}, // YUCK PHOOEY option: {}, // options, see newOption etc optionDefault: {}, // default option values flag: {}, // misc flags cache: {}, // page and image cache structures: {}, // navlink structures timer: {}, // all sorts of timers (too damn many) counter: {}, // .. and all sorts of counters current: {}, // state info fn: {}, // functions endoflist: null }; /* Bail if the gadget/script is being loaded twice */ if( window.pg ) { return; } /* Export to global context */ window.pg = pg; /// Local Variables: /// /// mode:c /// /// End: /// // ENDFILE: main.js // STARTFILE: actions.js function setupTooltips(container, remove, force, popData) { log('setupTooltips, container='+container+', remove='+remove); if (!container) { //<NOLITE> // the main initial call if (getValueOf('popupOnEditSelection') && document && document.editform && document.editform.wpTextbox1) { document.editform.wpTextbox1.onmouseup=doSelectionPopup; } //</NOLITE> // article/content is a structure-dependent thing container = defaultPopupsContainer(); } if (!remove && !force && container.ranSetupTooltipsAlready) { return; } container.ranSetupTooltipsAlready = !remove; var anchors; anchors=container.getElementsByTagName('A'); setupTooltipsLoop(anchors, 0, 250, 100, remove, popData); } function defaultPopupsContainer() { if (getValueOf('popupOnlyArticleLinks')) { return document.getElementById('mw_content') || document.getElementById('content') || document.getElementById('article') || document; } return document; } function setupTooltipsLoop(anchors,begin,howmany,sleep, remove, popData) { log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments)); var finish=begin+howmany; var loopend = Math.min(finish, anchors.length); var j=loopend - begin; log ('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin + ', howmany=' + howmany + ', loopend=' + loopend + ', remove=' + remove); var doTooltip= remove ? removeTooltip : addTooltip; // try a faster (?) loop construct if (j > 0) { do { var a=anchors[loopend - j]; if (typeof a==='undefined' || !a || !a.href) { log('got null anchor at index ' + loopend - j); continue; } doTooltip(a, popData); } while (--j); } if (finish < anchors.length) { setTimeout(function() { setupTooltipsLoop(anchors,finish,howmany,sleep,remove,popData);}, sleep); } else { if ( !remove && ! getValueOf('popupTocLinks')) { rmTocTooltips(); } pg.flag.finishedLoading=true; } } // eliminate popups from the TOC // This also kills any onclick stuff that used to be going on in the toc function rmTocTooltips() { var toc=document.getElementById('toc'); if (toc) { var tocLinks=toc.getElementsByTagName('A'); var tocLen = tocLinks.length; for (var j=0; j<tocLen; ++j) { removeTooltip(tocLinks[j], true); } } } function addTooltip(a, popData) { if ( !isPopupLink(a) ) { return; } a.onmouseover=mouseOverWikiLink; a.onmouseout= mouseOutWikiLink; a.onmousedown = killPopup; a.hasPopup = true; a.popData = popData; } function removeTooltip(a) { if ( !a.hasPopup ) { return; } a.onmouseover = null; a.onmouseout = null; if (a.originalTitle) { a.title = a.originalTitle; } a.hasPopup=false; } function removeTitle(a) { if (!a.originalTitle) { a.originalTitle=a.title; } a.title=''; } function restoreTitle(a) { if ( a.title || !a.originalTitle ) { return; } a.title = a.originalTitle; } function registerHooks(np) { var popupMaxWidth=getValueOf('popupMaxWidth'); if (typeof popupMaxWidth === 'number') { var setMaxWidth = function () { np.mainDiv.style.maxWidth = popupMaxWidth + 'px'; np.maxWidth = popupMaxWidth; }; np.addHook(setMaxWidth, 'unhide', 'before'); } //<NOLITE> np.addHook(addPopupShortcuts, 'unhide', 'after'); np.addHook(rmPopupShortcuts, 'hide', 'before'); //</NOLITE> } function removeModifierKeyHandler(a) { //remove listeners for modifier key if any that were added in mouseOverWikiLink document.removeEventListener('keydown', a.modifierKeyHandler, false); document.removeEventListener('keyup', a.modifierKeyHandler, false); } function mouseOverWikiLink(evt) { if (!evt && window.event) {evt=window.event;} // if the modifier is needed, listen for it, // we will remove the listener when we mouseout of this link or kill popup. if (getValueOf('popupModifier')) { // if popupModifierAction = enable, we should popup when the modifier is pressed // if popupModifierAction = disable, we should popup unless the modifier is pressed var action = getValueOf('popupModifierAction'); var key = action=='disable' ? 'keyup' : 'keydown'; var a = this; a.modifierKeyHandler = function(evt) { mouseOverWikiLink2(a, evt); }; document.addEventListener(key, a.modifierKeyHandler, false); } return mouseOverWikiLink2(this, evt); } /** * Gets the references list item that the provided footnote link targets. This * is typically a li element within the ol.references element inside the reflist. * @param {Element} a - A footnote link. * @returns {Element|boolean} The targeted element, or false if one can't be found. */ function footnoteTarget(a) { var aTitle=Title.fromAnchor(a); // We want ".3A" rather than "%3A" or "?" here, so use the anchor property directly var anch = aTitle.anchor; if ( ! /^(cite_note-|_note-|endnote)/.test(anch) ) { return false; } var lTitle=Title.fromURL(location.href); if ( lTitle.toString(true) !== aTitle.toString(true) ) { return false; } var el=document.getElementById(anch); while ( el && typeof el.nodeName === 'string') { var nt = el.nodeName.toLowerCase(); if ( nt === 'li' ) { return el; } else if ( nt === 'body' ) { return false; } else if ( el.parentNode ) { el=el.parentNode; } else { return false; } } return false; } function footnotePreview(x, navpop) { setPopupHTML('<hr />' + x.innerHTML, 'popupPreview', navpop.idNumber); } function modifierPressed(evt) { var mod=getValueOf('popupModifier'); if (!mod) { return false; } if (!evt && window.event) {evt=window.event;} return ( evt && mod && evt[mod.toLowerCase() + 'Key'] ); } // Checks if the correct modifier pressed/unpressed if needed function isCorrectModifier(a,evt) { if (!getValueOf('popupModifier')) { return true; } // if popupModifierAction = enable, we should popup when the modifier is pressed // if popupModifierAction = disable, we should popup unless the modifier is pressed var action = getValueOf('popupModifierAction'); return ( action == 'enable' && modifierPressed(evt) || action == 'disable' && !modifierPressed(evt) ); } function mouseOverWikiLink2(a, evt) { if (!isCorrectModifier(a,evt)) { return; } if ( getValueOf('removeTitles') ) { removeTitle(a); } if ( a==pg.current.link && a.navpopup && a.navpopup.isVisible() ) { return; } pg.current.link=a; if (getValueOf('simplePopups') && !pg.option.popupStructure) { // reset *default value* of popupStructure setDefault('popupStructure', 'original'); } var article=(new Title()).fromAnchor(a); // set global variable (ugh) to hold article (wikipage) pg.current.article = article; if (!a.navpopup) { a.navpopup=newNavpopup(a, article); pg.current.linksHash[a.href] = a.navpopup; pg.current.links.push(a); } if (a.navpopup.pending === null || a.navpopup.pending !== 0) { // either fresh popups or those with unfinshed business are redone from scratch simplePopupContent(a, article); } a.navpopup.showSoonIfStable(a.navpopup.delay); clearInterval(pg.timer.checkPopupPosition); pg.timer.checkPopupPosition=setInterval(checkPopupPosition, 600); if(getValueOf('simplePopups')) { if (getValueOf('popupPreviewButton') && !a.simpleNoMore) { var d=document.createElement('div'); d.className='popupPreviewButtonDiv'; var s=document.createElement('span'); d.appendChild(s); s.className='popupPreviewButton'; s['on' + getValueOf('popupPreviewButtonEvent')] = function() { a.simpleNoMore=true; d.style.display = "none"; nonsimplePopupContent(a,article); }; s.innerHTML=popupString('show preview'); setPopupHTML(d, 'popupPreview', a.navpopup.idNumber); } } if (a.navpopup.pending !== 0 ) { nonsimplePopupContent(a, article); } } // simplePopupContent: the content that do not require additional download // (it is shown even when simplePopups is true) function simplePopupContent(a, article) { /* FIXME hack */ a.navpopup.hasPopupMenu=false; a.navpopup.setInnerHTML(popupHTML(a)); fillEmptySpans({navpopup:a.navpopup}); if (getValueOf('popupDraggable')) { var dragHandle = getValueOf('popupDragHandle') || null; if (dragHandle && dragHandle != 'all') { dragHandle += a.navpopup.idNumber; } setTimeout(function(){a.navpopup.makeDraggable(dragHandle);}, 150); } //<NOLITE> if (getValueOf('popupRedlinkRemoval') && a.className=='new') { setPopupHTML('<br>'+popupRedlinkHTML(article), 'popupRedlink', a.navpopup.idNumber); } //</NOLITE> } function debugData(navpopup) { if(getValueOf('popupDebugging') && navpopup.idNumber) { setPopupHTML('idNumber='+navpopup.idNumber + ', pending=' + navpopup.pending, 'popupError', navpopup.idNumber); } } function newNavpopup(a, article) { var navpopup = new Navpopup(); navpopup.fuzz=5; navpopup.delay=getValueOf('popupDelay')*1000; // increment global counter now navpopup.idNumber = ++pg.idNumber; navpopup.parentAnchor = a; navpopup.parentPopup = (a.popData && a.popData.owner); navpopup.article = article; registerHooks(navpopup); return navpopup; } // Should we show nonsimple context? // If simplePopups is set to true, then we do not show nonsimple context, // but if a bottom "show preview" was clicked we do show nonsimple context function shouldShowNonSimple(a) { return !getValueOf('simplePopups') || a.simpleNoMore; } // Should we show nonsimple context govern by the option (e.g. popupUserInfo)? // If the user explicitly asked for nonsimple context by setting the option to true, // then we show it even in nonsimple mode. function shouldShow(a,option) { if (shouldShowNonSimple(a)) { return getValueOf(option); } else { return (typeof window[option] != 'undefined' ) && window[option]; } } function nonsimplePopupContent(a, article) { var diff=null, history=null; var params=parseParams(a.href); var oldid=(typeof params.oldid=='undefined' ? null : params.oldid); //<NOLITE> if(shouldShow(a,'popupPreviewDiffs')) { diff=params.diff; } if(shouldShow(a,'popupPreviewHistory')) { history=(params.action=='history'); } //</NOLITE> a.navpopup.pending=0; var referenceElement = footnoteTarget(a); if (referenceElement) { footnotePreview(referenceElement, a.navpopup); //<NOLITE> } else if ( diff || diff === 0 ) { loadDiff(article, oldid, diff, a.navpopup); } else if ( history ) { loadAPIPreview('history', article, a.navpopup); } else if ( shouldShowNonSimple(a) && pg.re.contribs.test(a.href) ) { loadAPIPreview('contribs', article, a.navpopup); } else if ( shouldShowNonSimple(a) && pg.re.backlinks.test(a.href) ) { loadAPIPreview('backlinks', article, a.navpopup); } else if ( // FIXME should be able to get all preview combinations with options article.namespaceId()==pg.nsImageId && ( shouldShow(a,'imagePopupsForImages') || ! anchorContainsImage(a) ) ) { loadAPIPreview('imagepagepreview', article, a.navpopup); loadImage(article, a.navpopup); //</NOLITE> } else { if (article.namespaceId() == pg.nsCategoryId && shouldShow(a,'popupCategoryMembers')) { loadAPIPreview('category', article, a.navpopup); } else if ((article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) && shouldShow(a,'popupUserInfo')) { loadAPIPreview('userinfo', article, a.navpopup); } if (shouldShowNonSimple(a)) startArticlePreview(article, oldid, a.navpopup); } } function pendingNavpopTask(navpop) { if (navpop && navpop.pending === null) { navpop.pending=0; } ++navpop.pending; debugData(navpop); } function completedNavpopTask(navpop) { if (navpop && navpop.pending) { --navpop.pending; } debugData(navpop); } function startArticlePreview(article, oldid, navpop) { navpop.redir=0; loadPreview(article, oldid, navpop); } function loadPreview(article, oldid, navpop) { if (!navpop.redir) { navpop.originalArticle=article; } article.oldid = oldid; loadAPIPreview('revision', article, navpop); } function loadPreviewFromRedir(redirMatch, navpop) { // redirMatch is a regex match var target = new Title().fromWikiText(redirMatch[2]); // overwrite (or add) anchor from original target // mediawiki does overwrite; eg User:Lupin/foo3#Done if ( navpop.article.anchor ) { target.anchor = navpop.article.anchor; } navpop.redir++; navpop.redirTarget=target; //<NOLITE> var warnRedir = redirLink(target, navpop.article); setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber); //</NOLITE> navpop.article=target; fillEmptySpans({redir: true, redirTarget: target, navpopup:navpop}); return loadPreview(target, null, navpop); } function insertPreview(download) { if (!download.owner) { return; } var redirMatch = pg.re.redirect.exec(download.data); if (download.owner.redir === 0 && redirMatch) { loadPreviewFromRedir(redirMatch, download.owner); return; } if (download.owner.visible || !getValueOf('popupLazyPreviews')) { insertPreviewNow(download); } else { var id=(download.owner.redir) ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK'; download.owner.addHook( function(){insertPreviewNow(download); return true;}, 'unhide', 'after', id ); } } function insertPreviewNow(download) { if (!download.owner) { return; } var wikiText=download.data; var navpop=download.owner; var art=navpop.redirTarget || navpop.originalArticle; //<NOLITE> makeFixDabs(wikiText, navpop); if (getValueOf('popupSummaryData')) { getPageInfo(wikiText, download); setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber); } var imagePage=''; if (art.namespaceId()==pg.nsImageId) { imagePage=art.toString(); } else { imagePage=getValidImageFromWikiText(wikiText); } if(imagePage) { loadImage(Title.fromWikiText(imagePage), navpop); } //</NOLITE> if (getValueOf('popupPreviews')) { insertArticlePreview(download, art, navpop); } } function insertArticlePreview(download, art, navpop) { if (download && typeof download.data == typeof ''){ if (art.namespaceId()==pg.nsTemplateId && getValueOf('popupPreviewRawTemplates')) { // FIXME compare/consolidate with diff escaping code for wikitext var h='<hr /><span style="font-family: monospace;">' + download.data.entify().split('\\n').join('<br />\\n') + '</span>'; setPopupHTML(h, 'popupPreview', navpop.idNumber); } else { var p=prepPreviewmaker(download.data, art, navpop); p.showPreview(); } } } function prepPreviewmaker(data, article, navpop) { // deal with tricksy anchors var d=anchorize(data, article.anchorString()); var urlBase=joinPath([pg.wiki.articlebase, article.urlString()]); var p=new Previewmaker(d, urlBase, navpop); return p; } // Try to imitate the way mediawiki generates HTML anchors from section titles function anchorize(d, anch) { if (!anch) { return d; } var anchRe=RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*'+getValueOf('popupAnchorRegexp')+'\\s*(?:\\|[^|}]*)*?\\s*'+literalizeRegex(anch)+'\\s*(?:\\|[^}]*)?}})'); var match=d.match(anchRe); if(match && match.length > 0 && match[0]) { return d.substring(d.indexOf(match[0])); } // now try to deal with == foo [[bar|baz]] boom == -> #foo_baz_boom var lines=d.split('\n'); for (var i=0; i<lines.length; ++i) { lines[i]=lines[i].replace(RegExp('[[]{2}([^|\\]]*?[|])?(.*?)[\\]]{2}', 'g'), '$2') .replace(/'''([^'])/g, '$1').replace(RegExp("''([^'])", 'g'), '$1'); if (lines[i].match(anchRe)) { return d.split('\n').slice(i).join('\n').replace(RegExp('^[^=]*'), ''); } } return d; } function killPopup() { removeModifierKeyHandler(this); if (getValueOf('popupShortcutKeys')) { rmPopupShortcuts(); } if (!pg) { return; } if (pg.current.link && pg.current.link.navpopup) { pg.current.link.navpopup.banish(); } pg.current.link=null; abortAllDownloads(); if (pg.timer.checkPopupPosition) { clearInterval(pg.timer.checkPopupPosition); pg.timer.checkPopupPosition=null; } return true; // preserve default action } // ENDFILE: actions.js // STARTFILE: domdrag.js /** @fileoverview The {@link Drag} object, which enables objects to be dragged around. <pre> ************************************************* dom-drag.js 09.25.2001 www.youngpup.net ************************************************** 10.28.2001 - fixed minor bug where events sometimes fired off the handle, not the root. ************************************************* Pared down, some hooks added by User:Lupin Copyright Aaron Boodman. Saying stupid things daily since March 2001. </pre> */ /** Creates a new Drag object. This is used to make various DOM elements draggable. @constructor */ function Drag () { /** Condition to determine whether or not to drag. This function should take one parameter, an Event. To disable this, set it to <code>null</code>. @type Function */ this.startCondition = null; /** Hook to be run when the drag finishes. This is passed the final coordinates of the dragged object (two integers, x and y). To disables this, set it to <code>null</code>. @type Function */ this.endHook = null; } /** Gets an event in a cross-browser manner. @param {Event} e @private */ Drag.prototype.fixE = function(e) { if (typeof e == 'undefined') { e = window.event; } if (typeof e.layerX == 'undefined') { e.layerX = e.offsetX; } if (typeof e.layerY == 'undefined') { e.layerY = e.offsetY; } return e; }; /** Initialises the Drag instance by telling it which object you want to be draggable, and what you want to drag it by. @param {DOMElement} o The "handle" by which <code>oRoot</code> is dragged. @param {DOMElement} oRoot The object which moves when <code>o</code> is dragged, or <code>o</code> if omitted. */ Drag.prototype.init = function(o, oRoot) { var dragObj = this; this.obj = o; o.onmousedown = function(e) { dragObj.start.apply( dragObj, [e]); }; o.dragging = false; o.popups_draggable = true; o.hmode = true; o.vmode = true; o.root = oRoot ? oRoot : o ; if (isNaN(parseInt(o.root.style.left, 10))) { o.root.style.left = "0px"; } if (isNaN(parseInt(o.root.style.top, 10))) { o.root.style.top = "0px"; } o.root.onthisStart = function(){}; o.root.onthisEnd = function(){}; o.root.onthis = function(){}; }; /** Starts the drag. @private @param {Event} e */ Drag.prototype.start = function(e) { var o = this.obj; // = this; e = this.fixE(e); if (this.startCondition && !this.startCondition(e)) { return; } var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10); o.root.onthisStart(x, y); o.lastMouseX = e.clientX; o.lastMouseY = e.clientY; var dragObj = this; o.onmousemoveDefault = document.onmousemove; o.dragging = true; document.onmousemove = function(e) { dragObj.drag.apply( dragObj, [e] ); }; document.onmouseup = function(e) { dragObj.end.apply( dragObj, [e] ); }; return false; }; /** Does the drag. @param {Event} e @private */ Drag.prototype.drag = function(e) { e = this.fixE(e); var o = this.obj; var ey = e.clientY; var ex = e.clientX; var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10 ); var nx, ny; nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1)); ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1)); this.obj.root.style[o.hmode ? "left" : "right"] = nx + "px"; this.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px"; this.obj.lastMouseX = ex; this.obj.lastMouseY = ey; this.obj.root.onthis(nx, ny); return false; }; /** Ends the drag. @private */ Drag.prototype.end = function() { document.onmousemove=this.obj.onmousemoveDefault; document.onmouseup = null; this.obj.dragging = false; if (this.endHook) { this.endHook( parseInt(this.obj.root.style[this.obj.hmode ? "left" : "right"], 10), parseInt(this.obj.root.style[this.obj.vmode ? "top" : "bottom"], 10)); } }; // ENDFILE: domdrag.js // STARTFILE: structures.js //<NOLITE> pg.structures.original={}; pg.structures.original.popupLayout=function () { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupUserData', 'popupData', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; pg.structures.original.popupRedirSpans=function () { return ['popupRedir', 'popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks']; }; pg.structures.original.popupTitle=function (x) { log ('defaultstructure.popupTitle'); if (!getValueOf('popupNavLinks')) { return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params); } return ''; }; pg.structures.original.popupTopLinks=function (x) { log ('defaultstructure.popupTopLinks'); if (getValueOf('popupNavLinks')) { return navLinksHTML(x.article, x.hint, x.params); } return ''; }; pg.structures.original.popupImage=function(x) { log ('original.popupImage, x.article='+x.article+', x.navpop.idNumber='+x.navpop.idNumber); return imageHTML(x.article, x.navpop.idNumber); }; pg.structures.original.popupRedirTitle=pg.structures.original.popupTitle; pg.structures.original.popupRedirTopLinks=pg.structures.original.popupTopLinks; function copyStructure(oldStructure, newStructure) { pg.structures[newStructure]={}; for (var prop in pg.structures[oldStructure]) { pg.structures[newStructure][prop]=pg.structures[oldStructure][prop]; } } copyStructure('original', 'nostalgia'); pg.structures.nostalgia.popupTopLinks=function(x) { var str=''; str += '<b><<mainlink|shortcut= >></b>'; // user links // contribs - log - count - email - block // count only if applicable; block only if popupAdminLinks str += 'if(user){<br><<contribs|shortcut=c>>'; str+='if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}'; // editing links // talkpage -> edit|new - history - un|watch - article|edit // other page -> edit - history - un|watch - talk|edit|new var editstr='<<edit|shortcut=e>>'; var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'; var historystr='<<history|shortcut=h>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; str += '<br>if(talk){' + editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + // not a talk page editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}'; // misc links str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>'; str += 'if(admin){<br>}else{*}<<move|shortcut=m>>'; // admin links str += 'if(admin){*<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>}'; return navlinkStringToHTML(str, x.article, x.params); }; pg.structures.nostalgia.popupRedirTopLinks=pg.structures.nostalgia.popupTopLinks; /** -- fancy -- **/ copyStructure('original', 'fancy'); pg.structures.fancy.popupTitle=function (x) { return navlinkStringToHTML('<font size=+0><<mainlink>></font>',x.article,x.params); }; pg.structures.fancy.popupTopLinks=function(x) { var hist='<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>|<<editors|shortcut=E|eds>>'; var watch='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; var move='<<move|shortcut=m|move>>'; return navlinkStringToHTML('if(talk){' + '<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' + hist + '*' + '<<article|shortcut=a>>|<<editArticle|edit>>' + '*' + watch + '*' + move + '}else{<<edit|shortcut=e>>*' + hist + '*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' + '*' + watch + '*' + move+'}<br>', x.article, x.params); }; pg.structures.fancy.popupOtherLinks=function(x) { var admin='<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*<<undelete|undeleteShort>>|<<delete|shortcut=d|del>>'; var user='<<contribs|shortcut=c>>if(wikimedia){|<<count|shortcut=#|#>>}'; user+='if(ipuser){|<<arin>>}else{*<<email|shortcut=E|'+ popupString('email')+'>>}if(admin){*<<block|shortcut=b>>}'; var normal='<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>'; return navlinkStringToHTML('<br>if(user){' + user + '*}if(admin){'+admin+'if(user){<br>}else{*}}' + normal, x.article, x.params); }; pg.structures.fancy.popupRedirTitle=pg.structures.fancy.popupTitle; pg.structures.fancy.popupRedirTopLinks=pg.structures.fancy.popupTopLinks; pg.structures.fancy.popupRedirOtherLinks=pg.structures.fancy.popupOtherLinks; /** -- fancy2 -- **/ // hack for User:MacGyverMagic copyStructure('fancy', 'fancy2'); pg.structures.fancy2.popupTopLinks=function(x) { // hack out the <br> at the end and put one at the beginning return '<br>'+pg.structures.fancy.popupTopLinks(x).replace(RegExp('<br>$','i'),''); }; pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title return ['popupError', 'popupImage', 'popupTitle', 'popupUserData', 'popupData', 'popupTopLinks', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; /** -- menus -- **/ copyStructure('original', 'menus'); pg.structures.menus.popupLayout=function () { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupUserData', 'popupData', 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; pg.structures.menus.popupTopLinks = function (x, shorter) { // FIXME maybe this stuff should be cached var s=[]; var dropdiv='<div class="popup_drop">'; var enddiv='</div>'; var hist='<<history|shortcut=h>>'; if (!shorter) { hist = '<menurow>' + hist + '|<<historyfeed|rss>>|<<editors|shortcut=E>></menurow>'; } var lastedit='<<lastEdit|shortcut=/|show last edit>>'; var thank='if(diff){<<thank|send thanks>>}'; var jsHistory='<<lastContrib|last set of edits>><<sinceMe|changes since mine>>'; var linkshere='<<whatLinksHere|shortcut=l|what links here>>'; var related='<<relatedChanges|shortcut=r|related changes>>'; var search='<menurow><<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}' + '|<<google|shortcut=G|web>></menurow>'; var watch='<menurow><<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>></menurow>'; var protect='<menurow><<unprotect|unprotectShort>>|' + '<<protect|shortcut=p>>|<<protectlog|log>></menurow>'; var del='<menurow><<undelete|undeleteShort>>|<<delete|shortcut=d>>|' + '<<deletelog|log>></menurow>'; var move='<<move|shortcut=m|move page>>'; var nullPurge='<menurow><<nullEdit|shortcut=n|null edit>>|<<purge|shortcut=P>></menurow>'; var viewOptions='<menurow><<view|shortcut=v>>|<<render|shortcut=S>>|<<raw>></menurow>'; var editRow='if(oldid){' + '<menurow><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></menurow>' + '<menurow><<revert|shortcut=v>>|<<undo>></menurow>' + '}else{<<edit|shortcut=e>>}'; var markPatrolled='if(rcid){<<markpatrolled|mark patrolled>>}'; var newTopic='if(talk){<<new|shortcut=+|new topic>>}'; var protectDelete='if(admin){' + protect + del + '}'; if (getValueOf('popupActionsMenu')) { s.push( '<<mainlink>>*' + dropdiv + menuTitle('actions')); } else { s.push( dropdiv + '<<mainlink>>'); } s.push( '<menu>'); s.push( editRow + markPatrolled + newTopic + hist + lastedit + thank ); if (!shorter) { s.push(jsHistory); } s.push( move + linkshere + related); if (!shorter) { s.push(nullPurge + search); } if (!shorter) { s.push(viewOptions); } s.push('<hr />' + watch + protectDelete); s.push('<hr />' + 'if(talk){<<article|shortcut=a|view article>><<editArticle|edit article>>}' + 'else{<<talk|shortcut=t|talk page>><<editTalk|edit talk>>' + '<<newTalk|shortcut=+|new topic>>}</menu>' + enddiv); // user menu starts here var email='<<email|shortcut=E|email user>>'; var contribs= 'if(wikimedia){<menurow>}<<contribs|shortcut=c|contributions>>if(wikimedia){</menurow>}' + 'if(admin){<menurow><<deletedContribs>></menurow>}'; s.push('if(user){*' + dropdiv + menuTitle('user')); s.push('<menu>'); s.push('<menurow><<userPage|shortcut=u|user&nbsp;page>>|<<userSpace|space>></menurow>'); s.push('<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' + '<<newUserTalk|shortcut=+|leave comment>>'); if(!shorter) { s.push( 'if(ipuser){<<arin>>}else{' + email + '}' ); } else { s.push( 'if(ipuser){}else{' + email + '}' ); } s.push('<hr />' + contribs + '<<userlog|shortcut=L|user log>>'); s.push('if(wikimedia){<<count|shortcut=#|edit counter>>}'); s.push('if(admin){<menurow><<unblock|unblockShort>>|<<block|shortcut=b|block user>></menurow>}'); s.push('<<blocklog|shortcut=B|block log>>'); s.push('</menu>' + enddiv + '}'); // popups menu starts here if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */) { x.navpop.hasPopupMenu=true; s.push('*' + dropdiv + menuTitle('popupsMenu') + '<menu>'); s.push('<<togglePreviews|toggle previews>>'); s.push('<<purgePopups|reset>>'); s.push('<<disablePopups|disable>>'); s.push('</menu>'+enddiv); } return navlinkStringToHTML(s.join(''), x.article, x.params); }; function menuTitle(s) { return '<a href="#" noPopup=1>' + popupString(s) + '</a>'; } pg.structures.menus.popupRedirTitle=pg.structures.menus.popupTitle; pg.structures.menus.popupRedirTopLinks=pg.structures.menus.popupTopLinks; copyStructure('menus', 'shortmenus'); pg.structures.shortmenus.popupTopLinks=function(x) { return pg.structures.menus.popupTopLinks(x,true); }; pg.structures.shortmenus.popupRedirTopLinks=pg.structures.shortmenus.popupTopLinks; //</NOLITE> pg.structures.lite={}; pg.structures.lite.popupLayout=function () { return ['popupTitle', 'popupPreview' ]; }; pg.structures.lite.popupTitle=function (x) { log (x.article + ': structures.lite.popupTitle'); //return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params); return '<div><span class="popup_mainlink"><b>' + x.article.toString() + '</b></span></div>'; }; // ENDFILE: structures.js // STARTFILE: autoedit.js //<NOLITE> function substitute(data,cmdBody) { // alert('sub\nfrom: '+cmdBody.from+'\nto: '+cmdBody.to+'\nflags: '+cmdBody.flags); var fromRe=RegExp(cmdBody.from, cmdBody.flags); return data.replace(fromRe, cmdBody.to); } function execCmds(data, cmdList) { for (var i=0; i<cmdList.length; ++i) { data=cmdList[i].action(data, cmdList[i]); } return data; } function parseCmd(str) { // returns a list of commands if (!str.length) { return []; } var p=false; switch (str.charAt(0)) { case 's': p=parseSubstitute(str); break; default: return false; } if (p) { return [p].concat(parseCmd(p.remainder)); } return false; } function unEscape(str, sep) { return str.split('\\\\').join('\\').split('\\'+sep).join(sep).split('\\n').join('\n'); } function parseSubstitute(str) { // takes a string like s/a/b/flags;othercmds and parses it var from,to,flags,tmp; if (str.length<4) { return false; } var sep=str.charAt(1); str=str.substring(2); tmp=skipOver(str,sep); if (tmp) { from=tmp.segment; str=tmp.remainder; } else { return false; } tmp=skipOver(str,sep); if (tmp) { to=tmp.segment; str=tmp.remainder; } else { return false; } flags=''; if (str.length) { tmp=skipOver(str,';') || skipToEnd(str, ';'); if (tmp) {flags=tmp.segment; str=tmp.remainder; } } return {action: substitute, from: from, to: to, flags: flags, remainder: str}; } function skipOver(str,sep) { var endSegment=findNext(str,sep); if (endSegment<0) { return false; } var segment=unEscape(str.substring(0,endSegment), sep); return {segment: segment, remainder: str.substring(endSegment+1)}; } /*eslint-disable*/ function skipToEnd(str,sep) { return {segment: str, remainder: ''}; } /*eslint-enable */ function findNext(str, ch) { for (var i=0; i<str.length; ++i) { if (str.charAt(i)=='\\') { i+=2; } if (str.charAt(i)==ch) { return i; } } return -1; } function setCheckbox(param, box) { var val=mw.util.getParamValue(param); if (val) { switch (val) { case '1': case 'yes': case 'true': box.checked=true; break; case '0': case 'no': case 'false': box.checked=false; } } } function autoEdit() { setupPopups( function () { if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version') ) { return false; } if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken')===autoClickToken()) { pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action')); } if (!document.editform) { return false; } if (autoEdit.alreadyRan) { return false; } autoEdit.alreadyRan=true; var cmdString=mw.util.getParamValue('autoedit'); if (cmdString) { try { var editbox=document.editform.wpTextbox1; var cmdList=parseCmd(cmdString); var input=editbox.value; var output=execCmds(input, cmdList); editbox.value=output; } catch (dang) { return; } // wikEd user script compatibility if (typeof(wikEdUseWikEd) != 'undefined') { if (wikEdUseWikEd === true) { WikEdUpdateFrame(); } } } setCheckbox('autominor', document.editform.wpMinoredit); setCheckbox('autowatch', document.editform.wpWatchthis); var rvid = mw.util.getParamValue('autorv'); if (rvid) { var url=pg.wiki.apiwikibase + '?action=query&format=json&formatversion=2&prop=revisions&revids='+rvid; startDownload(url, null, autoEdit2); } else { autoEdit2(); } } ); } function autoEdit2(d) { var summary=mw.util.getParamValue('autosummary'); var summaryprompt=mw.util.getParamValue('autosummaryprompt'); var summarynotice=''; if (d && d.data && mw.util.getParamValue('autorv')) { var s = getRvSummary(summary, d.data); if (s === false) { summaryprompt=true; summarynotice=popupString('Failed to get revision information, please edit manually.\n\n'); summary = simplePrintf(summary, [mw.util.getParamValue('autorv'), '(unknown)', '(unknown)']); } else { summary = s; } } if (summaryprompt) { var txt= summarynotice + popupString('Enter a non-empty edit summary or press cancel to abort'); var response=prompt(txt, summary); if (response) { summary=response; } else { return; } } if (summary) { document.editform.wpSummary.value=summary; } // Attempt to avoid possible premature clicking of the save button // (maybe delays in updates to the DOM are to blame?? or a red herring) setTimeout(autoEdit3, 100); } function autoClickToken() { return mw.user.sessionId(); } function autoEdit3() { if( mw.util.getParamValue('actoken') != autoClickToken()) { return; } var btn=mw.util.getParamValue('autoclick'); if (btn) { if (document.editform && document.editform[btn]) { var button=document.editform[btn]; var msg=tprintf('The %s button has been automatically clicked. Please wait for the next page to load.', [ button.value ]); bannerMessage(msg); document.title='('+document.title+')'; button.click(); } else { alert(tprintf('Could not find button %s. Please check the settings in your javascript file.', [ btn ])); } } } function bannerMessage(s) { var headings=document.getElementsByTagName('h1'); if (headings) { var div=document.createElement('div'); div.innerHTML='<font size=+1><b>' + s + '</b></font>'; headings[0].parentNode.insertBefore(div, headings[0]); } } function getRvSummary(template, json) { try { var o=getJsObj(json); var edit = anyChild(o.query.pages).revisions[0]; var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, ''); return simplePrintf(template, [edit.revid, timestamp, edit.userhidden ? '(hidden)' : edit.user ]); } catch (badness) { return false; } } //</NOLITE> // ENDFILE: autoedit.js // STARTFILE: downloader.js /** @fileoverview {@link Downloader}, a xmlhttprequest wrapper, and helper functions. */ /** Creates a new Downloader @constructor @class The Downloader class. Create a new instance of this class to download stuff. @param {String} url The url to download. This can be omitted and supplied later. */ function Downloader(url) { if (typeof XMLHttpRequest!='undefined') { this.http = new XMLHttpRequest(); } /** The url to download @type String */ this.url = url; /** A universally unique ID number @type integer */ this.id=null; /** Modification date, to be culled from the incoming headers @type Date @private */ this.lastModified = null; /** What to do when the download completes successfully @type Function @private */ this.callbackFunction = null; /** What to do on failure @type Function @private */ this.onFailure = null; /** Flag set on <code>abort</code> @type boolean */ this.aborted = false; /** HTTP method. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for possibilities. @type String */ this.method='GET'; /** Async flag. @type boolean */ this.async=true; } new Downloader(); /** Submits the http request. */ Downloader.prototype.send = function (x) { if (!this.http) { return null; } return this.http.send(x); }; /** Aborts the download, setting the <code>aborted</code> field to true. */ Downloader.prototype.abort = function () { if (!this.http) { return null; } this.aborted=true; return this.http.abort(); }; /** Returns the downloaded data. */ Downloader.prototype.getData = function () {if (!this.http) { return null; } return this.http.responseText;}; /** Prepares the download. */ Downloader.prototype.setTarget = function () { if (!this.http) { return null; } this.http.open(this.method, this.url, this.async); this.http.setRequestHeader( 'Api-User-Agent', pg.misc.userAgent ); }; /** Gets the state of the download. */ Downloader.prototype.getReadyState=function () {if (!this.http) { return null; } return this.http.readyState;}; pg.misc.downloadsInProgress = { }; /** Starts the download. Note that setTarget {@link Downloader#setTarget} must be run first */ Downloader.prototype.start=function () { if (!this.http) { return; } pg.misc.downloadsInProgress[this.id] = this; this.http.send(null); }; /** Gets the 'Last-Modified' date from the download headers. Should be run after the download completes. Returns <code>null</code> on failure. @return {Date} */ Downloader.prototype.getLastModifiedDate=function () { if(!this.http) { return null; } var lastmod=null; try { lastmod=this.http.getResponseHeader('Last-Modified'); } catch (err) {} if (lastmod) { return new Date(lastmod); } return null; }; /** Sets the callback function. @param {Function} f callback function, called as <code>f(this)</code> on success */ Downloader.prototype.setCallback = function (f) { if(!this.http) { return; } this.http.onreadystatechange = f; }; Downloader.prototype.getStatus = function() { if (!this.http) { return null; } return this.http.status; }; ////////////////////////////////////////////////// // helper functions /** Creates a new {@link Downloader} and prepares it for action. @param {String} url The url to download @param {integer} id The ID of the {@link Downloader} object @param {Function} callback The callback function invoked on success @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser */ function newDownload(url, id, callback, onfailure) { var d=new Downloader(url); if (!d.http) { return 'ohdear'; } d.id=id; d.setTarget(); if (!onfailure) { onfailure=2; } var f = function () { if (d.getReadyState() == 4) { delete pg.misc.downloadsInProgress[this.id]; try { if ( d.getStatus() == 200 ) { d.data=d.getData(); d.lastModified=d.getLastModifiedDate(); callback(d); } else if (typeof onfailure == typeof 1) { if (onfailure > 0) { // retry newDownload(url, id, callback, onfailure - 1); } } else if (typeof onfailure === 'function') { onfailure(d,url,id,callback); } } catch (somerr) { /* ignore it */ } } }; d.setCallback(f); return d; } /** Simulates a download from cached data. The supplied data is put into a {@link Downloader} as if it had downloaded it. @param {String} url The url. @param {integer} id The ID. @param {Function} callback The callback, which is invoked immediately as <code>callback(d)</code>, where <code>d</code> is the new {@link Downloader}. @param {String} data The (cached) data. @param {Date} lastModified The (cached) last modified date. */ function fakeDownload(url, id, callback, data, lastModified, owner) { var d=newDownload(url,callback); d.owner=owner; d.id=id; d.data=data; d.lastModified=lastModified; return callback(d); } /** Starts a download. @param {String} url The url to download @param {integer} id The ID of the {@link Downloader} object @param {Function} callback The callback function invoked on success @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser */ function startDownload(url, id, callback) { var d=newDownload(url, id, callback); if (typeof d == typeof '' ) { return d; } d.start(); return d; } /** Aborts all downloads which have been started. */ function abortAllDownloads() { for ( var x in pg.misc.downloadsInProgress ) { try { pg.misc.downloadsInProgress[x].aborted=true; pg.misc.downloadsInProgress[x].abort(); delete pg.misc.downloadsInProgress[x]; } catch (e) {} } } // ENDFILE: downloader.js // STARTFILE: livepreview.js // TODO: location is often not correct (eg relative links in previews) // NOTE: removed md5 and image and math parsing. was broken, lots of bytes. /** * InstaView - a Mediawiki to HTML converter in JavaScript * Version 0.6.1 * Copyright (C) Pedro Fayolle 2005-2006 * https://en.wikipedia.org/wiki/User:Pilaf * Distributed under the BSD license * * Changelog: * * 0.6.1 * - Fixed problem caused by \r characters * - Improved inline formatting parser * * 0.6 * - Changed name to InstaView * - Some major code reorganizations and factored out some common functions * - Handled conversion of relative links (i.e. [[/foo]]) * - Fixed misrendering of adjacent definition list items * - Fixed bug in table headings handling * - Changed date format in signatures to reflect Mediawiki's * - Fixed handling of [[:Image:...]] * - Updated MD5 function (hopefully it will work with UTF-8) * - Fixed bug in handling of links inside images * * To do: * - Better support for math tags * - Full support for <nowiki> * - Parser-based (as opposed to RegExp-based) inline wikicode handling (make it one-pass and bullet-proof) * - Support for templates (through AJAX) * - Support for coloured links (AJAX) */ var Insta = {}; function setupLivePreview() { // options Insta.conf = { baseUrl: '', user: {}, wiki: { lang: pg.wiki.lang, interwiki: pg.wiki.interwiki, default_thumb_width: 180 }, paths: { articles: pg.wiki.articlePath + '/', // Only used for Insta previews with images. (not in popups) math: '/math/', images: '//upload.wikimedia.org/wikipedia/en/', // FIXME getImageUrlStart(pg.wiki.hostname), images_fallback: '//upload.wikimedia.org/wikipedia/commons/', }, locale: { user: mw.config.get('wgFormattedNamespaces')[pg.nsUserId], image: mw.config.get('wgFormattedNamespaces')[pg.nsImageId], category: mw.config.get('wgFormattedNamespaces')[pg.nsCategoryId], // shouldn't be used in popup previews, i think months: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'] } }; // options with default values or backreferences Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian'; Insta.conf.user.signature = '[['+Insta.conf.locale.user+':'+Insta.conf.user.name+'|'+Insta.conf.user.name+']]'; //Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/'; // define constants Insta.BLOCK_IMAGE = new RegExp('^\\[\\[(?:File|Image|'+Insta.conf.locale.image+ '):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)', 'i'); } Insta.dump = function(from, to) { if (typeof from == 'string') { from = document.getElementById(from); } if (typeof to == 'string') { to = document.getElementById(to); } to.innerHTML = this.convert(from.value); }; Insta.convert = function(wiki) { var ll = (typeof wiki == 'string')? wiki.replace(/\r/g,'').split(/\n/): wiki, // lines of wikicode o = '', // output p = 0, // para flag $r; // result of passing a regexp to $() // some shorthands function remain() { return ll.length; } function sh() { return ll.shift(); } // shift function ps(s) { o += s; } // push // similar to C's printf, uses ? as placeholders, ?? to escape question marks function f() { var i=1, a=arguments, f=a[0], o='', c, p; for (; i<a.length; i++) { if ((p=f.indexOf('?'))+1) { // allow character escaping i -= c = f.charAt(p+1)=='?' ? 1 : 0; o += f.substring(0,p) + (c ? '?' : a[i]); f = f.substr(p+1+c); } else { break; } } return o+f; } function html_entities(s) { return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); } // Wiki text parsing to html is a nightmare. // The below functions deliberately don't escape the ampersand since this would make it more difficult, // and we don't absolutely need to for how we need it. // This means that any unescaped ampersands in wikitext will remain unescaped and can cause invalid HTML. // Browsers should all be able to handle it though. // We also escape significant wikimarkup characters to prevent further matching on the processed text function htmlescape_text(s) { return s.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/:/g,"&#58;").replace(/\[/g,"&#91;").replace(/]/g,"&#93;"); } function htmlescape_attr(s) { return htmlescape_text(s).replace(/'/g,"&#39;").replace(/"/g,"&quot;"); } // return the first non matching character position between two strings function str_imatch(a, b) { for (var i=0, l=Math.min(a.length, b.length); i<l; i++) { if (a.charAt(i)!=b.charAt(i)) { break; } } return i; } // compare current line against a string or regexp // if passed a string it will compare only the first string.length characters // if passed a regexp the result is stored in $r function $(c) { return (typeof c == 'string') ? (ll[0].substr(0,c.length)==c) : ($r = ll[0].match(c)); } function $$(c) { return ll[0]==c; } // compare current line against a string function _(p) { return ll[0].charAt(p); } // return char at pos p function endl(s) { ps(s); sh(); } function parse_list() { var prev=''; while (remain() && $(/^([*#:;]+)(.*)$/)) { var l_match = $r; sh(); var ipos = str_imatch(prev, l_match[1]); // close uncontinued lists for (var prevPos=prev.length-1; prevPos >= ipos; prevPos--) { var pi = prev.charAt(prevPos); if (pi=='*') { ps('</ul>'); } else if (pi=='#') { ps('</ol>'); } // close a dl only if the new item is not a dl item (:, ; or empty) else if($.inArray(l_match[1].charAt(prevPos), ['','*','#'])) { ps('</dl>'); } } // open new lists for (var matchPos=ipos; matchPos<l_match[1].length; matchPos++) { var li = l_match[1].charAt(matchPos); if (li=='*') { ps('<ul>'); } else if (li=='#') { ps('<ol>'); } // open a new dl only if the prev item is not a dl item (:, ; or empty) else if ($.inArray(prev.charAt(matchPos), ['','*','#'])) { ps('<dl>'); } } switch (l_match[1].charAt(l_match[1].length-1)) { case '*': case '#': ps('<li>' + parse_inline_nowiki(l_match[2])); break; case ';': ps('<dt>'); var dt_match = l_match[2].match(/(.*?)(:.*?)$/); // handle ;dt :dd format if (dt_match) { ps(parse_inline_nowiki(dt_match[1])); ll.unshift(dt_match[2]); } else ps(parse_inline_nowiki(l_match[2])); break; case ':': ps('<dd>' + parse_inline_nowiki(l_match[2])); } prev=l_match[1]; } // close remaining lists for (var i=prev.length-1; i>=0; i--) { ps(f('</?>', (prev.charAt(i)=='*')? 'ul': ((prev.charAt(i)=='#')? 'ol': 'dl'))); } } function parse_table() { endl(f('<table>', $(/^\{\|( .*)$/)? $r[1]: '')); for (;remain();) if ($('|')) switch (_(1)) { case '}': endl('</table>'); return; case '-': endl(f('<tr>', $(/\|-*(.*)/)[1])); break; default: parse_table_data(); } else if ($('!')) { parse_table_data(); } else { sh(); } } function parse_table_data() { var td_line, match_i; // 1: "|+", '|' or '+' // 2: ?? // 3: attributes ?? // TODO: finish commenting this regexp var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/); if (td_match[1] == '|+') ps('<caption'); else ps('<t' + ((td_match[1]=='|')?'d':'h')); if (typeof td_match[3] != 'undefined') { //ps(' ' + td_match[3]) match_i = 4; } else match_i = 2; ps('>'); if (td_match[1] != '|+') { // use || or !! as a cell separator depending on context // NOTE: when split() is passed a regexp make sure to use non-capturing brackets td_line = td_match[match_i].split((td_match[1] == '|')? '||': /(?:\|\||!!)/); ps(parse_inline_nowiki(td_line.shift())); while (td_line.length) ll.unshift(td_match[1] + td_line.pop()); } else ps(td_match[match_i]); var tc = 0, td = []; while (remain()) { td.push(sh()); if ($('|')) { if (!tc) break; // we're at the outer-most level (no nested tables), skip to td parse else if (_(1)=='}') tc--; } else if (!tc && $('!')) break; else if ($('{|')) tc++; } if (td.length) ps(Insta.convert(td)); } function parse_pre() { ps('<pre>'); do { endl(parse_inline_nowiki(ll[0].substring(1)) + "\n"); } while (remain() && $(' ')); ps('</pre>'); } function parse_block_image() { ps(parse_image(sh())); } function parse_image(str) { //<NOLITE> // get what's in between "[[Image:" and "]]" var tag = str.substring(str.indexOf(':') + 1, str.length - 2); /* eslint-disable no-unused-vars */ var width; var attr = [], filename, caption = ''; var thumb=0, frame=0, center=0; var align=''; /* eslint-enable no-unused-vars */ if (tag.match(/\|/)) { // manage nested links var nesting = 0; var last_attr; for (var i = tag.length-1; i > 0; i--) { if (tag.charAt(i) == '|' && !nesting) { last_attr = tag.substr(i+1); tag = tag.substring(0, i); break; } else switch (tag.substr(i-1, 2)) { case ']]': nesting++; i--; break; case '[[': nesting--; i--; } } attr = tag.split(/\s*\|\s*/); attr.push(last_attr); filename = attr.shift(); var w_match; for (;attr.length; attr.shift()) { w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/); if (w_match) width = w_match[1]; else switch(attr[0]) { case 'thumb': case 'thumbnail': thumb=true; frame=true; break; case 'frame': frame=true; break; case 'none': case 'right': case 'left': center=false; align=attr[0]; break; case 'center': center=true; align='none'; break; default: if (attr.length == 1) caption = attr[0]; } } } else filename = tag; return ''; //</NOLITE> } function parse_inline_nowiki(str) { var start, lastend=0; var substart=0, nestlev=0, open, close, subloop; var html=''; while (-1 != (start = str.indexOf('<nowiki>', substart))) { html += parse_inline_wiki(str.substring(lastend, start)); start += 8; substart = start; subloop = true; do { open = str.indexOf('<nowiki>', substart); close = str.indexOf('</nowiki>', substart); if (close<=open || open==-1) { if (close==-1) { return html + html_entities(str.substr(start)); } substart = close+9; if (nestlev) { nestlev--; } else { lastend = substart; html += html_entities(str.substring(start, lastend-9)); subloop = false; } } else { substart = open+8; nestlev++; } } while (subloop); } return html + parse_inline_wiki(str.substr(lastend)); } function parse_inline_images(str) { //<NOLITE> var start, substart=0, nestlev=0; var loop, close, open, wiki, html; while (-1 != (start=str.indexOf('[[', substart))) { if(str.substr(start+2).match(RegExp('^(Image|File|' + Insta.conf.locale.image + '):','i'))) { loop=true; substart=start; do { substart+=2; close=str.indexOf(']]',substart); open=str.indexOf('[[',substart); if (close<=open||open==-1) { if (close==-1) return str; substart=close; if (nestlev) { nestlev--; } else { wiki=str.substring(start,close+2); html=parse_image(wiki); str=str.replace(wiki,html); substart=start+html.length; loop=false; } } else { substart=open; nestlev++; } } while (loop); } else break; } //</NOLITE> return str; } // the output of this function doesn't respect the FILO structure of HTML // but since most browsers can handle it I'll save myself the hassle function parse_inline_formatting(str) { var em,st,i,li,o=''; while ((i=str.indexOf("''",li))+1) { o += str.substring(li,i); li=i+2; if (str.charAt(i+2)=="'") { li++; st=!st; o+=st?'<strong>':'</strong>'; } else { em=!em; o+=em?'<em>':'</em>'; } } return o+str.substr(li); } function parse_inline_wiki(str) { str = parse_inline_images(str); str = parse_inline_formatting(str); // math str = str.replace(/<(?:)math>(.*?)<\/math>/ig, ''); // Build a Mediawiki-formatted date string var date = new Date(); var minutes = date.getUTCMinutes(); if (minutes < 10) minutes = '0' + minutes; date = f("?:?, ? ? ? (UTC)", date.getUTCHours(), minutes, date.getUTCDate(), Insta.conf.locale.months[date.getUTCMonth()], date.getUTCFullYear()); // text formatting return str. // signatures replace(/~{5}(?!~)/g, date). replace(/~{4}(?!~)/g, Insta.conf.user.name+' '+date). replace(/~{3}(?!~)/g, Insta.conf.user.name). // [[:Category:...]], [[:Image:...]], etc... replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):[^|]*?)\\]\\](\\w*)','gi'), function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2));}). // remove straight category and interwiki tags replace(RegExp('\\[\\[(?:'+Insta.conf.locale.category+'|'+Insta.conf.wiki.interwiki+'):.*?\\]\\]','gi'),''). // [[:Category:...|Links]], [[:Image:...|Links]], etc... replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):.*?)\\|([^\\]]+?)\\]\\](\\w*)','gi'), function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));}). // [[/Relative links]] replace(/\[\[(\/[^|]*?)\]\]/g, function($0,$1){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($1)); }). // [[/Replaced|Relative links]] replace(/\[\[(\/.*?)\|(.+?)\]\]/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($2)); }). // [[Common links]] replace(/\[\[([^[|]*?)\]\](\w*)/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2)); }). // [[Replaced|Links]] replace(/\[\[([^[]*?)\|([^\]]+?)\]\](\w*)/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3)); }). // [[Stripped:Namespace|Namespace]] replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1) + htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($2)); }). // External links replace(/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g, function($0,$1,$2,$3,$4){return f("<a class='external' href='?:?'>?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($4)); }). replace(/\[http:\/\/(.*?)\]/g, function($0,$1){return f("<a class='external' href='http://?'>[#]</a>", htmlescape_attr($1)); }). replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, function($0,$1,$2,$3){return f("<a class='external' href='?:?'>?:?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($1), htmlescape_text($2) + htmlescape_text($3)); }). replace(/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*[^.,!?;: $])/g, function($0,$1,$2,$3,$4){return f("?<a class='external' href='?:?'>?:?</a>", htmlescape_text($1), htmlescape_attr($2), htmlescape_attr($3) + htmlescape_attr($4), htmlescape_text($2), htmlescape_text($3) + htmlescape_text($4)); }). replace('__NOTOC__',''). replace('__NOEDITSECTION__',''); } // begin parsing for (;remain();) if ($(/^(={1,6})(.*)\1(.*)$/)) { p=0; endl(f('<h?>?</h?>?', $r[1].length, parse_inline_nowiki($r[2]), $r[1].length, $r[3])); } else if ($(/^[*#:;]/)) { p=0; parse_list(); } else if ($(' ')) { p=0; parse_pre(); } else if ($('{|')) { p=0; parse_table(); } else if ($(/^----+$/)) { p=0; endl('<hr />'); } else if ($(Insta.BLOCK_IMAGE)) { p=0; parse_block_image(); } else { // handle paragraphs if ($$('')) { p = (remain()>1 && ll[1]===('')); if (p) endl('<p><br>'); } else { if(!p) { ps('<p>'); p=1; } ps(parse_inline_nowiki(ll[0]) + ' '); } sh(); } return o; }; function wiki2html(txt,baseurl) { Insta.conf.baseUrl=baseurl; return Insta.convert(txt); } // ENDFILE: livepreview.js // STARTFILE: pageinfo.js //<NOLITE> function popupFilterPageSize(data) { return formatBytes(data.length); } function popupFilterCountLinks(data) { var num=countLinks(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('wikiLinks'):popupString('wikiLink')); } function popupFilterCountImages(data) { var num=countImages(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('images'):popupString('image')); } function popupFilterCountCategories(data) { var num=countCategories(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('categories'):popupString('category')); } function popupFilterLastModified(data,download) { var lastmod=download.lastModified; var now=new Date(); var age=now-lastmod; if (lastmod && getValueOf('popupLastModified')) { return (tprintf('%s old', [formatAge(age)])).replace(RegExp(' ','g'), '&nbsp;'); } return ''; } function formatAge(age) { // coerce into a number var a=0+age, aa=a; var seclen = 1000; var minlen = 60*seclen; var hourlen = 60*minlen; var daylen = 24*hourlen; var weeklen = 7*daylen; var numweeks = (a-a%weeklen)/weeklen; a = a-numweeks*weeklen; var sweeks = addunit(numweeks, 'week'); var numdays = (a-a%daylen)/daylen; a = a-numdays*daylen; var sdays = addunit(numdays, 'day'); var numhours = (a-a%hourlen)/hourlen; a = a-numhours*hourlen; var shours = addunit(numhours,'hour'); var nummins = (a-a%minlen)/minlen; a = a-nummins*minlen; var smins = addunit(nummins, 'minute'); var numsecs = (a-a%seclen)/seclen; a = a-numsecs*seclen; var ssecs = addunit(numsecs, 'second'); if (aa > 4*weeklen) { return sweeks; } if (aa > weeklen) { return sweeks + ' ' + sdays; } if (aa > daylen) { return sdays + ' ' + shours; } if (aa > 6*hourlen) { return shours; } if (aa > hourlen) { return shours + ' ' + smins; } if (aa > 10*minlen) { return smins; } if (aa > minlen) { return smins + ' ' + ssecs; } return ssecs; } function addunit(num,str) { return '' + num + ' ' + ((num!=1) ? popupString(str+'s') : popupString(str)) ;} function runPopupFilters(list, data, download) { var ret=[]; for (var i=0; i<list.length; ++i) { if (list[i] && typeof list[i] == 'function') { var s=list[i](data, download, download.owner.article); if (s) { ret.push(s); } } } return ret; } function getPageInfo(data, download) { if (!data || data.length === 0) { return popupString('Empty page'); } var popupFilters=getValueOf('popupFilters') || []; var extraPopupFilters = getValueOf('extraPopupFilters') || []; var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download); var pageInfo=pageInfoArray.join(', '); if (pageInfo !== '' ) { pageInfo = upcaseFirst(pageInfo); } return pageInfo; } // this could be improved! function countLinks(wikiText) { return wikiText.split('[[').length - 1; } // if N = # matches, n = # brackets, then // String.parenSplit(regex) intersperses the N+1 split elements // with Nn other elements. So total length is // L= N+1 + Nn = N(n+1)+1. So N=(L-1)/(n+1). function countImages(wikiText) { return (wikiText.parenSplit(pg.re.image).length - 1) / (pg.re.imageBracketCount + 1); } function countCategories(wikiText) { return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1); } function popupFilterStubDetect(data, download, article) { var counts=stubCount(data, article); if (counts.real) { return popupString('stub'); } if (counts.sect) { return popupString('section stub'); } return ''; } function popupFilterDisambigDetect(data, download, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return ''; } return (isDisambig(data, article)) ? popupString('disambig') : ''; } function formatBytes(num) { return (num > 949) ? (Math.round(num/100)/10+popupString('kB')) : (num +'&nbsp;' + popupString('bytes')) ; } //</NOLITE> // ENDFILE: pageinfo.js // STARTFILE: titles.js /** @fileoverview Defines the {@link Title} class, and associated crufty functions. <code>Title</code> deals with article titles and their various forms. {@link Stringwrapper} is the parent class of <code>Title</code>, which exists simply to make things a little neater. */ /** Creates a new Stringwrapper. @constructor @class the Stringwrapper class. This base class is not really useful on its own; it just wraps various common string operations. */ function Stringwrapper() { /** Wrapper for this.toString().indexOf() @param {String} x @type integer */ this.indexOf=function(x){return this.toString().indexOf(x);}; /** Returns this.value. @type String */ this.toString=function(){return this.value;}; /** Wrapper for {@link String#parenSplit} applied to this.toString() @param {RegExp} x @type Array */ this.parenSplit=function(x){return this.toString().parenSplit(x);}; /** Wrapper for this.toString().substring() @param {String} x @param {String} y (optional) @type String */ this.substring=function(x,y){ if (typeof y=='undefined') { return this.toString().substring(x); } return this.toString().substring(x,y); }; /** Wrapper for this.toString().split() @param {String} x @type Array */ this.split=function(x){return this.toString().split(x);}; /** Wrapper for this.toString().replace() @param {String} x @param {String} y @type String */ this.replace=function(x,y){ return this.toString().replace(x,y); }; } /** Creates a new <code>Title</code>. @constructor @class The Title class. Holds article titles and converts them into various forms. Also deals with anchors, by which we mean the bits of the article URL after a # character, representing locations within an article. @param {String} value The initial value to assign to the article. This must be the canonical title (see {@link Title#value}. Omit this in the constructor and use another function to set the title if this is unavailable. */ function Title(val) { /** The canonical article title. This must be in UTF-8 with no entities, escaping or nasties. Also, underscores should be replaced with spaces. @type String @private */ this.value=null; /** The canonical form of the anchor. This should be exactly as it appears in the URL, i.e. with the .C3.0A bits in. @type String */ this.anchor=''; this.setUtf(val); } Title.prototype=new Stringwrapper(); /** Returns the canonical representation of the article title, optionally without anchor. @param {boolean} omitAnchor @fixme Decide specs for anchor @return String The article title and the anchor. */ Title.prototype.toString=function(omitAnchor) { return this.value + ( (!omitAnchor && this.anchor) ? '#' + this.anchorString() : '' ); }; Title.prototype.anchorString=function() { if (!this.anchor) { return ''; } var split=this.anchor.parenSplit(/((?:[.][0-9A-F]{2})+)/); var len=split.length; for (var j=1; j<len; j+=2) { // FIXME s/decodeURI/decodeURIComponent/g ? split[j]=decodeURIComponent(split[j].split('.').join('%')).split('_').join(' '); } return split.join(''); }; Title.prototype.urlAnchor=function() { var split=this.anchor.parenSplit('/((?:[%][0-9A-F]{2})+)/'); var len=split.length; for (var j=1; j<len; j+=2) { split[j]=split[j].split('%').join('.'); } return split.join(''); }; Title.prototype.anchorFromUtf=function(str) { this.anchor=encodeURIComponent(str.split(' ').join('_')) .split('%3A').join(':').split("'").join('%27').split('%').join('.'); }; Title.fromURL=function(h) { return new Title().fromURL(h); }; Title.prototype.fromURL=function(h) { if (typeof h != 'string') { this.value=null; return this; } // NOTE : playing with decodeURI, encodeURI, escape, unescape, // we seem to be able to replicate the IE borked encoding // IE doesn't do this new-fangled utf-8 thing. // and it's worse than that. // IE seems to treat the query string differently to the rest of the url // the query is treated as bona-fide utf8, but the first bit of the url is pissed around with // we fix up & for all browsers, just in case. var splitted=h.split('?'); splitted[0]=splitted[0].split('&').join('%26'); h=splitted.join('?'); var contribs=pg.re.contribs.exec(h); if (contribs) { if (contribs[1]=='title=') { contribs[3]=contribs[3].split('+').join(' '); } var u=new Title(contribs[3]); this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + u.stripNamespace())); return this; } var email=pg.re.email.exec(h); if (email) { this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(email[3]).stripNamespace())); return this; } var backlinks=pg.re.backlinks.exec(h); if (backlinks) { this.setUtf(this.decodeNasties(new Title(backlinks[3]))); return this; } //A dummy title object for a Special:Diff link. var specialdiff=pg.re.specialdiff.exec(h); if (specialdiff) { this.setUtf(this.decodeNasties(new Title(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Diff'))); return this; } // no more special cases to check -- // hopefully it's not a disguised user-related or specially treated special page var m=pg.re.main.exec(h); if(m === null) { this.value=null; } else { var fromBotInterface = /[?](.+[&])?title=/.test(h); if (fromBotInterface) { m[2]=m[2].split('+').join('_'); } var extracted = m[2] + (m[3] ? '#' + m[3] : ''); if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) { // Fix Safari issue // Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3. this.setUtf(decodeURIComponent(unescape(extracted))); } else { this.setUtf(this.decodeNasties(extracted)); } } return this; }; Title.prototype.decodeNasties=function(txt) { var ret= this.decodeEscapes(decodeURI(txt)); ret = ret.replace(/[_ ]*$/, ''); return ret; }; Title.prototype.decodeEscapes=function(txt) { var split=txt.parenSplit(/((?:[%][0-9A-Fa-f]{2})+)/); var len=split.length; for (var i=1; i<len; i=i+2) { // FIXME is decodeURIComponent better? split[i]=unescape(split[i]); } return split.join(''); }; Title.fromAnchor=function(a) { return new Title().fromAnchor(a); }; Title.prototype.fromAnchor=function(a) { if (!a) { this.value=null; return this; } return this.fromURL(a.href); }; Title.fromWikiText=function(txt) { return new Title().fromWikiText(txt); }; Title.prototype.fromWikiText=function(txt) { // FIXME - testing needed txt=myDecodeURI(txt); this.setUtf(txt); return this; }; Title.prototype.hintValue=function(){ if(!this.value) { return ''; } return safeDecodeURI(this.value); }; //<NOLITE> Title.prototype.toUserName=function(withNs) { if (this.namespaceId() != pg.nsUserId && this.namespaceId() != pg.nsUsertalkId) { this.value=null; return; } this.value = (withNs ? mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' : '') + this.stripNamespace().split('/')[0]; }; Title.prototype.userName=function(withNs) { var t=(new Title(this.value)); t.toUserName(withNs); if (t.value) { return t; } return null; }; Title.prototype.toTalkPage=function() { // convert article to a talk page, or if we can't, return null // In other words: return null if this ALREADY IS a talk page // and return the corresponding talk page otherwise // // Per https://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces // * All discussion namespaces have odd-integer indices // * The discussion namespace index for a specific namespace with index n is n + 1 if (this.value === null) { return null; } var namespaceId = this.namespaceId(); if (namespaceId>=0 && namespaceId % 2 === 0) //non-special and subject namespace { var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId+1]; if (typeof localizedNamespace!=='undefined') { if (localizedNamespace === '') { this.value = this.stripNamespace(); } else { this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace(); } return this.value; } } this.value=null; return null; }; //</NOLITE> // Return canonical, localized namespace Title.prototype.namespace=function() { return mw.config.get('wgFormattedNamespaces')[this.namespaceId()]; }; Title.prototype.namespaceId=function() { var n=this.value.indexOf(':'); if (n<0) { return 0; } //mainspace var namespaceId = mw.config.get('wgNamespaceIds')[this.value.substring(0,n).split(' ').join('_').toLowerCase()]; if (typeof namespaceId=='undefined') return 0; //mainspace return namespaceId; }; //<NOLITE> Title.prototype.talkPage=function() { var t=new Title(this.value); t.toTalkPage(); if (t.value) { return t; } return null; }; Title.prototype.isTalkPage=function() { if (this.talkPage()===null) { return true; } return false; }; Title.prototype.toArticleFromTalkPage=function() { //largely copy/paste from toTalkPage above. if (this.value === null) { return null; } var namespaceId = this.namespaceId(); if (namespaceId >= 0 && namespaceId % 2 == 1) //non-special and talk namespace { var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId-1]; if (typeof localizedNamespace!=='undefined') { if (localizedNamespace === '') { this.value = this.stripNamespace(); } else { this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace(); } return this.value; } } this.value=null; return null; }; Title.prototype.articleFromTalkPage=function() { var t=new Title(this.value); t.toArticleFromTalkPage(); if (t.value) { return t; } return null; }; Title.prototype.articleFromTalkOrArticle=function() { var t=new Title(this.value); if ( t.toArticleFromTalkPage() ) { return t; } return this; }; Title.prototype.isIpUser=function() { return pg.re.ipUser.test(this.userName()); }; //</NOLITE> Title.prototype.stripNamespace=function(){ // returns a string, not a Title var n=this.value.indexOf(':'); if (n<0) { return this.value; } var namespaceId = this.namespaceId(); if (namespaceId === pg.nsMainspaceId) return this.value; return this.value.substring(n+1); }; Title.prototype.setUtf=function(value){ if (!value) { this.value=''; return; } var anch=value.indexOf('#'); if(anch < 0) { this.value=value.split('_').join(' '); this.anchor=''; return; } this.value=value.substring(0,anch).split('_').join(' '); this.anchor=value.substring(anch+1); this.ns=null; // wait until namespace() is called }; Title.prototype.setUrl=function(urlfrag) { var anch=urlfrag.indexOf('#'); this.value=safeDecodeURI(urlfrag.substring(0,anch)); this.anchor=this.value.substring(anch+1); }; Title.prototype.append=function(x){ this.setUtf(this.value + x); }; Title.prototype.urlString=function(x) { if(!x) { x={}; } var v=this.toString(true); if (!x.omitAnchor && this.anchor) { v+= '#' + this.urlAnchor(); } if (!x.keepSpaces) { v=v.split(' ').join('_'); } return encodeURI(v).split('&').join('%26').split('?').join('%3F').split('+').join('%2B'); }; Title.prototype.removeAnchor=function() { return new Title(this.toString(true)); }; Title.prototype.toUrl=function() { return pg.wiki.titlebase + this.urlString(); }; function parseParams(url) { var specialDiff = pg.re.specialdiff.exec(url); if (specialDiff) { var split= specialDiff[1].split('/'); if (split.length==1) return {oldid:split[0], diff: 'prev'}; else if (split.length==2) return {oldid: split[0], diff: split[1]}; } var ret={}; if (url.indexOf('?')==-1) { return ret; } url = url.split('#')[0]; var s=url.split('?').slice(1).join(); var t=s.split('&'); for (var i=0; i<t.length; ++i) { var z=t[i].split('='); z.push(null); ret[z[0]]=z[1]; } //Diff revision with no oldid is interpreted as a diff to the previous revision by MediaWiki if (ret.diff && typeof(ret.oldid)==='undefined') { ret.oldid = "prev"; } //Documentation seems to say something different, but oldid can also accept prev/next, and Echo is emitting such URLs. Simple fixup during parameter decoding: if (ret.oldid && (ret.oldid==='prev' || ret.oldid==='next' || ret.oldid==='cur')) { var helper = ret.diff; ret.diff = ret.oldid; ret.oldid = helper; } return ret; } // (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup) // (b) change spaces to underscores // (c) encodeURI (just the straight one, no pg.re.urlNoPopup) function myDecodeURI (str) { var ret; // FIXME decodeURIComponent?? try { ret=decodeURI(str.toString()); } catch (summat) { return str; } for (var i=0; i<pg.misc.decodeExtras.length; ++i) { var from=pg.misc.decodeExtras[i].from; var to=pg.misc.decodeExtras[i].to; ret=ret.split(from).join(to); } return ret; } function safeDecodeURI(str) { var ret=myDecodeURI(str); return ret || str; } /////////// // TESTS // /////////// //<NOLITE> function isDisambig(data, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; } return ! article.isTalkPage() && pg.re.disambig.test(data); } function stubCount(data, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; } var sectStub=0; var realStub=0; if (pg.re.stub.test(data)) { var s=data.parenSplit(pg.re.stub); for (var i=1; i<s.length; i=i+2) { if (s[i]) { ++sectStub; } else { ++realStub; } } } return { real: realStub, sect: sectStub }; } function isValidImageName(str){ // extend as needed... return ( str.indexOf('{') == -1 ); } function isInStrippableNamespace(article) { // Does the namespace allow subpages // Note, would be better if we had access to wgNamespacesWithSubpages return ( article.namespaceId() !== 0 ); } function isInMainNamespace(article) { return article.namespaceId() === 0; } function anchorContainsImage(a) { // iterate over children of anchor a // see if any are images if (a === null) { return false; } var kids=a.childNodes; for (var i=0; i<kids.length; ++i) { if (kids[i].nodeName=='IMG') { return true; } } return false; } //</NOLITE> function isPopupLink(a) { // NB for performance reasons, TOC links generally return true // they should be stripped out later if (!markNopopupSpanLinks.done) { markNopopupSpanLinks(); } if (a.inNopopupSpan) { return false; } // FIXME is this faster inline? if (a.onmousedown || a.getAttribute('nopopup')) { return false; } var h=a.href; if (h === document.location.href+'#') { return false; } if (!pg.re.basenames.test(h)) { return false; } if (!pg.re.urlNoPopup.test(h)) { return true; } return ( (pg.re.email.test(h) || pg.re.contribs.test(h) || pg.re.backlinks.test(h) || pg.re.specialdiff.test(h)) && h.indexOf('&limit=') == -1 ); } function markNopopupSpanLinks() { if( !getValueOf('popupOnlyArticleLinks')) fixVectorMenuPopups(); var s = $('.nopopups').toArray(); for (var i=0; i<s.length; ++i) { var as=s[i].getElementsByTagName('a'); for (var j=0; j<as.length; ++j) { as[j].inNopopupSpan=true; } } markNopopupSpanLinks.done=true; } function fixVectorMenuPopups() { $('div.vectorMenu h3:first a:first').prop('inNopopupSpan', true); } // ENDFILE: titles.js // STARTFILE: getpage.js ////////////////////////////////////////////////// // Wiki-specific downloading // // Schematic for a getWiki call // // getPageWithCaching // | // false | true // getPage<-[findPictureInCache]->-onComplete(a fake download) // \. // (async)->addPageToCache(download)->-onComplete(download) // check cache to see if page exists function getPageWithCaching(url, onComplete, owner) { log('getPageWithCaching, url='+url); var i=findInPageCache(url); var d; if (i > -1) { d=fakeDownload(url, owner.idNumber, onComplete, pg.cache.pages[i].data, pg.cache.pages[i].lastModified, owner); } else { d=getPage(url, onComplete, owner); if (d && owner && owner.addDownload) { owner.addDownload(d); d.owner=owner; } } } function getPage(url, onComplete, owner) { log('getPage'); var callback= function (d) { if (!d.aborted) {addPageToCache(d); onComplete(d);} }; return startDownload(url, owner.idNumber, callback); } function findInPageCache(url) { for (var i=0; i<pg.cache.pages.length; ++i) { if (url==pg.cache.pages[i].url) { return i; } } return -1; } function addPageToCache(download) { log('addPageToCache '+download.url); var page = {url: download.url, data: download.data, lastModified: download.lastModified}; return pg.cache.pages.push(page); } // ENDFILE: getpage.js // STARTFILE: parensplit.js ////////////////////////////////////////////////// // parenSplit // String.prototype.parenSplit should do what ECMAscript says String.prototype.split does, // interspersing paren matches (regex capturing groups) between the split elements. // i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c'] if (String('abc'.split(/(b)/))!='a,b,c') { // broken String.split, e.g. konq, IE < 10 String.prototype.parenSplit=function (re) { re=nonGlobalRegex(re); var s=this; var m=re.exec(s); var ret=[]; while (m && s) { // without the following loop, we have // 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/) for(var i=0; i<m.length; ++i) { if (typeof m[i]=='undefined') m[i]=''; } ret.push(s.substring(0,m.index)); ret = ret.concat(m.slice(1)); s=s.substring(m.index + m[0].length); m=re.exec(s); } ret.push(s); return ret; }; } else { String.prototype.parenSplit=function (re) { return this.split(re); }; String.prototype.parenSplit.isNative=true; } function nonGlobalRegex(re) { var s=re.toString(); var flags=''; for (var j=s.length; s.charAt(j) != '/'; --j) { if (s.charAt(j) != 'g') { flags += s.charAt(j); } } var t=s.substring(1,j); return RegExp(t,flags); } // ENDFILE: parensplit.js // STARTFILE: tools.js // IE madness with encoding // ======================== // // suppose throughout that the page is in utf8, like wikipedia // // if a is an anchor DOM element and a.href should consist of // // http://host.name.here/wiki/foo?bar=baz // // then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie)) // but IE gives bar=baz correctly as plain utf8 // // --------------------------------- // // IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here. // // --------------------------------- // // summat else // Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm //<NOLITE> function getJsObj(json) { try { var json_ret = JSON.parse(json); if( json_ret.warnings ) { for( var w=0; w < json_ret.warnings.length; w++ ) { if( json_ret.warnings[w]['*'] ) { log( json_ret.warnings[w]['*'] ); } else { log( json_ret.warnings[w]['warnings'] ); } } } else if ( json_ret.error ) { errlog( json_ret.error.code + ': ' + json_ret.error.info ); } return json_ret; } catch (someError) { errlog('Something went wrong with getJsObj, json='+json); return 1; } } function anyChild(obj) { for (var p in obj) { return obj[p]; } return null; } //</NOLITE> function upcaseFirst(str) { if (typeof str != typeof '' || str === '') return ''; return str.charAt(0).toUpperCase() + str.substring(1); } function findInArray(arr, foo) { if (!arr || !arr.length) { return -1; } var len=arr.length; for (var i=0; i<len; ++i) { if (arr[i]==foo) { return i; } } return -1; } /* eslint-disable no-unused-vars */ function nextOne (array, value) { // NB if the array has two consecutive entries equal // then this will loop on successive calls var i=findInArray(array, value); if (i<0) { return null; } return array[i+1]; } /* eslint-enable no-unused-vars */ function literalizeRegex(str){ return mw.util.escapeRegExp(str); } String.prototype.entify=function() { //var shy='&shy;'; return this.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;'/*+shy*/).split('"').join('&quot;'); }; // Array filter function function removeNulls(val) { return val !== null; } function joinPath(list) { return list.filter(removeNulls).join('/'); } function simplePrintf(str, subs) { if (!str || !subs) { return str; } var ret=[]; var s=str.parenSplit(/(%s|\$[0-9]+)/); var i=0; do { ret.push(s.shift()); if ( !s.length ) { break; } var cmd=s.shift(); if (cmd == '%s') { if ( i < subs.length ) { ret.push(subs[i]); } else { ret.push(cmd); } ++i; } else { var j=parseInt( cmd.replace('$', ''), 10 ) - 1; if ( j > -1 && j < subs.length ) { ret.push(subs[j]); } else { ret.push(cmd); } } } while (s.length > 0); return ret.join(''); } /* eslint-disable no-unused-vars */ function isString(x) { return (typeof x === 'string' || x instanceof String); } function isNumber(x) { return (typeof x === 'number' || x instanceof Number); } function isRegExp(x) { return x instanceof RegExp; } function isArray (x) { return x instanceof Array; } function isObject(x) { return x instanceof Object; } function isFunction(x) { return !isRegExp(x) && (typeof x === 'function' || x instanceof Function); } /* eslint-enable no-unused-vars */ function repeatString(s,mult) { var ret=''; for (var i=0; i<mult; ++i) { ret += s; } return ret; } function zeroFill(s, min) { min = min || 2; var t=s.toString(); return repeatString('0', min - t.length) + t; } function map(f, o) { if (isArray(o)) { return map_array(f,o); } return map_object(f,o); } function map_array(f,o) { var ret=[]; for (var i=0; i<o.length; ++i) { ret.push(f(o[i])); } return ret; } function map_object(f,o) { var ret={}; for (var i in o) { ret[o]=f(o[i]); } return ret; } pg.escapeQuotesHTML = function ( text ) { return text .replace(/&/g, "&amp;") .replace(/"/g, "&quot;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;"); }; // ENDFILE: tools.js // STARTFILE: dab.js //<NOLITE> ////////////////////////////////////////////////// // Dab-fixing code // function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) { log('retargetDab: newTarget='+newTarget + ' oldTarget=' + oldTarget); return changeLinkTargetLink( {newTarget: newTarget, text: newTarget.split(' ').join('&nbsp;'), hint: tprintf('disambigHint', [newTarget]), summary: simplePrintf( getValueOf('popupFixDabsSummary'), [friendlyCurrentArticleName, newTarget ]), clickButton: getValueOf('popupDabsAutoClick'), minor: true, oldTarget: oldTarget, watch: getValueOf('popupWatchDisambiggedPages'), title: titleToEdit}); } function listLinks(wikitext, oldTarget, titleToEdit) { // mediawiki strips trailing spaces, so we do the same // testcase: https://en.wikipedia.org/w/index.php?title=Radial&oldid=97365633 var reg=RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi'); var ret=[]; var splitted=wikitext.parenSplit(reg); // ^[a-z]+ should match interwiki links, hopefully (case-insensitive) // and ^[a-z]* should match those and [[:Category...]] style links too var omitRegex=RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory'); var friendlyCurrentArticleName= oldTarget.toString(); var wikPos = getValueOf('popupDabWiktionary'); for (var i=1; i<splitted.length; i=i+3) { if (typeof splitted[i] == typeof 'string' && splitted[i].length>0 && !omitRegex.test(splitted[i])) { ret.push( retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit) ); } /* if */ } /* for loop */ ret = rmDupesFromSortedList(ret.sort()); if (wikPos) { var wikTarget='wiktionary:' + friendlyCurrentArticleName.replace( RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1' ); var meth; if (wikPos.toLowerCase() == 'first') { meth = 'unshift'; } else { meth = 'push'; } ret[meth]( retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) ); } ret.push(changeLinkTargetLink( { newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'), hint: popupString("remove all links to this disambig page from this article"), clickButton: getValueOf('popupDabsAutoClick'), oldTarget: oldTarget, summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]), watch: getValueOf('popupWatchDisambiggedPages'), title: titleToEdit })); return ret; } function rmDupesFromSortedList(list) { var ret=[]; for (var i=0; i<list.length; ++i) { if (ret.length === 0 || list[i]!=ret[ret.length-1]) { ret.push(list[i]); } } return ret; } function makeFixDab(data, navpop) { // grab title from parent popup if there is one; default exists in changeLinkTargetLink var titleToEdit=(navpop.parentPopup && navpop.parentPopup.article.toString()); var list=listLinks(data, navpop.originalArticle, titleToEdit); if (list.length === 0) { log('listLinks returned empty list'); return null; } var html='<hr />' + popupString('Click to disambiguate this link to:') + '<br>'; html+=list.join(', '); return html; } function makeFixDabs(wikiText, navpop) { if (getValueOf('popupFixDabs') && isDisambig(wikiText, navpop.article) && Title.fromURL(location.href).namespaceId() != pg.nsSpecialId && navpop.article.talkPage() ) { setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber); } } function popupRedlinkHTML(article) { return changeLinkTargetLink( { newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'), hint: popupString("remove all links to this page from this article"), clickButton: getValueOf('popupRedlinkAutoClick'), oldTarget: article.toString(), summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()])}); } //</NOLITE> // ENDFILE: dab.js // STARTFILE: htmloutput.js // this has to use a timer loop as we don't know if the DOM element exists when we want to set the text function setPopupHTML (str, elementId, popupId, onSuccess, append) { if (typeof popupId === 'undefined') { //console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100)); popupId = pg.idNumber; } var popupElement=document.getElementById(elementId+popupId); if (popupElement) { if (!append) { popupElement.innerHTML=''; } if (isString(str)) { popupElement.innerHTML+=str; } else { popupElement.appendChild(str); } if (onSuccess) { onSuccess(); } setTimeout(checkPopupPosition, 100); return true; } else { // call this function again in a little while... setTimeout(function(){ setPopupHTML(str,elementId,popupId,onSuccess); }, 600); } return null; } //<NOLITE> function setPopupTrailer(str,id) {return setPopupHTML(str, 'popupData', id);} //</NOLITE> // args.navpopup is mandatory // optional: args.redir, args.redirTarget // FIXME: ye gods, this is ugly stuff function fillEmptySpans(args) { // if redir is present and true then redirTarget is mandatory var redir=true; var rcid; if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) { redir=false; } var a=args.navpopup.parentAnchor; var article, hint=null, oldid=null, params={}; if (redir && typeof args.redirTarget == typeof {}) { article=args.redirTarget; //hint=article.hintValue(); } else { article=(new Title()).fromAnchor(a); hint=a.originalTitle || article.hintValue(); params=parseParams(a.href); oldid=(getValueOf('popupHistoricalLinks')) ? params.oldid : null; rcid=params.rcid; } var x={ article:article, hint: hint, oldid: oldid, rcid: rcid, navpop:args.navpopup, params:params }; var structure=pg.structures[getValueOf('popupStructure')]; if (typeof structure != 'object') { setPopupHTML('popupError', 'Unknown structure (this should never happen): '+ pg.option.popupStructure, args.navpopup.idNumber); return; } var spans=flatten(pg.misc.layout); var numspans = spans.length; var redirs=pg.misc.redirSpans; for (var i=0; i<numspans; ++i) { var found = redirs && (redirs.indexOf( spans[i] ) !== -1); //log('redir='+redir+', found='+found+', spans[i]='+spans[i]); if ( (found && !redir) || (!found && redir) ) { //log('skipping this set of the loop'); continue; } var structurefn=structure[spans[i]]; var setfn = setPopupHTML; if (getValueOf('popupActiveNavlinks') && (spans[i].indexOf('popupTopLinks')===0 || spans[i].indexOf('popupRedirTopLinks')===0) ) { setfn = setPopupTipsAndHTML; } switch (typeof structurefn) { case 'function': log('running '+spans[i]+'({article:'+x.article+', hint:'+x.hint+', oldid: '+x.oldid+'})'); setfn(structurefn(x), spans[i], args.navpopup.idNumber); break; case 'string': setfn(structurefn, spans[i], args.navpopup.idNumber); break; default: errlog('unknown thing with label '+spans[i] + ' (span index was ' + i + ')'); break; } } } // flatten an array function flatten(list, start) { var ret=[]; if (typeof start == 'undefined') { start=0; } for (var i=start; i<list.length; ++i) { if (typeof list[i] == typeof []) { return ret.concat(flatten(list[i])).concat(flatten(list, i+1)); } else { ret.push(list[i]); } } return ret; } // Generate html for whole popup function popupHTML (a) { getValueOf('popupStructure'); var structure=pg.structures[pg.option.popupStructure]; if (typeof structure != 'object') { //return 'Unknown structure: '+pg.option.popupStructure; // override user choice pg.option.popupStructure=pg.optionDefault.popupStructure; return popupHTML(a); } if (typeof structure.popupLayout != 'function') { return 'Bad layout'; } pg.misc.layout=structure.popupLayout(); if (typeof structure.popupRedirSpans === 'function') { pg.misc.redirSpans=structure.popupRedirSpans(); } else { pg.misc.redirSpans=[]; } return makeEmptySpans(pg.misc.layout, a.navpopup); } function makeEmptySpans (list, navpop) { var ret=''; for (var i=0; i<list.length; ++i) { if (typeof list[i] == typeof '') { ret += emptySpanHTML(list[i], navpop.idNumber, 'div'); } else if (typeof list[i] == typeof [] && list[i].length > 0 ) { ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop)); } else if (typeof list[i] == typeof {} && list[i].nodeType ) { ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType); } } return ret; } function emptySpanHTML(name, id, tag, classname) { tag = tag || 'span'; if (!classname) { classname = emptySpanHTML.classAliases[name]; } classname = classname || name; if (name == getValueOf('popupDragHandle')) { classname += ' popupDragHandle'; } return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]); } emptySpanHTML.classAliases={ 'popupSecondPreview': 'popupPreview' }; // generate html for popup image // <a id="popupImageLinkn"><img id="popupImagen"> // where n=idNumber function imageHTML(article, idNumber) { return simplePrintf('<a id="popupImageLink$1">' + '<img align="right" valign="top" id="popupImg$1" style="display: none;"></img>' + '</a>', [ idNumber ]); } function popTipsSoonFn(id, when, popData) { if (!when) { when=250; } var popTips=function(){ setupTooltips(document.getElementById(id), false, true, popData); }; return function() { setTimeout( popTips, when, popData ); }; } function setPopupTipsAndHTML(html, divname, idnumber, popData) { setPopupHTML(html, divname, idnumber, getValueOf('popupSubpopups') ? popTipsSoonFn(divname + idnumber, null, popData) : null); } // ENDFILE: htmloutput.js // STARTFILE: mouseout.js ////////////////////////////////////////////////// // fuzzy checks function fuzzyCursorOffMenus(x,y, fuzz, parent) { if (!parent) { return null; } var uls=parent.getElementsByTagName('ul'); for (var i=0; i<uls.length; ++i) { if (uls[i].className=='popup_menu') { if (uls[i].offsetWidth > 0) return false; } // else {document.title+='.';} } return true; } function checkPopupPosition () { // stop the popup running off the right of the screen // FIXME avoid pg.current.link if (pg.current.link && pg.current.link.navpopup) pg.current.link.navpopup.limitHorizontalPosition(); } function mouseOutWikiLink () { //console ('mouseOutWikiLink'); var a=this; removeModifierKeyHandler(a); if (a.navpopup === null || typeof a.navpopup === 'undefined') return; if ( ! a.navpopup.isVisible() ) { a.navpopup.banish(); return; } restoreTitle(a); Navpopup.tracker.addHook(posCheckerHook(a.navpopup)); } function posCheckerHook(navpop) { return function() { if (!navpop.isVisible()) { return true; /* remove this hook */ } if (Navpopup.tracker.dirty) { return false; } var x=Navpopup.tracker.x, y=Navpopup.tracker.y; var mouseOverNavpop = navpop.isWithin(x,y,navpop.fuzz, navpop.mainDiv) || !fuzzyCursorOffMenus(x,y,navpop.fuzz, navpop.mainDiv); // FIXME it'd be prettier to do this internal to the Navpopup objects var t=getValueOf('popupHideDelay'); if (t) { t = t * 1000; } if (!t) { if(!mouseOverNavpop) { if(navpop.parentAnchor) { restoreTitle( navpop.parentAnchor ); } navpop.banish(); return true; /* remove this hook */ } return false; } // we have a hide delay set var d=+(new Date()); if ( !navpop.mouseLeavingTime ) { navpop.mouseLeavingTime = d; return false; } if ( mouseOverNavpop ) { navpop.mouseLeavingTime=null; return false; } if (d - navpop.mouseLeavingTime > t) { navpop.mouseLeavingTime=null; navpop.banish(); return true; /* remove this hook */ } return false; }; } function runStopPopupTimer(navpop) { // at this point, we should have left the link but remain within the popup // so we call this function again until we leave the popup. if (!navpop.stopPopupTimer) { navpop.stopPopupTimer=setInterval(posCheckerHook(navpop), 500); navpop.addHook(function(){clearInterval(navpop.stopPopupTimer);}, 'hide', 'before'); } } // ENDFILE: mouseout.js // STARTFILE: previewmaker.js /** @fileoverview Defines the {@link Previewmaker} object, which generates short previews from wiki markup. */ /** Creates a new Previewmaker @constructor @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext. @param {String} wikiText The Wikitext source of the page we wish to preview. @param {String} baseUrl The url we should prepend when creating relative urls. @param {Navpopup} owner The navpop associated to this preview generator */ function Previewmaker(wikiText, baseUrl, owner) { /** The wikitext which is manipulated to generate the preview. */ this.originalData=wikiText; this.baseUrl=baseUrl; this.owner=owner; this.maxCharacters=getValueOf('popupMaxPreviewCharacters'); this.maxSentences=getValueOf('popupMaxPreviewSentences'); this.setData(); } Previewmaker.prototype.setData=function() { var maxSize=Math.max(10000, 2*this.maxCharacters); this.data=this.originalData.substring(0,maxSize); }; /** Remove HTML comments @private */ Previewmaker.prototype.killComments = function () { // this also kills one trailing newline, eg [[diamyo]] this.data=this.data.replace(RegExp('^<!--[^$]*?-->\\n|\\n<!--[^$]*?-->(?=\\n)|<!--[^$]*?-->', 'g'), ''); }; /** @private */ Previewmaker.prototype.killDivs = function () { // say goodbye, divs (can be nested, so use * not *?) this.data=this.data.replace(RegExp('< *div[^>]* *>[\\s\\S]*?< */ *div *>', 'gi'), ''); }; /** @private */ Previewmaker.prototype.killGalleries = function () { this.data=this.data.replace(RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>', 'gi'), ''); }; /** @private */ Previewmaker.prototype.kill = function(opening, closing, subopening, subclosing, repl) { var oldk=this.data; var k=this.killStuff(this.data, opening, closing, subopening, subclosing, repl); while (k.length < oldk.length) { oldk=k; k=this.killStuff(k, opening, closing, subopening, subclosing, repl); } this.data=k; }; /** @private */ Previewmaker.prototype.killStuff = function (txt, opening, closing, subopening, subclosing, repl) { var op=this.makeRegexp(opening); var cl=this.makeRegexp(closing, '^'); var sb=subopening ? this.makeRegexp(subopening, '^') : null; var sc=subclosing ? this.makeRegexp(subclosing, '^') : cl; if (!op || !cl) { alert('Navigation Popups error: op or cl is null! something is wrong.'); return; } if (!op.test(txt)) { return txt; } var ret=''; var opResult = op.exec(txt); ret = txt.substring(0,opResult.index); txt=txt.substring(opResult.index+opResult[0].length); var depth = 1; while (txt.length > 0) { var removal=0; if (depth==1 && cl.test(txt)) { depth--; removal=cl.exec(txt)[0].length; } else if (depth > 1 && sc.test(txt)) { depth--; removal=sc.exec(txt)[0].length; }else if (sb && sb.test(txt)) { depth++; removal=sb.exec(txt)[0].length; } if ( !removal ) { removal = 1; } txt=txt.substring(removal); if (depth === 0) { break; } } return ret + (repl || '') + txt; }; /** @private */ Previewmaker.prototype.makeRegexp = function (x, prefix, suffix) { prefix = prefix || ''; suffix = suffix || ''; var reStr=''; var flags=''; if (isString(x)) { reStr=prefix + literalizeRegex(x) + suffix; } else if (isRegExp(x)) { var s=x.toString().substring(1); var sp=s.split('/'); flags=sp[sp.length-1]; sp[sp.length-1]=''; s=sp.join('/'); s=s.substring(0,s.length-1); reStr= prefix + s + suffix; } else { log ('makeRegexp failed'); } log ('makeRegexp: got reStr=' + reStr + ', flags=' + flags); return RegExp(reStr, flags); }; /** @private */ Previewmaker.prototype.killBoxTemplates = function () { // taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general // also, have float_begin, ... float_end this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /[}][}]\s*/, '{{'); // infoboxes etc // from User:Zyxw/popups.js: kill frames too this.kill(RegExp('[{][{][^{}\\s|]*?(infobox|elementbox|frame)[_ ]', 'i'), /[}][}]\s*/, '{{'); }; /** @private */ Previewmaker.prototype.killTemplates = function () { // the plus sign is to avoid transcluding [[Template:', ']] this.kill('{'+'{', '}'+'}', '{', '}', ' '); }; /** @private */ Previewmaker.prototype.killTables = function () { // tables are bad, too // this can be slow, but it's an inprovement over a browser hang // torture test: [[Comparison_of_Intel_Central_Processing_Units]] this.kill('{|', /[|]}\s*/, '{|'); this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i); // remove lines starting with a pipe for the hell of it (?) this.data=this.data.replace(RegExp('^[|].*$', 'mg'), ''); }; /** @private */ Previewmaker.prototype.killImages = function () { var forbiddenNamespaceAliases = []; jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) { if (_namespaceId!=pg.nsImageId && _namespaceId!=pg.nsCategoryId) return; forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments! }); // images and categories are a nono this.kill(RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'), /\]\]\s*/, '[', ']'); }; /** @private */ Previewmaker.prototype.killHTML = function () { // kill <ref ...>...</ref> this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i); // let's also delete entire lines starting with <. it's worth a try. this.data=this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n'); // and those pesky html tags, but not <nowiki> or <blockquote> var splitted=this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/); var len=splitted.length; for (var i=1; i<len; i=i+2) { switch (splitted[i]) { case '<nowiki>': case '</nowiki>': case '<blockquote>': case '</blockquote>': break; default: splitted[i]=''; } } this.data=splitted.join(''); }; /** @private */ Previewmaker.prototype.killChunks = function() { // heuristics alert // chunks of italic text? you crazy, man? var italicChunkRegex=new RegExp ("((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+", 'g'); // keep stuff separated, though, so stick in \n (fixes [[Union Jack]]? this.data=this.data.replace(italicChunkRegex, '\n'); }; /** @private */ Previewmaker.prototype.mopup = function () { // we simply *can't* be doing with horizontal rules right now this.data=this.data.replace(RegExp('^-{4,}','mg'),''); // no indented lines this.data=this.data.replace(RegExp('(^|\\n) *:[^\\n]*','g'), ''); // replace __TOC__, __NOTOC__ and whatever else there is // this'll probably do this.data=this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'),''); }; /** @private */ Previewmaker.prototype.firstBit = function () { // dont't be givin' me no subsequent paragraphs, you hear me? /// first we "normalize" section headings, removing whitespace after, adding before var d=this.data; if (getValueOf('popupPreviewCutHeadings')) { this.data=this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 '); /// then we want to get rid of paragraph breaks whose text ends badly this.data=this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n'); this.data=this.data.replace(RegExp('^[\\s\\n]*'), ''); var stuff=(RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data); if (stuff) { d = stuff[0]; } if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; } /// now put \n\n after sections so that bullets and numbered lists work d=d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n'); } // Split sentences. Superfluous sentences are RIGHT OUT. // note: exactly 1 set of parens here needed to make the slice work d = d.parenSplit(RegExp('([!?.]+["'+"'"+']*\\s)','g')); // leading space is bad, mmkay? d[0]=d[0].replace(RegExp('^\\s*'), ''); var notSentenceEnds=RegExp('([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$', 'i'); d = this.fixSentenceEnds(d, notSentenceEnds); this.fullLength=d.join('').length; var n=this.maxSentences; var dd=this.firstSentences(d,n); do { dd=this.firstSentences(d,n); --n; } while ( dd.length > this.maxCharacters && n !== 0 ); this.data = dd; }; /** @private */ Previewmaker.prototype.fixSentenceEnds = function(strs, reg) { // take an array of strings, strs // join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg for (var i=0; i<strs.length-2; ++i) { if (reg.test(strs[i])) { var a=[]; for (var j=0; j<strs.length; ++j) { if (j<i) a[j]=strs[j]; if (j==i) a[i]=strs[i]+strs[i+1]+strs[i+2]; if (j>i+2) a[j-2]=strs[j]; } return this.fixSentenceEnds(a,reg); } } return strs; }; /** @private */ Previewmaker.prototype.firstSentences = function(strs, howmany) { var t=strs.slice(0, 2*howmany); return t.join(''); }; /** @private */ Previewmaker.prototype.killBadWhitespace = function() { // also cleans up isolated '''', eg [[Suntory Sungoliath]] this.data=this.data.replace(RegExp('^ *\'+ *$', 'gm'), ''); }; /** Runs the various methods to generate the preview. The preview is stored in the <code>html</html> field. @private */ Previewmaker.prototype.makePreview = function() { if (this.owner.article.namespaceId()!=pg.nsTemplateId && this.owner.article.namespaceId()!=pg.nsImageId ) { this.killComments(); this.killDivs(); this.killGalleries(); this.killBoxTemplates(); if (getValueOf('popupPreviewKillTemplates')) { this.killTemplates(); } else { this.killMultilineTemplates(); } this.killTables(); this.killImages(); this.killHTML(); this.killChunks(); this.mopup(); this.firstBit(); this.killBadWhitespace(); } else { this.killHTML(); } this.html=wiki2html(this.data, this.baseUrl); // needs livepreview this.fixHTML(); this.stripLongTemplates(); }; /** @private */ Previewmaker.prototype.esWiki2HtmlPart = function(data) { var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink reLinks.lastIndex = 0; //reset regex var match; var result = ""; var postfixIndex = 0; while ((match = reLinks.exec(data))) //match all wikilinks { //FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases. result += pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) + '<a href="'+Insta.conf.paths.articles+pg.escapeQuotesHTML(match[1])+'">'+pg.escapeQuotesHTML((match[2]?match[2]:match[1])+match[3])+"</a>"; postfixIndex = reLinks.lastIndex; } //append the rest result += pg.escapeQuotesHTML(data.substring(postfixIndex)); return result; }; Previewmaker.prototype.editSummaryPreview=function() { var reAes = /\/\* *(.*?) *\*\//g; //match the first section marker reAes.lastIndex = 0; //reset regex var match; match = reAes.exec(this.data); if (match) { //we have a section link. Split it, process it, combine it. var prefix = this.data.substring(0,match.index-1); var section = match[1]; var postfix = this.data.substring(reAes.lastIndex); var start = "<span class='autocomment'>"; var end = "</span>"; if (prefix.length>0) start = this.esWiki2HtmlPart(prefix) + " " + start + "- "; if (postfix.length>0) end = ": " + end + this.esWiki2HtmlPart(postfix); var t=new Title().fromURL(this.baseUrl); t.anchorFromUtf(section); var sectionLink = Insta.conf.paths.articles + pg.escapeQuotesHTML(t.toString(true)) + '#' + pg.escapeQuotesHTML(t.anchor); return start + '<a href="'+sectionLink+'">&rarr;</a> '+pg.escapeQuotesHTML(section) + end; } //else there's no section link, htmlify the whole thing. return this.esWiki2HtmlPart(this.data); }; //<NOLITE> /** Test function for debugging preview problems one step at a time. */ /*eslint-disable */ function previewSteps(txt) { try { txt=txt || document.editform.wpTextbox1.value; } catch (err) { if (pg.cache.pages.length > 0) { txt=pg.cache.pages[pg.cache.pages.length-1].data; } else { alert('provide text or use an edit page'); } } txt=txt.substring(0,10000); var base=pg.wiki.articlebase + Title.fromURL(document.location.href).urlString(); var p=new Previewmaker(txt, base, pg.current.link.navpopup); if (this.owner.article.namespaceId() != pg.nsTemplateId) { p.killComments(); if (!confirm('done killComments(). Continue?\n---\n' + p.data)) { return; } p.killDivs(); if (!confirm('done killDivs(). Continue?\n---\n' + p.data)) { return; } p.killGalleries(); if (!confirm('done killGalleries(). Continue?\n---\n' + p.data)) { return; } p.killBoxTemplates(); if (!confirm('done killBoxTemplates(). Continue?\n---\n' + p.data)) { return; } if (getValueOf('popupPreviewKillTemplates')) { p.killTemplates(); if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) { return; } } else { p.killMultilineTemplates(); if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) { return; } } p.killTables(); if (!confirm('done killTables(). Continue?\n---\n' + p.data)) { return; } p.killImages(); if (!confirm('done killImages(). Continue?\n---\n' + p.data)) { return; } p.killHTML(); if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) { return; } p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; } p.mopup(); if (!confirm('done mopup(). Continue?\n---\n' + p.data)) { return; } p.firstBit(); if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) { return; } p.killBadWhitespace(); if (!confirm('done killBadWhitespace(). Continue?\n---\n' + p.data)) { return; } } p.html=wiki2html(p.data, base); // needs livepreview p.fixHTML(); if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) { return; } p.stripLongTemplates(); if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) { return; } alert('finished preview - end result follows.\n---\n' + p.html); } /*eslint-enable */ //</NOLITE> /** Works around livepreview bugs. @private */ Previewmaker.prototype.fixHTML = function() { if(!this.html) return; var ret = this.html; // fix question marks in wiki links // maybe this'll break some stuff :-( ret=ret.replace(RegExp('(<a href="' + pg.wiki.articlePath + '/[^"]*)[?](.*?")', 'g'), '$1%3F$2'); ret=ret.replace(RegExp('(<a href=\'' + pg.wiki.articlePath + '/[^\']*)[?](.*?\')', 'g'), '$1%3F$2'); // FIXME fix up % too this.html=ret; }; /** Generates the preview and displays it in the current popup. Does nothing if the generated preview is invalid or consists of whitespace only. Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true. */ Previewmaker.prototype.showPreview = function () { this.makePreview(); if (typeof this.html != typeof '') return; if (RegExp('^\\s*$').test(this.html)) return; setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber); setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, { owner: this.owner }); var more = (this.fullLength > this.data.length) ? this.moreLink() : ''; setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber); }; /** @private */ Previewmaker.prototype.moreLink=function() { var a=document.createElement('a'); a.className='popupMoreLink'; a.innerHTML=popupString('more...'); var savedThis=this; a.onclick=function() { savedThis.maxCharacters+=2000; savedThis.maxSentences+=20; savedThis.setData(); savedThis.showPreview(); }; return a; }; /** @private */ Previewmaker.prototype.stripLongTemplates = function() { // operates on the HTML! this.html=this.html.replace(RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'), ''); this.html=this.html.split('\n').join(' '); // workaround for <pre> templates this.html=this.html.replace(RegExp('[{][{][^}]*<pre>[^}]*[}][}]','gi'), ''); }; /** @private */ Previewmaker.prototype.killMultilineTemplates = function() { this.kill('{{{', '}}}'); this.kill(RegExp('\\s*[{][{][^{}]*\\n'), '}}', '{{'); }; // ENDFILE: previewmaker.js // STARTFILE: querypreview.js function loadAPIPreview(queryType, article, navpop) { var art=new Title(article).urlString(); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&'; var htmlGenerator=function(/*a, d*/){alert('invalid html generator');}; var usernameart = ''; switch (queryType) { case 'history': url += 'titles=' + art + '&prop=revisions&rvlimit=' + getValueOf('popupHistoryPreviewLimit'); htmlGenerator=APIhistoryPreviewHTML; break; case 'category': url += 'list=categorymembers&cmtitle=' + art; htmlGenerator=APIcategoryPreviewHTML; break; case 'userinfo': var username = new Title( article ).userName(); usernameart = encodeURIComponent( username ); if (pg.re.ipUser.test(username)) { url += 'list=blocks&bkprop=range|restrictions&bkip=' + usernameart; } else { url += 'list=users|usercontribs&usprop=blockinfo|groups|editcount|registration|gender&ususers=' + usernameart + "&meta=globaluserinfo&guiprop=groups|unattached&guiuser="+ usernameart + "&uclimit=1&ucprop=timestamp&ucuser=" + usernameart; } htmlGenerator=APIuserInfoPreviewHTML; break; case 'contribs': usernameart = encodeURIComponent( new Title( article ).userName() ); url += 'list=usercontribs&ucuser=' + usernameart + '&uclimit=' + getValueOf('popupContribsPreviewLimit'); htmlGenerator=APIcontribsPreviewHTML; break; case 'imagepagepreview': var trail=''; if (getValueOf('popupImageLinks')) { trail = '&list=imageusage&iutitle=' + art; } url += 'titles=' + art + '&prop=revisions|imageinfo&rvprop=content' + trail; htmlGenerator=APIimagepagePreviewHTML; break; case 'backlinks': url += 'list=backlinks&bltitle=' + art; htmlGenerator=APIbacklinksPreviewHTML; break; case 'revision': if (article.oldid) { url += 'revids=' + article.oldid; } else { url += 'titles=' + article.removeAnchor().urlString(); } url += '&prop=revisions|pageprops|info|images|categories&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max'; htmlGenerator=APIrevisionPreviewHTML; break; } pendingNavpopTask(navpop); var callback=function(d){ log( "callback of API functions was hit" ); showAPIPreview(queryType, htmlGenerator(article,d,navpop), navpop.idNumber, navpop, d); }; var go = function(){ getPageWithCaching(url, callback, navpop); return true; }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_'+queryType+'_QUERY_DATA'); } } function linkList(list) { list.sort(function(x,y) { return (x==y ? 0 : (x<y ? -1 : 1)); }); var buf=[]; for (var i=0; i<list.length; ++i) { buf.push(wikiLink({article: new Title(list[i]), text: list[i].split(' ').join('&nbsp;'), action: 'view'})); } return buf.join(', '); } function getTimeOffset() { var tz = mw.user.options.get('timecorrection'); if(tz) { if( tz.indexOf('|') > -1 ) { // New format return parseInt(tz.split('|')[1],10); } else if ( tz.indexOf(':') > -1 ) { // Old format return( parseInt(tz,10)*60 + parseInt(tz.split(':')[1],10) ); } } return 0; } /* * Creates a HTML table that's shown in the history and user-contribs popups. * @param {Object[]} h - a list of revisions, returned from the API * @param {boolean} reallyContribs - true only if we're displaying user contributions */ function editPreviewTable(article, h, reallyContribs, timeOffset) { var html=['<table>']; var day=null; var curart=article; var page=null; var makeFirstColumnLinks; if(reallyContribs) { // We're showing user contributions, so make (diff | hist) links makeFirstColumnLinks = function(currentRevision) { var result = '('; result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&diff=prev' + '&oldid=' + currentRevision.revid + '">' + popupString('diff') + '</a>'; result += '&nbsp;|&nbsp;'; result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&action=history">' + popupString('hist') + '</a>'; result += ')'; return result; }; } else { // It's a regular history page, so make (cur | last) links var firstRevid = h[0].revid; makeFirstColumnLinks = function(currentRevision) { var result = '('; result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=' + firstRevid + '&oldid=' + currentRevision.revid + '">' + popupString('cur') + '</a>'; result += '&nbsp;|&nbsp;'; result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=prev&oldid=' + currentRevision.revid + '">' + popupString('last') + '</a>'; result += ')'; return result; }; } for (var i=0; i<h.length; ++i) { if (reallyContribs) { page = h[i].title; curart = new Title(page); } var minor = h[i].minor ? '<b>m </b>' : ''; var editDate = adjustDate(getDateFromTimestamp(h[i].timestamp), timeOffset); var thisDay = dayFormat(editDate); var thisTime = timeFormat(editDate); if (thisDay == day) { thisDay = ''; } else { day = thisDay; } if (thisDay) { html.push( '<tr><td colspan=3><span class="popup_history_date">' + thisDay+'</span></td></tr>' ); } html.push('<tr class="popup_history_row_' + ( (i%2) ? 'odd' : 'even') + '">'); html.push('<td>' + makeFirstColumnLinks(h[i]) + '</td>'); html.push('<td>' + '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&oldid=' + h[i].revid + '">' + thisTime + '</a></td>'); var col3url='', col3txt=''; if (!reallyContribs) { var user=h[i].user; if( !h[i].userhidden ) { if( pg.re.ipUser.test(user) ) { col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Contributions&target=' + new Title(user).urlString(); } else { col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(user).urlString(); } col3txt=pg.escapeQuotesHTML(user); } else { col3url=getValueOf('popupRevDelUrl'); col3txt=pg.escapeQuotesHTML( popupString('revdel')); } } else { col3url=pg.wiki.titlebase + curart.urlString(); col3txt=pg.escapeQuotesHTML(page); } html.push('<td>' + (reallyContribs ? minor : '') + '<a href="' + col3url + '">' + col3txt + '</a></td>'); var comment=''; var c=h[i].comment || h[i].content; if (c) { comment=new Previewmaker(c, new Title(curart).toUrl()).editSummaryPreview(); } else if ( h[i].commenthidden ) { comment=popupString('revdel'); } html.push('<td>' + (!reallyContribs ? minor : '') + comment + '</td>'); html.push('</tr>'); html=[html.join('')]; } html.push('</table>'); return html.join(''); } function getDateFromTimestamp(t) { var s=t.split(/[^0-9]/); switch(s.length) { case 0: return null; case 1: return new Date(s[0]); case 2: return new Date(s[0], s[1]-1); case 3: return new Date(s[0], s[1]-1, s[2]); case 4: return new Date(s[0], s[1]-1, s[2], s[3]); case 5: return new Date(s[0], s[1]-1, s[2], s[3], s[4]); case 6: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5]); default: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5], s[6]); } } function adjustDate(d, offset) { // offset is in minutes var o=offset * 60 * 1000; return new Date( +d + o); } function dayFormat(editDate, utc) { if (utc) { return map(zeroFill, [editDate.getUTCFullYear(), editDate.getUTCMonth()+1, editDate.getUTCDate()]).join('-'); } return map(zeroFill, [editDate.getFullYear(), editDate.getMonth()+1, editDate.getDate()]).join('-'); } function timeFormat(editDate, utc) { if (utc) { return map(zeroFill, [editDate.getUTCHours(), editDate.getUTCMinutes(), editDate.getUTCSeconds()]).join(':'); } return map(zeroFill, [editDate.getHours(), editDate.getMinutes(), editDate.getSeconds()]).join(':'); } function showAPIPreview(queryType, html, id, navpop, download) { // DJ: done var target='popupPreview'; completedNavpopTask(navpop); switch (queryType) { case 'imagelinks': case 'category': target='popupPostPreview'; break; case 'userinfo': target='popupUserData'; break; case 'revision': insertPreview(download); return; } setPopupTipsAndHTML(html, target, id); } function APIrevisionPreviewHTML(article, download) { try{ var jsObj=getJsObj(download.data); var page=anyChild(jsObj.query.pages); if( page.missing ) { // TODO we need to fix this proper later on download.owner = null; return; } var content = ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; if( typeof content === 'string' ) { download.data = content; download.lastModified = new Date(page.revisions[0].timestamp); } } catch(someError) { return 'Revision preview failed :('; } } function APIbacklinksPreviewHTML(article, download/*, navpop*/ ) { try { var jsObj=getJsObj(download.data); var list=jsObj.query.backlinks; var html=[]; if (!list) { return popupString('No backlinks found'); } for ( var i=0; i < list.length; i++ ) { var t=new Title(list[i].title); html.push('<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t + '</a>'); } html=html.join(', '); if (jsObj['continue'] && jsObj['continue'].blcontinue) { html += popupString(' and more'); } return html; } catch (someError) { return 'backlinksPreviewHTML went wonky'; } } pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) { log( "APIsharedImagePagePreviewHTML" ); var popupid = obj.requestid; if( obj.query && obj.query.pages ) { var page=anyChild(obj.query.pages ); var content = ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; if( typeof content === 'string' ) { /* Not entirely safe, but the best we can do */ var p=new Previewmaker(content, pg.current.link.navpopup.article, pg.current.link.navpopup); p.makePreview(); setPopupHTML( p.html, "popupSecondPreview", popupid ); } } }; function APIimagepagePreviewHTML(article, download, navpop) { try { var jsObj=getJsObj(download.data); var page=anyChild(jsObj.query.pages); var content= ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; var ret=''; var alt=''; try{alt=navpop.parentAnchor.childNodes[0].alt;} catch(e){} if (alt) { ret = ret + '<hr /><b>' + popupString('Alt text:') + '</b> ' + pg.escapeQuotesHTML(alt); } if (typeof content === 'string') { var p=prepPreviewmaker(content, article, navpop); p.makePreview(); if (p.html) { ret += '<hr />' + p.html; } if (getValueOf('popupSummaryData')) { var info=getPageInfo(content, download); log(info); setPopupTrailer(info, navpop.idNumber); } } if (page && page.imagerepository == "shared" ) { var art=new Title(article); var encart = encodeURIComponent( "File:" + art.stripNamespace() ); var shared_url = pg.wiki.apicommonsbase + '?format=json&formatversion=2' + '&callback=pg.fn.APIsharedImagePagePreviewHTML' + '&requestid=' + navpop.idNumber + '&action=query&prop=revisions&rvprop=content&titles=' + encart; ret = ret +'<hr />' + popupString( 'Image from Commons') + ': <a href="' + pg.wiki.commonsbase + '?title=' + encart + '">' + popupString( 'Description page') + '</a>'; mw.loader.load( shared_url ); } showAPIPreview('imagelinks', APIimagelinksPreviewHTML(article,download), navpop.idNumber, download); return ret; } catch (someError) { return 'API imagepage preview failed :('; } } function APIimagelinksPreviewHTML(article, download) { try { var jsobj=getJsObj(download.data); var list=jsobj.query.imageusage; if (list) { var ret=[]; for (var i=0; i < list.length; i++) { ret.push(list[i].title); } if (ret.length === 0) { return popupString('No image links found'); } return '<h2>' + popupString('File links') + '</h2>' + linkList(ret); } else { return popupString('No image links found'); } } catch(someError) { return 'Image links preview generation failed :('; } } function APIcategoryPreviewHTML(article, download) { try{ var jsobj=getJsObj(download.data); var list=jsobj.query.categorymembers; var ret=[]; for (var p=0; p < list.length; p++) { ret.push(list[p].title); } if (ret.length === 0) { return popupString('Empty category'); } ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' +linkList(ret); if (jsobj['continue'] && jsobj['continue'].cmcontinue) { ret += popupString(' and more'); } return ret; } catch(someError) { return 'Category preview failed :('; } } function APIuserInfoPreviewHTML(article, download) { var ret=[]; var queryobj = {}; try{ queryobj=getJsObj(download.data).query; } catch(someError) { return 'Userinfo preview failed :('; } var user=anyChild(queryobj.users); if (user) { var globaluserinfo=queryobj.globaluserinfo; if (user.invalid === '') { ret.push( popupString( 'Invalid user') ); } else if (user.missing === '') { ret.push( popupString( 'Not a registered username') ); } if( user.blockedby ) { if( user.blockpartial ) { ret.push('<b>' + popupString('Has blocks') + '</b>'); } else { ret.push('<b>' + popupString('CẤM') + '</b>'); } } if( globaluserinfo && ( 'locked' in globaluserinfo || 'hidden' in globaluserinfo ) ) { var lockedSulAccountIsAttachedToThis = true; for( var i=0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) { if ( globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname') ) { lockedSulAccountIsAttachedToThis=false; break; } } if (lockedSulAccountIsAttachedToThis) { if ( 'locked' in globaluserinfo ) ret.push('<b><i>' + popupString('LOCKED') + '</i></b>'); if ( 'hidden' in globaluserinfo ) ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>'); } } if( getValueOf('popupShowGender') && user.gender ) { switch( user.gender ) { case "male": ret.push( popupString( "\u2642" ) ); break; case "female": ret.push( popupString( "\u2640" ) ); break; } } if( user.groups ) { for( var j=0; j < user.groups.length; j++) { var currentGroup = user.groups[j]; if( ["*", "user", "autoconfirmed", "extendedconfirmed"].indexOf( currentGroup ) === -1 ) { ret.push( pg.escapeQuotesHTML(user.groups[j]) ); } } } if( globaluserinfo && globaluserinfo.groups ) { for( var k=0; k < globaluserinfo.groups.length; k++) { ret.push( '<i>'+pg.escapeQuotesHTML(globaluserinfo.groups[k])+'</i>' ); } } if( user.registration ) ret.push( pg.escapeQuotesHTML((user.editcount?user.editcount:'0') + popupString(' sửa đổi từ: ') + (user.registration?dayFormat(getDateFromTimestamp(user.registration)):'')) ); } if (queryobj.usercontribs && queryobj.usercontribs.length) { ret.push( popupString('sửa đổi lần cuối vào ') + dayFormat(getDateFromTimestamp(queryobj.usercontribs[0].timestamp)) ); } if (queryobj.blocks) { ret.push( popupString( 'IP user') ); //we only request list=blocks for IPs for (var l=0; l<queryobj.blocks.length; l++) { console.log(queryobj); var rbstr = queryobj.blocks[l].rangestart === queryobj.blocks[l].rangeend ? 'BLOCK' : 'RANGEBLOCK'; rbstr = (!Array.isArray(queryobj.blocks[l].restrictions) ? 'Has ' + rbstr.toLowerCase() + 's' : rbstr + 'ED'); ret.push('<b>' + popupString(rbstr) + '</b>' ); } } ret = '<hr />' + ret.join( ', ' ); return ret; } function APIcontribsPreviewHTML(article, download, navpop) { return APIhistoryPreviewHTML(article, download, navpop, true); } function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) { try { var jsobj=getJsObj(download.data); var edits = []; if( reallyContribs ) { edits=jsobj.query.usercontribs; } else { edits=anyChild(jsobj.query.pages).revisions; } var ret=editPreviewTable(article, edits, reallyContribs, getTimeOffset()); return ret; } catch (someError) { return 'History preview failed :-('; } } //</NOLITE> // ENDFILE: querypreview.js // STARTFILE: debug.js //////////////////////////////////////////////////////////////////// // Debugging functions //////////////////////////////////////////////////////////////////// function setupDebugging() { //<NOLITE> if (window.popupDebug) { // popupDebug is set from .version window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; }; window.console.log(x); }; window.errlog=function(x) { window.console.error(x); }; log('Initializing logger'); } else { //</NOLITE> window.log = function() {}; window.errlog = function() {}; //<NOLITE> } //</NOLITE> } // ENDFILE: debug.js // STARTFILE: images.js // load image of type Title. function loadImage(image, navpop) { if (typeof image.stripNamespace != 'function') { alert('loadImages bad'); } // API call to retrieve image info. if ( !getValueOf('popupImages') ) return; if ( !isValidImageName(image) ) return false; var art=image.urlString(); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query'; url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge'); url += '&titles=' + art; pendingNavpopTask(navpop); var callback=function(d){ popupsInsertImage(navpop.idNumber, navpop, d); }; var go = function(){ getPageWithCaching(url, callback, navpop); return true; }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA'); } } function popupsInsertImage(id, navpop, download) { log( "popupsInsertImage"); var imageinfo; try { var jsObj=getJsObj(download.data); var imagepage=anyChild(jsObj.query.pages); if (typeof imagepage.imageinfo === 'undefined') return; imageinfo = imagepage.imageinfo[0]; } catch (someError) { log( "popupsInsertImage failed :(" ); return; } var popupImage = document.getElementById("popupImg"+id); if (!popupImage) { log( "could not find insertion point for image"); return; } popupImage.width=getValueOf('popupImageSize'); popupImage.style.display='inline'; // Set the source for the image. if( imageinfo.thumburl ) popupImage.src=imageinfo.thumburl; else if( imageinfo.mime.indexOf("image") === 0 ){ popupImage.src=imageinfo.url; log( "a thumb could not be found, using original image" ); } else log( "fullsize imagethumb, but not sure if it's an image"); var a=document.getElementById("popupImageLink"+id); if (a === null) { return null; } // Determine the action of the surrouding imagelink. switch (getValueOf('popupThumbAction')) { case 'imagepage': if (pg.current.article.namespaceId()!=pg.nsImageId) { a.href=imageinfo.descriptionurl; // FIXME: unreliable pg.idNumber popTipsSoonFn('popupImage' + id)(); break; } /* falls through */ case 'sizetoggle': a.onclick=toggleSize; a.title=popupString('Toggle image size'); return; case 'linkfull': a.href = imageinfo.url; a.title=popupString('Open full-size image'); return; } } // Toggles the image between inline small and navpop fullwidth. // It's the same image, no actual sizechange occurs, only display width. function toggleSize() { var imgContainer=this; if (!imgContainer) { alert('imgContainer is null :/'); return; } var img=imgContainer.firstChild; if (!img) { alert('img is null :/'); return; } if (!img.style.width || img.style.width==='') { img.style.width='100%'; } else { img.style.width=''; } } // Returns one title of an image from wikiText. function getValidImageFromWikiText(wikiText) { // nb in pg.re.image we're interested in the second bracketed expression // this may change if the regex changes :-( //var match=pg.re.image.exec(wikiText); var matched=null; var match; // strip html comments, used by evil bots :-( var t = removeMatchesUnless(wikiText, RegExp('(<!--[\\s\\S]*?-->)'), 1, RegExp('^<!--[^[]*popup', 'i')); while ( ( match = pg.re.image.exec(t) ) ) { // now find a sane image name - exclude templates by seeking { var m = match[2] || match[6]; if ( isValidImageName(m) ) { matched=m; break; } } pg.re.image.lastIndex=0; if (!matched) { return null; } return mw.config.get('wgFormattedNamespaces')[pg.nsImageId]+':'+upcaseFirst(matched); } function removeMatchesUnless(str, re1, parencount, re2) { var split=str.parenSplit(re1); var c=parencount + 1; for (var i=0; i<split.length; ++i) { if ( i%c === 0 || re2.test(split[i]) ) { continue; } split[i]=''; } return split.join(''); } //</NOLITE> // ENDFILE: images.js // STARTFILE: namespaces.js // Set up namespaces and other non-strings.js localization // (currently that means redirs too) function setNamespaces() { pg.nsSpecialId = -1; pg.nsMainspaceId = 0; pg.nsImageId = 6; pg.nsUserId = 2; pg.nsUsertalkId = 3; pg.nsCategoryId = 14; pg.nsTemplateId = 10; } function setRedirs() { var r='redirect'; var R='REDIRECT'; var redirLists={ //<NOLITE> 'ar': [ R, 'تحويل' ], 'be': [ r, 'перанакіраваньне' ], 'bg': [ r, 'пренасочване', 'виж' ], 'bs': [ r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI' ], 'cs': [ R, 'PŘESMĚRUJ' ], 'cy': [ r, 'ail-cyfeirio' ], 'de': [ R, 'WEITERLEITUNG' ], 'el': [ R, 'ΑΝΑΚΑΤΕΥΘΥΝΣΗ'], 'eo': [ R, 'ALIDIREKTU', 'ALIDIREKTI' ], 'es': [ R, 'REDIRECCIÓN' ], 'et': [ r, 'suuna' ], 'ga': [ r, 'athsheoladh' ], 'gl': [ r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'], 'he': [ R, 'הפניה' ], 'hu': [ R, 'ÁTIRÁNYÍTÁS' ], 'is': [ r, 'tilvísun', 'TILVÍSUN' ], 'it': [ R, 'RINVIA', 'Rinvia'], 'ja': [ R, '転送' ], 'mk': [ r, 'пренасочување', 'види' ], 'nds': [ r, 'wiederleiden' ], 'nl': [ R, 'DOORVERWIJZING' ], 'nn': [ r, 'omdiriger' ], 'pl': [ R, 'PATRZ', 'PRZEKIERUJ', 'TAM' ], 'pt': [ R, 'redir' ], 'ru': [ R, 'ПЕРЕНАПРАВЛЕНИЕ', 'ПЕРЕНАПР' ], 'sk': [ r, 'presmeruj' ], 'sr': [ r, 'Преусмери', 'преусмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI' ], 'tt': [ R, 'yünältü', 'перенаправление', 'перенапр' ], 'uk': [ R, 'ПЕРЕНАПРАВЛЕННЯ', 'ПЕРЕНАПР' ], 'vi': [ r, 'đổi' ], 'zh': [ R, '重定向'] // no comma //</NOLITE> }; var redirList=redirLists[ pg.wiki.lang ] || [r, R]; // Mediawiki is very tolerant about what comes after the #redirect at the start pg.re.redirect=RegExp('^\\s*[#](' + redirList.join('|') + ').*?\\[{2}([^\\|\\]]*)(|[^\\]]*)?\\]{2}\\s*(.*)', 'i'); } function setInterwiki() { if (pg.wiki.wikimedia) { // From https://meta.wikimedia.org/wiki/List_of_Wikipedias pg.wiki.interwiki='aa|ab|ace|af|ak|als|am|an|ang|ar|arc|arz|as|ast|av|ay|az|ba|bar|bat-smg|bcl|be|be-x-old|bg|bh|bi|bjn|bm|bn|bo|bpy|br|bs|bug|bxr|ca|cbk-zam|cdo|ce|ceb|ch|cho|chr|chy|ckb|co|cr|crh|cs|csb|cu|cv|cy|da|de|diq|dsb|dv|dz|ee|el|eml|en|eo|es|et|eu|ext|fa|ff|fi|fiu-vro|fj|fo|fr|frp|frr|fur|fy|ga|gag|gan|gd|gl|glk|gn|got|gu|gv|ha|hak|haw|he|hi|hif|ho|hr|hsb|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|ilo|io|is|it|iu|ja|jbo|jv|ka|kaa|kab|kbd|kg|ki|kj|kk|kl|km|kn|ko|koi|kr|krc|ks|ksh|ku|kv|kw|ky|la|lad|lb|lbe|lg|li|lij|lmo|ln|lo|lt|ltg|lv|map-bms|mdf|mg|mh|mhr|mi|mk|ml|mn|mo|mr|mrj|ms|mt|mus|mwl|my|myv|mzn|na|nah|nap|nds|nds-nl|ne|new|ng|nl|nn|no|nov|nrm|nv|ny|oc|om|or|os|pa|pag|pam|pap|pcd|pdc|pfl|pi|pih|pl|pms|pnb|pnt|ps|pt|qu|rm|rmy|rn|ro|roa-rup|roa-tara|ru|rue|rw|sa|sah|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|sn|so|sq|sr|srn|ss|st|stq|su|sv|sw|szl|ta|te|tet|tg|th|ti|tk|tl|tn|to|tpi|tr|ts|tt|tum|tw|ty|udm|ug|uk|ur|uz|ve|vec|vi|vls|vo|wa|war|wo|wuu|xal|xh|yi|yo|za|zea|zh|zh-classical|zh-min-nan|zh-yue|zu'; pg.re.interwiki=RegExp('^'+pg.wiki.interwiki+':'); } else { pg.wiki.interwiki=null; pg.re.interwiki=RegExp('^$'); } } // return a regexp pattern matching all variants to write the given namespace function nsRe(namespaceId) { var imageNamespaceVariants = []; jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) { if (_namespaceId!=namespaceId) return; _localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc); imageNamespaceVariants.push(mw.util.escapeRegExp(_localizedNamespaceLc).split(' ').join('[ _]')); imageNamespaceVariants.push(mw.util.escapeRegExp(encodeURI(_localizedNamespaceLc))); }); return '(?:' + imageNamespaceVariants.join('|') + ')'; } function nsReImage() { return nsRe(pg.nsImageId); } // ENDFILE: namespaces.js // STARTFILE: selpop.js //<NOLITE> function getEditboxSelection() { // see http://www.webgurusforum.com/8/12/0 var editbox; try { editbox=document.editform.wpTextbox1; } catch (dang) { return; } // IE, Opera if (document.selection) { return document.selection.createRange().text; } // Mozilla var selStart = editbox.selectionStart; var selEnd = editbox.selectionEnd; return (editbox.value).substring(selStart, selEnd); } function doSelectionPopup() { // popup if the selection looks like [[foo|anything afterwards at all // or [[foo|bar]]text without ']]' // or [[foo|bar]] var sel=getEditboxSelection(); var open=sel.indexOf('[['); var pipe=sel.indexOf('|'); var close=sel.indexOf(']]'); if (open == -1 || ( pipe == -1 && close == -1) ) { return; } if (pipe != -1 && open > pipe || close != -1 && open > close) { return; } if (getValueOf('popupOnEditSelection')=='boxpreview') { return doSeparateSelectionPopup(sel); } var article=new Title(sel.substring(open+2, (pipe < 0) ? close : pipe)).urlString(); if (close > 0 && sel.substring(close+2).indexOf('[[') >= 0) { return; } var a=document.createElement('a'); a.href=pg.wiki.titlebase + article; mouseOverWikiLink2(a); if (a.navpopup) { a.navpopup.addHook(function(){runStopPopupTimer(a.navpopup);}, 'unhide', 'after'); } } function doSeparateSelectionPopup(str) { var div=document.getElementById('selectionPreview'); if (!div) { div = document.createElement('div'); div.id='selectionPreview'; try { var box=document.editform.wpTextbox1; box.parentNode.insertBefore(div, box); } catch (error) { return; } } div.innerHTML=wiki2html(str); div.ranSetupTooltipsAlready = false; popTipsSoonFn('selectionPreview')(); } //</NOLITE> // ENDFILE: selpop.js // STARTFILE: navpopup.js /** @fileoverview Defines two classes: {@link Navpopup} and {@link Mousetracker}. <code>Navpopup</code> describes popups: when they appear, where, what they look like and so on. <code>Mousetracker</code> "captures" the mouse using <code>document.onmousemove</code>. */ /** Creates a new Mousetracker. @constructor @class The Mousetracker class. This monitors mouse movements and manages associated hooks. */ function Mousetracker() { /** Interval to regularly run the hooks anyway, in milliseconds. @type Integer */ this.loopDelay=400; /** Timer for the loop. @type Timer */ this.timer=null; /** Flag - are we switched on? @type Boolean */ this.active=false; /** Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position? */ this.dirty=true; /** Array of hook functions. @private @type Array */ this.hooks=[]; } /** Adds a hook, to be called when we get events. @param {Function} f A function which is called as <code>f(x,y)</code>. It should return <code>true</code> when it wants to be removed, and <code>false</code> otherwise. */ Mousetracker.prototype.addHook = function (f) { this.hooks.push(f); }; /** Runs hooks, passing them the x and y coords of the mouse. Hook functions that return true are passed to {@link Mousetracker#removeHooks} for removal. @private */ Mousetracker.prototype.runHooks = function () { if (!this.hooks || !this.hooks.length) { return; } //log('Mousetracker.runHooks; we got some hooks to run'); var remove=false; var removeObj={}; // this method gets called a LOT - // pre-cache some variables var x=this.x, y=this.y, len = this.hooks.length; for (var i=0; i<len; ++i) { //~ run the hook function, and remove it if it returns true if (this.hooks[i](x, y)===true) { remove=true; removeObj[i]=true; } } if (remove) { this.removeHooks(removeObj); } }; /** Removes hooks. @private @param {Object} removeObj An object whose keys are the index numbers of functions for removal, with values that evaluate to true */ Mousetracker.prototype.removeHooks = function(removeObj) { var newHooks=[]; var len = this.hooks.length; for (var i=0; i<len; ++i) { if (! removeObj[i]) { newHooks.push(this.hooks[i]); } } this.hooks=newHooks; }; /** Event handler for mouse wiggles. We simply grab the event, set x and y and run the hooks. This makes the cpu all hot and bothered :-( @private @param {Event} e Mousemove event */ Mousetracker.prototype.track=function (e) { //~ Apparently this is needed in IE. e = e || window.event; var x, y; if (e) { if (e.pageX) { x=e.pageX; y=e.pageY; } else if (typeof e.clientX!='undefined') { var left, top, docElt = document.documentElement; if (docElt) { left=docElt.scrollLeft; } left = left || document.body.scrollLeft || document.scrollLeft || 0; if (docElt) { top=docElt.scrollTop; } top = top || document.body.scrollTop || document.scrollTop || 0; x=e.clientX + left; y=e.clientY + top; } else { return; } this.setPosition(x,y); } }; /** Sets the x and y coordinates stored and takes appropriate action, running hooks as appropriate. @param {Integer} x, y Screen coordinates to set */ Mousetracker.prototype.setPosition=function(x,y) { this.x = x; this.y = y; if (this.dirty || this.hooks.length === 0) { this.dirty=false; return; } if (typeof this.lastHook_x != 'number') { this.lastHook_x = -100; this.lastHook_y=-100; } var diff = (this.lastHook_x - x)*(this.lastHook_y - y); diff = (diff >= 0) ? diff : -diff; if ( diff > 1 ) { this.lastHook_x=x; this.lastHook_y=y; if (this.dirty) { this.dirty = false; } else { this.runHooks(); } } }; /** Sets things in motion, unless they are already that is, registering an event handler on <code>document.onmousemove</code>. A half-hearted attempt is made to preserve the old event handler if there is one. */ Mousetracker.prototype.enable = function () { if (this.active) { return; } this.active=true; //~ Save the current handler for mousemove events. This isn't too //~ robust, of course. this.savedHandler=document.onmousemove; //~ Gotta save @tt{this} again for the closure, and use apply for //~ the member function. var savedThis=this; document.onmousemove=function (e) {savedThis.track.apply(savedThis, [e]);}; if (this.loopDelay) { this.timer = setInterval(function() { //log('loop delay in mousetracker is working'); savedThis.runHooks();}, this.loopDelay); } }; /** Disables the tracker, removing the event handler. */ Mousetracker.prototype.disable = function () { if (!this.active) { return; } if (typeof this.savedHandler === 'function') { document.onmousemove=this.savedHandler; } else { delete document.onmousemove; } if (this.timer) { clearInterval(this.timer); } this.active=false; }; /** Creates a new Navpopup. Gets a UID for the popup and @param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable. @constructor @class The Navpopup class. This generates popup hints, and does some management of them. */ function Navpopup(/*init*/) { //alert('new Navpopup(init)'); /** UID for each Navpopup instance. Read-only. @type integer */ this.uid=Navpopup.uid++; /** Read-only flag for current visibility of the popup. @type boolean @private */ this.visible=false; /** Flag to be set when we want to cancel a previous request to show the popup in a little while. @private @type boolean */ this.noshow=false; /** Categorised list of hooks. @see #runHooks @see #addHook @private @type Object */ this.hooks={ 'create': [], 'unhide': [], 'hide': [] }; /** list of unique IDs of hook functions, to avoid duplicates @private */ this.hookIds={}; /** List of downloads associated with the popup. @private @type Array */ this.downloads=[]; /** Number of uncompleted downloads. @type integer */ this.pending=null; /** Tolerance in pixels when detecting whether the mouse has left the popup. @type integer */ this.fuzz=5; /** Flag to toggle running {@link #limitHorizontalPosition} to regulate the popup's position. @type boolean */ this.constrained=true; /** The popup width in pixels. @private @type integer */ this.width=0; /** The popup width in pixels. @private @type integer */ this.height=0; /** The main content DIV element. @type HTMLDivElement */ this.mainDiv=null; this.createMainDiv(); // if (!init || typeof init.popups_draggable=='undefined' || init.popups_draggable) { // this.makeDraggable(true); // } } /** A UID for each Navpopup. This constructor property is just a counter. @type integer @private */ Navpopup.uid=0; /** Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible. @type boolean */ Navpopup.prototype.isVisible=function() { return this.visible; }; /** Repositions popup using CSS style. @private @param {integer} x x-coordinate (px) @param {integer} y y-coordinate (px) @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition} */ Navpopup.prototype.reposition= function (x,y, noLimitHor) { log ('reposition('+x+','+y+','+noLimitHor+')'); if (typeof x != 'undefined' && x !== null) { this.left=x; } if (typeof y != 'undefined' && y !== null) { this.top=y; } if (typeof this.left != 'undefined' && typeof this.top != 'undefined') { this.mainDiv.style.left=this.left + 'px'; this.mainDiv.style.top=this.top + 'px'; } if (!noLimitHor) { this.limitHorizontalPosition(); } //console.log('navpop'+this.uid+' - (left,top)=(' + this.left + ',' + this.top + '), css=(' //+ this.mainDiv.style.left + ',' + this.mainDiv.style.top + ')'); }; /** Prevents popups from being in silly locations. Hopefully. Should not be run if {@link #constrained} is true. @private */ Navpopup.prototype.limitHorizontalPosition=function() { if (!this.constrained || this.tooWide) { return; } this.updateDimensions(); var x=this.left; var w=this.width; var cWidth=document.body.clientWidth; // log('limitHorizontalPosition: x='+x+ // ', this.left=' + this.left + // ', this.width=' + this.width + // ', cWidth=' + cWidth); if ( (x+w) >= cWidth || ( x > 0 && this.maxWidth && this.width < this.maxWidth && this.height > this.width && x > cWidth - this.maxWidth ) ) { // This is a very nasty hack. There has to be a better way! // We find the "natural" width of the div by positioning it at the far left // then reset it so that it should be flush right (well, nearly) this.mainDiv.style.left='-10000px'; this.mainDiv.style.width = this.maxWidth + 'px'; var naturalWidth=parseInt(this.mainDiv.offsetWidth, 10); var newLeft=cWidth - naturalWidth - 1; if (newLeft < 0) { newLeft = 0; this.tooWide=true; } // still unstable for really wide popups? log ('limitHorizontalPosition: moving to ('+newLeft + ','+ this.top+');' + ' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth); this.reposition(newLeft, null, true); } }; /** Counter indicating the z-order of the "highest" popup. We start the z-index at 1000 so that popups are above everything else on the screen. @private @type integer */ Navpopup.highest=1000; /** Brings popup to the top of the z-order. We increment the {@link #highest} property of the contructor here. @private */ Navpopup.prototype.raise = function () { this.mainDiv.style.zIndex=Navpopup.highest + 1; ++Navpopup.highest; }; /** Shows the popup provided {@link #noshow} is not true. Updates the position, brings the popup to the top of the z-order and unhides it. */ Navpopup.prototype.show = function () { //document.title+='s'; if (this.noshow) { return; } //document.title+='t'; this.reposition(); this.raise(); this.unhide(); }; /** Checks to see if the mouse pointer has stabilised (checking every <code>time</code>/2 milliseconds) and runs the {@link #show} method if it has. @param {integer} time The minimum time (ms) before the popup may be shown. */ Navpopup.prototype.showSoonIfStable = function (time) { log ('showSoonIfStable, time='+time); if (this.visible) { return; } this.noshow = false; //~ initialize these variables so that we never run @tt{show} after //~ just half the time this.stable_x = -10000; this.stable_y = -10000; var stableShow = function() { log('stableShow called'); var new_x = Navpopup.tracker.x, new_y = Navpopup.tracker.y; var dx = savedThis.stable_x - new_x, dy = savedThis.stable_y - new_y; var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz; //document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] '; if ( dx * dx <= fuzz2 && dy * dy <= fuzz2 ) { log ('mouse is stable'); clearInterval(savedThis.showSoonStableTimer); savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]); savedThis.show.apply(savedThis, []); savedThis.limitHorizontalPosition.apply(savedThis, []); return; } savedThis.stable_x = new_x; savedThis.stable_y = new_y; }; var savedThis = this; this.showSoonStableTimer = setInterval(stableShow, time/2); }; /** Sets the {@link #noshow} flag and hides the popup. This should be called when the mouse leaves the link before (or after) it's actually been displayed. */ Navpopup.prototype.banish = function () { log ('banish called'); // hide and prevent showing with showSoon in the future this.noshow=true; if (this.showSoonStableTimer) { log('clearing showSoonStableTimer'); clearInterval(this.showSoonStableTimer); } this.hide(); }; /** Runs hooks added with {@link #addHook}. @private @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide' @param {String} when Controls exactly when the hook is run: either 'before' or 'after' */ Navpopup.prototype.runHooks = function (key, when) { if (!this.hooks[key]) { return; } var keyHooks=this.hooks[key]; var len=keyHooks.length; for (var i=0; i< len; ++i) { if (keyHooks[i] && keyHooks[i].when == when) { if (keyHooks[i].hook.apply(this, [])) { // remove the hook if (keyHooks[i].hookId) { delete this.hookIds[keyHooks[i].hookId]; } keyHooks[i]=null; } } } }; /** Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the Navpopup instance, and no arguments. @param {Function} hook The hook function. Functions that return true are deleted. @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide' @param {String} when Controls exactly when the hook is run: either 'before' or 'after' @param {String} uid A truthy string identifying the hook function; if it matches another hook in this position, it won't be added again. */ Navpopup.prototype.addHook = function ( hook, key, when, uid ) { when = when || 'after'; if (!this.hooks[key]) { return; } // if uid is specified, don't add duplicates var hookId=null; if (uid) { hookId=[key,when,uid].join('|'); if (this.hookIds[hookId]) { return; } this.hookIds[hookId]=true; } this.hooks[key].push( {hook: hook, when: when, hookId: hookId} ); }; /** Creates the main DIV element, which contains all the actual popup content. Runs hooks with key 'create'. @private */ Navpopup.prototype.createMainDiv = function () { if (this.mainDiv) { return; } this.runHooks('create', 'before'); var mainDiv=document.createElement('div'); var savedThis=this; mainDiv.onclick=function(e) {savedThis.onclickHandler(e);}; mainDiv.className=(this.className) ? this.className : 'navpopup_maindiv'; mainDiv.id=mainDiv.className + this.uid; mainDiv.style.position='absolute'; mainDiv.style.minWidth = '350px'; mainDiv.style.display='none'; mainDiv.className='navpopup'; // easy access to javascript object through DOM functions mainDiv.navpopup=this; this.mainDiv=mainDiv; document.body.appendChild(mainDiv); this.runHooks('create', 'after'); }; /** Calls the {@link #raise} method. @private */ Navpopup.prototype.onclickHandler=function(/*e*/) { this.raise(); }; /** Makes the popup draggable, using a {@link Drag} object. @private */ Navpopup.prototype.makeDraggable=function(handleName) { if (!this.mainDiv) { this.createMainDiv(); } var drag=new Drag(); if (!handleName) { drag.startCondition=function(e) { try { if (!e.shiftKey) { return false; } } catch (err) { return false; } return true; }; } var dragHandle; if (handleName) dragHandle = document.getElementById(handleName); if (!dragHandle) dragHandle = this.mainDiv; var np=this; drag.endHook=function(x,y) { Navpopup.tracker.dirty=true; np.reposition(x,y); }; drag.init(dragHandle,this.mainDiv); }; /** Hides the popup using CSS. Runs hooks with key 'hide'. Sets {@link #visible} appropriately. {@link #banish} should be called externally instead of this method. @private */ Navpopup.prototype.hide = function () { this.runHooks('hide', 'before'); this.abortDownloads(); if (typeof this.visible != 'undefined' && this.visible) { this.mainDiv.style.display='none'; this.visible=false; } this.runHooks('hide', 'after'); }; /** Shows the popup using CSS. Runs hooks with key 'unhide'. Sets {@link #visible} appropriately. {@link #show} should be called externally instead of this method. @private */ Navpopup.prototype.unhide = function () { this.runHooks('unhide', 'before'); if (typeof this.visible != 'undefined' && !this.visible) { this.mainDiv.style.display='inline'; this.visible=true; } this.runHooks('unhide', 'after'); }; /** Sets the <code>innerHTML</code> attribute of the main div containing the popup content. @param {String} html The HTML to set. */ Navpopup.prototype.setInnerHTML = function (html) { this.mainDiv.innerHTML = html; }; /** Updates the {@link #width} and {@link #height} attributes with the CSS properties. @private */ Navpopup.prototype.updateDimensions = function () { this.width=parseInt(this.mainDiv.offsetWidth, 10); this.height=parseInt(this.mainDiv.offsetHeight, 10); }; /** Checks if the point (x,y) is within {@link #fuzz} of the {@link #mainDiv}. @param {integer} x x-coordinate (px) @param {integer} y y-coordinate (px) @type boolean */ Navpopup.prototype.isWithin = function(x,y) { //~ If we're not even visible, no point should be considered as //~ being within the popup. if (!this.visible) { return false; } this.updateDimensions(); var fuzz=this.fuzz || 0; //~ Use a simple box metric here. return (x+fuzz >= this.left && x-fuzz <= this.left + this.width && y+fuzz >= this.top && y-fuzz <= this.top + this.height); }; /** Adds a download to {@link #downloads}. @param {Downloader} download */ Navpopup.prototype.addDownload=function(download) { if (!download) { return; } this.downloads.push(download); }; /** Aborts the downloads listed in {@link #downloads}. @see Downloader#abort */ Navpopup.prototype.abortDownloads=function() { for(var i=0; i<this.downloads.length; ++i) { var d=this.downloads[i]; if (d && d.abort) { d.abort(); } } this.downloads=[]; }; /** A {@link Mousetracker} instance which is a property of the constructor (pseudo-global). */ Navpopup.tracker=new Mousetracker(); // ENDFILE: navpopup.js // STARTFILE: diff.js //<NOLITE> /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) and [[:en:User:Lupin]] * * More Info: * http://ejohn.org/projects/javascript-diff-algorithm/ */ function delFmt(x) { if (!x.length) { return ''; } return "<del class='popupDiff'>" + x.join('') +"</del>"; } function insFmt(x) { if (!x.length) { return ''; } return "<ins class='popupDiff'>" + x.join('') +"</ins>"; } function countCrossings(a, b, i, eject) { // count the crossings on the edge starting at b[i] if (!b[i].row && b[i].row !== 0) { return -1; } var count=0; for (var j=0; j<a.length; ++j) { if (!a[j].row && a[j].row !== 0) { continue; } if ( (j-b[i].row)*(i-a[j].row) > 0) { if(eject) { return true; } count++; } } return count; } function shortenDiffString(str, context) { var re=RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)'); var splitted=str.parenSplit(re); var ret=['']; for (var i=0; i<splitted.length; i+=2) { if (splitted[i].length < 2*context) { ret[ret.length-1] += splitted[i]; if (i+1<splitted.length) { ret[ret.length-1] += splitted[i+1]; } continue; } else { if (i > 0) { ret[ret.length-1] += splitted[i].substring(0,context); } if (i+1 < splitted.length) { ret.push(splitted[i].substring(splitted[i].length-context) + splitted[i+1]); } } } while (ret.length > 0 && !ret[0]) { ret = ret.slice(1); } return ret; } function diffString( o, n, simpleSplit ) { var splitRe=RegExp('([[]{2}|[\\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\\s|\\b)'); // We need to split the strings o and n first, and entify() the parts // individually, so that the HTML entities are never cut apart. (AxelBoldt) var out, i, oSplitted, nSplitted; if (simpleSplit) { oSplitted=o.split(/\b/); nSplitted=n.split(/\b/); } else { oSplitted=o.parenSplit(splitRe); nSplitted=n.parenSplit(splitRe); } for (i=0; i<oSplitted.length; ++i) {oSplitted[i]=oSplitted[i].entify();} for (i=0; i<nSplitted.length; ++i) {nSplitted[i]=nSplitted[i].entify();} out = diff (oSplitted, nSplitted); var str = ""; var acc=[]; // accumulator for prettier output // crossing pairings -- eg 'A B' vs 'B A' -- cause problems, so let's iron them out // this doesn't always do things optimally but it should be fast enough var maxOutputPair=0; for (i=0; i<out.n.length; ++i) { if ( out.n[i].paired ) { if( maxOutputPair > out.n[i].row ) { // tangle - delete pairing out.o[ out.n[i].row ]=out.o[ out.n[i].row ].text; out.n[i]=out.n[i].text; } if (maxOutputPair < out.n[i].row) { maxOutputPair = out.n[i].row; } } } // output the stuff preceding the first paired old line for (i=0; i<out.o.length && !out.o[i].paired; ++i) { acc.push( out.o[i] ); } str += delFmt(acc); acc=[]; // main loop for ( i = 0; i < out.n.length; ++i ) { // output unpaired new "lines" while ( i < out.n.length && !out.n[i].paired ) { acc.push( out.n[i++] ); } str += insFmt(acc); acc=[]; if ( i < out.n.length ) { // this new "line" is paired with the (out.n[i].row)th old "line" str += out.n[i].text; // output unpaired old rows starting after this new line's partner var m = out.n[i].row + 1; while ( m < out.o.length && !out.o[m].paired ) { acc.push ( out.o[m++] ); } str += delFmt(acc); acc=[]; } } return str; } // see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object // FIXME: use obj.hasOwnProperty instead of this kludge! var jsReservedProperties=RegExp('^(constructor|prototype|__((define|lookup)[GS]etter)__' + '|eval|hasOwnProperty|propertyIsEnumerable' + '|to(Source|String|LocaleString)|(un)?watch|valueOf)$'); function diffBugAlert(word) { if (!diffBugAlert.list[word]) { diffBugAlert.list[word]=1; alert('Bad word: '+word+'\n\nPlease report this bug.'); } } diffBugAlert.list={}; function makeDiffHashtable(src) { var ret={}; for ( var i = 0; i < src.length; i++ ) { if ( jsReservedProperties.test(src[i]) ) { src[i] += '<!-- -->'; } if ( !ret[ src[i] ] ) { ret[ src[i] ] = []; } try { ret[ src[i] ].push( i ); } catch (err) { diffBugAlert(src[i]); } } return ret; } function diff( o, n ) { // pass 1: make hashtable ns with new rows as keys var ns = makeDiffHashtable(n); // pass 2: make hashtable os with old rows as keys var os = makeDiffHashtable(o); // pass 3: pair unique new rows and matching unique old rows var i; for ( i in ns ) { if ( ns[i].length == 1 && os[i] && os[i].length == 1 ) { n[ ns[i][0] ] = { text: n[ ns[i][0] ], row: os[i][0], paired: true }; o[ os[i][0] ] = { text: o[ os[i][0] ], row: ns[i][0], paired: true }; } } // pass 4: pair matching rows immediately following paired rows (not necessarily unique) for ( i = 0; i < n.length - 1; i++ ) { if ( n[i].paired && ! n[i+1].paired && n[i].row + 1 < o.length && ! o[ n[i].row + 1 ].paired && n[i+1] == o[ n[i].row + 1 ] ) { n[i+1] = { text: n[i+1], row: n[i].row + 1, paired: true }; o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1, paired: true }; } } // pass 5: pair matching rows immediately preceding paired rows (not necessarily unique) for ( i = n.length - 1; i > 0; i-- ) { if ( n[i].paired && ! n[i-1].paired && n[i].row > 0 && ! o[ n[i].row - 1 ].paired && n[i-1] == o[ n[i].row - 1 ] ) { n[i-1] = { text: n[i-1], row: n[i].row - 1, paired: true }; o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1, paired: true }; } } return { o: o, n: n }; } //</NOLITE> // ENDFILE: diff.js // STARTFILE: init.js function setSiteInfo() { if (window.popupLocalDebug) { pg.wiki.hostname = 'en.wikipedia.org'; } else { pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?) } pg.wiki.wikimedia=RegExp('(wiki([pm]edia|source|books|news|quote|versity)|wiktionary|mediawiki)[.]org').test(pg.wiki.hostname); pg.wiki.wikia=RegExp('[.]wikia[.]com$', 'i').test(pg.wiki.hostname); pg.wiki.isLocal=RegExp('^localhost').test(pg.wiki.hostname); pg.wiki.commons=( pg.wiki.wikimedia && pg.wiki.hostname != 'commons.wikimedia.org') ? 'commons.wikimedia.org' : null; pg.wiki.lang = mw.config.get('wgContentLanguage'); var port = location.port ? ':' + location.port : ''; pg.wiki.sitebase = pg.wiki.hostname + port; } function setUserInfo() { var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'query', list: 'users', ususers: mw.config.get('wgUserName'), usprop: 'rights' }; pg.user.canReview = false; if (getValueOf('popupReview')) { api.get(params).done(function(data){ var rights = data.query.users[0].rights; pg.user.canReview = rights.indexOf('review') !== -1; // TODO: Should it be a getValueOf('ReviewRight') ? }); } } function setTitleBase() { var protocol = ( window.popupLocalDebug ? 'http:' : location.protocol ); pg.wiki.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, ""); // as in http://some.thing.com/wiki/Article pg.wiki.botInterfacePath = mw.config.get('wgScript'); pg.wiki.APIPath = mw.config.get('wgScriptPath') +"/api.php"; // default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo var titletail = pg.wiki.botInterfacePath + '?title='; //var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?title=']); // other sites may need to add code here to set titletail depending on how their urls work pg.wiki.titlebase = protocol + '//' + pg.wiki.sitebase + titletail; //pg.wiki.titlebase2 = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]); pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath; pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath; pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath; pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.botInterfacePath; pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.APIPath; pg.re.basenames = RegExp( '^(' + map( literalizeRegex, [ pg.wiki.titlebase, //pg.wiki.titlebase2, pg.wiki.articlebase ]).join('|') + ')' ); } ////////////////////////////////////////////////// // Global regexps function setMainRegex() { var reStart='[^:]*://'; var preTitles = literalizeRegex( mw.config.get('wgScriptPath') ) + '/(?:index[.]php|wiki[.]phtml)[?]title='; preTitles += '|' + literalizeRegex( pg.wiki.articlePath + '/' ); var reEnd='(' + preTitles + ')([^&?#]*)[^#]*(?:#(.+))?'; pg.re.main = RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd); } function setRegexps() { // TODO: We shoud use an api call to get the aliases for special pages, now it does not work for non-English wikipedias: // E.g., https://ru.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=specialpagealiases&formatversion=2 setMainRegex(); var sp=nsRe(pg.nsSpecialId); pg.re.urlNoPopup=RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)') ; pg.re.contribs =RegExp('(title=|/)' + sp + '(?:%3A|:)Contributions' + '(&target=|/|/' + nsRe(pg.nsUserId)+':)(.*)') ; pg.re.email =RegExp('(title=|/)' + sp + '(?:%3A|:)EmailUser' + '(&target=|/|/(?:' + nsRe(pg.nsUserId)+':)?)(.*)') ; pg.re.backlinks =RegExp('(title=|/)' + sp + '(?:%3A|:)WhatLinksHere' + '(&target=|/)([^&]*)'); pg.re.specialdiff=RegExp('/' + sp + '(?:%3A|:)Diff/([^?#]*)'); //<NOLITE> var im=nsReImage(); // note: tries to get images in infobox templates too, e.g. movie pages, album pages etc // (^|\[\[)image: *([^|\]]*[^|\] ]) * // (^|\[\[)image: *([^|\]]*[^|\] ])([^0-9\]]*([0-9]+) *px)? // $4 = 120 as in 120px pg.re.image = RegExp('(^|\\[\\[)' + im + ': *([^|\\]]*[^|\\] ])' + '([^0-9\\]]*([0-9]+) *px)?|(?:\\n *[|]?|[|]) *' + '(' + getValueOf('popupImageVarsRegexp') + ')' + ' *= *(?:\\[\\[ *)?(?:' + im + ':)?' + '([^|]*?)(?:\\]\\])? *[|]? *\\n', 'img') ; pg.re.imageBracketCount = 6; pg.re.category = RegExp('\\[\\[' +nsRe(pg.nsCategoryId) + ': *([^|\\]]*[^|\\] ]) *', 'i'); pg.re.categoryBracketCount = 1; pg.re.ipUser=RegExp('^' + // IPv6 '(?::(?::|(?::[0-9A-Fa-f]{1,4}){1,7})|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,6}::|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){7})' + // IPv4 '|(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' + '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$'); pg.re.stub= RegExp(getValueOf('popupStubRegexp'), 'im'); pg.re.disambig=RegExp(getValueOf('popupDabRegexp'), 'im'); //</NOLITE> // FIXME replace with general parameter parsing function, this is daft pg.re.oldid=RegExp('[?&]oldid=([^&]*)'); pg.re.diff=RegExp('[?&]diff=([^&]*)'); } ////////////////////////////////////////////////// // miscellany function setupCache() { // page caching pg.cache.pages = []; } function setMisc() { pg.current.link=null; pg.current.links=[]; pg.current.linksHash={}; setupCache(); pg.timer.checkPopupPosition=null; pg.counter.loop=0; // ids change with each popup: popupImage0, popupImage1 etc pg.idNumber=0; // for myDecodeURI pg.misc.decodeExtras = [ {from: '%2C', to: ',' }, {from: '_', to: ' ' }, {from: '%24', to: '$'}, {from: '%26', to: '&' } // no , ]; pg.misc.userAgent = 'Navigation popups/1.0 (' + mw.config.get( 'wgServerName' ) +')'; } // We need a callback since this might end up asynchronous because of // the mw.loader.using() call. function setupPopups( callback ) { if ( setupPopups.completed ) { if ( typeof callback === 'function' ) { callback(); } return; } // These dependencies are also enforced from the gadget, // but not everyone loads this as a gadget, so double check mw.loader.using( ['mediawiki.util', 'mediawiki.user', 'user.options'] ).then( function() { // NB translatable strings should be set up first (strings.js) // basics setupDebugging(); setSiteInfo(); setTitleBase(); setOptions(); // see options.js setUserInfo(); // namespaces etc setNamespaces(); setInterwiki(); // regexps setRegexps(); setRedirs(); // other stuff setMisc(); setupLivePreview(); // main deal here setupTooltips(); log('In setupPopups(), just called setupTooltips()'); Navpopup.tracker.enable(); setupPopups.completed = true; if ( typeof callback === 'function' ) { callback(); } }); } // ENDFILE: init.js // STARTFILE: navlinks.js //<NOLITE> ////////////////////////////////////////////////// // navlinks... let the fun begin // function defaultNavlinkSpec() { var str=''; str += '<b><<mainlink|shortcut= >></b>'; if (getValueOf('popupLastEditLink')) { str += '*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}'; } // user links // contribs - log - count - email - block // count only if applicable; block only if popupAdminLinks str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>'; str+='if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}'; // editing links // talkpage -> edit|new - history - un|watch - article|edit // other page -> edit - history - un|watch - talk|edit|new var editstr='<<edit|shortcut=e>>'; var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'; var historystr='<<history|shortcut=h>>|<<editors|shortcut=E|>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; str+='<br>if(talk){' + editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + // not a talk page editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}'; // misc links str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>'; // admin links str += 'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}'; return str; } function navLinksHTML (article, hint, params) { //oldid, rcid) { var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>'; // BAM return navlinkStringToHTML(str, article, params); } function expandConditionalNavlinkString(s,article,z,recursionCount) { var oldid=z.oldid, rcid=z.rcid, diff=z.diff; // nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out) if (typeof recursionCount!=typeof 0) { recursionCount=0; } var conditionalSplitRegex=RegExp( //(1 if \\( (2 2) \\) {(3 3)} (4 else {(5 5)} 4)1) '(;?\\s*if\\s*\\(\\s*([\\w]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))', 'i'); var splitted=s.parenSplit(conditionalSplitRegex); // $1: whole conditional // $2: test condition // $3: true expansion // $4: else clause (possibly empty) // $5: false expansion (possibly null) var numParens=5; var ret = splitted[0]; for (var i=1; i<splitted.length; i=i+numParens+1) { var testString=splitted[i+2-1]; var trueString=splitted[i+3-1]; var falseString=splitted[i+5-1]; if (typeof falseString=='undefined' || !falseString) { falseString=''; } var testResult=null; switch (testString) { case 'user': testResult=(article.userName())?true:false; break; case 'talk': testResult=(article.talkPage())?false:true; // talkPage converts _articles_ to talkPages break; case 'admin': testResult=getValueOf('popupAdminLinks')?true:false; break; case 'oldid': testResult=(typeof oldid != 'undefined' && oldid)?true:false; break; case 'rcid': testResult=(typeof rcid != 'undefined' && rcid)?true:false; break; case 'ipuser': testResult=(article.isIpUser())?true:false; break; case 'mainspace_en': testResult=isInMainNamespace(article) && pg.wiki.hostname=='en.wikipedia.org'; break; case 'wikimedia': testResult=(pg.wiki.wikimedia) ? true : false; break; case 'diff': testResult=(typeof diff != 'undefined' && diff)?true:false; break; } switch(testResult) { case null: ret+=splitted[i]; break; case true: ret+=trueString; break; case false: ret+=falseString; break; } // append non-conditional string ret += splitted[i+numParens]; } if (conditionalSplitRegex.test(ret) && recursionCount < 10) { return expandConditionalNavlinkString(ret,article,z,recursionCount+1); } return ret; } function navlinkStringToArray(s, article, params) { s=expandConditionalNavlinkString(s,article,params); var splitted=s.parenSplit(RegExp('<<(.*?)>>')); var ret=[]; for (var i=0; i<splitted.length; ++i) { if (i%2) { // i odd, so s is a tag var t=new navlinkTag(); var ss=splitted[i].split('|'); t.id=ss[0]; for (var j=1; j<ss.length; ++j) { var sss=ss[j].split('='); if (sss.length>1) { t[sss[0]]=sss[1]; } else { // no assignment (no "="), so treat this as a title (overwriting the last one) t.text=popupString(sss[0]); } } t.article=article; var oldid=params.oldid, rcid=params.rcid, diff=params.diff; if (typeof oldid !== 'undefined' && oldid !== null) { t.oldid=oldid; } if (typeof rcid !== 'undefined' && rcid !== null) { t.rcid=rcid; } if (typeof diff !== 'undefined' && diff !== null) { t.diff=diff; } if (!t.text && t.id !== 'mainlink') { t.text=popupString(t.id); } ret.push(t); } else { // plain HTML ret.push(splitted[i]); } } return ret; } function navlinkSubstituteHTML(s) { return s.split('*').join(getValueOf('popupNavLinkSeparator')) .split('<menurow>').join('<li class="popup_menu_row">') .split('</menurow>').join('</li>') .split('<menu>').join('<ul class="popup_menu">') .split('</menu>').join('</ul>'); } function navlinkDepth(magic,s) { return s.split('<' + magic + '>').length - s.split('</' + magic + '>').length; } // navlinkString: * becomes the separator // <<foo|bar=baz|fubar>> becomes a foo-link with attribute bar='baz' // and visible text 'fubar' // if(test){...} and if(test){...}else{...} work too (nested ok) function navlinkStringToHTML(s,article,params) { //limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article)); var p=navlinkStringToArray(s,article,params); var html=''; var menudepth = 0; // nested menus not currently allowed, but doesn't do any harm to code for it var menurowdepth = 0; for (var i=0; i<p.length; ++i) { if (typeof p[i] == typeof '') { html+=navlinkSubstituteHTML(p[i]); menudepth += navlinkDepth('menu', p[i]); menurowdepth += navlinkDepth('menurow', p[i]); // if (menudepth === 0) { // tagType='span'; // } else if (menurowdepth === 0) { // tagType='li'; // } else { // tagType = null; // } } else if (typeof p[i].type != 'undefined' && p[i].type=='navlinkTag') { if (menudepth > 0 && menurowdepth === 0) { html += '<li class="popup_menu_item">' + p[i].html() + '</li>'; } else { html+=p[i].html(); } } } return html; } function navlinkTag() { this.type='navlinkTag'; } navlinkTag.prototype.html=function () { this.getNewWin(); this.getPrintFunction(); var html=''; var opening, closing; var tagType='span'; if (!tagType) { opening = ''; closing = ''; } else { opening = '<' + tagType + ' class="popup_' + this.id + '">'; closing = '</' + tagType + '>'; } if (typeof this.print!='function') { errlog ('Oh dear - invalid print function for a navlinkTag, id='+this.id); } else { html=this.print(this); if (typeof html != typeof '') {html='';} else if (typeof this.shortcut!='undefined') html=addPopupShortcut(html, this.shortcut); } return opening + html + closing; }; navlinkTag.prototype.getNewWin=function() { getValueOf('popupLinksNewWindow'); if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') { this.newWin=null; } this.newWin=pg.option.popupLinksNewWindow[this.id]; }; navlinkTag.prototype.getPrintFunction=function() { //think about this some more // this.id and this.article should already be defined if (typeof this.id!=typeof '' || typeof this.article!=typeof {} ) { return; } this.noPopup=1; switch (this.id) { case 'contribs': case 'history': case 'whatLinksHere': case 'userPage': case 'monobook': case 'userTalk': case 'talk': case 'article': case 'lastEdit': this.noPopup=null; } switch (this.id) { case 'email': case 'contribs': case 'block': case 'unblock': case 'userlog': case 'userSpace': case 'deletedContribs': this.article=this.article.userName(); } switch (this.id) { case 'userTalk': case 'newUserTalk': case 'editUserTalk': case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog': this.article=this.article.userName(true); /* fall through */ case 'pagelog': case 'deletelog': case 'protectlog': delete this.oldid; } if (this.id=='editMonobook' || this.id=='monobook') { this.article.append('/monobook.js'); } if (this.id != 'mainlink') { // FIXME anchor handling should be done differently with Title object this.article=this.article.removeAnchor(); // if (typeof this.text=='undefined') this.text=popupString(this.id); } switch (this.id) { case 'undelete': this.print=specialLink; this.specialpage='Undelete'; this.sep='/'; break; case 'whatLinksHere': this.print=specialLink; this.specialpage='Whatlinkshere'; break; case 'relatedChanges': this.print=specialLink; this.specialpage='Recentchangeslinked'; break; case 'move': this.print=specialLink; this.specialpage='Movepage'; break; case 'contribs': this.print=specialLink; this.specialpage='Contributions'; break; case 'deletedContribs':this.print=specialLink; this.specialpage='Deletedcontributions'; break; case 'email': this.print=specialLink; this.specialpage='EmailUser'; this.sep='/'; break; case 'block': this.print=specialLink; this.specialpage='Blockip'; this.sep='&ip='; break; case 'unblock': this.print=specialLink; this.specialpage='Ipblocklist'; this.sep='&action=unblock&ip='; break; case 'userlog': this.print=specialLink; this.specialpage='Log'; this.sep='&user='; break; case 'blocklog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=block&page='; break; case 'pagelog': this.print=specialLink; this.specialpage='Log'; this.sep='&page='; break; case 'protectlog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=protect&page='; break; case 'deletelog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=delete&page='; break; case 'userSpace': this.print=specialLink; this.specialpage='PrefixIndex'; this.sep='&namespace=2&prefix='; break; case 'search': this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break; case 'thank': this.print=specialLink; this.specialpage='Thanks'; this.sep='/'; this.article.value = this.diff; break; case 'unwatch': case 'watch': this.print=magicWatchLink; this.action=this.id+'&autowatchlist=1&autoimpl=' + popupString('autoedit_version') + '&actoken='+autoClickToken(); break; case 'history': case 'historyfeed': case 'unprotect': case 'protect': this.print=wikiLink; this.action=this.id; break; case 'delete': this.print=wikiLink; this.action='delete'; if (this.article.namespaceId()==pg.nsImageId) { var img=this.article.stripNamespace(); this.action+='&image='+img; } break; case 'markpatrolled': case 'edit': // editOld should keep the oldid, but edit should not. delete this.oldid; /* fall through */ case 'view': case 'purge': case 'render': this.print=wikiLink; this.action=this.id; break; case 'raw': this.print=wikiLink; this.action='raw'; break; case 'new': this.print=wikiLink; this.action='edit&section=new'; break; case 'mainlink': if (typeof this.text=='undefined') { this.text=this.article.toString().entify(); } if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) { // only show the /subpage part of the title text var s=this.text.split('/'); this.text=s[s.length-1]; if (this.text==='' && s.length > 1) { this.text=s[s.length-2]; } } this.print=titledWikiLink; if (typeof this.title==='undefined' && pg.current.link && typeof pg.current.link.href !== 'undefined') { this.title=safeDecodeURI((pg.current.link.originalTitle)?pg.current.link.originalTitle:this.article); if (typeof this.oldid !== 'undefined' && this.oldid) { this.title=tprintf('Revision %s of %s', [this.oldid, this.title]); } } this.action='view'; break; case 'userPage': case 'article': case 'monobook': case 'editMonobook': case 'editArticle': delete this.oldid; //alert(this.id+'\n'+this.article + '\n'+ typeof this.article); this.article=this.article.articleFromTalkOrArticle(); //alert(this.id+'\n'+this.article + '\n'+ typeof this.article); this.print=wikiLink; if (this.id.indexOf('edit')===0) { this.action='edit'; } else { this.action='view';} break; case 'userTalk': case 'talk': this.article=this.article.talkPage(); delete this.oldid; this.print=wikiLink; this.action='view'; break; case 'arin': this.print=arinLink; break; case 'count': this.print=editCounterLink; break; case 'google': this.print=googleLink; break; case 'editors': this.print=editorListLink; break; case 'globalsearch': this.print=globalSearchLink; break; case 'lastEdit': this.print=titledDiffLink; this.title=popupString('Show the last edit'); this.from='prev'; this.to='cur'; break; case 'oldEdit': this.print=titledDiffLink; this.title=popupString('Show the edit made to get revision') + ' ' + this.oldid; this.from='prev'; this.to=this.oldid; break; case 'editOld': this.print=wikiLink; this.action='edit'; break; case 'undo': this.print=wikiLink; this.action='edit&undo='; break; case 'revert': this.print=wikiLink; this.action='revert'; break; case 'nullEdit': this.print=wikiLink; this.action='nullEdit'; break; case 'diffCur': this.print=titledDiffLink; this.title=tprintf('Show changes since revision %s', [this.oldid]); this.from=this.oldid; this.to='cur'; break; case 'editUserTalk': case 'editTalk': delete this.oldid; this.article=this.article.talkPage(); this.action='edit'; this.print=wikiLink; break; case 'newUserTalk': case 'newTalk': this.article=this.article.talkPage(); this.action='edit&section=new'; this.print=wikiLink; break; case 'lastContrib': case 'sinceMe': this.print=magicHistoryLink; break; case 'togglePreviews': this.text=popupString(pg.option.simplePopups ? 'enable previews' : 'disable previews'); /* fall through */ case 'disablePopups': case 'purgePopups': this.print=popupMenuLink; break; default: this.print=function () {return 'Unknown navlink type: '+this.id+'';}; } }; // // end navlinks ////////////////////////////////////////////////// //</NOLITE> // ENDFILE: navlinks.js // STARTFILE: shortcutkeys.js //<NOLITE> function popupHandleKeypress(evt) { var keyCode = window.event ? window.event.keyCode : ( evt.keyCode ? evt.keyCode : evt.which); if (!keyCode || !pg.current.link || !pg.current.link.navpopup) { return; } if (keyCode==27) { // escape killPopup(); return false; // swallow keypress } var letter=String.fromCharCode(keyCode); var links=pg.current.link.navpopup.mainDiv.getElementsByTagName('A'); var startLink=0; var i,j; if (popupHandleKeypress.lastPopupLinkSelected) { for (i=0; i<links.length; ++i) { if (links[i]==popupHandleKeypress.lastPopupLinkSelected) { startLink=i; } } } for (j=0; j<links.length; ++j) { i=(startLink + j + 1) % links.length; if (links[i].getAttribute('popupkey')==letter) { if (evt && evt.preventDefault) evt.preventDefault(); links[i].focus(); popupHandleKeypress.lastPopupLinkSelected=links[i]; return false; // swallow keypress } } // pass keypress on if (document.oldPopupOnkeypress) { return document.oldPopupOnkeypress(evt); } return true; } function addPopupShortcuts() { if (document.onkeypress!=popupHandleKeypress) { document.oldPopupOnkeypress=document.onkeypress; } document.onkeypress=popupHandleKeypress; } function rmPopupShortcuts() { popupHandleKeypress.lastPopupLinkSelected=null; try { if (document.oldPopupOnkeypress && document.oldPopupOnkeypress==popupHandleKeypress) { // panic document.onkeypress=null; //function () {}; return; } document.onkeypress=document.oldPopupOnkeypress; } catch (nasties) { /* IE goes here */ } } function addLinkProperty(html, property) { // take "<a href=...>...</a> and add a property // not sophisticated at all, easily broken var i=html.indexOf('>'); if (i<0) { return html; } return html.substring(0,i) + ' ' + property + html.substring(i); } function addPopupShortcut(html, key) { if (!getValueOf('popupShortcutKeys')) { return html; } var ret= addLinkProperty(html, 'popupkey="'+key+'"'); if (key==' ') { key=popupString('spacebar'); } return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'),'$1$2$3 ['+key+']$4'); } //</NOLITE> // ENDFILE: shortcutkeys.js // STARTFILE: diffpreview.js //<NOLITE> //lets jump through hoops to find the rev ids we need to retrieve function loadDiff(article, oldid, diff, navpop) { navpop.diffData={ oldRev: {}, newRev: {} }; mw.loader.using( 'mediawiki.api' ).then( function() { var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'compare', prop: 'ids|title' }; if(article.title){ params.fromtitle = article.title; } switch (diff) { case 'cur': switch ( oldid ) { case null: case '': case 'prev': // this can only work if we have the title // cur -> prev params.torelative = 'prev'; break; default: params.fromrev = oldid; params.torelative = 'cur'; break; } break; case 'prev': if( oldid ) { params.fromrev = oldid; } else { params.fromtitle; } params.torelative = 'prev'; break; case 'next': params.fromrev = oldid || 0; params.torelative = 'next'; break; default: params.fromrev = oldid || 0; params.torev = diff || 0; break; } api.get( params ).then( function( data ) { navpop.diffData.oldRev.revid = data.compare.fromrevid; navpop.diffData.newRev.revid = data.compare.torevid; addReviewLink(navpop, 'popupMiscTools'); var go = function() { pendingNavpopTask(navpop); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&'; url += 'revids=' + navpop.diffData.oldRev.revid + '|' + navpop.diffData.newRev.revid; url += '&prop=revisions&rvprop=ids|timestamp|content'; getPageWithCaching(url, doneDiff, navpop); return true; // remove hook once run }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS'); } } ); } ); } // Put a "mark patrolled" link to an element target // TODO: Allow patrol a revision, as well as a diff function addReviewLink (navpop, target) { if (! pg.user.canReview) return; // If 'newRev' is older than 'oldRev' than it could be confusing, so we do not show the review link. if (navpop.diffData.newRev.revid <= navpop.diffData.oldRev.revid) return; var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'query', prop: 'info|flagged', revids: navpop.diffData.oldRev.revid, formatversion : 2 }; api.get (params).then(function(data){ var stable_revid = data.query.pages[0].flagged && data.query.pages[0].flagged.stable_revid || 0; // The diff can be reviewed if the old version is the last reviewed version // TODO: Other possible conditions that we may want to implement instead of this one: // * old version is patrolled and the new version is not patrolled // * old version is patrolled and the new version is more recent than the last reviewed version if (stable_revid == navpop.diffData.oldRev.revid) { var a = document.createElement('a'); a.innerHTML = popupString('mark patrolled'); a.title=popupString('markpatrolledHint'); a.onclick = function() { var params = { action: 'review', revid: navpop.diffData.newRev.revid, comment: tprintf('defaultpopupReviewedSummary', [navpop.diffData.oldRev.revid, navpop.diffData.newRev.revid]) }; api.postWithToken('csrf',params).done(function(){ a.style.display = "none"; // TODO: Update current page and other already constructed popups } ).fail(function(){ alert(popupString('Could not marked this edit as patrolled')); }); }; setPopupHTML(a, target, navpop.idNumber,null,true); } }); } function doneDiff(download) { if (!download.owner || !download.owner.diffData) { return; } var navpop=download.owner; completedNavpopTask(navpop); var pages, revisions=[]; try{ // Process the downloads pages = getJsObj(download.data).query.pages; for(var i=0; i < pages.length; i++ ) { revisions = revisions.concat(pages[i].revisions); } for(i=0; i< revisions.length; i++){ if(revisions[i].revid == navpop.diffData.oldRev.revid) { navpop.diffData.oldRev.revision = revisions[i]; } else if (revisions[i].revid == navpop.diffData.newRev.revid) { navpop.diffData.newRev.revision = revisions[i]; } } } catch(someError) { errlog( 'Could not get diff' ); } insertDiff(navpop); } function rmBoringLines(a,b,context) { if (typeof context == 'undefined') { context=2; } // this is fairly slow... i think it's quicker than doing a word-based diff from the off, though var aa=[], aaa=[]; var bb=[], bbb=[]; var i, j; // first, gather all disconnected nodes in a and all crossing nodes in a and b for (i=0; i<a.length; ++i ) { if(!a[i].paired) { aa[i]=1; } else if (countCrossings(b,a,i, true)) { aa[i]=1; bb[ a[i].row ] = 1; } } // pick up remaining disconnected nodes in b for (i=0; i<b.length; ++i ) { if (bb[i]==1) { continue; } if(!b[i].paired) { bb[i]=1; } } // another pass to gather context: we want the neighbours of included nodes which are not yet included // we have to add in partners of these nodes, but we don't want to add context for *those* nodes in the next pass for (i=0; i<b.length; ++i) { if ( bb[i] == 1 ) { for (j=Math.max(0,i-context); j < Math.min(b.length, i+context); ++j) { if ( !bb[j] ) { bb[j] = 1; aa[ b[j].row ] = 0.5; } } } } for (i=0; i<a.length; ++i) { if ( aa[i] == 1 ) { for (j=Math.max(0,i-context); j < Math.min(a.length, i+context); ++j) { if ( !aa[j] ) { aa[j] = 1; bb[ a[j].row ] = 0.5; } } } } for (i=0; i<bb.length; ++i) { if (bb[i] > 0) { // it's a row we need if (b[i].paired) { bbb.push(b[i].text); } // joined; partner should be in aa else { bbb.push(b[i]); } } } for (i=0; i<aa.length; ++i) { if (aa[i] > 0) { // it's a row we need if (a[i].paired) { aaa.push(a[i].text); } // joined; partner should be in aa else { aaa.push(a[i]); } } } return { a: aaa, b: bbb}; } function stripOuterCommonLines(a,b,context) { var i=0; while (i<a.length && i < b.length && a[i]==b[i]) { ++i; } var j=a.length-1; var k=b.length-1; while ( j>=0 && k>=0 && a[j]==b[k] ) { --j; --k; } return { a: a.slice(Math.max(0,i - 1 - context), Math.min(a.length+1, j + context+1)), b: b.slice(Math.max(0,i - 1 - context), Math.min(b.length+1, k + context+1)) }; } function insertDiff(navpop) { // for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff // FIXME: sometimes this gives misleading diffs as distant chunks are squashed together var oldlines = navpop.diffData.oldRev.revision.content.split('\n'); var newlines = navpop.diffData.newRev.revision.content.split('\n'); var inner=stripOuterCommonLines(oldlines,newlines,getValueOf('popupDiffContextLines')); oldlines=inner.a; newlines=inner.b; var truncated=false; getValueOf('popupDiffMaxLines'); if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) { // truncate truncated=true; inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines), newlines.slice(0,pg.option.popupDiffMaxLines), pg.option.popupDiffContextLines); oldlines=inner.a; newlines=inner.b; } var lineDiff=diff(oldlines, newlines); var lines2=rmBoringLines(lineDiff.o, lineDiff.n); var oldlines2=lines2.a; var newlines2=lines2.b; var simpleSplit = !String.prototype.parenSplit.isNative; var html='<hr />'; if (getValueOf('popupDiffDates')) { html += diffDatesTable(navpop); html += '<hr />'; } html += shortenDiffString( diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit), getValueOf('popupDiffContextCharacters') ).join('<hr />'); setPopupTipsAndHTML(html.split('\n').join('<br>') + (truncated ? '<hr /><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') , 'popupPreview', navpop.idNumber); } function diffDatesTable( navpop ) { var html='<table class="popup_diff_dates">'; html += diffDatesTableRow( navpop.diffData.newRev.revision, tprintf('New revision')); html += diffDatesTableRow( navpop.diffData.oldRev.revision, tprintf('Old revision')); html += '</table>'; return html; } function diffDatesTableRow( revision, label ) { var txt=''; var lastModifiedDate = new Date(revision.timestamp); var datePrint=getValueOf('popupDiffDatePrinter'); if (typeof lastModifiedDate[datePrint] == 'function') { var d2 = adjustDate(lastModifiedDate, getTimeOffset()); txt = dayFormat(d2, true) + ' ' + timeFormat(d2, true); } else { txt = tprintf('Invalid %s %s', ['popupDiffDatePrinter', datePrint]); } var revlink = generalLink({url: mw.config.get('wgScript') + '?oldid='+revision.revid, text: label, title: label}); return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [ revlink, txt ]); } //</NOLITE> // ENDFILE: diffpreview.js // STARTFILE: links.js //<NOLITE> ///////////////////// // LINK GENERATION // ///////////////////// // titledDiffLink --> titledWikiLink --> generalLink // wikiLink --> titledWikiLink --> generalLink // editCounterLink --> generalLink // TODO Make these functions return Element objects, not just raw HTML strings. function titledDiffLink(l) { // article, text, title, from, to) { return titledWikiLink({article: l.article, action: l.to + '&oldid=' + l.from, newWin: l.newWin, noPopup: l.noPopup, text: l.text, title: l.title, /* hack: no oldid here */ actionName: 'diff'}); } function wikiLink(l) { //{article:article, action:action, text:text, oldid, newid}) { if (! (typeof l.article == typeof {} && typeof l.action == typeof '' && typeof l.text==typeof '')) return null; if (typeof l.oldid == 'undefined') { l.oldid=null; } var savedOldid = l.oldid; if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) { l.oldid=null; } var hint=popupString(l.action + 'Hint'); // revertHint etc etc etc var oldidData=[l.oldid, safeDecodeURI(l.article)]; var revisionString = tprintf('revision %s of %s', oldidData); log('revisionString='+revisionString); switch (l.action) { case 'edit&section=new': hint = popupString('newSectionHint'); break; case 'edit&undo=': if (l.diff && l.diff != 'prev' && savedOldid ) { l.action += l.diff + '&undoafter=' + savedOldid; } else if (savedOldid) { l.action += savedOldid; } hint = popupString('undoHint'); break; case 'raw&ctype=text/css': hint=popupString('rawHint'); break; case 'revert': var p=parseParams(pg.current.link.href); l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=' + revertSummary(l.oldid, p.diff); if (p.diff=='prev') { l.action += '&direction=prev'; revisionString = tprintf('the revision prior to revision %s of %s', oldidData); } if (getValueOf('popupRevertSummaryPrompt')) { l.action += '&autosummaryprompt=true'; } if (getValueOf('popupMinorReverts')) { l.action += '&autominor=true'; } log('revisionString is now '+revisionString); break; case 'nullEdit': l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=null'; break; case 'historyfeed': l.action='history&feed=rss'; break; case 'markpatrolled': l.action='markpatrolled&rcid='+l.rcid; } if (hint) { if (l.oldid) { hint = simplePrintf(hint, [revisionString]); } else { hint = simplePrintf(hint, [safeDecodeURI(l.article)]); } } else { hint = safeDecodeURI(l.article + '&action=' + l.action) + (l.oldid) ? '&oldid='+l.oldid : ''; } return titledWikiLink({article: l.article, action: l.action, text: l.text, newWin:l.newWin, title: hint, oldid: l.oldid, noPopup: l.noPopup, onclick: l.onclick}); } function revertSummary(oldid, diff) { var ret=''; if (diff == 'prev') { ret=getValueOf('popupQueriedRevertToPreviousSummary'); } else { ret = getValueOf('popupQueriedRevertSummary'); } return ret + '&autorv=' + oldid; } function titledWikiLink(l) { // possible properties of argument: // article, action, text, title, oldid, actionName, className, noPopup // oldid = null is fine here // article and action are mandatory args if (typeof l.article == 'undefined' || typeof l.action=='undefined') { errlog('got undefined article or action in titledWikiLink'); return null; } var base = pg.wiki.titlebase + l.article.urlString(); var url=base; if (typeof l.actionName=='undefined' || !l.actionName) { l.actionName='action'; } // no need to add &action=view, and this confuses anchors if (l.action != 'view') { url = base + '&' + l.actionName + '=' + l.action; } if (typeof l.oldid!='undefined' && l.oldid) { url+='&oldid='+l.oldid; } var cssClass=pg.misc.defaultNavlinkClassname; if (typeof l.className!='undefined' && l.className) { cssClass=l.className; } return generalNavLink({url: url, newWin: l.newWin, title: (typeof l.title != 'undefined') ? l.title : null, text: (typeof l.text!='undefined')?l.text:null, className: cssClass, noPopup:l.noPopup, onclick:l.onclick}); } pg.fn.getLastContrib = function getLastContrib(wikipage, newWin) { getHistoryInfo(wikipage, function(x) { processLastContribInfo(x, {page: wikipage, newWin: newWin}); }); }; function processLastContribInfo(info, stuff) { if(!info.edits || !info.edits.length) { alert('Popups: an odd thing happened. Please retry.'); return; } if(!info.firstNewEditor) { alert(tprintf('Only found one editor: %s made %s edits', [info.edits[0].editor,info.edits.length])); return; } var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+info.firstNewEditor.oldid; displayUrl(newUrl, stuff.newWin); } pg.fn.getDiffSinceMyEdit = function getDiffSinceMyEdit(wikipage, newWin) { getHistoryInfo(wikipage, function(x){ processDiffSinceMyEdit(x, {page: wikipage, newWin: newWin}); }); }; function processDiffSinceMyEdit(info, stuff) { if(!info.edits || !info.edits.length) { alert('Popups: something fishy happened. Please try again.'); return; } var friendlyName=stuff.page.split('_').join(' '); if(!info.myLastEdit) { alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s', [info.userName, getValueOf('popupHistoryLimit'), friendlyName])); return; } if(info.myLastEdit.index === 0) { alert(tprintf("%s seems to be the last editor to the page %s", [info.userName, friendlyName])); return; } var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+ info.myLastEdit.oldid; displayUrl(newUrl, stuff.newWin); } function displayUrl(url, newWin){ if(newWin) { window.open(url); } else { document.location=url; } } pg.fn.purgePopups = function purgePopups() { processAllPopups(true); setupCache(); // deletes all cached items (not browser cached, though...) pg.option={}; abortAllDownloads(); }; function processAllPopups(nullify, banish) { for (var i=0; pg.current.links && i<pg.current.links.length; ++i) { if (!pg.current.links[i].navpopup) { continue; } if (nullify || banish) pg.current.links[i].navpopup.banish(); pg.current.links[i].simpleNoMore=false; if (nullify) pg.current.links[i].navpopup=null; } } pg.fn.disablePopups = function disablePopups(){ processAllPopups(false, true); setupTooltips(null, true); }; pg.fn.togglePreviews = function togglePreviews() { processAllPopups(true, true); pg.option.simplePopups=!pg.option.simplePopups; abortAllDownloads(); }; function magicWatchLink(l) { //Yuck!! Would require a thorough redesign to add this as a click event though ... l.onclick = simplePrintf( 'pg.fn.modifyWatchlist(\'%s\',\'%s\');return false;', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), this.id] ); return wikiLink(l); } pg.fn.modifyWatchlist = function modifyWatchlist(title, action) { var reqData = { 'action': 'watch', 'formatversion': 2, 'titles': title, 'uselang': mw.config.get('wgUserLanguage') }; if ( action === 'unwatch' ) reqData.unwatch = true; var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); // Load the Addedwatchtext or Removedwatchtext message and show it var mwTitle = mw.Title.newFromText( title ); var messageName; if ( mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1 ) { messageName = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk'; } else { messageName = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext'; } $.when( api.postWithToken( 'watch', reqData ), mw.loader.using( [ 'mediawiki.api', 'mediawiki.jqueryMsg' ] ).then( function () { return api.loadMessagesIfMissing( [ messageName ] ); } ) ).done( function () { mw.notify( mw.message( messageName, title ).parseDom() ); } ); }; function magicHistoryLink(l) { // FIXME use onclick change href trick to sort this out instead of window.open var jsUrl='', title='', onClick=''; switch(l.id) { case 'lastContrib': onClick=simplePrintf('pg.fn.getLastContrib(\'%s\',%s)', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]); title=popupString('lastContribHint'); break; case 'sinceMe': onClick=simplePrintf('pg.fn.getDiffSinceMyEdit(\'%s\',%s)', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]); title=popupString('sinceMeHint'); break; } jsUrl = 'javascript:' + onClick; // jshint ignore:line onClick += ';return false;'; return generalNavLink({url: jsUrl, newWin: false, // can't have new windows with JS links, I think title: title, text: l.text, noPopup: l.noPopup, onclick: onClick }); } function popupMenuLink(l) { var jsUrl=simplePrintf('javascript:pg.fn.%s()', [l.id]); // jshint ignore:line var title=popupString(simplePrintf('%sHint', [l.id])); var onClick=simplePrintf('pg.fn.%s();return false;', [l.id]); return generalNavLink({url: jsUrl, newWin:false, title:title, text:l.text, noPopup:l.noPopup, onclick: onClick}); } function specialLink(l) { // properties: article, specialpage, text, sep if (typeof l.specialpage=='undefined'||!l.specialpage) return null; var base = pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId]+':'+l.specialpage; if (typeof l.sep == 'undefined' || l.sep === null) l.sep='&target='; var article=l.article.urlString({keepSpaces: l.specialpage=='Search'}); var hint=popupString(l.specialpage+'Hint'); switch (l.specialpage) { case 'Log': switch (l.sep) { case '&user=': hint=popupString('userLogHint'); break; case '&type=block&page=': hint=popupString('blockLogHint'); break; case '&page=': hint=popupString('pageLogHint'); break; case '&type=protect&page=': hint=popupString('protectLogHint'); break; case '&type=delete&page=': hint=popupString('deleteLogHint'); break; default: log('Unknown log type, sep=' + l.sep); hint='Missing hint (FIXME)'; } break; case 'PrefixIndex': article += '/'; break; } if (hint) hint = simplePrintf(hint, [safeDecodeURI(l.article)]); else hint = safeDecodeURI(l.specialpage+':'+l.article) ; var url = base + l.sep + article; return generalNavLink({url: url, title: hint, text: l.text, newWin:l.newWin, noPopup:l.noPopup}); } function generalLink(l) { // l.url, l.text, l.title, l.newWin, l.className, l.noPopup, l.onclick if (typeof l.url=='undefined') return null; // only quotation marks in the url can screw us up now... I think var url=l.url.split('"').join('%22'); var ret='<a href="' + url + '"'; if (typeof l.title!='undefined' && l.title) { ret += ' title="' + pg.escapeQuotesHTML(l.title) + '"'; } if (typeof l.onclick!='undefined' && l.onclick) { ret += ' onclick="' + pg.escapeQuotesHTML(l.onclick) + '"'; } if (l.noPopup) { ret += ' noPopup=1'; } var newWin; if (typeof l.newWin=='undefined' || l.newWin === null) { newWin=getValueOf('popupNewWindows'); } else { newWin=l.newWin; } if (newWin) { ret += ' target="_blank"'; } if (typeof l.className!='undefined'&&l.className) { ret+=' class="'+l.className+'"'; } ret += '>'; if (typeof l.text==typeof '') { ret+= l.text; } ret +='</a>'; return ret; } function appendParamsToLink(linkstr, params) { var sp=linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i')); if (sp.length<2) return null; var ret=sp.shift() + sp.shift(); ret += '&' + params + '"'; ret += sp.join(''); return ret; } function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor, title (optional), alsoChangeLabel { if (x.newTarget) { log ('changeLinkTargetLink: newTarget=' + x.newTarget); } if (x.oldTarget !== decodeURIComponent( x.oldTarget ) ) { log ('This might be an input problem: ' + x.oldTarget ); } // FIXME: first character of page title as well as namespace should be case insensitive // eg [[category:X1]] and [[Category:X1]] are equivalent // this'll break if charAt(0) is nasty var cA = mw.util.escapeRegExp(x.oldTarget); var chs = cA.charAt(0).toUpperCase(); chs='['+chs + chs.toLowerCase()+']'; var currentArticleRegexBit=chs+cA.substring(1); currentArticleRegexBit=currentArticleRegexBit .split(RegExp('(?:[_ ]+|%20)', 'g')).join('(?:[_ ]+|%20)') .split('\\(').join('(?:%28|\\()') .split('\\)').join('(?:%29|\\))'); // why does this need to match encoded strings ? links in the document ? // leading and trailing space should be ignored, and anchor bits optional: currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + '(?:#[^\\[\\|]*)?)\\s*'; // e.g. Computer (archaic) -> \s*([Cc]omputer[_ ](?:%2528|\()archaic(?:%2528|\)))\s* // autoedit=s~\[\[([Cc]ad)\]\]~[[Computer-aided%20design|$1]]~g;s~\[\[([Cc]AD)[|]~[[Computer-aided%20design|~g var title=x.title || mw.config.get('wgPageName').split('_').join(' '); var lk=titledWikiLink({article: new Title(title), newWin:x.newWin, action: 'edit', text: x.text, title: x.hint, className: 'popup_change_title_link' }); var cmd=''; if (x.newTarget) { // escape '&' and other nasties var t = x.newTarget; var s = mw.util.escapeRegExp(x.newTarget); if (x.alsoChangeLabel) { cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + ']]~g;'; cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;'; cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g'; } else { cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + '|$1]]~g;'; cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;'; cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g'; } } else { cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~$1~g;'; cmd += 's~\\[\\['+currentArticleRegexBit+'[|](.*?)\\]\\]~$2~g'; } // Build query cmd = 'autoedit=' + encodeURIComponent ( cmd ); cmd += '&autoclick='+ encodeURIComponent( x.clickButton ) + '&actoken=' + encodeURIComponent( autoClickToken() ); cmd += ( x.minor === null ) ? '' : '&autominor='+ encodeURIComponent( x.minor ); cmd += ( x.watch === null ) ? '' : '&autowatch='+ encodeURIComponent( x.watch ); cmd += '&autosummary='+encodeURIComponent(x.summary); cmd += '&autoimpl='+encodeURIComponent( popupString('autoedit_version') ); return appendParamsToLink(lk, cmd); } function redirLink(redirMatch, article) { // NB redirMatch is in wikiText var ret=''; if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) { ret += '<hr />'; if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) { ret += popupString('Redirects to: (Fix '); log('redirLink: newTarget=' + redirMatch); ret += addPopupShortcut(changeLinkTargetLink({ newTarget: redirMatch, text: popupString('target'), hint: popupString('Fix this redirect, changing just the link target'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]), oldTarget: article.toString(), clickButton: getValueOf('popupRedirAutoClick'), minor: true, watch: getValueOf('popupWatchRedirredPages') }), 'R'); ret += popupString(' or '); ret += addPopupShortcut(changeLinkTargetLink({ newTarget: redirMatch, text: popupString('target & label'), hint: popupString('Fix this redirect, changing the link target and label'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]), oldTarget: article.toString(), clickButton: getValueOf('popupRedirAutoClick'), minor: true, watch: getValueOf('popupWatchRedirredPages'), alsoChangeLabel: true }), 'R'); ret += popupString(')'); } else ret += popupString('Redirects') + popupString(' to '); return ret; } else return '<br> ' + popupString('Redirects') + popupString(' to ') + titledWikiLink({article: new Title().fromWikiText(redirMatch), action: 'view', /* FIXME: newWin */ text: safeDecodeURI(redirMatch), title: popupString('Bypass redirect')}); } function arinLink(l) { if (!saneLinkCheck(l)) { return null; } if ( ! l.article.isIpUser() || ! pg.wiki.wikimedia) return null; var uN=l.article.userName(); return generalNavLink({url:'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + encodeURIComponent(uN), newWin:l.newWin, title: tprintf('Look up %s in ARIN whois database', [uN]), text: l.text, noPopup:1}); } function toolDbName(cookieStyle) { var ret = mw.config.get('wgDBname'); if (!cookieStyle) { ret+= '_p'; } return ret; } function saneLinkCheck(l) { if (typeof l.article != typeof {} || typeof l.text != typeof '') { return false; } return true; } function editCounterLink(l) { if(!saneLinkCheck(l)) return null; if (! pg.wiki.wikimedia) return null; var uN=l.article.userName(); var tool=getValueOf('popupEditCounterTool'); var url; var defaultToolUrl='//tools.wmflabs.org/supercount/index.php?user=$1&project=$2.$3'; switch(tool) { case 'custom': url=simplePrintf(getValueOf('popupEditCounterUrl'), [ encodeURIComponent(uN), toolDbName() ]); break; case 'soxred': // no longer available case 'kate': // no longer available case 'interiot':// no longer available /* fall through */ case 'supercount': default: var theWiki=pg.wiki.hostname.split('.'); url=simplePrintf(defaultToolUrl, [ encodeURIComponent(uN), theWiki[0], theWiki[1] ]); } return generalNavLink({url:url, title: tprintf('editCounterLinkHint', [uN]), newWin:l.newWin, text: l.text, noPopup:1}); } function globalSearchLink(l) { if(!saneLinkCheck(l)) return null; var base='http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search='; var article=l.article.urlString({keepSpaces:true}); return generalNavLink({url:base + article, newWin:l.newWin, title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]), text: l.text, noPopup:1}); } function googleLink(l) { if(!saneLinkCheck(l)) return null; var base='https://www.google.com/search?q='; var article=l.article.urlString({keepSpaces:true}); return generalNavLink({url:base + '%22' + article + '%22', newWin:l.newWin, title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]), text: l.text, noPopup:1}); } function editorListLink(l) { if(!saneLinkCheck(l)) return null; var article= l.article.articleFromTalkPage() || l.article; var url='https://xtools.wmflabs.org/articleinfo/' + encodeURI( pg.wiki.hostname ) + '/' + article.urlString() + '?uselang=' + mw.config.get('wgUserLanguage'); return generalNavLink({url:url, title: tprintf('editorListHint', [article]), newWin:l.newWin, text: l.text, noPopup:1}); } function generalNavLink(l) { l.className = (l.className === null) ? 'popupNavLink' : l.className; return generalLink(l); } ////////////////////////////////////////////////// // magic history links // function getHistoryInfo(wikipage, whatNext) { log('getHistoryInfo'); getHistory(wikipage, whatNext ? function(d){whatNext(processHistory(d));} : processHistory); } // FIXME eliminate pg.idNumber ... how? :-( function getHistory(wikipage, onComplete) { log('getHistory'); var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&prop=revisions&titles=' + new Title(wikipage).urlString() + '&rvlimit=' + getValueOf('popupHistoryLimit'); log('getHistory: url='+url); return startDownload(url, pg.idNumber+'history', onComplete); } function processHistory(download) { var jsobj = getJsObj(download.data); try { var revisions = anyChild(jsobj.query.pages).revisions; var edits=[]; for (var i=0; i<revisions.length; ++i) { edits.push({ oldid: revisions[i].revid, editor: revisions[i].user }); } log('processed ' + edits.length + ' edits'); return finishProcessHistory( edits, mw.config.get('wgUserName') ); } catch (someError) { log('Something went wrong with JSON business'); return finishProcessHistory([]); } } function finishProcessHistory(edits, userName) { var histInfo={}; histInfo.edits=edits; histInfo.userName=userName; for (var i=0; i<edits.length; ++i) { if (typeof histInfo.myLastEdit === 'undefined' && userName && edits[i].editor==userName) { histInfo.myLastEdit={index: i, oldid: edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)}; } if (typeof histInfo.firstNewEditor === 'undefined' && edits[i].editor != edits[0].editor) { histInfo.firstNewEditor={index:i, oldid:edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)}; } } //pg.misc.historyInfo=histInfo; return histInfo; } //</NOLITE> // ENDFILE: links.js // STARTFILE: options.js ////////////////////////////////////////////////// // options // check for existing value, else use default function defaultize(x) { if (pg.option[x]===null || typeof pg.option[x]=='undefined') { if (typeof window[x] != 'undefined' ) pg.option[x]=window[x]; else pg.option[x]=pg.optionDefault[x]; } } function newOption(x, def) { pg.optionDefault[x]=def; } function setDefault(x, def) { return newOption(x, def); } function getValueOf(varName) { defaultize(varName); return pg.option[varName]; } /*eslint-disable */ function useDefaultOptions() { // for testing for (var p in pg.optionDefault) { pg.option[p]=pg.optionDefault[p]; if (typeof window[p]!='undefined') { delete window[p]; } } } /*eslint-enable */ function setOptions() { // user-settable parameters and defaults var userIsSysop = false; if ( mw.config.get('wgUserGroups') ) { for ( var g = 0; g < mw.config.get('wgUserGroups').length; ++g ) { if ( mw.config.get('wgUserGroups')[g] == "sysop" ) userIsSysop = true; } } // Basic options newOption('popupDelay', 0.5); newOption('popupHideDelay', 0.5); newOption('simplePopups', false); newOption('popupStructure', 'shortmenus'); // see later - default for popupStructure is 'original' if simplePopups is true newOption('popupActionsMenu', true); newOption('popupSetupMenu', true); newOption('popupAdminLinks', userIsSysop); newOption('popupShortcutKeys', false); newOption('popupHistoricalLinks', true); newOption('popupOnlyArticleLinks', true); newOption('removeTitles', true); newOption('popupMaxWidth', 350); newOption('popupSimplifyMainLink', true); newOption('popupAppendRedirNavLinks', true); newOption('popupTocLinks', false); newOption('popupSubpopups', true); newOption('popupDragHandle', false /* 'popupTopLinks'*/); newOption('popupLazyPreviews', true); newOption('popupLazyDownloads', true); newOption('popupAllDabsStubs', false); newOption('popupDebugging', false); newOption('popupActiveNavlinks', true); newOption('popupModifier', false); // ctrl, shift, alt or meta newOption('popupModifierAction', 'enable'); // or 'disable' newOption('popupDraggable', true); newOption('popupReview', false); //<NOLITE> // images newOption('popupImages', true); newOption('imagePopupsForImages', true); newOption('popupNeverGetThumbs', false); //newOption('popupImagesToggleSize', true); newOption('popupThumbAction', 'imagepage'); //'sizetoggle'); newOption('popupImageSize', 60); newOption('popupImageSizeLarge', 200); // redirs, dabs, reversion newOption('popupFixRedirs', false); newOption('popupRedirAutoClick', 'wpDiff'); newOption('popupFixDabs', false); newOption('popupDabsAutoClick', 'wpDiff'); newOption('popupRevertSummaryPrompt', false); newOption('popupMinorReverts', false); newOption('popupRedlinkRemoval', false); newOption('popupRedlinkAutoClick', 'wpDiff'); newOption('popupWatchDisambiggedPages', null); newOption('popupWatchRedirredPages', null); newOption('popupDabWiktionary', 'last'); // navlinks newOption('popupNavLinks', true); newOption('popupNavLinkSeparator', ' &sdot; '); newOption('popupLastEditLink', true); newOption('popupEditCounterTool', 'supercount'); newOption('popupEditCounterUrl', ''); //</NOLITE> // previews etc newOption('popupPreviews', true); newOption('popupSummaryData', true); newOption('popupMaxPreviewSentences', 9); newOption('popupMaxPreviewCharacters', 900); newOption('popupLastModified', true); newOption('popupPreviewKillTemplates', true); newOption('popupPreviewRawTemplates', true); newOption('popupPreviewFirstParOnly', false); newOption('popupPreviewCutHeadings', false); newOption('popupPreviewButton', false); newOption('popupPreviewButtonEvent', 'click'); //<NOLITE> // diffs newOption('popupPreviewDiffs', true); newOption('popupDiffMaxLines', 100); newOption('popupDiffContextLines', 2); newOption('popupDiffContextCharacters', 40); newOption('popupDiffDates', true); newOption('popupDiffDatePrinter', 'toLocaleString'); // edit summaries. God, these are ugly. newOption('popupReviewedSummary', popupString('defaultpopupReviewedSummary') ); newOption('popupFixDabsSummary', popupString('defaultpopupFixDabsSummary') ); newOption('popupExtendedRevertSummary', popupString('defaultpopupExtendedRevertSummary') ); newOption('popupRevertSummary', popupString('defaultpopupRevertSummary') ); newOption('popupRevertToPreviousSummary', popupString('defaultpopupRevertToPreviousSummary') ); newOption('popupQueriedRevertSummary', popupString('defaultpopupQueriedRevertSummary') ); newOption('popupQueriedRevertToPreviousSummary', popupString('defaultpopupQueriedRevertToPreviousSummary') ); newOption('popupFixRedirsSummary', popupString('defaultpopupFixRedirsSummary') ); newOption('popupRedlinkSummary', popupString('defaultpopupRedlinkSummary') ); newOption('popupRmDabLinkSummary', popupString('defaultpopupRmDabLinkSummary') ); //</NOLITE> // misc newOption('popupHistoryLimit', 50); //<NOLITE> newOption('popupFilters', [popupFilterStubDetect, popupFilterDisambigDetect, popupFilterPageSize, popupFilterCountLinks, popupFilterCountImages, popupFilterCountCategories, popupFilterLastModified]); newOption('extraPopupFilters', []); newOption('popupOnEditSelection', 'cursor'); newOption('popupPreviewHistory', true); newOption('popupImageLinks', true); newOption('popupCategoryMembers', true); newOption('popupUserInfo', true); newOption('popupHistoryPreviewLimit', 25); newOption('popupContribsPreviewLimit',25); newOption('popupRevDelUrl', '//en.wikipedia.org/wiki/Wikipedia:Revision_deletion'); newOption('popupShowGender', true); //</NOLITE> // new windows newOption('popupNewWindows', false); newOption('popupLinksNewWindow', {'lastContrib': true, 'sinceMe': true}); // regexps newOption('popupDabRegexp', '(\\{\\{\\s*disambig(?!uation needed)|disambig(uation|)\\s*\\}\\}|disamb\\s*\\}\\}|dab\\s*\\}\\})|\\{\\{\\s*(((geo|hn|road?|school|number)dis)|[234][lc][acw]|(road|ship)index)(\\s*[|][^}]*)?\\s*[}][}]|is a .*disambiguation.*page'); newOption('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub'); newOption('popupImageVarsRegexp', 'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo'); } // ENDFILE: options.js // STARTFILE: strings.js //<NOLITE> ////////////////////////////////////////////////// // Translatable strings ////////////////////////////////////////////////// // // See instructions at // https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation pg.string = { ///////////////////////////////////// // summary data, searching etc. ///////////////////////////////////// 'article': 'article', 'category': 'category', 'categories': 'categories', 'image': 'image', 'images': 'images', 'stub': 'stub', 'section stub': 'section stub', 'Empty page': 'Empty page', 'kB': 'kB', 'bytes': 'bytes', 'day': 'day', 'days': 'days', 'hour': 'hour', 'hours': 'hours', 'minute': 'minute', 'minutes': 'minutes', 'second': 'second', 'seconds': 'seconds', 'week': 'week', 'weeks': 'weeks', 'search': 'search', 'SearchHint': 'Find English Wikipedia articles containing %s', 'web': 'web', 'global': 'global', 'globalSearchHint': 'Search across Wikipedias in different languages for %s', 'googleSearchHint': 'Google for %s', ///////////////////////////////////// // article-related actions and info // (some actions also apply to user pages) ///////////////////////////////////// 'actions': 'actions', ///// view articles and view talk 'popupsMenu': 'popups', 'togglePreviewsHint': 'Toggle preview generation in popups on this page', 'enable previews': 'enable previews', 'disable previews': 'disable previews', 'toggle previews': 'toggle previews', 'show preview': 'show preview', 'reset': 'reset', 'more...': 'more...', 'disable': 'disable popups', 'disablePopupsHint': 'Disable popups on this page. Reload page to re-enable.', 'historyfeedHint': 'RSS feed of recent changes to this page', 'purgePopupsHint': 'Reset popups, clearing all cached popup data.', 'PopupsHint': 'Reset popups, clearing all cached popup data.', 'spacebar': 'space', 'view': 'view', 'view article': 'view article', 'viewHint': 'Go to %s', 'talk': 'talk', 'talk page': 'talk page', 'this&nbsp;revision': 'this&nbsp;revision', 'revision %s of %s': 'revision %s of %s', 'Revision %s of %s': 'Revision %s of %s', 'the revision prior to revision %s of %s': 'the revision prior to revision %s of %s', 'Toggle image size': 'Click to toggle image size', 'del': 'del', ///// delete, protect, move 'delete': 'delete', 'deleteHint': 'Delete %s', 'undeleteShort': 'un', 'UndeleteHint': 'Show the deletion history for %s', 'protect': 'protect', 'protectHint': 'Restrict editing rights to %s', 'unprotectShort': 'un', 'unprotectHint': 'Allow %s to be edited by anyone again', 'send thanks': 'send thanks', 'ThanksHint': 'Send a thank you notification to this user', 'move': 'move', 'move page': 'move page', 'MovepageHint': 'Change the title of %s', 'edit': 'edit', ///// edit articles and talk 'edit article': 'edit article', 'editHint': 'Change the content of %s', 'edit talk': 'edit talk', 'new': 'new', 'new topic': 'new topic', 'newSectionHint': 'Start a new section on %s', 'null edit': 'null edit', 'nullEditHint': 'Submit an edit to %s, making no changes ', 'hist': 'hist', ///// history, diffs, editors, related 'history': 'history', 'historyHint': 'List the changes made to %s', 'last': 'prev', // For labelling the previous revision in history pages; the key is "last" for backwards compatibility 'lastEdit': 'lastEdit', 'mark patrolled': 'mark patrolled', 'markpatrolledHint': 'Mark this edit as patrolled', 'Could not marked this edit as patrolled': 'Could not marked this edit as patrolled', 'show last edit': 'most recent edit', 'Show the last edit': 'Show the effects of the most recent change', 'lastContrib': 'lastContrib', 'last set of edits': 'latest edits', 'lastContribHint': 'Show the net effect of changes made by the last editor', 'cur': 'cur', 'diffCur': 'diffCur', 'Show changes since revision %s': 'Show changes since revision %s', '%s old': '%s old', // as in 4 weeks old 'oldEdit': 'oldEdit', 'purge': 'purge', 'purgeHint': 'Demand a fresh copy of %s', 'raw': 'source', 'rawHint': 'Download the source of %s', 'render': 'simple', 'renderHint': 'Show a plain HTML version of %s', 'Show the edit made to get revision': 'Show the edit made to get revision', 'sinceMe': 'sinceMe', 'changes since mine': 'diff my edit', 'sinceMeHint': 'Show changes since my last edit', 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s': 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s', 'eds': 'eds', 'editors': 'editors', 'editorListHint': 'List the users who have edited %s', 'related': 'related', 'relatedChanges': 'relatedChanges', 'related changes': 'related changes', 'RecentchangeslinkedHint': 'Show changes in articles related to %s', 'editOld': 'editOld', ///// edit old version, or revert 'rv': 'rv', 'revert': 'revert', 'revertHint': 'Revert to %s', 'defaultpopupReviewedSummary': 'Accepted by reviewing the [[Special:diff/%s/%s|difference]] between this version and previously accepted version using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRedlinkSummary': 'Removing link to empty page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupFixDabsSummary': 'Disambiguate [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupFixRedirsSummary': 'Redirect bypass from [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupExtendedRevertSummary': 'Revert to revision dated %s by %s, oldid %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRevertToPreviousSummary': 'Revert to the revision prior to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRevertSummary': 'Revert to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupQueriedRevertToPreviousSummary': 'Revert to the revision prior to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupQueriedRevertSummary': 'Revert to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRmDabLinkSummary': 'Remove link to dab page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'Redirects': 'Redirects', // as in Redirects to ... ' to ': ' to ', // as in Redirects to ... 'Bypass redirect': 'Bypass redirect', 'Fix this redirect': 'Fix this redirect', 'disambig': 'disambig', ///// add or remove dab etc. 'disambigHint': 'Disambiguate this link to [[%s]]', 'Click to disambiguate this link to:': 'Click to disambiguate this link to:', 'remove this link': 'remove this link', 'remove all links to this page from this article': 'remove all links to this page from this article', 'remove all links to this disambig page from this article': 'remove all links to this disambig page from this article', 'mainlink': 'mainlink', ///// links, watch, unwatch 'wikiLink': 'wikiLink', 'wikiLinks': 'wikiLinks', 'links here': 'links here', 'whatLinksHere': 'whatLinksHere', 'what links here': 'what links here', 'WhatlinkshereHint': 'List the pages that are hyperlinked to %s', 'unwatchShort': 'un', 'watchThingy': 'watch', // called watchThingy because {}.watch is a function 'watchHint': 'Add %s to my watchlist', 'unwatchHint': 'Remove %s from my watchlist', 'Only found one editor: %s made %s edits': 'Only found one editor: %s made %s edits', '%s seems to be the last editor to the page %s': '%s seems to be the last editor to the page %s', 'rss': 'rss', ///////////////////////////////////// // diff previews ///////////////////////////////////// 'Diff truncated for performance reasons': 'Diff truncated for performance reasons', 'Old revision': 'Old revision', 'New revision': 'New revision', 'Something went wrong :-(': 'Something went wrong :-(', 'Empty revision, maybe non-existent': 'Empty revision, maybe non-existent', 'Unknown date': 'Unknown date', ///////////////////////////////////// // other special previews ///////////////////////////////////// 'Empty category': 'Empty category', 'Category members (%s shown)': 'Category members (%s shown)', 'No image links found': 'No image links found', 'File links': 'File links', 'No image found': 'No image found', 'Image from Commons': 'Image from Commons', 'Description page': 'Description page', 'Alt text:': 'Alt text:', 'revdel':'Hidden revision', ///////////////////////////////////// // user-related actions and info ///////////////////////////////////// 'user': 'user', ///// user page, talk, email, space 'user&nbsp;page': 'user&nbsp;page', 'user talk': 'user talk', 'edit user talk': 'edit user talk', 'leave comment': 'leave comment', 'email': 'email', 'email user': 'email user', 'EmailuserHint': 'Send an email to %s', 'space': 'space', // short form for userSpace link 'PrefixIndexHint': 'Show pages in the userspace of %s', 'count': 'count', ///// contributions, log 'edit counter': 'edit counter', 'editCounterLinkHint': 'Count the contributions made by %s', 'contribs': 'contribs', 'contributions': 'contributions', 'deletedContribs': 'deleted contributions', 'DeletedcontributionsHint': 'List deleted edits made by %s', 'ContributionsHint': 'List the contributions made by %s', 'log': 'log', 'user log': 'user log', 'userLogHint': 'Show %s\'s user log', 'arin': 'ARIN lookup', ///// ARIN lookup, block user or IP 'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database', 'unblockShort': 'un', 'block': 'block', 'block user': 'block user', 'IpblocklistHint': 'Unblock %s', 'BlockipHint': 'Prevent %s from editing', 'block log': 'block log', 'blockLogHint': 'Show the block log for %s', 'protectLogHint': 'Show the protection log for %s', 'pageLogHint': 'Show the page log for %s', 'deleteLogHint': 'Show the deletion log for %s', 'Invalid %s %s': 'The option %s is invalid: %s', 'No backlinks found': 'No backlinks found', ' and more': ' and more', 'undo': 'undo', 'undoHint': 'undo this edit', 'Download preview data': 'Download preview data', 'Invalid or IP user': 'Invalid or IP user', 'Not a registered username': 'Not a registered username', 'CẤM': 'CẤM', 'Has blocks': 'Has blocks', ' sửa đổi từ: ': ' sửa đổi từ: ', 'sửa đổi lần cuối vào ': 'sửa đổi lần cuối vào ', ///////////////////////////////////// // Autoediting ///////////////////////////////////// 'Enter a non-empty edit summary or press cancel to abort': 'Enter a non-empty edit summary or press cancel to abort', 'Failed to get revision information, please edit manually.\n\n': 'Failed to get revision information, please edit manually.\n\n', 'The %s button has been automatically clicked. Please wait for the next page to load.': 'The %s button has been automatically clicked. Please wait for the next page to load.', 'Could not find button %s. Please check the settings in your javascript file.': 'Could not find button %s. Please check the settings in your javascript file.', ///////////////////////////////////// // Popups setup ///////////////////////////////////// 'Open full-size image': 'Open full-size image', 'zxy': 'zxy', 'autoedit_version': 'np20140416' }; function popupString(str) { if (typeof popupStrings != 'undefined' && popupStrings && popupStrings[str]) { return popupStrings[str]; } if (pg.string[str]) { return pg.string[str]; } return str; } function tprintf(str,subs) { if (typeof subs != typeof []) { subs = [subs]; } return simplePrintf(popupString(str), subs); } //</NOLITE> // ENDFILE: strings.js // STARTFILE: run.js //////////////////////////////////////////////////////////////////// // Run things //////////////////////////////////////////////////////////////////// // For some reason popups requires a fully loaded page jQuery.ready(...) causes problems for some. // The old addOnloadHook did something similar to the below if (document.readyState=="complete") autoEdit(); //will setup popups else $( window ).on( 'load', autoEdit ); // Support for MediaWiki's live preview, VisualEditor's saves and Echo's flyout. ( function () { var once = true; function dynamicContentHandler( $content ) { // Try to detect the hook fired on initial page load and disregard // it, we already hook to onload (possibly to different parts of // page - it's configurable) and running twice might be bad. Ugly… if ( $content.attr( 'id' ) == 'mw-content-text' ) { if ( once ) { once = false; return; } } function registerHooksForVisibleNavpops () { for (var i=0; pg.current.links && i<pg.current.links.length; ++i) { var navpop = pg.current.links[i].navpopup; if (!navpop || !navpop.isVisible()) { continue; } Navpopup.tracker.addHook(posCheckerHook(navpop)); } } function doIt () { registerHooksForVisibleNavpops(); $content.each( function () { this.ranSetupTooltipsAlready = false; setupTooltips( this ); } ); } setupPopups( doIt ); } // This hook is also fired after page load. mw.hook( 'wikipage.content' ).add( dynamicContentHandler ); mw.hook( 'ext.echo.overlay.beforeShowingOverlay' ).add( function($overlay){ dynamicContentHandler( $overlay.find(".mw-echo-state") ); }); } )(); // Forked from User:Connel MacKenzie/mess-with-popups.js Previewmaker.prototype.firstBit = function() { try{ if (mw.config.get("wgNamespaceNumber") !== 0){ // dont't be givin' me no subsequent paragraphs, you hear me? /// first we "normalize" section headings, removing whitespace after, adding before var d = this.data; if (getValueOf('popupPreviewCutHeadings')) { this.data = this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 '); /// then we want to get rid of paragraph breaks whose text ends badly this.data = this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n'); this.data = this.data.replace(RegExp('^[\\s\\n]*'), ''); stuff = (RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data); if (stuff) { d = stuff[0]; } if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; } /// now put \n\n after sections so that bullets and numbered lists work d = d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n'); } // superfluous sentences are RIGHT OUT. // note: exactly 1 set of parens here needed to make the slice work d = d.parenSplit(RegExp('([!?.]+["' + "'" + ']*\\s)', 'g')); // leading space is bad, mmkay? d[0] = d[0].replace(RegExp('^\\s*'), ''); var notSentenceEnds = RegExp('([^.][a-z][.][a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|\\[[^\\]]*|\\s[A-Zvclm])$', 'i'); d = this.fixSentenceEnds(d, notSentenceEnds); var maxChars=getValueOf('popupMaxPreviewCharacters'); var n = getValueOf('popupMaxPreviewSentences'); var dd; do { dd = this.firstSentences(d, n); --n; } while (dd.length > maxChars && n > 0); this.data = dd; } else { //TODO: Limit this override to urls that start with http://*.wiktionary.org/ //TODO: extend the above section to also preview Wikipedia links var charsWikt = 0; var linesWikt = this.data.split('\n'); for (jWikt = 0; jWikt < linesWikt.length; jWikt++) { linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{[in]transitive}}/g, ''); linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{[un]countable}}/g, ''); linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{cattag\|(.*?)}}/g, ""); linesWikt[jWikt] = linesWikt[jWikt].replace(/[\{\}]/g, "'"); if (linesWikt[jWikt].search(/^[#]/) == -1) { if (linesWikt[jWikt].search(/^==[A-Z]/) == -1) { linesWikt[jWikt] = ''; } } charsWikt += linesWikt[jWikt].length; } if (charsWikt < 2) { linesWikt[0] = "No properly formatted content"; } var maxChars = getValueOf('popupMaxPreviewCharacters'); var n = getValueOf('popupMaxPreviewSentences'); var dd = ''; for (jWikt = 0; jWikt < linesWikt.length; jWikt++) { if (linesWikt[jWikt] !== '') { dd += linesWikt[jWikt] + '\n'; } if (dd.length < maxChars) { this.data = dd; } } } } catch (e) { console.log("error from mess with popups: ", e); } }; }); iqm3e9u946v48zncugkw15jz5v1cn7x 2513982 2513951 2026-08-14T08:12:27Z Hiyuune 50834 2513982 javascript text/javascript // {{documentation}} // _________________________________________________________________________________________ // | | // | === WARNING: GLOBAL GADGET FILE === | // | Changes to this page affect many users. | // | Please discuss changes on the [[Wiktionary:Grease Pit]] before editing. | // |_________________________________________________________________________________________| mw.loader.load('//en.wikipedia.org/w/index.php?action=raw&title=MediaWiki:Gadget-navpop.css&ctype=text/css', "text/css"); // Forked from [[:w:MediaWiki:Gadget-popups.js]] $(function () { ////////////////////////////////////////////////// // Globals // // Trying to shove as many of these as possible into the pg (popup globals) object var pg = { re: {}, // regexps ns: {}, // namespaces string: {}, // translatable strings wiki: {}, // local site info user: {}, // current user info misc: {}, // YUCK PHOOEY option: {}, // options, see newOption etc optionDefault: {}, // default option values flag: {}, // misc flags cache: {}, // page and image cache structures: {}, // navlink structures timer: {}, // all sorts of timers (too damn many) counter: {}, // .. and all sorts of counters current: {}, // state info fn: {}, // functions endoflist: null }; /* Bail if the gadget/script is being loaded twice */ if( window.pg ) { return; } /* Export to global context */ window.pg = pg; /// Local Variables: /// /// mode:c /// /// End: /// // ENDFILE: main.js // STARTFILE: actions.js function setupTooltips(container, remove, force, popData) { log('setupTooltips, container='+container+', remove='+remove); if (!container) { //<NOLITE> // the main initial call if (getValueOf('popupOnEditSelection') && document && document.editform && document.editform.wpTextbox1) { document.editform.wpTextbox1.onmouseup=doSelectionPopup; } //</NOLITE> // article/content is a structure-dependent thing container = defaultPopupsContainer(); } if (!remove && !force && container.ranSetupTooltipsAlready) { return; } container.ranSetupTooltipsAlready = !remove; var anchors; anchors=container.getElementsByTagName('A'); setupTooltipsLoop(anchors, 0, 250, 100, remove, popData); } function defaultPopupsContainer() { if (getValueOf('popupOnlyArticleLinks')) { return document.getElementById('mw_content') || document.getElementById('content') || document.getElementById('article') || document; } return document; } function setupTooltipsLoop(anchors,begin,howmany,sleep, remove, popData) { log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments)); var finish=begin+howmany; var loopend = Math.min(finish, anchors.length); var j=loopend - begin; log ('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin + ', howmany=' + howmany + ', loopend=' + loopend + ', remove=' + remove); var doTooltip= remove ? removeTooltip : addTooltip; // try a faster (?) loop construct if (j > 0) { do { var a=anchors[loopend - j]; if (typeof a==='undefined' || !a || !a.href) { log('got null anchor at index ' + loopend - j); continue; } doTooltip(a, popData); } while (--j); } if (finish < anchors.length) { setTimeout(function() { setupTooltipsLoop(anchors,finish,howmany,sleep,remove,popData);}, sleep); } else { if ( !remove && ! getValueOf('popupTocLinks')) { rmTocTooltips(); } pg.flag.finishedLoading=true; } } // eliminate popups from the TOC // This also kills any onclick stuff that used to be going on in the toc function rmTocTooltips() { var toc=document.getElementById('toc'); if (toc) { var tocLinks=toc.getElementsByTagName('A'); var tocLen = tocLinks.length; for (var j=0; j<tocLen; ++j) { removeTooltip(tocLinks[j], true); } } } function addTooltip(a, popData) { if ( !isPopupLink(a) ) { return; } a.onmouseover=mouseOverWikiLink; a.onmouseout= mouseOutWikiLink; a.onmousedown = killPopup; a.hasPopup = true; a.popData = popData; } function removeTooltip(a) { if ( !a.hasPopup ) { return; } a.onmouseover = null; a.onmouseout = null; if (a.originalTitle) { a.title = a.originalTitle; } a.hasPopup=false; } function removeTitle(a) { if (!a.originalTitle) { a.originalTitle=a.title; } a.title=''; } function restoreTitle(a) { if ( a.title || !a.originalTitle ) { return; } a.title = a.originalTitle; } function registerHooks(np) { var popupMaxWidth=getValueOf('popupMaxWidth'); if (typeof popupMaxWidth === 'number') { var setMaxWidth = function () { np.mainDiv.style.maxWidth = popupMaxWidth + 'px'; np.maxWidth = popupMaxWidth; }; np.addHook(setMaxWidth, 'unhide', 'before'); } //<NOLITE> np.addHook(addPopupShortcuts, 'unhide', 'after'); np.addHook(rmPopupShortcuts, 'hide', 'before'); //</NOLITE> } function removeModifierKeyHandler(a) { //remove listeners for modifier key if any that were added in mouseOverWikiLink document.removeEventListener('keydown', a.modifierKeyHandler, false); document.removeEventListener('keyup', a.modifierKeyHandler, false); } function mouseOverWikiLink(evt) { if (!evt && window.event) {evt=window.event;} // if the modifier is needed, listen for it, // we will remove the listener when we mouseout of this link or kill popup. if (getValueOf('popupModifier')) { // if popupModifierAction = enable, we should popup when the modifier is pressed // if popupModifierAction = disable, we should popup unless the modifier is pressed var action = getValueOf('popupModifierAction'); var key = action=='disable' ? 'keyup' : 'keydown'; var a = this; a.modifierKeyHandler = function(evt) { mouseOverWikiLink2(a, evt); }; document.addEventListener(key, a.modifierKeyHandler, false); } return mouseOverWikiLink2(this, evt); } /** * Gets the references list item that the provided footnote link targets. This * is typically a li element within the ol.references element inside the reflist. * @param {Element} a - A footnote link. * @returns {Element|boolean} The targeted element, or false if one can't be found. */ function footnoteTarget(a) { var aTitle=Title.fromAnchor(a); // We want ".3A" rather than "%3A" or "?" here, so use the anchor property directly var anch = aTitle.anchor; if ( ! /^(cite_note-|_note-|endnote)/.test(anch) ) { return false; } var lTitle=Title.fromURL(location.href); if ( lTitle.toString(true) !== aTitle.toString(true) ) { return false; } var el=document.getElementById(anch); while ( el && typeof el.nodeName === 'string') { var nt = el.nodeName.toLowerCase(); if ( nt === 'li' ) { return el; } else if ( nt === 'body' ) { return false; } else if ( el.parentNode ) { el=el.parentNode; } else { return false; } } return false; } function footnotePreview(x, navpop) { setPopupHTML('<hr />' + x.innerHTML, 'popupPreview', navpop.idNumber); } function modifierPressed(evt) { var mod=getValueOf('popupModifier'); if (!mod) { return false; } if (!evt && window.event) {evt=window.event;} return ( evt && mod && evt[mod.toLowerCase() + 'Key'] ); } // Checks if the correct modifier pressed/unpressed if needed function isCorrectModifier(a,evt) { if (!getValueOf('popupModifier')) { return true; } // if popupModifierAction = enable, we should popup when the modifier is pressed // if popupModifierAction = disable, we should popup unless the modifier is pressed var action = getValueOf('popupModifierAction'); return ( action == 'enable' && modifierPressed(evt) || action == 'disable' && !modifierPressed(evt) ); } function mouseOverWikiLink2(a, evt) { if (!isCorrectModifier(a,evt)) { return; } if ( getValueOf('removeTitles') ) { removeTitle(a); } if ( a==pg.current.link && a.navpopup && a.navpopup.isVisible() ) { return; } pg.current.link=a; if (getValueOf('simplePopups') && !pg.option.popupStructure) { // reset *default value* of popupStructure setDefault('popupStructure', 'original'); } var article=(new Title()).fromAnchor(a); // set global variable (ugh) to hold article (wikipage) pg.current.article = article; if (!a.navpopup) { a.navpopup=newNavpopup(a, article); pg.current.linksHash[a.href] = a.navpopup; pg.current.links.push(a); } if (a.navpopup.pending === null || a.navpopup.pending !== 0) { // either fresh popups or those with unfinshed business are redone from scratch simplePopupContent(a, article); } a.navpopup.showSoonIfStable(a.navpopup.delay); clearInterval(pg.timer.checkPopupPosition); pg.timer.checkPopupPosition=setInterval(checkPopupPosition, 600); if(getValueOf('simplePopups')) { if (getValueOf('popupPreviewButton') && !a.simpleNoMore) { var d=document.createElement('div'); d.className='popupPreviewButtonDiv'; var s=document.createElement('span'); d.appendChild(s); s.className='popupPreviewButton'; s['on' + getValueOf('popupPreviewButtonEvent')] = function() { a.simpleNoMore=true; d.style.display = "none"; nonsimplePopupContent(a,article); }; s.innerHTML=popupString('show preview'); setPopupHTML(d, 'popupPreview', a.navpopup.idNumber); } } if (a.navpopup.pending !== 0 ) { nonsimplePopupContent(a, article); } } // simplePopupContent: the content that do not require additional download // (it is shown even when simplePopups is true) function simplePopupContent(a, article) { /* FIXME hack */ a.navpopup.hasPopupMenu=false; a.navpopup.setInnerHTML(popupHTML(a)); fillEmptySpans({navpopup:a.navpopup}); if (getValueOf('popupDraggable')) { var dragHandle = getValueOf('popupDragHandle') || null; if (dragHandle && dragHandle != 'all') { dragHandle += a.navpopup.idNumber; } setTimeout(function(){a.navpopup.makeDraggable(dragHandle);}, 150); } //<NOLITE> if (getValueOf('popupRedlinkRemoval') && a.className=='new') { setPopupHTML('<br>'+popupRedlinkHTML(article), 'popupRedlink', a.navpopup.idNumber); } //</NOLITE> } function debugData(navpopup) { if(getValueOf('popupDebugging') && navpopup.idNumber) { setPopupHTML('idNumber='+navpopup.idNumber + ', pending=' + navpopup.pending, 'popupError', navpopup.idNumber); } } function newNavpopup(a, article) { var navpopup = new Navpopup(); navpopup.fuzz=5; navpopup.delay=getValueOf('popupDelay')*1000; // increment global counter now navpopup.idNumber = ++pg.idNumber; navpopup.parentAnchor = a; navpopup.parentPopup = (a.popData && a.popData.owner); navpopup.article = article; registerHooks(navpopup); return navpopup; } // Should we show nonsimple context? // If simplePopups is set to true, then we do not show nonsimple context, // but if a bottom "show preview" was clicked we do show nonsimple context function shouldShowNonSimple(a) { return !getValueOf('simplePopups') || a.simpleNoMore; } // Should we show nonsimple context govern by the option (e.g. popupUserInfo)? // If the user explicitly asked for nonsimple context by setting the option to true, // then we show it even in nonsimple mode. function shouldShow(a,option) { if (shouldShowNonSimple(a)) { return getValueOf(option); } else { return (typeof window[option] != 'undefined' ) && window[option]; } } function nonsimplePopupContent(a, article) { var diff=null, history=null; var params=parseParams(a.href); var oldid=(typeof params.oldid=='undefined' ? null : params.oldid); //<NOLITE> if(shouldShow(a,'popupPreviewDiffs')) { diff=params.diff; } if(shouldShow(a,'popupPreviewHistory')) { history=(params.action=='history'); } //</NOLITE> a.navpopup.pending=0; var referenceElement = footnoteTarget(a); if (referenceElement) { footnotePreview(referenceElement, a.navpopup); //<NOLITE> } else if ( diff || diff === 0 ) { loadDiff(article, oldid, diff, a.navpopup); } else if ( history ) { loadAPIPreview('history', article, a.navpopup); } else if ( shouldShowNonSimple(a) && pg.re.contribs.test(a.href) ) { loadAPIPreview('contribs', article, a.navpopup); } else if ( shouldShowNonSimple(a) && pg.re.backlinks.test(a.href) ) { loadAPIPreview('backlinks', article, a.navpopup); } else if ( // FIXME should be able to get all preview combinations with options article.namespaceId()==pg.nsImageId && ( shouldShow(a,'imagePopupsForImages') || ! anchorContainsImage(a) ) ) { loadAPIPreview('imagepagepreview', article, a.navpopup); loadImage(article, a.navpopup); //</NOLITE> } else { if (article.namespaceId() == pg.nsCategoryId && shouldShow(a,'popupCategoryMembers')) { loadAPIPreview('category', article, a.navpopup); } else if ((article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) && shouldShow(a,'popupUserInfo')) { loadAPIPreview('userinfo', article, a.navpopup); } if (shouldShowNonSimple(a)) startArticlePreview(article, oldid, a.navpopup); } } function pendingNavpopTask(navpop) { if (navpop && navpop.pending === null) { navpop.pending=0; } ++navpop.pending; debugData(navpop); } function completedNavpopTask(navpop) { if (navpop && navpop.pending) { --navpop.pending; } debugData(navpop); } function startArticlePreview(article, oldid, navpop) { navpop.redir=0; loadPreview(article, oldid, navpop); } function loadPreview(article, oldid, navpop) { if (!navpop.redir) { navpop.originalArticle=article; } article.oldid = oldid; loadAPIPreview('revision', article, navpop); } function loadPreviewFromRedir(redirMatch, navpop) { // redirMatch is a regex match var target = new Title().fromWikiText(redirMatch[2]); // overwrite (or add) anchor from original target // mediawiki does overwrite; eg User:Lupin/foo3#Done if ( navpop.article.anchor ) { target.anchor = navpop.article.anchor; } navpop.redir++; navpop.redirTarget=target; //<NOLITE> var warnRedir = redirLink(target, navpop.article); setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber); //</NOLITE> navpop.article=target; fillEmptySpans({redir: true, redirTarget: target, navpopup:navpop}); return loadPreview(target, null, navpop); } function insertPreview(download) { if (!download.owner) { return; } var redirMatch = pg.re.redirect.exec(download.data); if (download.owner.redir === 0 && redirMatch) { loadPreviewFromRedir(redirMatch, download.owner); return; } if (download.owner.visible || !getValueOf('popupLazyPreviews')) { insertPreviewNow(download); } else { var id=(download.owner.redir) ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK'; download.owner.addHook( function(){insertPreviewNow(download); return true;}, 'unhide', 'after', id ); } } function insertPreviewNow(download) { if (!download.owner) { return; } var wikiText=download.data; var navpop=download.owner; var art=navpop.redirTarget || navpop.originalArticle; //<NOLITE> makeFixDabs(wikiText, navpop); if (getValueOf('popupSummaryData')) { getPageInfo(wikiText, download); setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber); } var imagePage=''; if (art.namespaceId()==pg.nsImageId) { imagePage=art.toString(); } else { imagePage=getValidImageFromWikiText(wikiText); } if(imagePage) { loadImage(Title.fromWikiText(imagePage), navpop); } //</NOLITE> if (getValueOf('popupPreviews')) { insertArticlePreview(download, art, navpop); } } function insertArticlePreview(download, art, navpop) { if (download && typeof download.data == typeof ''){ if (art.namespaceId()==pg.nsTemplateId && getValueOf('popupPreviewRawTemplates')) { // FIXME compare/consolidate with diff escaping code for wikitext var h='<hr /><span style="font-family: monospace;">' + download.data.entify().split('\\n').join('<br />\\n') + '</span>'; setPopupHTML(h, 'popupPreview', navpop.idNumber); } else { var p=prepPreviewmaker(download.data, art, navpop); p.showPreview(); } } } function prepPreviewmaker(data, article, navpop) { // deal with tricksy anchors var d=anchorize(data, article.anchorString()); var urlBase=joinPath([pg.wiki.articlebase, article.urlString()]); var p=new Previewmaker(d, urlBase, navpop); return p; } // Try to imitate the way mediawiki generates HTML anchors from section titles function anchorize(d, anch) { if (!anch) { return d; } var anchRe=RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*'+getValueOf('popupAnchorRegexp')+'\\s*(?:\\|[^|}]*)*?\\s*'+literalizeRegex(anch)+'\\s*(?:\\|[^}]*)?}})'); var match=d.match(anchRe); if(match && match.length > 0 && match[0]) { return d.substring(d.indexOf(match[0])); } // now try to deal with == foo [[bar|baz]] boom == -> #foo_baz_boom var lines=d.split('\n'); for (var i=0; i<lines.length; ++i) { lines[i]=lines[i].replace(RegExp('[[]{2}([^|\\]]*?[|])?(.*?)[\\]]{2}', 'g'), '$2') .replace(/'''([^'])/g, '$1').replace(RegExp("''([^'])", 'g'), '$1'); if (lines[i].match(anchRe)) { return d.split('\n').slice(i).join('\n').replace(RegExp('^[^=]*'), ''); } } return d; } function killPopup() { removeModifierKeyHandler(this); if (getValueOf('popupShortcutKeys')) { rmPopupShortcuts(); } if (!pg) { return; } if (pg.current.link && pg.current.link.navpopup) { pg.current.link.navpopup.banish(); } pg.current.link=null; abortAllDownloads(); if (pg.timer.checkPopupPosition) { clearInterval(pg.timer.checkPopupPosition); pg.timer.checkPopupPosition=null; } return true; // preserve default action } // ENDFILE: actions.js // STARTFILE: domdrag.js /** @fileoverview The {@link Drag} object, which enables objects to be dragged around. <pre> ************************************************* dom-drag.js 09.25.2001 www.youngpup.net ************************************************** 10.28.2001 - fixed minor bug where events sometimes fired off the handle, not the root. ************************************************* Pared down, some hooks added by User:Lupin Copyright Aaron Boodman. Saying stupid things daily since March 2001. </pre> */ /** Creates a new Drag object. This is used to make various DOM elements draggable. @constructor */ function Drag () { /** Condition to determine whether or not to drag. This function should take one parameter, an Event. To disable this, set it to <code>null</code>. @type Function */ this.startCondition = null; /** Hook to be run when the drag finishes. This is passed the final coordinates of the dragged object (two integers, x and y). To disables this, set it to <code>null</code>. @type Function */ this.endHook = null; } /** Gets an event in a cross-browser manner. @param {Event} e @private */ Drag.prototype.fixE = function(e) { if (typeof e == 'undefined') { e = window.event; } if (typeof e.layerX == 'undefined') { e.layerX = e.offsetX; } if (typeof e.layerY == 'undefined') { e.layerY = e.offsetY; } return e; }; /** Initialises the Drag instance by telling it which object you want to be draggable, and what you want to drag it by. @param {DOMElement} o The "handle" by which <code>oRoot</code> is dragged. @param {DOMElement} oRoot The object which moves when <code>o</code> is dragged, or <code>o</code> if omitted. */ Drag.prototype.init = function(o, oRoot) { var dragObj = this; this.obj = o; o.onmousedown = function(e) { dragObj.start.apply( dragObj, [e]); }; o.dragging = false; o.popups_draggable = true; o.hmode = true; o.vmode = true; o.root = oRoot ? oRoot : o ; if (isNaN(parseInt(o.root.style.left, 10))) { o.root.style.left = "0px"; } if (isNaN(parseInt(o.root.style.top, 10))) { o.root.style.top = "0px"; } o.root.onthisStart = function(){}; o.root.onthisEnd = function(){}; o.root.onthis = function(){}; }; /** Starts the drag. @private @param {Event} e */ Drag.prototype.start = function(e) { var o = this.obj; // = this; e = this.fixE(e); if (this.startCondition && !this.startCondition(e)) { return; } var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10); o.root.onthisStart(x, y); o.lastMouseX = e.clientX; o.lastMouseY = e.clientY; var dragObj = this; o.onmousemoveDefault = document.onmousemove; o.dragging = true; document.onmousemove = function(e) { dragObj.drag.apply( dragObj, [e] ); }; document.onmouseup = function(e) { dragObj.end.apply( dragObj, [e] ); }; return false; }; /** Does the drag. @param {Event} e @private */ Drag.prototype.drag = function(e) { e = this.fixE(e); var o = this.obj; var ey = e.clientY; var ex = e.clientX; var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10 ); var nx, ny; nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1)); ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1)); this.obj.root.style[o.hmode ? "left" : "right"] = nx + "px"; this.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px"; this.obj.lastMouseX = ex; this.obj.lastMouseY = ey; this.obj.root.onthis(nx, ny); return false; }; /** Ends the drag. @private */ Drag.prototype.end = function() { document.onmousemove=this.obj.onmousemoveDefault; document.onmouseup = null; this.obj.dragging = false; if (this.endHook) { this.endHook( parseInt(this.obj.root.style[this.obj.hmode ? "left" : "right"], 10), parseInt(this.obj.root.style[this.obj.vmode ? "top" : "bottom"], 10)); } }; // ENDFILE: domdrag.js // STARTFILE: structures.js //<NOLITE> pg.structures.original={}; pg.structures.original.popupLayout=function () { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupUserData', 'popupData', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; pg.structures.original.popupRedirSpans=function () { return ['popupRedir', 'popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks']; }; pg.structures.original.popupTitle=function (x) { log ('defaultstructure.popupTitle'); if (!getValueOf('popupNavLinks')) { return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params); } return ''; }; pg.structures.original.popupTopLinks=function (x) { log ('defaultstructure.popupTopLinks'); if (getValueOf('popupNavLinks')) { return navLinksHTML(x.article, x.hint, x.params); } return ''; }; pg.structures.original.popupImage=function(x) { log ('original.popupImage, x.article='+x.article+', x.navpop.idNumber='+x.navpop.idNumber); return imageHTML(x.article, x.navpop.idNumber); }; pg.structures.original.popupRedirTitle=pg.structures.original.popupTitle; pg.structures.original.popupRedirTopLinks=pg.structures.original.popupTopLinks; function copyStructure(oldStructure, newStructure) { pg.structures[newStructure]={}; for (var prop in pg.structures[oldStructure]) { pg.structures[newStructure][prop]=pg.structures[oldStructure][prop]; } } copyStructure('original', 'nostalgia'); pg.structures.nostalgia.popupTopLinks=function(x) { var str=''; str += '<b><<mainlink|shortcut= >></b>'; // user links // contribs - log - count - email - block // count only if applicable; block only if popupAdminLinks str += 'if(user){<br><<contribs|shortcut=c>>'; str+='if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}'; // editing links // talkpage -> edit|new - history - un|watch - article|edit // other page -> edit - history - un|watch - talk|edit|new var editstr='<<edit|shortcut=e>>'; var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'; var historystr='<<history|shortcut=h>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; str += '<br>if(talk){' + editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + // not a talk page editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}'; // misc links str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>'; str += 'if(admin){<br>}else{*}<<move|shortcut=m>>'; // admin links str += 'if(admin){*<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>}'; return navlinkStringToHTML(str, x.article, x.params); }; pg.structures.nostalgia.popupRedirTopLinks=pg.structures.nostalgia.popupTopLinks; /** -- fancy -- **/ copyStructure('original', 'fancy'); pg.structures.fancy.popupTitle=function (x) { return navlinkStringToHTML('<font size=+0><<mainlink>></font>',x.article,x.params); }; pg.structures.fancy.popupTopLinks=function(x) { var hist='<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>|<<editors|shortcut=E|eds>>'; var watch='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; var move='<<move|shortcut=m|move>>'; return navlinkStringToHTML('if(talk){' + '<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' + hist + '*' + '<<article|shortcut=a>>|<<editArticle|edit>>' + '*' + watch + '*' + move + '}else{<<edit|shortcut=e>>*' + hist + '*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' + '*' + watch + '*' + move+'}<br>', x.article, x.params); }; pg.structures.fancy.popupOtherLinks=function(x) { var admin='<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*<<undelete|undeleteShort>>|<<delete|shortcut=d|del>>'; var user='<<contribs|shortcut=c>>if(wikimedia){|<<count|shortcut=#|#>>}'; user+='if(ipuser){|<<arin>>}else{*<<email|shortcut=E|'+ popupString('email')+'>>}if(admin){*<<block|shortcut=b>>}'; var normal='<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>'; return navlinkStringToHTML('<br>if(user){' + user + '*}if(admin){'+admin+'if(user){<br>}else{*}}' + normal, x.article, x.params); }; pg.structures.fancy.popupRedirTitle=pg.structures.fancy.popupTitle; pg.structures.fancy.popupRedirTopLinks=pg.structures.fancy.popupTopLinks; pg.structures.fancy.popupRedirOtherLinks=pg.structures.fancy.popupOtherLinks; /** -- fancy2 -- **/ // hack for User:MacGyverMagic copyStructure('fancy', 'fancy2'); pg.structures.fancy2.popupTopLinks=function(x) { // hack out the <br> at the end and put one at the beginning return '<br>'+pg.structures.fancy.popupTopLinks(x).replace(RegExp('<br>$','i'),''); }; pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title return ['popupError', 'popupImage', 'popupTitle', 'popupUserData', 'popupData', 'popupTopLinks', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; /** -- menus -- **/ copyStructure('original', 'menus'); pg.structures.menus.popupLayout=function () { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupUserData', 'popupData', 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; pg.structures.menus.popupTopLinks = function (x, shorter) { // FIXME maybe this stuff should be cached var s=[]; var dropdiv='<div class="popup_drop">'; var enddiv='</div>'; var hist='<<history|shortcut=h>>'; if (!shorter) { hist = '<menurow>' + hist + '|<<historyfeed|rss>>|<<editors|shortcut=E>></menurow>'; } var lastedit='<<lastEdit|shortcut=/|show last edit>>'; var thank='if(diff){<<thank|send thanks>>}'; var jsHistory='<<lastContrib|last set of edits>><<sinceMe|changes since mine>>'; var linkshere='<<whatLinksHere|shortcut=l|what links here>>'; var related='<<relatedChanges|shortcut=r|related changes>>'; var search='<menurow><<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}' + '|<<google|shortcut=G|web>></menurow>'; var watch='<menurow><<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>></menurow>'; var protect='<menurow><<unprotect|unprotectShort>>|' + '<<protect|shortcut=p>>|<<protectlog|log>></menurow>'; var del='<menurow><<undelete|undeleteShort>>|<<delete|shortcut=d>>|' + '<<deletelog|log>></menurow>'; var move='<<move|shortcut=m|move page>>'; var nullPurge='<menurow><<nullEdit|shortcut=n|null edit>>|<<purge|shortcut=P>></menurow>'; var viewOptions='<menurow><<view|shortcut=v>>|<<render|shortcut=S>>|<<raw>></menurow>'; var editRow='if(oldid){' + '<menurow><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></menurow>' + '<menurow><<revert|shortcut=v>>|<<undo>></menurow>' + '}else{<<edit|shortcut=e>>}'; var markPatrolled='if(rcid){<<markpatrolled|mark patrolled>>}'; var newTopic='if(talk){<<new|shortcut=+|new topic>>}'; var protectDelete='if(admin){' + protect + del + '}'; if (getValueOf('popupActionsMenu')) { s.push( '<<mainlink>>*' + dropdiv + menuTitle('tác vụ')); } else { s.push( dropdiv + '<<mainlink>>'); } s.push( '<menu>'); s.push( editRow + markPatrolled + newTopic + hist + lastedit + thank ); if (!shorter) { s.push(jsHistory); } s.push( move + linkshere + related); if (!shorter) { s.push(nullPurge + search); } if (!shorter) { s.push(viewOptions); } s.push('<hr />' + watch + protectDelete); s.push('<hr />' + 'if(talk){<<article|shortcut=a|view article>><<editArticle|edit article>>}' + 'else{<<talk|shortcut=t|talk page>><<editTalk|edit talk>>' + '<<newTalk|shortcut=+|new topic>>}</menu>' + enddiv); // user menu starts here var email='<<email|shortcut=E|email user>>'; var contribs= 'if(wikimedia){<menurow>}<<contribs|shortcut=c|contributions>>if(wikimedia){</menurow>}' + 'if(admin){<menurow><<deletedContribs>></menurow>}'; s.push('if(user){*' + dropdiv + menuTitle('user')); s.push('<menu>'); s.push('<menurow><<userPage|shortcut=u|user&nbsp;page>>|<<userSpace|space>></menurow>'); s.push('<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' + '<<newUserTalk|shortcut=+|leave comment>>'); if(!shorter) { s.push( 'if(ipuser){<<arin>>}else{' + email + '}' ); } else { s.push( 'if(ipuser){}else{' + email + '}' ); } s.push('<hr />' + contribs + '<<userlog|shortcut=L|user log>>'); s.push('if(wikimedia){<<count|shortcut=#|edit counter>>}'); s.push('if(admin){<menurow><<unblock|unblockShort>>|<<block|shortcut=b|block user>></menurow>}'); s.push('<<blocklog|shortcut=B|block log>>'); s.push('</menu>' + enddiv + '}'); // popups menu starts here if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */) { x.navpop.hasPopupMenu=true; s.push('*' + dropdiv + menuTitle('popupsMenu') + '<menu>'); s.push('<<togglePreviews|toggle previews>>'); s.push('<<purgePopups|reset>>'); s.push('<<disablePopups|disable>>'); s.push('</menu>'+enddiv); } return navlinkStringToHTML(s.join(''), x.article, x.params); }; function menuTitle(s) { return '<a href="#" noPopup=1>' + popupString(s) + '</a>'; } pg.structures.menus.popupRedirTitle=pg.structures.menus.popupTitle; pg.structures.menus.popupRedirTopLinks=pg.structures.menus.popupTopLinks; copyStructure('menus', 'shortmenus'); pg.structures.shortmenus.popupTopLinks=function(x) { return pg.structures.menus.popupTopLinks(x,true); }; pg.structures.shortmenus.popupRedirTopLinks=pg.structures.shortmenus.popupTopLinks; //</NOLITE> pg.structures.lite={}; pg.structures.lite.popupLayout=function () { return ['popupTitle', 'popupPreview' ]; }; pg.structures.lite.popupTitle=function (x) { log (x.article + ': structures.lite.popupTitle'); //return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params); return '<div><span class="popup_mainlink"><b>' + x.article.toString() + '</b></span></div>'; }; // ENDFILE: structures.js // STARTFILE: autoedit.js //<NOLITE> function substitute(data,cmdBody) { // alert('sub\nfrom: '+cmdBody.from+'\nto: '+cmdBody.to+'\nflags: '+cmdBody.flags); var fromRe=RegExp(cmdBody.from, cmdBody.flags); return data.replace(fromRe, cmdBody.to); } function execCmds(data, cmdList) { for (var i=0; i<cmdList.length; ++i) { data=cmdList[i].action(data, cmdList[i]); } return data; } function parseCmd(str) { // returns a list of commands if (!str.length) { return []; } var p=false; switch (str.charAt(0)) { case 's': p=parseSubstitute(str); break; default: return false; } if (p) { return [p].concat(parseCmd(p.remainder)); } return false; } function unEscape(str, sep) { return str.split('\\\\').join('\\').split('\\'+sep).join(sep).split('\\n').join('\n'); } function parseSubstitute(str) { // takes a string like s/a/b/flags;othercmds and parses it var from,to,flags,tmp; if (str.length<4) { return false; } var sep=str.charAt(1); str=str.substring(2); tmp=skipOver(str,sep); if (tmp) { from=tmp.segment; str=tmp.remainder; } else { return false; } tmp=skipOver(str,sep); if (tmp) { to=tmp.segment; str=tmp.remainder; } else { return false; } flags=''; if (str.length) { tmp=skipOver(str,';') || skipToEnd(str, ';'); if (tmp) {flags=tmp.segment; str=tmp.remainder; } } return {action: substitute, from: from, to: to, flags: flags, remainder: str}; } function skipOver(str,sep) { var endSegment=findNext(str,sep); if (endSegment<0) { return false; } var segment=unEscape(str.substring(0,endSegment), sep); return {segment: segment, remainder: str.substring(endSegment+1)}; } /*eslint-disable*/ function skipToEnd(str,sep) { return {segment: str, remainder: ''}; } /*eslint-enable */ function findNext(str, ch) { for (var i=0; i<str.length; ++i) { if (str.charAt(i)=='\\') { i+=2; } if (str.charAt(i)==ch) { return i; } } return -1; } function setCheckbox(param, box) { var val=mw.util.getParamValue(param); if (val) { switch (val) { case '1': case 'yes': case 'true': box.checked=true; break; case '0': case 'no': case 'false': box.checked=false; } } } function autoEdit() { setupPopups( function () { if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version') ) { return false; } if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken')===autoClickToken()) { pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action')); } if (!document.editform) { return false; } if (autoEdit.alreadyRan) { return false; } autoEdit.alreadyRan=true; var cmdString=mw.util.getParamValue('autoedit'); if (cmdString) { try { var editbox=document.editform.wpTextbox1; var cmdList=parseCmd(cmdString); var input=editbox.value; var output=execCmds(input, cmdList); editbox.value=output; } catch (dang) { return; } // wikEd user script compatibility if (typeof(wikEdUseWikEd) != 'undefined') { if (wikEdUseWikEd === true) { WikEdUpdateFrame(); } } } setCheckbox('autominor', document.editform.wpMinoredit); setCheckbox('autowatch', document.editform.wpWatchthis); var rvid = mw.util.getParamValue('autorv'); if (rvid) { var url=pg.wiki.apiwikibase + '?action=query&format=json&formatversion=2&prop=revisions&revids='+rvid; startDownload(url, null, autoEdit2); } else { autoEdit2(); } } ); } function autoEdit2(d) { var summary=mw.util.getParamValue('autosummary'); var summaryprompt=mw.util.getParamValue('autosummaryprompt'); var summarynotice=''; if (d && d.data && mw.util.getParamValue('autorv')) { var s = getRvSummary(summary, d.data); if (s === false) { summaryprompt=true; summarynotice=popupString('Failed to get revision information, please edit manually.\n\n'); summary = simplePrintf(summary, [mw.util.getParamValue('autorv'), '(unknown)', '(unknown)']); } else { summary = s; } } if (summaryprompt) { var txt= summarynotice + popupString('Enter a non-empty edit summary or press cancel to abort'); var response=prompt(txt, summary); if (response) { summary=response; } else { return; } } if (summary) { document.editform.wpSummary.value=summary; } // Attempt to avoid possible premature clicking of the save button // (maybe delays in updates to the DOM are to blame?? or a red herring) setTimeout(autoEdit3, 100); } function autoClickToken() { return mw.user.sessionId(); } function autoEdit3() { if( mw.util.getParamValue('actoken') != autoClickToken()) { return; } var btn=mw.util.getParamValue('autoclick'); if (btn) { if (document.editform && document.editform[btn]) { var button=document.editform[btn]; var msg=tprintf('The %s button has been automatically clicked. Please wait for the next page to load.', [ button.value ]); bannerMessage(msg); document.title='('+document.title+')'; button.click(); } else { alert(tprintf('Could not find button %s. Please check the settings in your javascript file.', [ btn ])); } } } function bannerMessage(s) { var headings=document.getElementsByTagName('h1'); if (headings) { var div=document.createElement('div'); div.innerHTML='<font size=+1><b>' + s + '</b></font>'; headings[0].parentNode.insertBefore(div, headings[0]); } } function getRvSummary(template, json) { try { var o=getJsObj(json); var edit = anyChild(o.query.pages).revisions[0]; var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, ''); return simplePrintf(template, [edit.revid, timestamp, edit.userhidden ? '(hidden)' : edit.user ]); } catch (badness) { return false; } } //</NOLITE> // ENDFILE: autoedit.js // STARTFILE: downloader.js /** @fileoverview {@link Downloader}, a xmlhttprequest wrapper, and helper functions. */ /** Creates a new Downloader @constructor @class The Downloader class. Create a new instance of this class to download stuff. @param {String} url The url to download. This can be omitted and supplied later. */ function Downloader(url) { if (typeof XMLHttpRequest!='undefined') { this.http = new XMLHttpRequest(); } /** The url to download @type String */ this.url = url; /** A universally unique ID number @type integer */ this.id=null; /** Modification date, to be culled from the incoming headers @type Date @private */ this.lastModified = null; /** What to do when the download completes successfully @type Function @private */ this.callbackFunction = null; /** What to do on failure @type Function @private */ this.onFailure = null; /** Flag set on <code>abort</code> @type boolean */ this.aborted = false; /** HTTP method. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for possibilities. @type String */ this.method='GET'; /** Async flag. @type boolean */ this.async=true; } new Downloader(); /** Submits the http request. */ Downloader.prototype.send = function (x) { if (!this.http) { return null; } return this.http.send(x); }; /** Aborts the download, setting the <code>aborted</code> field to true. */ Downloader.prototype.abort = function () { if (!this.http) { return null; } this.aborted=true; return this.http.abort(); }; /** Returns the downloaded data. */ Downloader.prototype.getData = function () {if (!this.http) { return null; } return this.http.responseText;}; /** Prepares the download. */ Downloader.prototype.setTarget = function () { if (!this.http) { return null; } this.http.open(this.method, this.url, this.async); this.http.setRequestHeader( 'Api-User-Agent', pg.misc.userAgent ); }; /** Gets the state of the download. */ Downloader.prototype.getReadyState=function () {if (!this.http) { return null; } return this.http.readyState;}; pg.misc.downloadsInProgress = { }; /** Starts the download. Note that setTarget {@link Downloader#setTarget} must be run first */ Downloader.prototype.start=function () { if (!this.http) { return; } pg.misc.downloadsInProgress[this.id] = this; this.http.send(null); }; /** Gets the 'Last-Modified' date from the download headers. Should be run after the download completes. Returns <code>null</code> on failure. @return {Date} */ Downloader.prototype.getLastModifiedDate=function () { if(!this.http) { return null; } var lastmod=null; try { lastmod=this.http.getResponseHeader('Last-Modified'); } catch (err) {} if (lastmod) { return new Date(lastmod); } return null; }; /** Sets the callback function. @param {Function} f callback function, called as <code>f(this)</code> on success */ Downloader.prototype.setCallback = function (f) { if(!this.http) { return; } this.http.onreadystatechange = f; }; Downloader.prototype.getStatus = function() { if (!this.http) { return null; } return this.http.status; }; ////////////////////////////////////////////////// // helper functions /** Creates a new {@link Downloader} and prepares it for action. @param {String} url The url to download @param {integer} id The ID of the {@link Downloader} object @param {Function} callback The callback function invoked on success @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser */ function newDownload(url, id, callback, onfailure) { var d=new Downloader(url); if (!d.http) { return 'ohdear'; } d.id=id; d.setTarget(); if (!onfailure) { onfailure=2; } var f = function () { if (d.getReadyState() == 4) { delete pg.misc.downloadsInProgress[this.id]; try { if ( d.getStatus() == 200 ) { d.data=d.getData(); d.lastModified=d.getLastModifiedDate(); callback(d); } else if (typeof onfailure == typeof 1) { if (onfailure > 0) { // retry newDownload(url, id, callback, onfailure - 1); } } else if (typeof onfailure === 'function') { onfailure(d,url,id,callback); } } catch (somerr) { /* ignore it */ } } }; d.setCallback(f); return d; } /** Simulates a download from cached data. The supplied data is put into a {@link Downloader} as if it had downloaded it. @param {String} url The url. @param {integer} id The ID. @param {Function} callback The callback, which is invoked immediately as <code>callback(d)</code>, where <code>d</code> is the new {@link Downloader}. @param {String} data The (cached) data. @param {Date} lastModified The (cached) last modified date. */ function fakeDownload(url, id, callback, data, lastModified, owner) { var d=newDownload(url,callback); d.owner=owner; d.id=id; d.data=data; d.lastModified=lastModified; return callback(d); } /** Starts a download. @param {String} url The url to download @param {integer} id The ID of the {@link Downloader} object @param {Function} callback The callback function invoked on success @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser */ function startDownload(url, id, callback) { var d=newDownload(url, id, callback); if (typeof d == typeof '' ) { return d; } d.start(); return d; } /** Aborts all downloads which have been started. */ function abortAllDownloads() { for ( var x in pg.misc.downloadsInProgress ) { try { pg.misc.downloadsInProgress[x].aborted=true; pg.misc.downloadsInProgress[x].abort(); delete pg.misc.downloadsInProgress[x]; } catch (e) {} } } // ENDFILE: downloader.js // STARTFILE: livepreview.js // TODO: location is often not correct (eg relative links in previews) // NOTE: removed md5 and image and math parsing. was broken, lots of bytes. /** * InstaView - a Mediawiki to HTML converter in JavaScript * Version 0.6.1 * Copyright (C) Pedro Fayolle 2005-2006 * https://en.wikipedia.org/wiki/User:Pilaf * Distributed under the BSD license * * Changelog: * * 0.6.1 * - Fixed problem caused by \r characters * - Improved inline formatting parser * * 0.6 * - Changed name to InstaView * - Some major code reorganizations and factored out some common functions * - Handled conversion of relative links (i.e. [[/foo]]) * - Fixed misrendering of adjacent definition list items * - Fixed bug in table headings handling * - Changed date format in signatures to reflect Mediawiki's * - Fixed handling of [[:Image:...]] * - Updated MD5 function (hopefully it will work with UTF-8) * - Fixed bug in handling of links inside images * * To do: * - Better support for math tags * - Full support for <nowiki> * - Parser-based (as opposed to RegExp-based) inline wikicode handling (make it one-pass and bullet-proof) * - Support for templates (through AJAX) * - Support for coloured links (AJAX) */ var Insta = {}; function setupLivePreview() { // options Insta.conf = { baseUrl: '', user: {}, wiki: { lang: pg.wiki.lang, interwiki: pg.wiki.interwiki, default_thumb_width: 180 }, paths: { articles: pg.wiki.articlePath + '/', // Only used for Insta previews with images. (not in popups) math: '/math/', images: '//upload.wikimedia.org/wikipedia/en/', // FIXME getImageUrlStart(pg.wiki.hostname), images_fallback: '//upload.wikimedia.org/wikipedia/commons/', }, locale: { user: mw.config.get('wgFormattedNamespaces')[pg.nsUserId], image: mw.config.get('wgFormattedNamespaces')[pg.nsImageId], category: mw.config.get('wgFormattedNamespaces')[pg.nsCategoryId], // shouldn't be used in popup previews, i think months: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'] } }; // options with default values or backreferences Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian'; Insta.conf.user.signature = '[['+Insta.conf.locale.user+':'+Insta.conf.user.name+'|'+Insta.conf.user.name+']]'; //Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/'; // define constants Insta.BLOCK_IMAGE = new RegExp('^\\[\\[(?:File|Image|'+Insta.conf.locale.image+ '):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)', 'i'); } Insta.dump = function(from, to) { if (typeof from == 'string') { from = document.getElementById(from); } if (typeof to == 'string') { to = document.getElementById(to); } to.innerHTML = this.convert(from.value); }; Insta.convert = function(wiki) { var ll = (typeof wiki == 'string')? wiki.replace(/\r/g,'').split(/\n/): wiki, // lines of wikicode o = '', // output p = 0, // para flag $r; // result of passing a regexp to $() // some shorthands function remain() { return ll.length; } function sh() { return ll.shift(); } // shift function ps(s) { o += s; } // push // similar to C's printf, uses ? as placeholders, ?? to escape question marks function f() { var i=1, a=arguments, f=a[0], o='', c, p; for (; i<a.length; i++) { if ((p=f.indexOf('?'))+1) { // allow character escaping i -= c = f.charAt(p+1)=='?' ? 1 : 0; o += f.substring(0,p) + (c ? '?' : a[i]); f = f.substr(p+1+c); } else { break; } } return o+f; } function html_entities(s) { return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); } // Wiki text parsing to html is a nightmare. // The below functions deliberately don't escape the ampersand since this would make it more difficult, // and we don't absolutely need to for how we need it. // This means that any unescaped ampersands in wikitext will remain unescaped and can cause invalid HTML. // Browsers should all be able to handle it though. // We also escape significant wikimarkup characters to prevent further matching on the processed text function htmlescape_text(s) { return s.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/:/g,"&#58;").replace(/\[/g,"&#91;").replace(/]/g,"&#93;"); } function htmlescape_attr(s) { return htmlescape_text(s).replace(/'/g,"&#39;").replace(/"/g,"&quot;"); } // return the first non matching character position between two strings function str_imatch(a, b) { for (var i=0, l=Math.min(a.length, b.length); i<l; i++) { if (a.charAt(i)!=b.charAt(i)) { break; } } return i; } // compare current line against a string or regexp // if passed a string it will compare only the first string.length characters // if passed a regexp the result is stored in $r function $(c) { return (typeof c == 'string') ? (ll[0].substr(0,c.length)==c) : ($r = ll[0].match(c)); } function $$(c) { return ll[0]==c; } // compare current line against a string function _(p) { return ll[0].charAt(p); } // return char at pos p function endl(s) { ps(s); sh(); } function parse_list() { var prev=''; while (remain() && $(/^([*#:;]+)(.*)$/)) { var l_match = $r; sh(); var ipos = str_imatch(prev, l_match[1]); // close uncontinued lists for (var prevPos=prev.length-1; prevPos >= ipos; prevPos--) { var pi = prev.charAt(prevPos); if (pi=='*') { ps('</ul>'); } else if (pi=='#') { ps('</ol>'); } // close a dl only if the new item is not a dl item (:, ; or empty) else if($.inArray(l_match[1].charAt(prevPos), ['','*','#'])) { ps('</dl>'); } } // open new lists for (var matchPos=ipos; matchPos<l_match[1].length; matchPos++) { var li = l_match[1].charAt(matchPos); if (li=='*') { ps('<ul>'); } else if (li=='#') { ps('<ol>'); } // open a new dl only if the prev item is not a dl item (:, ; or empty) else if ($.inArray(prev.charAt(matchPos), ['','*','#'])) { ps('<dl>'); } } switch (l_match[1].charAt(l_match[1].length-1)) { case '*': case '#': ps('<li>' + parse_inline_nowiki(l_match[2])); break; case ';': ps('<dt>'); var dt_match = l_match[2].match(/(.*?)(:.*?)$/); // handle ;dt :dd format if (dt_match) { ps(parse_inline_nowiki(dt_match[1])); ll.unshift(dt_match[2]); } else ps(parse_inline_nowiki(l_match[2])); break; case ':': ps('<dd>' + parse_inline_nowiki(l_match[2])); } prev=l_match[1]; } // close remaining lists for (var i=prev.length-1; i>=0; i--) { ps(f('</?>', (prev.charAt(i)=='*')? 'ul': ((prev.charAt(i)=='#')? 'ol': 'dl'))); } } function parse_table() { endl(f('<table>', $(/^\{\|( .*)$/)? $r[1]: '')); for (;remain();) if ($('|')) switch (_(1)) { case '}': endl('</table>'); return; case '-': endl(f('<tr>', $(/\|-*(.*)/)[1])); break; default: parse_table_data(); } else if ($('!')) { parse_table_data(); } else { sh(); } } function parse_table_data() { var td_line, match_i; // 1: "|+", '|' or '+' // 2: ?? // 3: attributes ?? // TODO: finish commenting this regexp var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/); if (td_match[1] == '|+') ps('<caption'); else ps('<t' + ((td_match[1]=='|')?'d':'h')); if (typeof td_match[3] != 'undefined') { //ps(' ' + td_match[3]) match_i = 4; } else match_i = 2; ps('>'); if (td_match[1] != '|+') { // use || or !! as a cell separator depending on context // NOTE: when split() is passed a regexp make sure to use non-capturing brackets td_line = td_match[match_i].split((td_match[1] == '|')? '||': /(?:\|\||!!)/); ps(parse_inline_nowiki(td_line.shift())); while (td_line.length) ll.unshift(td_match[1] + td_line.pop()); } else ps(td_match[match_i]); var tc = 0, td = []; while (remain()) { td.push(sh()); if ($('|')) { if (!tc) break; // we're at the outer-most level (no nested tables), skip to td parse else if (_(1)=='}') tc--; } else if (!tc && $('!')) break; else if ($('{|')) tc++; } if (td.length) ps(Insta.convert(td)); } function parse_pre() { ps('<pre>'); do { endl(parse_inline_nowiki(ll[0].substring(1)) + "\n"); } while (remain() && $(' ')); ps('</pre>'); } function parse_block_image() { ps(parse_image(sh())); } function parse_image(str) { //<NOLITE> // get what's in between "[[Image:" and "]]" var tag = str.substring(str.indexOf(':') + 1, str.length - 2); /* eslint-disable no-unused-vars */ var width; var attr = [], filename, caption = ''; var thumb=0, frame=0, center=0; var align=''; /* eslint-enable no-unused-vars */ if (tag.match(/\|/)) { // manage nested links var nesting = 0; var last_attr; for (var i = tag.length-1; i > 0; i--) { if (tag.charAt(i) == '|' && !nesting) { last_attr = tag.substr(i+1); tag = tag.substring(0, i); break; } else switch (tag.substr(i-1, 2)) { case ']]': nesting++; i--; break; case '[[': nesting--; i--; } } attr = tag.split(/\s*\|\s*/); attr.push(last_attr); filename = attr.shift(); var w_match; for (;attr.length; attr.shift()) { w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/); if (w_match) width = w_match[1]; else switch(attr[0]) { case 'thumb': case 'thumbnail': thumb=true; frame=true; break; case 'frame': frame=true; break; case 'none': case 'right': case 'left': center=false; align=attr[0]; break; case 'center': center=true; align='none'; break; default: if (attr.length == 1) caption = attr[0]; } } } else filename = tag; return ''; //</NOLITE> } function parse_inline_nowiki(str) { var start, lastend=0; var substart=0, nestlev=0, open, close, subloop; var html=''; while (-1 != (start = str.indexOf('<nowiki>', substart))) { html += parse_inline_wiki(str.substring(lastend, start)); start += 8; substart = start; subloop = true; do { open = str.indexOf('<nowiki>', substart); close = str.indexOf('</nowiki>', substart); if (close<=open || open==-1) { if (close==-1) { return html + html_entities(str.substr(start)); } substart = close+9; if (nestlev) { nestlev--; } else { lastend = substart; html += html_entities(str.substring(start, lastend-9)); subloop = false; } } else { substart = open+8; nestlev++; } } while (subloop); } return html + parse_inline_wiki(str.substr(lastend)); } function parse_inline_images(str) { //<NOLITE> var start, substart=0, nestlev=0; var loop, close, open, wiki, html; while (-1 != (start=str.indexOf('[[', substart))) { if(str.substr(start+2).match(RegExp('^(Image|File|' + Insta.conf.locale.image + '):','i'))) { loop=true; substart=start; do { substart+=2; close=str.indexOf(']]',substart); open=str.indexOf('[[',substart); if (close<=open||open==-1) { if (close==-1) return str; substart=close; if (nestlev) { nestlev--; } else { wiki=str.substring(start,close+2); html=parse_image(wiki); str=str.replace(wiki,html); substart=start+html.length; loop=false; } } else { substart=open; nestlev++; } } while (loop); } else break; } //</NOLITE> return str; } // the output of this function doesn't respect the FILO structure of HTML // but since most browsers can handle it I'll save myself the hassle function parse_inline_formatting(str) { var em,st,i,li,o=''; while ((i=str.indexOf("''",li))+1) { o += str.substring(li,i); li=i+2; if (str.charAt(i+2)=="'") { li++; st=!st; o+=st?'<strong>':'</strong>'; } else { em=!em; o+=em?'<em>':'</em>'; } } return o+str.substr(li); } function parse_inline_wiki(str) { str = parse_inline_images(str); str = parse_inline_formatting(str); // math str = str.replace(/<(?:)math>(.*?)<\/math>/ig, ''); // Build a Mediawiki-formatted date string var date = new Date(); var minutes = date.getUTCMinutes(); if (minutes < 10) minutes = '0' + minutes; date = f("?:?, ? ? ? (UTC)", date.getUTCHours(), minutes, date.getUTCDate(), Insta.conf.locale.months[date.getUTCMonth()], date.getUTCFullYear()); // text formatting return str. // signatures replace(/~{5}(?!~)/g, date). replace(/~{4}(?!~)/g, Insta.conf.user.name+' '+date). replace(/~{3}(?!~)/g, Insta.conf.user.name). // [[:Category:...]], [[:Image:...]], etc... replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):[^|]*?)\\]\\](\\w*)','gi'), function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2));}). // remove straight category and interwiki tags replace(RegExp('\\[\\[(?:'+Insta.conf.locale.category+'|'+Insta.conf.wiki.interwiki+'):.*?\\]\\]','gi'),''). // [[:Category:...|Links]], [[:Image:...|Links]], etc... replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):.*?)\\|([^\\]]+?)\\]\\](\\w*)','gi'), function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));}). // [[/Relative links]] replace(/\[\[(\/[^|]*?)\]\]/g, function($0,$1){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($1)); }). // [[/Replaced|Relative links]] replace(/\[\[(\/.*?)\|(.+?)\]\]/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($2)); }). // [[Common links]] replace(/\[\[([^[|]*?)\]\](\w*)/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2)); }). // [[Replaced|Links]] replace(/\[\[([^[]*?)\|([^\]]+?)\]\](\w*)/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3)); }). // [[Stripped:Namespace|Namespace]] replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1) + htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($2)); }). // External links replace(/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g, function($0,$1,$2,$3,$4){return f("<a class='external' href='?:?'>?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($4)); }). replace(/\[http:\/\/(.*?)\]/g, function($0,$1){return f("<a class='external' href='http://?'>[#]</a>", htmlescape_attr($1)); }). replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, function($0,$1,$2,$3){return f("<a class='external' href='?:?'>?:?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($1), htmlescape_text($2) + htmlescape_text($3)); }). replace(/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*[^.,!?;: $])/g, function($0,$1,$2,$3,$4){return f("?<a class='external' href='?:?'>?:?</a>", htmlescape_text($1), htmlescape_attr($2), htmlescape_attr($3) + htmlescape_attr($4), htmlescape_text($2), htmlescape_text($3) + htmlescape_text($4)); }). replace('__NOTOC__',''). replace('__NOEDITSECTION__',''); } // begin parsing for (;remain();) if ($(/^(={1,6})(.*)\1(.*)$/)) { p=0; endl(f('<h?>?</h?>?', $r[1].length, parse_inline_nowiki($r[2]), $r[1].length, $r[3])); } else if ($(/^[*#:;]/)) { p=0; parse_list(); } else if ($(' ')) { p=0; parse_pre(); } else if ($('{|')) { p=0; parse_table(); } else if ($(/^----+$/)) { p=0; endl('<hr />'); } else if ($(Insta.BLOCK_IMAGE)) { p=0; parse_block_image(); } else { // handle paragraphs if ($$('')) { p = (remain()>1 && ll[1]===('')); if (p) endl('<p><br>'); } else { if(!p) { ps('<p>'); p=1; } ps(parse_inline_nowiki(ll[0]) + ' '); } sh(); } return o; }; function wiki2html(txt,baseurl) { Insta.conf.baseUrl=baseurl; return Insta.convert(txt); } // ENDFILE: livepreview.js // STARTFILE: pageinfo.js //<NOLITE> function popupFilterPageSize(data) { return formatBytes(data.length); } function popupFilterCountLinks(data) { var num=countLinks(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('wikiLinks'):popupString('wikiLink')); } function popupFilterCountImages(data) { var num=countImages(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('images'):popupString('image')); } function popupFilterCountCategories(data) { var num=countCategories(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('categories'):popupString('category')); } function popupFilterLastModified(data,download) { var lastmod=download.lastModified; var now=new Date(); var age=now-lastmod; if (lastmod && getValueOf('popupLastModified')) { return (tprintf('%s old', [formatAge(age)])).replace(RegExp(' ','g'), '&nbsp;'); } return ''; } function formatAge(age) { // coerce into a number var a=0+age, aa=a; var seclen = 1000; var minlen = 60*seclen; var hourlen = 60*minlen; var daylen = 24*hourlen; var weeklen = 7*daylen; var numweeks = (a-a%weeklen)/weeklen; a = a-numweeks*weeklen; var sweeks = addunit(numweeks, 'week'); var numdays = (a-a%daylen)/daylen; a = a-numdays*daylen; var sdays = addunit(numdays, 'day'); var numhours = (a-a%hourlen)/hourlen; a = a-numhours*hourlen; var shours = addunit(numhours,'hour'); var nummins = (a-a%minlen)/minlen; a = a-nummins*minlen; var smins = addunit(nummins, 'minute'); var numsecs = (a-a%seclen)/seclen; a = a-numsecs*seclen; var ssecs = addunit(numsecs, 'second'); if (aa > 4*weeklen) { return sweeks; } if (aa > weeklen) { return sweeks + ' ' + sdays; } if (aa > daylen) { return sdays + ' ' + shours; } if (aa > 6*hourlen) { return shours; } if (aa > hourlen) { return shours + ' ' + smins; } if (aa > 10*minlen) { return smins; } if (aa > minlen) { return smins + ' ' + ssecs; } return ssecs; } function addunit(num,str) { return '' + num + ' ' + ((num!=1) ? popupString(str+'s') : popupString(str)) ;} function runPopupFilters(list, data, download) { var ret=[]; for (var i=0; i<list.length; ++i) { if (list[i] && typeof list[i] == 'function') { var s=list[i](data, download, download.owner.article); if (s) { ret.push(s); } } } return ret; } function getPageInfo(data, download) { if (!data || data.length === 0) { return popupString('Empty page'); } var popupFilters=getValueOf('popupFilters') || []; var extraPopupFilters = getValueOf('extraPopupFilters') || []; var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download); var pageInfo=pageInfoArray.join(', '); if (pageInfo !== '' ) { pageInfo = upcaseFirst(pageInfo); } return pageInfo; } // this could be improved! function countLinks(wikiText) { return wikiText.split('[[').length - 1; } // if N = # matches, n = # brackets, then // String.parenSplit(regex) intersperses the N+1 split elements // with Nn other elements. So total length is // L= N+1 + Nn = N(n+1)+1. So N=(L-1)/(n+1). function countImages(wikiText) { return (wikiText.parenSplit(pg.re.image).length - 1) / (pg.re.imageBracketCount + 1); } function countCategories(wikiText) { return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1); } function popupFilterStubDetect(data, download, article) { var counts=stubCount(data, article); if (counts.real) { return popupString('stub'); } if (counts.sect) { return popupString('section stub'); } return ''; } function popupFilterDisambigDetect(data, download, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return ''; } return (isDisambig(data, article)) ? popupString('disambig') : ''; } function formatBytes(num) { return (num > 949) ? (Math.round(num/100)/10+popupString('kB')) : (num +'&nbsp;' + popupString('bytes')) ; } //</NOLITE> // ENDFILE: pageinfo.js // STARTFILE: titles.js /** @fileoverview Defines the {@link Title} class, and associated crufty functions. <code>Title</code> deals with article titles and their various forms. {@link Stringwrapper} is the parent class of <code>Title</code>, which exists simply to make things a little neater. */ /** Creates a new Stringwrapper. @constructor @class the Stringwrapper class. This base class is not really useful on its own; it just wraps various common string operations. */ function Stringwrapper() { /** Wrapper for this.toString().indexOf() @param {String} x @type integer */ this.indexOf=function(x){return this.toString().indexOf(x);}; /** Returns this.value. @type String */ this.toString=function(){return this.value;}; /** Wrapper for {@link String#parenSplit} applied to this.toString() @param {RegExp} x @type Array */ this.parenSplit=function(x){return this.toString().parenSplit(x);}; /** Wrapper for this.toString().substring() @param {String} x @param {String} y (optional) @type String */ this.substring=function(x,y){ if (typeof y=='undefined') { return this.toString().substring(x); } return this.toString().substring(x,y); }; /** Wrapper for this.toString().split() @param {String} x @type Array */ this.split=function(x){return this.toString().split(x);}; /** Wrapper for this.toString().replace() @param {String} x @param {String} y @type String */ this.replace=function(x,y){ return this.toString().replace(x,y); }; } /** Creates a new <code>Title</code>. @constructor @class The Title class. Holds article titles and converts them into various forms. Also deals with anchors, by which we mean the bits of the article URL after a # character, representing locations within an article. @param {String} value The initial value to assign to the article. This must be the canonical title (see {@link Title#value}. Omit this in the constructor and use another function to set the title if this is unavailable. */ function Title(val) { /** The canonical article title. This must be in UTF-8 with no entities, escaping or nasties. Also, underscores should be replaced with spaces. @type String @private */ this.value=null; /** The canonical form of the anchor. This should be exactly as it appears in the URL, i.e. with the .C3.0A bits in. @type String */ this.anchor=''; this.setUtf(val); } Title.prototype=new Stringwrapper(); /** Returns the canonical representation of the article title, optionally without anchor. @param {boolean} omitAnchor @fixme Decide specs for anchor @return String The article title and the anchor. */ Title.prototype.toString=function(omitAnchor) { return this.value + ( (!omitAnchor && this.anchor) ? '#' + this.anchorString() : '' ); }; Title.prototype.anchorString=function() { if (!this.anchor) { return ''; } var split=this.anchor.parenSplit(/((?:[.][0-9A-F]{2})+)/); var len=split.length; for (var j=1; j<len; j+=2) { // FIXME s/decodeURI/decodeURIComponent/g ? split[j]=decodeURIComponent(split[j].split('.').join('%')).split('_').join(' '); } return split.join(''); }; Title.prototype.urlAnchor=function() { var split=this.anchor.parenSplit('/((?:[%][0-9A-F]{2})+)/'); var len=split.length; for (var j=1; j<len; j+=2) { split[j]=split[j].split('%').join('.'); } return split.join(''); }; Title.prototype.anchorFromUtf=function(str) { this.anchor=encodeURIComponent(str.split(' ').join('_')) .split('%3A').join(':').split("'").join('%27').split('%').join('.'); }; Title.fromURL=function(h) { return new Title().fromURL(h); }; Title.prototype.fromURL=function(h) { if (typeof h != 'string') { this.value=null; return this; } // NOTE : playing with decodeURI, encodeURI, escape, unescape, // we seem to be able to replicate the IE borked encoding // IE doesn't do this new-fangled utf-8 thing. // and it's worse than that. // IE seems to treat the query string differently to the rest of the url // the query is treated as bona-fide utf8, but the first bit of the url is pissed around with // we fix up & for all browsers, just in case. var splitted=h.split('?'); splitted[0]=splitted[0].split('&').join('%26'); h=splitted.join('?'); var contribs=pg.re.contribs.exec(h); if (contribs) { if (contribs[1]=='title=') { contribs[3]=contribs[3].split('+').join(' '); } var u=new Title(contribs[3]); this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + u.stripNamespace())); return this; } var email=pg.re.email.exec(h); if (email) { this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(email[3]).stripNamespace())); return this; } var backlinks=pg.re.backlinks.exec(h); if (backlinks) { this.setUtf(this.decodeNasties(new Title(backlinks[3]))); return this; } //A dummy title object for a Special:Diff link. var specialdiff=pg.re.specialdiff.exec(h); if (specialdiff) { this.setUtf(this.decodeNasties(new Title(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Diff'))); return this; } // no more special cases to check -- // hopefully it's not a disguised user-related or specially treated special page var m=pg.re.main.exec(h); if(m === null) { this.value=null; } else { var fromBotInterface = /[?](.+[&])?title=/.test(h); if (fromBotInterface) { m[2]=m[2].split('+').join('_'); } var extracted = m[2] + (m[3] ? '#' + m[3] : ''); if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) { // Fix Safari issue // Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3. this.setUtf(decodeURIComponent(unescape(extracted))); } else { this.setUtf(this.decodeNasties(extracted)); } } return this; }; Title.prototype.decodeNasties=function(txt) { var ret= this.decodeEscapes(decodeURI(txt)); ret = ret.replace(/[_ ]*$/, ''); return ret; }; Title.prototype.decodeEscapes=function(txt) { var split=txt.parenSplit(/((?:[%][0-9A-Fa-f]{2})+)/); var len=split.length; for (var i=1; i<len; i=i+2) { // FIXME is decodeURIComponent better? split[i]=unescape(split[i]); } return split.join(''); }; Title.fromAnchor=function(a) { return new Title().fromAnchor(a); }; Title.prototype.fromAnchor=function(a) { if (!a) { this.value=null; return this; } return this.fromURL(a.href); }; Title.fromWikiText=function(txt) { return new Title().fromWikiText(txt); }; Title.prototype.fromWikiText=function(txt) { // FIXME - testing needed txt=myDecodeURI(txt); this.setUtf(txt); return this; }; Title.prototype.hintValue=function(){ if(!this.value) { return ''; } return safeDecodeURI(this.value); }; //<NOLITE> Title.prototype.toUserName=function(withNs) { if (this.namespaceId() != pg.nsUserId && this.namespaceId() != pg.nsUsertalkId) { this.value=null; return; } this.value = (withNs ? mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' : '') + this.stripNamespace().split('/')[0]; }; Title.prototype.userName=function(withNs) { var t=(new Title(this.value)); t.toUserName(withNs); if (t.value) { return t; } return null; }; Title.prototype.toTalkPage=function() { // convert article to a talk page, or if we can't, return null // In other words: return null if this ALREADY IS a talk page // and return the corresponding talk page otherwise // // Per https://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces // * All discussion namespaces have odd-integer indices // * The discussion namespace index for a specific namespace with index n is n + 1 if (this.value === null) { return null; } var namespaceId = this.namespaceId(); if (namespaceId>=0 && namespaceId % 2 === 0) //non-special and subject namespace { var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId+1]; if (typeof localizedNamespace!=='undefined') { if (localizedNamespace === '') { this.value = this.stripNamespace(); } else { this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace(); } return this.value; } } this.value=null; return null; }; //</NOLITE> // Return canonical, localized namespace Title.prototype.namespace=function() { return mw.config.get('wgFormattedNamespaces')[this.namespaceId()]; }; Title.prototype.namespaceId=function() { var n=this.value.indexOf(':'); if (n<0) { return 0; } //mainspace var namespaceId = mw.config.get('wgNamespaceIds')[this.value.substring(0,n).split(' ').join('_').toLowerCase()]; if (typeof namespaceId=='undefined') return 0; //mainspace return namespaceId; }; //<NOLITE> Title.prototype.talkPage=function() { var t=new Title(this.value); t.toTalkPage(); if (t.value) { return t; } return null; }; Title.prototype.isTalkPage=function() { if (this.talkPage()===null) { return true; } return false; }; Title.prototype.toArticleFromTalkPage=function() { //largely copy/paste from toTalkPage above. if (this.value === null) { return null; } var namespaceId = this.namespaceId(); if (namespaceId >= 0 && namespaceId % 2 == 1) //non-special and talk namespace { var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId-1]; if (typeof localizedNamespace!=='undefined') { if (localizedNamespace === '') { this.value = this.stripNamespace(); } else { this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace(); } return this.value; } } this.value=null; return null; }; Title.prototype.articleFromTalkPage=function() { var t=new Title(this.value); t.toArticleFromTalkPage(); if (t.value) { return t; } return null; }; Title.prototype.articleFromTalkOrArticle=function() { var t=new Title(this.value); if ( t.toArticleFromTalkPage() ) { return t; } return this; }; Title.prototype.isIpUser=function() { return pg.re.ipUser.test(this.userName()); }; //</NOLITE> Title.prototype.stripNamespace=function(){ // returns a string, not a Title var n=this.value.indexOf(':'); if (n<0) { return this.value; } var namespaceId = this.namespaceId(); if (namespaceId === pg.nsMainspaceId) return this.value; return this.value.substring(n+1); }; Title.prototype.setUtf=function(value){ if (!value) { this.value=''; return; } var anch=value.indexOf('#'); if(anch < 0) { this.value=value.split('_').join(' '); this.anchor=''; return; } this.value=value.substring(0,anch).split('_').join(' '); this.anchor=value.substring(anch+1); this.ns=null; // wait until namespace() is called }; Title.prototype.setUrl=function(urlfrag) { var anch=urlfrag.indexOf('#'); this.value=safeDecodeURI(urlfrag.substring(0,anch)); this.anchor=this.value.substring(anch+1); }; Title.prototype.append=function(x){ this.setUtf(this.value + x); }; Title.prototype.urlString=function(x) { if(!x) { x={}; } var v=this.toString(true); if (!x.omitAnchor && this.anchor) { v+= '#' + this.urlAnchor(); } if (!x.keepSpaces) { v=v.split(' ').join('_'); } return encodeURI(v).split('&').join('%26').split('?').join('%3F').split('+').join('%2B'); }; Title.prototype.removeAnchor=function() { return new Title(this.toString(true)); }; Title.prototype.toUrl=function() { return pg.wiki.titlebase + this.urlString(); }; function parseParams(url) { var specialDiff = pg.re.specialdiff.exec(url); if (specialDiff) { var split= specialDiff[1].split('/'); if (split.length==1) return {oldid:split[0], diff: 'prev'}; else if (split.length==2) return {oldid: split[0], diff: split[1]}; } var ret={}; if (url.indexOf('?')==-1) { return ret; } url = url.split('#')[0]; var s=url.split('?').slice(1).join(); var t=s.split('&'); for (var i=0; i<t.length; ++i) { var z=t[i].split('='); z.push(null); ret[z[0]]=z[1]; } //Diff revision with no oldid is interpreted as a diff to the previous revision by MediaWiki if (ret.diff && typeof(ret.oldid)==='undefined') { ret.oldid = "prev"; } //Documentation seems to say something different, but oldid can also accept prev/next, and Echo is emitting such URLs. Simple fixup during parameter decoding: if (ret.oldid && (ret.oldid==='prev' || ret.oldid==='next' || ret.oldid==='cur')) { var helper = ret.diff; ret.diff = ret.oldid; ret.oldid = helper; } return ret; } // (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup) // (b) change spaces to underscores // (c) encodeURI (just the straight one, no pg.re.urlNoPopup) function myDecodeURI (str) { var ret; // FIXME decodeURIComponent?? try { ret=decodeURI(str.toString()); } catch (summat) { return str; } for (var i=0; i<pg.misc.decodeExtras.length; ++i) { var from=pg.misc.decodeExtras[i].from; var to=pg.misc.decodeExtras[i].to; ret=ret.split(from).join(to); } return ret; } function safeDecodeURI(str) { var ret=myDecodeURI(str); return ret || str; } /////////// // TESTS // /////////// //<NOLITE> function isDisambig(data, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; } return ! article.isTalkPage() && pg.re.disambig.test(data); } function stubCount(data, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; } var sectStub=0; var realStub=0; if (pg.re.stub.test(data)) { var s=data.parenSplit(pg.re.stub); for (var i=1; i<s.length; i=i+2) { if (s[i]) { ++sectStub; } else { ++realStub; } } } return { real: realStub, sect: sectStub }; } function isValidImageName(str){ // extend as needed... return ( str.indexOf('{') == -1 ); } function isInStrippableNamespace(article) { // Does the namespace allow subpages // Note, would be better if we had access to wgNamespacesWithSubpages return ( article.namespaceId() !== 0 ); } function isInMainNamespace(article) { return article.namespaceId() === 0; } function anchorContainsImage(a) { // iterate over children of anchor a // see if any are images if (a === null) { return false; } var kids=a.childNodes; for (var i=0; i<kids.length; ++i) { if (kids[i].nodeName=='IMG') { return true; } } return false; } //</NOLITE> function isPopupLink(a) { // NB for performance reasons, TOC links generally return true // they should be stripped out later if (!markNopopupSpanLinks.done) { markNopopupSpanLinks(); } if (a.inNopopupSpan) { return false; } // FIXME is this faster inline? if (a.onmousedown || a.getAttribute('nopopup')) { return false; } var h=a.href; if (h === document.location.href+'#') { return false; } if (!pg.re.basenames.test(h)) { return false; } if (!pg.re.urlNoPopup.test(h)) { return true; } return ( (pg.re.email.test(h) || pg.re.contribs.test(h) || pg.re.backlinks.test(h) || pg.re.specialdiff.test(h)) && h.indexOf('&limit=') == -1 ); } function markNopopupSpanLinks() { if( !getValueOf('popupOnlyArticleLinks')) fixVectorMenuPopups(); var s = $('.nopopups').toArray(); for (var i=0; i<s.length; ++i) { var as=s[i].getElementsByTagName('a'); for (var j=0; j<as.length; ++j) { as[j].inNopopupSpan=true; } } markNopopupSpanLinks.done=true; } function fixVectorMenuPopups() { $('div.vectorMenu h3:first a:first').prop('inNopopupSpan', true); } // ENDFILE: titles.js // STARTFILE: getpage.js ////////////////////////////////////////////////// // Wiki-specific downloading // // Schematic for a getWiki call // // getPageWithCaching // | // false | true // getPage<-[findPictureInCache]->-onComplete(a fake download) // \. // (async)->addPageToCache(download)->-onComplete(download) // check cache to see if page exists function getPageWithCaching(url, onComplete, owner) { log('getPageWithCaching, url='+url); var i=findInPageCache(url); var d; if (i > -1) { d=fakeDownload(url, owner.idNumber, onComplete, pg.cache.pages[i].data, pg.cache.pages[i].lastModified, owner); } else { d=getPage(url, onComplete, owner); if (d && owner && owner.addDownload) { owner.addDownload(d); d.owner=owner; } } } function getPage(url, onComplete, owner) { log('getPage'); var callback= function (d) { if (!d.aborted) {addPageToCache(d); onComplete(d);} }; return startDownload(url, owner.idNumber, callback); } function findInPageCache(url) { for (var i=0; i<pg.cache.pages.length; ++i) { if (url==pg.cache.pages[i].url) { return i; } } return -1; } function addPageToCache(download) { log('addPageToCache '+download.url); var page = {url: download.url, data: download.data, lastModified: download.lastModified}; return pg.cache.pages.push(page); } // ENDFILE: getpage.js // STARTFILE: parensplit.js ////////////////////////////////////////////////// // parenSplit // String.prototype.parenSplit should do what ECMAscript says String.prototype.split does, // interspersing paren matches (regex capturing groups) between the split elements. // i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c'] if (String('abc'.split(/(b)/))!='a,b,c') { // broken String.split, e.g. konq, IE < 10 String.prototype.parenSplit=function (re) { re=nonGlobalRegex(re); var s=this; var m=re.exec(s); var ret=[]; while (m && s) { // without the following loop, we have // 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/) for(var i=0; i<m.length; ++i) { if (typeof m[i]=='undefined') m[i]=''; } ret.push(s.substring(0,m.index)); ret = ret.concat(m.slice(1)); s=s.substring(m.index + m[0].length); m=re.exec(s); } ret.push(s); return ret; }; } else { String.prototype.parenSplit=function (re) { return this.split(re); }; String.prototype.parenSplit.isNative=true; } function nonGlobalRegex(re) { var s=re.toString(); var flags=''; for (var j=s.length; s.charAt(j) != '/'; --j) { if (s.charAt(j) != 'g') { flags += s.charAt(j); } } var t=s.substring(1,j); return RegExp(t,flags); } // ENDFILE: parensplit.js // STARTFILE: tools.js // IE madness with encoding // ======================== // // suppose throughout that the page is in utf8, like wikipedia // // if a is an anchor DOM element and a.href should consist of // // http://host.name.here/wiki/foo?bar=baz // // then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie)) // but IE gives bar=baz correctly as plain utf8 // // --------------------------------- // // IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here. // // --------------------------------- // // summat else // Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm //<NOLITE> function getJsObj(json) { try { var json_ret = JSON.parse(json); if( json_ret.warnings ) { for( var w=0; w < json_ret.warnings.length; w++ ) { if( json_ret.warnings[w]['*'] ) { log( json_ret.warnings[w]['*'] ); } else { log( json_ret.warnings[w]['warnings'] ); } } } else if ( json_ret.error ) { errlog( json_ret.error.code + ': ' + json_ret.error.info ); } return json_ret; } catch (someError) { errlog('Something went wrong with getJsObj, json='+json); return 1; } } function anyChild(obj) { for (var p in obj) { return obj[p]; } return null; } //</NOLITE> function upcaseFirst(str) { if (typeof str != typeof '' || str === '') return ''; return str.charAt(0).toUpperCase() + str.substring(1); } function findInArray(arr, foo) { if (!arr || !arr.length) { return -1; } var len=arr.length; for (var i=0; i<len; ++i) { if (arr[i]==foo) { return i; } } return -1; } /* eslint-disable no-unused-vars */ function nextOne (array, value) { // NB if the array has two consecutive entries equal // then this will loop on successive calls var i=findInArray(array, value); if (i<0) { return null; } return array[i+1]; } /* eslint-enable no-unused-vars */ function literalizeRegex(str){ return mw.util.escapeRegExp(str); } String.prototype.entify=function() { //var shy='&shy;'; return this.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;'/*+shy*/).split('"').join('&quot;'); }; // Array filter function function removeNulls(val) { return val !== null; } function joinPath(list) { return list.filter(removeNulls).join('/'); } function simplePrintf(str, subs) { if (!str || !subs) { return str; } var ret=[]; var s=str.parenSplit(/(%s|\$[0-9]+)/); var i=0; do { ret.push(s.shift()); if ( !s.length ) { break; } var cmd=s.shift(); if (cmd == '%s') { if ( i < subs.length ) { ret.push(subs[i]); } else { ret.push(cmd); } ++i; } else { var j=parseInt( cmd.replace('$', ''), 10 ) - 1; if ( j > -1 && j < subs.length ) { ret.push(subs[j]); } else { ret.push(cmd); } } } while (s.length > 0); return ret.join(''); } /* eslint-disable no-unused-vars */ function isString(x) { return (typeof x === 'string' || x instanceof String); } function isNumber(x) { return (typeof x === 'number' || x instanceof Number); } function isRegExp(x) { return x instanceof RegExp; } function isArray (x) { return x instanceof Array; } function isObject(x) { return x instanceof Object; } function isFunction(x) { return !isRegExp(x) && (typeof x === 'function' || x instanceof Function); } /* eslint-enable no-unused-vars */ function repeatString(s,mult) { var ret=''; for (var i=0; i<mult; ++i) { ret += s; } return ret; } function zeroFill(s, min) { min = min || 2; var t=s.toString(); return repeatString('0', min - t.length) + t; } function map(f, o) { if (isArray(o)) { return map_array(f,o); } return map_object(f,o); } function map_array(f,o) { var ret=[]; for (var i=0; i<o.length; ++i) { ret.push(f(o[i])); } return ret; } function map_object(f,o) { var ret={}; for (var i in o) { ret[o]=f(o[i]); } return ret; } pg.escapeQuotesHTML = function ( text ) { return text .replace(/&/g, "&amp;") .replace(/"/g, "&quot;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;"); }; // ENDFILE: tools.js // STARTFILE: dab.js //<NOLITE> ////////////////////////////////////////////////// // Dab-fixing code // function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) { log('retargetDab: newTarget='+newTarget + ' oldTarget=' + oldTarget); return changeLinkTargetLink( {newTarget: newTarget, text: newTarget.split(' ').join('&nbsp;'), hint: tprintf('disambigHint', [newTarget]), summary: simplePrintf( getValueOf('popupFixDabsSummary'), [friendlyCurrentArticleName, newTarget ]), clickButton: getValueOf('popupDabsAutoClick'), minor: true, oldTarget: oldTarget, watch: getValueOf('popupWatchDisambiggedPages'), title: titleToEdit}); } function listLinks(wikitext, oldTarget, titleToEdit) { // mediawiki strips trailing spaces, so we do the same // testcase: https://en.wikipedia.org/w/index.php?title=Radial&oldid=97365633 var reg=RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi'); var ret=[]; var splitted=wikitext.parenSplit(reg); // ^[a-z]+ should match interwiki links, hopefully (case-insensitive) // and ^[a-z]* should match those and [[:Category...]] style links too var omitRegex=RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory'); var friendlyCurrentArticleName= oldTarget.toString(); var wikPos = getValueOf('popupDabWiktionary'); for (var i=1; i<splitted.length; i=i+3) { if (typeof splitted[i] == typeof 'string' && splitted[i].length>0 && !omitRegex.test(splitted[i])) { ret.push( retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit) ); } /* if */ } /* for loop */ ret = rmDupesFromSortedList(ret.sort()); if (wikPos) { var wikTarget='wiktionary:' + friendlyCurrentArticleName.replace( RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1' ); var meth; if (wikPos.toLowerCase() == 'first') { meth = 'unshift'; } else { meth = 'push'; } ret[meth]( retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) ); } ret.push(changeLinkTargetLink( { newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'), hint: popupString("remove all links to this disambig page from this article"), clickButton: getValueOf('popupDabsAutoClick'), oldTarget: oldTarget, summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]), watch: getValueOf('popupWatchDisambiggedPages'), title: titleToEdit })); return ret; } function rmDupesFromSortedList(list) { var ret=[]; for (var i=0; i<list.length; ++i) { if (ret.length === 0 || list[i]!=ret[ret.length-1]) { ret.push(list[i]); } } return ret; } function makeFixDab(data, navpop) { // grab title from parent popup if there is one; default exists in changeLinkTargetLink var titleToEdit=(navpop.parentPopup && navpop.parentPopup.article.toString()); var list=listLinks(data, navpop.originalArticle, titleToEdit); if (list.length === 0) { log('listLinks returned empty list'); return null; } var html='<hr />' + popupString('Click to disambiguate this link to:') + '<br>'; html+=list.join(', '); return html; } function makeFixDabs(wikiText, navpop) { if (getValueOf('popupFixDabs') && isDisambig(wikiText, navpop.article) && Title.fromURL(location.href).namespaceId() != pg.nsSpecialId && navpop.article.talkPage() ) { setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber); } } function popupRedlinkHTML(article) { return changeLinkTargetLink( { newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'), hint: popupString("remove all links to this page from this article"), clickButton: getValueOf('popupRedlinkAutoClick'), oldTarget: article.toString(), summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()])}); } //</NOLITE> // ENDFILE: dab.js // STARTFILE: htmloutput.js // this has to use a timer loop as we don't know if the DOM element exists when we want to set the text function setPopupHTML (str, elementId, popupId, onSuccess, append) { if (typeof popupId === 'undefined') { //console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100)); popupId = pg.idNumber; } var popupElement=document.getElementById(elementId+popupId); if (popupElement) { if (!append) { popupElement.innerHTML=''; } if (isString(str)) { popupElement.innerHTML+=str; } else { popupElement.appendChild(str); } if (onSuccess) { onSuccess(); } setTimeout(checkPopupPosition, 100); return true; } else { // call this function again in a little while... setTimeout(function(){ setPopupHTML(str,elementId,popupId,onSuccess); }, 600); } return null; } //<NOLITE> function setPopupTrailer(str,id) {return setPopupHTML(str, 'popupData', id);} //</NOLITE> // args.navpopup is mandatory // optional: args.redir, args.redirTarget // FIXME: ye gods, this is ugly stuff function fillEmptySpans(args) { // if redir is present and true then redirTarget is mandatory var redir=true; var rcid; if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) { redir=false; } var a=args.navpopup.parentAnchor; var article, hint=null, oldid=null, params={}; if (redir && typeof args.redirTarget == typeof {}) { article=args.redirTarget; //hint=article.hintValue(); } else { article=(new Title()).fromAnchor(a); hint=a.originalTitle || article.hintValue(); params=parseParams(a.href); oldid=(getValueOf('popupHistoricalLinks')) ? params.oldid : null; rcid=params.rcid; } var x={ article:article, hint: hint, oldid: oldid, rcid: rcid, navpop:args.navpopup, params:params }; var structure=pg.structures[getValueOf('popupStructure')]; if (typeof structure != 'object') { setPopupHTML('popupError', 'Unknown structure (this should never happen): '+ pg.option.popupStructure, args.navpopup.idNumber); return; } var spans=flatten(pg.misc.layout); var numspans = spans.length; var redirs=pg.misc.redirSpans; for (var i=0; i<numspans; ++i) { var found = redirs && (redirs.indexOf( spans[i] ) !== -1); //log('redir='+redir+', found='+found+', spans[i]='+spans[i]); if ( (found && !redir) || (!found && redir) ) { //log('skipping this set of the loop'); continue; } var structurefn=structure[spans[i]]; var setfn = setPopupHTML; if (getValueOf('popupActiveNavlinks') && (spans[i].indexOf('popupTopLinks')===0 || spans[i].indexOf('popupRedirTopLinks')===0) ) { setfn = setPopupTipsAndHTML; } switch (typeof structurefn) { case 'function': log('running '+spans[i]+'({article:'+x.article+', hint:'+x.hint+', oldid: '+x.oldid+'})'); setfn(structurefn(x), spans[i], args.navpopup.idNumber); break; case 'string': setfn(structurefn, spans[i], args.navpopup.idNumber); break; default: errlog('unknown thing with label '+spans[i] + ' (span index was ' + i + ')'); break; } } } // flatten an array function flatten(list, start) { var ret=[]; if (typeof start == 'undefined') { start=0; } for (var i=start; i<list.length; ++i) { if (typeof list[i] == typeof []) { return ret.concat(flatten(list[i])).concat(flatten(list, i+1)); } else { ret.push(list[i]); } } return ret; } // Generate html for whole popup function popupHTML (a) { getValueOf('popupStructure'); var structure=pg.structures[pg.option.popupStructure]; if (typeof structure != 'object') { //return 'Unknown structure: '+pg.option.popupStructure; // override user choice pg.option.popupStructure=pg.optionDefault.popupStructure; return popupHTML(a); } if (typeof structure.popupLayout != 'function') { return 'Bad layout'; } pg.misc.layout=structure.popupLayout(); if (typeof structure.popupRedirSpans === 'function') { pg.misc.redirSpans=structure.popupRedirSpans(); } else { pg.misc.redirSpans=[]; } return makeEmptySpans(pg.misc.layout, a.navpopup); } function makeEmptySpans (list, navpop) { var ret=''; for (var i=0; i<list.length; ++i) { if (typeof list[i] == typeof '') { ret += emptySpanHTML(list[i], navpop.idNumber, 'div'); } else if (typeof list[i] == typeof [] && list[i].length > 0 ) { ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop)); } else if (typeof list[i] == typeof {} && list[i].nodeType ) { ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType); } } return ret; } function emptySpanHTML(name, id, tag, classname) { tag = tag || 'span'; if (!classname) { classname = emptySpanHTML.classAliases[name]; } classname = classname || name; if (name == getValueOf('popupDragHandle')) { classname += ' popupDragHandle'; } return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]); } emptySpanHTML.classAliases={ 'popupSecondPreview': 'popupPreview' }; // generate html for popup image // <a id="popupImageLinkn"><img id="popupImagen"> // where n=idNumber function imageHTML(article, idNumber) { return simplePrintf('<a id="popupImageLink$1">' + '<img align="right" valign="top" id="popupImg$1" style="display: none;"></img>' + '</a>', [ idNumber ]); } function popTipsSoonFn(id, when, popData) { if (!when) { when=250; } var popTips=function(){ setupTooltips(document.getElementById(id), false, true, popData); }; return function() { setTimeout( popTips, when, popData ); }; } function setPopupTipsAndHTML(html, divname, idnumber, popData) { setPopupHTML(html, divname, idnumber, getValueOf('popupSubpopups') ? popTipsSoonFn(divname + idnumber, null, popData) : null); } // ENDFILE: htmloutput.js // STARTFILE: mouseout.js ////////////////////////////////////////////////// // fuzzy checks function fuzzyCursorOffMenus(x,y, fuzz, parent) { if (!parent) { return null; } var uls=parent.getElementsByTagName('ul'); for (var i=0; i<uls.length; ++i) { if (uls[i].className=='popup_menu') { if (uls[i].offsetWidth > 0) return false; } // else {document.title+='.';} } return true; } function checkPopupPosition () { // stop the popup running off the right of the screen // FIXME avoid pg.current.link if (pg.current.link && pg.current.link.navpopup) pg.current.link.navpopup.limitHorizontalPosition(); } function mouseOutWikiLink () { //console ('mouseOutWikiLink'); var a=this; removeModifierKeyHandler(a); if (a.navpopup === null || typeof a.navpopup === 'undefined') return; if ( ! a.navpopup.isVisible() ) { a.navpopup.banish(); return; } restoreTitle(a); Navpopup.tracker.addHook(posCheckerHook(a.navpopup)); } function posCheckerHook(navpop) { return function() { if (!navpop.isVisible()) { return true; /* remove this hook */ } if (Navpopup.tracker.dirty) { return false; } var x=Navpopup.tracker.x, y=Navpopup.tracker.y; var mouseOverNavpop = navpop.isWithin(x,y,navpop.fuzz, navpop.mainDiv) || !fuzzyCursorOffMenus(x,y,navpop.fuzz, navpop.mainDiv); // FIXME it'd be prettier to do this internal to the Navpopup objects var t=getValueOf('popupHideDelay'); if (t) { t = t * 1000; } if (!t) { if(!mouseOverNavpop) { if(navpop.parentAnchor) { restoreTitle( navpop.parentAnchor ); } navpop.banish(); return true; /* remove this hook */ } return false; } // we have a hide delay set var d=+(new Date()); if ( !navpop.mouseLeavingTime ) { navpop.mouseLeavingTime = d; return false; } if ( mouseOverNavpop ) { navpop.mouseLeavingTime=null; return false; } if (d - navpop.mouseLeavingTime > t) { navpop.mouseLeavingTime=null; navpop.banish(); return true; /* remove this hook */ } return false; }; } function runStopPopupTimer(navpop) { // at this point, we should have left the link but remain within the popup // so we call this function again until we leave the popup. if (!navpop.stopPopupTimer) { navpop.stopPopupTimer=setInterval(posCheckerHook(navpop), 500); navpop.addHook(function(){clearInterval(navpop.stopPopupTimer);}, 'hide', 'before'); } } // ENDFILE: mouseout.js // STARTFILE: previewmaker.js /** @fileoverview Defines the {@link Previewmaker} object, which generates short previews from wiki markup. */ /** Creates a new Previewmaker @constructor @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext. @param {String} wikiText The Wikitext source of the page we wish to preview. @param {String} baseUrl The url we should prepend when creating relative urls. @param {Navpopup} owner The navpop associated to this preview generator */ function Previewmaker(wikiText, baseUrl, owner) { /** The wikitext which is manipulated to generate the preview. */ this.originalData=wikiText; this.baseUrl=baseUrl; this.owner=owner; this.maxCharacters=getValueOf('popupMaxPreviewCharacters'); this.maxSentences=getValueOf('popupMaxPreviewSentences'); this.setData(); } Previewmaker.prototype.setData=function() { var maxSize=Math.max(10000, 2*this.maxCharacters); this.data=this.originalData.substring(0,maxSize); }; /** Remove HTML comments @private */ Previewmaker.prototype.killComments = function () { // this also kills one trailing newline, eg [[diamyo]] this.data=this.data.replace(RegExp('^<!--[^$]*?-->\\n|\\n<!--[^$]*?-->(?=\\n)|<!--[^$]*?-->', 'g'), ''); }; /** @private */ Previewmaker.prototype.killDivs = function () { // say goodbye, divs (can be nested, so use * not *?) this.data=this.data.replace(RegExp('< *div[^>]* *>[\\s\\S]*?< */ *div *>', 'gi'), ''); }; /** @private */ Previewmaker.prototype.killGalleries = function () { this.data=this.data.replace(RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>', 'gi'), ''); }; /** @private */ Previewmaker.prototype.kill = function(opening, closing, subopening, subclosing, repl) { var oldk=this.data; var k=this.killStuff(this.data, opening, closing, subopening, subclosing, repl); while (k.length < oldk.length) { oldk=k; k=this.killStuff(k, opening, closing, subopening, subclosing, repl); } this.data=k; }; /** @private */ Previewmaker.prototype.killStuff = function (txt, opening, closing, subopening, subclosing, repl) { var op=this.makeRegexp(opening); var cl=this.makeRegexp(closing, '^'); var sb=subopening ? this.makeRegexp(subopening, '^') : null; var sc=subclosing ? this.makeRegexp(subclosing, '^') : cl; if (!op || !cl) { alert('Navigation Popups error: op or cl is null! something is wrong.'); return; } if (!op.test(txt)) { return txt; } var ret=''; var opResult = op.exec(txt); ret = txt.substring(0,opResult.index); txt=txt.substring(opResult.index+opResult[0].length); var depth = 1; while (txt.length > 0) { var removal=0; if (depth==1 && cl.test(txt)) { depth--; removal=cl.exec(txt)[0].length; } else if (depth > 1 && sc.test(txt)) { depth--; removal=sc.exec(txt)[0].length; }else if (sb && sb.test(txt)) { depth++; removal=sb.exec(txt)[0].length; } if ( !removal ) { removal = 1; } txt=txt.substring(removal); if (depth === 0) { break; } } return ret + (repl || '') + txt; }; /** @private */ Previewmaker.prototype.makeRegexp = function (x, prefix, suffix) { prefix = prefix || ''; suffix = suffix || ''; var reStr=''; var flags=''; if (isString(x)) { reStr=prefix + literalizeRegex(x) + suffix; } else if (isRegExp(x)) { var s=x.toString().substring(1); var sp=s.split('/'); flags=sp[sp.length-1]; sp[sp.length-1]=''; s=sp.join('/'); s=s.substring(0,s.length-1); reStr= prefix + s + suffix; } else { log ('makeRegexp failed'); } log ('makeRegexp: got reStr=' + reStr + ', flags=' + flags); return RegExp(reStr, flags); }; /** @private */ Previewmaker.prototype.killBoxTemplates = function () { // taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general // also, have float_begin, ... float_end this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /[}][}]\s*/, '{{'); // infoboxes etc // from User:Zyxw/popups.js: kill frames too this.kill(RegExp('[{][{][^{}\\s|]*?(infobox|elementbox|frame)[_ ]', 'i'), /[}][}]\s*/, '{{'); }; /** @private */ Previewmaker.prototype.killTemplates = function () { // the plus sign is to avoid transcluding [[Template:', ']] this.kill('{'+'{', '}'+'}', '{', '}', ' '); }; /** @private */ Previewmaker.prototype.killTables = function () { // tables are bad, too // this can be slow, but it's an inprovement over a browser hang // torture test: [[Comparison_of_Intel_Central_Processing_Units]] this.kill('{|', /[|]}\s*/, '{|'); this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i); // remove lines starting with a pipe for the hell of it (?) this.data=this.data.replace(RegExp('^[|].*$', 'mg'), ''); }; /** @private */ Previewmaker.prototype.killImages = function () { var forbiddenNamespaceAliases = []; jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) { if (_namespaceId!=pg.nsImageId && _namespaceId!=pg.nsCategoryId) return; forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments! }); // images and categories are a nono this.kill(RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'), /\]\]\s*/, '[', ']'); }; /** @private */ Previewmaker.prototype.killHTML = function () { // kill <ref ...>...</ref> this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i); // let's also delete entire lines starting with <. it's worth a try. this.data=this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n'); // and those pesky html tags, but not <nowiki> or <blockquote> var splitted=this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/); var len=splitted.length; for (var i=1; i<len; i=i+2) { switch (splitted[i]) { case '<nowiki>': case '</nowiki>': case '<blockquote>': case '</blockquote>': break; default: splitted[i]=''; } } this.data=splitted.join(''); }; /** @private */ Previewmaker.prototype.killChunks = function() { // heuristics alert // chunks of italic text? you crazy, man? var italicChunkRegex=new RegExp ("((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+", 'g'); // keep stuff separated, though, so stick in \n (fixes [[Union Jack]]? this.data=this.data.replace(italicChunkRegex, '\n'); }; /** @private */ Previewmaker.prototype.mopup = function () { // we simply *can't* be doing with horizontal rules right now this.data=this.data.replace(RegExp('^-{4,}','mg'),''); // no indented lines this.data=this.data.replace(RegExp('(^|\\n) *:[^\\n]*','g'), ''); // replace __TOC__, __NOTOC__ and whatever else there is // this'll probably do this.data=this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'),''); }; /** @private */ Previewmaker.prototype.firstBit = function () { // dont't be givin' me no subsequent paragraphs, you hear me? /// first we "normalize" section headings, removing whitespace after, adding before var d=this.data; if (getValueOf('popupPreviewCutHeadings')) { this.data=this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 '); /// then we want to get rid of paragraph breaks whose text ends badly this.data=this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n'); this.data=this.data.replace(RegExp('^[\\s\\n]*'), ''); var stuff=(RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data); if (stuff) { d = stuff[0]; } if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; } /// now put \n\n after sections so that bullets and numbered lists work d=d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n'); } // Split sentences. Superfluous sentences are RIGHT OUT. // note: exactly 1 set of parens here needed to make the slice work d = d.parenSplit(RegExp('([!?.]+["'+"'"+']*\\s)','g')); // leading space is bad, mmkay? d[0]=d[0].replace(RegExp('^\\s*'), ''); var notSentenceEnds=RegExp('([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$', 'i'); d = this.fixSentenceEnds(d, notSentenceEnds); this.fullLength=d.join('').length; var n=this.maxSentences; var dd=this.firstSentences(d,n); do { dd=this.firstSentences(d,n); --n; } while ( dd.length > this.maxCharacters && n !== 0 ); this.data = dd; }; /** @private */ Previewmaker.prototype.fixSentenceEnds = function(strs, reg) { // take an array of strings, strs // join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg for (var i=0; i<strs.length-2; ++i) { if (reg.test(strs[i])) { var a=[]; for (var j=0; j<strs.length; ++j) { if (j<i) a[j]=strs[j]; if (j==i) a[i]=strs[i]+strs[i+1]+strs[i+2]; if (j>i+2) a[j-2]=strs[j]; } return this.fixSentenceEnds(a,reg); } } return strs; }; /** @private */ Previewmaker.prototype.firstSentences = function(strs, howmany) { var t=strs.slice(0, 2*howmany); return t.join(''); }; /** @private */ Previewmaker.prototype.killBadWhitespace = function() { // also cleans up isolated '''', eg [[Suntory Sungoliath]] this.data=this.data.replace(RegExp('^ *\'+ *$', 'gm'), ''); }; /** Runs the various methods to generate the preview. The preview is stored in the <code>html</html> field. @private */ Previewmaker.prototype.makePreview = function() { if (this.owner.article.namespaceId()!=pg.nsTemplateId && this.owner.article.namespaceId()!=pg.nsImageId ) { this.killComments(); this.killDivs(); this.killGalleries(); this.killBoxTemplates(); if (getValueOf('popupPreviewKillTemplates')) { this.killTemplates(); } else { this.killMultilineTemplates(); } this.killTables(); this.killImages(); this.killHTML(); this.killChunks(); this.mopup(); this.firstBit(); this.killBadWhitespace(); } else { this.killHTML(); } this.html=wiki2html(this.data, this.baseUrl); // needs livepreview this.fixHTML(); this.stripLongTemplates(); }; /** @private */ Previewmaker.prototype.esWiki2HtmlPart = function(data) { var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink reLinks.lastIndex = 0; //reset regex var match; var result = ""; var postfixIndex = 0; while ((match = reLinks.exec(data))) //match all wikilinks { //FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases. result += pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) + '<a href="'+Insta.conf.paths.articles+pg.escapeQuotesHTML(match[1])+'">'+pg.escapeQuotesHTML((match[2]?match[2]:match[1])+match[3])+"</a>"; postfixIndex = reLinks.lastIndex; } //append the rest result += pg.escapeQuotesHTML(data.substring(postfixIndex)); return result; }; Previewmaker.prototype.editSummaryPreview=function() { var reAes = /\/\* *(.*?) *\*\//g; //match the first section marker reAes.lastIndex = 0; //reset regex var match; match = reAes.exec(this.data); if (match) { //we have a section link. Split it, process it, combine it. var prefix = this.data.substring(0,match.index-1); var section = match[1]; var postfix = this.data.substring(reAes.lastIndex); var start = "<span class='autocomment'>"; var end = "</span>"; if (prefix.length>0) start = this.esWiki2HtmlPart(prefix) + " " + start + "- "; if (postfix.length>0) end = ": " + end + this.esWiki2HtmlPart(postfix); var t=new Title().fromURL(this.baseUrl); t.anchorFromUtf(section); var sectionLink = Insta.conf.paths.articles + pg.escapeQuotesHTML(t.toString(true)) + '#' + pg.escapeQuotesHTML(t.anchor); return start + '<a href="'+sectionLink+'">&rarr;</a> '+pg.escapeQuotesHTML(section) + end; } //else there's no section link, htmlify the whole thing. return this.esWiki2HtmlPart(this.data); }; //<NOLITE> /** Test function for debugging preview problems one step at a time. */ /*eslint-disable */ function previewSteps(txt) { try { txt=txt || document.editform.wpTextbox1.value; } catch (err) { if (pg.cache.pages.length > 0) { txt=pg.cache.pages[pg.cache.pages.length-1].data; } else { alert('provide text or use an edit page'); } } txt=txt.substring(0,10000); var base=pg.wiki.articlebase + Title.fromURL(document.location.href).urlString(); var p=new Previewmaker(txt, base, pg.current.link.navpopup); if (this.owner.article.namespaceId() != pg.nsTemplateId) { p.killComments(); if (!confirm('done killComments(). Continue?\n---\n' + p.data)) { return; } p.killDivs(); if (!confirm('done killDivs(). Continue?\n---\n' + p.data)) { return; } p.killGalleries(); if (!confirm('done killGalleries(). Continue?\n---\n' + p.data)) { return; } p.killBoxTemplates(); if (!confirm('done killBoxTemplates(). Continue?\n---\n' + p.data)) { return; } if (getValueOf('popupPreviewKillTemplates')) { p.killTemplates(); if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) { return; } } else { p.killMultilineTemplates(); if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) { return; } } p.killTables(); if (!confirm('done killTables(). Continue?\n---\n' + p.data)) { return; } p.killImages(); if (!confirm('done killImages(). Continue?\n---\n' + p.data)) { return; } p.killHTML(); if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) { return; } p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; } p.mopup(); if (!confirm('done mopup(). Continue?\n---\n' + p.data)) { return; } p.firstBit(); if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) { return; } p.killBadWhitespace(); if (!confirm('done killBadWhitespace(). Continue?\n---\n' + p.data)) { return; } } p.html=wiki2html(p.data, base); // needs livepreview p.fixHTML(); if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) { return; } p.stripLongTemplates(); if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) { return; } alert('finished preview - end result follows.\n---\n' + p.html); } /*eslint-enable */ //</NOLITE> /** Works around livepreview bugs. @private */ Previewmaker.prototype.fixHTML = function() { if(!this.html) return; var ret = this.html; // fix question marks in wiki links // maybe this'll break some stuff :-( ret=ret.replace(RegExp('(<a href="' + pg.wiki.articlePath + '/[^"]*)[?](.*?")', 'g'), '$1%3F$2'); ret=ret.replace(RegExp('(<a href=\'' + pg.wiki.articlePath + '/[^\']*)[?](.*?\')', 'g'), '$1%3F$2'); // FIXME fix up % too this.html=ret; }; /** Generates the preview and displays it in the current popup. Does nothing if the generated preview is invalid or consists of whitespace only. Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true. */ Previewmaker.prototype.showPreview = function () { this.makePreview(); if (typeof this.html != typeof '') return; if (RegExp('^\\s*$').test(this.html)) return; setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber); setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, { owner: this.owner }); var more = (this.fullLength > this.data.length) ? this.moreLink() : ''; setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber); }; /** @private */ Previewmaker.prototype.moreLink=function() { var a=document.createElement('a'); a.className='popupMoreLink'; a.innerHTML=popupString('more...'); var savedThis=this; a.onclick=function() { savedThis.maxCharacters+=2000; savedThis.maxSentences+=20; savedThis.setData(); savedThis.showPreview(); }; return a; }; /** @private */ Previewmaker.prototype.stripLongTemplates = function() { // operates on the HTML! this.html=this.html.replace(RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'), ''); this.html=this.html.split('\n').join(' '); // workaround for <pre> templates this.html=this.html.replace(RegExp('[{][{][^}]*<pre>[^}]*[}][}]','gi'), ''); }; /** @private */ Previewmaker.prototype.killMultilineTemplates = function() { this.kill('{{{', '}}}'); this.kill(RegExp('\\s*[{][{][^{}]*\\n'), '}}', '{{'); }; // ENDFILE: previewmaker.js // STARTFILE: querypreview.js function loadAPIPreview(queryType, article, navpop) { var art=new Title(article).urlString(); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&'; var htmlGenerator=function(/*a, d*/){alert('invalid html generator');}; var usernameart = ''; switch (queryType) { case 'history': url += 'titles=' + art + '&prop=revisions&rvlimit=' + getValueOf('popupHistoryPreviewLimit'); htmlGenerator=APIhistoryPreviewHTML; break; case 'category': url += 'list=categorymembers&cmtitle=' + art; htmlGenerator=APIcategoryPreviewHTML; break; case 'userinfo': var username = new Title( article ).userName(); usernameart = encodeURIComponent( username ); if (pg.re.ipUser.test(username)) { url += 'list=blocks&bkprop=range|restrictions&bkip=' + usernameart; } else { url += 'list=users|usercontribs&usprop=blockinfo|groups|editcount|registration|gender&ususers=' + usernameart + "&meta=globaluserinfo&guiprop=groups|unattached&guiuser="+ usernameart + "&uclimit=1&ucprop=timestamp&ucuser=" + usernameart; } htmlGenerator=APIuserInfoPreviewHTML; break; case 'contribs': usernameart = encodeURIComponent( new Title( article ).userName() ); url += 'list=usercontribs&ucuser=' + usernameart + '&uclimit=' + getValueOf('popupContribsPreviewLimit'); htmlGenerator=APIcontribsPreviewHTML; break; case 'imagepagepreview': var trail=''; if (getValueOf('popupImageLinks')) { trail = '&list=imageusage&iutitle=' + art; } url += 'titles=' + art + '&prop=revisions|imageinfo&rvprop=content' + trail; htmlGenerator=APIimagepagePreviewHTML; break; case 'backlinks': url += 'list=backlinks&bltitle=' + art; htmlGenerator=APIbacklinksPreviewHTML; break; case 'revision': if (article.oldid) { url += 'revids=' + article.oldid; } else { url += 'titles=' + article.removeAnchor().urlString(); } url += '&prop=revisions|pageprops|info|images|categories&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max'; htmlGenerator=APIrevisionPreviewHTML; break; } pendingNavpopTask(navpop); var callback=function(d){ log( "callback of API functions was hit" ); showAPIPreview(queryType, htmlGenerator(article,d,navpop), navpop.idNumber, navpop, d); }; var go = function(){ getPageWithCaching(url, callback, navpop); return true; }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_'+queryType+'_QUERY_DATA'); } } function linkList(list) { list.sort(function(x,y) { return (x==y ? 0 : (x<y ? -1 : 1)); }); var buf=[]; for (var i=0; i<list.length; ++i) { buf.push(wikiLink({article: new Title(list[i]), text: list[i].split(' ').join('&nbsp;'), action: 'view'})); } return buf.join(', '); } function getTimeOffset() { var tz = mw.user.options.get('timecorrection'); if(tz) { if( tz.indexOf('|') > -1 ) { // New format return parseInt(tz.split('|')[1],10); } else if ( tz.indexOf(':') > -1 ) { // Old format return( parseInt(tz,10)*60 + parseInt(tz.split(':')[1],10) ); } } return 0; } /* * Creates a HTML table that's shown in the history and user-contribs popups. * @param {Object[]} h - a list of revisions, returned from the API * @param {boolean} reallyContribs - true only if we're displaying user contributions */ function editPreviewTable(article, h, reallyContribs, timeOffset) { var html=['<table>']; var day=null; var curart=article; var page=null; var makeFirstColumnLinks; if(reallyContribs) { // We're showing user contributions, so make (diff | hist) links makeFirstColumnLinks = function(currentRevision) { var result = '('; result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&diff=prev' + '&oldid=' + currentRevision.revid + '">' + popupString('diff') + '</a>'; result += '&nbsp;|&nbsp;'; result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&action=history">' + popupString('hist') + '</a>'; result += ')'; return result; }; } else { // It's a regular history page, so make (cur | last) links var firstRevid = h[0].revid; makeFirstColumnLinks = function(currentRevision) { var result = '('; result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=' + firstRevid + '&oldid=' + currentRevision.revid + '">' + popupString('cur') + '</a>'; result += '&nbsp;|&nbsp;'; result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=prev&oldid=' + currentRevision.revid + '">' + popupString('last') + '</a>'; result += ')'; return result; }; } for (var i=0; i<h.length; ++i) { if (reallyContribs) { page = h[i].title; curart = new Title(page); } var minor = h[i].minor ? '<b>m </b>' : ''; var editDate = adjustDate(getDateFromTimestamp(h[i].timestamp), timeOffset); var thisDay = dayFormat(editDate); var thisTime = timeFormat(editDate); if (thisDay == day) { thisDay = ''; } else { day = thisDay; } if (thisDay) { html.push( '<tr><td colspan=3><span class="popup_history_date">' + thisDay+'</span></td></tr>' ); } html.push('<tr class="popup_history_row_' + ( (i%2) ? 'odd' : 'even') + '">'); html.push('<td>' + makeFirstColumnLinks(h[i]) + '</td>'); html.push('<td>' + '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&oldid=' + h[i].revid + '">' + thisTime + '</a></td>'); var col3url='', col3txt=''; if (!reallyContribs) { var user=h[i].user; if( !h[i].userhidden ) { if( pg.re.ipUser.test(user) ) { col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Contributions&target=' + new Title(user).urlString(); } else { col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(user).urlString(); } col3txt=pg.escapeQuotesHTML(user); } else { col3url=getValueOf('popupRevDelUrl'); col3txt=pg.escapeQuotesHTML( popupString('revdel')); } } else { col3url=pg.wiki.titlebase + curart.urlString(); col3txt=pg.escapeQuotesHTML(page); } html.push('<td>' + (reallyContribs ? minor : '') + '<a href="' + col3url + '">' + col3txt + '</a></td>'); var comment=''; var c=h[i].comment || h[i].content; if (c) { comment=new Previewmaker(c, new Title(curart).toUrl()).editSummaryPreview(); } else if ( h[i].commenthidden ) { comment=popupString('revdel'); } html.push('<td>' + (!reallyContribs ? minor : '') + comment + '</td>'); html.push('</tr>'); html=[html.join('')]; } html.push('</table>'); return html.join(''); } function getDateFromTimestamp(t) { var s=t.split(/[^0-9]/); switch(s.length) { case 0: return null; case 1: return new Date(s[0]); case 2: return new Date(s[0], s[1]-1); case 3: return new Date(s[0], s[1]-1, s[2]); case 4: return new Date(s[0], s[1]-1, s[2], s[3]); case 5: return new Date(s[0], s[1]-1, s[2], s[3], s[4]); case 6: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5]); default: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5], s[6]); } } function adjustDate(d, offset) { // offset is in minutes var o=offset * 60 * 1000; return new Date( +d + o); } function dayFormat(editDate, utc) { if (utc) { return map(zeroFill, [editDate.getUTCFullYear(), editDate.getUTCMonth()+1, editDate.getUTCDate()]).join('-'); } return map(zeroFill, [editDate.getFullYear(), editDate.getMonth()+1, editDate.getDate()]).join('-'); } function timeFormat(editDate, utc) { if (utc) { return map(zeroFill, [editDate.getUTCHours(), editDate.getUTCMinutes(), editDate.getUTCSeconds()]).join(':'); } return map(zeroFill, [editDate.getHours(), editDate.getMinutes(), editDate.getSeconds()]).join(':'); } function showAPIPreview(queryType, html, id, navpop, download) { // DJ: done var target='popupPreview'; completedNavpopTask(navpop); switch (queryType) { case 'imagelinks': case 'category': target='popupPostPreview'; break; case 'userinfo': target='popupUserData'; break; case 'revision': insertPreview(download); return; } setPopupTipsAndHTML(html, target, id); } function APIrevisionPreviewHTML(article, download) { try{ var jsObj=getJsObj(download.data); var page=anyChild(jsObj.query.pages); if( page.missing ) { // TODO we need to fix this proper later on download.owner = null; return; } var content = ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; if( typeof content === 'string' ) { download.data = content; download.lastModified = new Date(page.revisions[0].timestamp); } } catch(someError) { return 'Revision preview failed :('; } } function APIbacklinksPreviewHTML(article, download/*, navpop*/ ) { try { var jsObj=getJsObj(download.data); var list=jsObj.query.backlinks; var html=[]; if (!list) { return popupString('No backlinks found'); } for ( var i=0; i < list.length; i++ ) { var t=new Title(list[i].title); html.push('<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t + '</a>'); } html=html.join(', '); if (jsObj['continue'] && jsObj['continue'].blcontinue) { html += popupString(' and more'); } return html; } catch (someError) { return 'backlinksPreviewHTML went wonky'; } } pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) { log( "APIsharedImagePagePreviewHTML" ); var popupid = obj.requestid; if( obj.query && obj.query.pages ) { var page=anyChild(obj.query.pages ); var content = ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; if( typeof content === 'string' ) { /* Not entirely safe, but the best we can do */ var p=new Previewmaker(content, pg.current.link.navpopup.article, pg.current.link.navpopup); p.makePreview(); setPopupHTML( p.html, "popupSecondPreview", popupid ); } } }; function APIimagepagePreviewHTML(article, download, navpop) { try { var jsObj=getJsObj(download.data); var page=anyChild(jsObj.query.pages); var content= ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; var ret=''; var alt=''; try{alt=navpop.parentAnchor.childNodes[0].alt;} catch(e){} if (alt) { ret = ret + '<hr /><b>' + popupString('Alt text:') + '</b> ' + pg.escapeQuotesHTML(alt); } if (typeof content === 'string') { var p=prepPreviewmaker(content, article, navpop); p.makePreview(); if (p.html) { ret += '<hr />' + p.html; } if (getValueOf('popupSummaryData')) { var info=getPageInfo(content, download); log(info); setPopupTrailer(info, navpop.idNumber); } } if (page && page.imagerepository == "shared" ) { var art=new Title(article); var encart = encodeURIComponent( "File:" + art.stripNamespace() ); var shared_url = pg.wiki.apicommonsbase + '?format=json&formatversion=2' + '&callback=pg.fn.APIsharedImagePagePreviewHTML' + '&requestid=' + navpop.idNumber + '&action=query&prop=revisions&rvprop=content&titles=' + encart; ret = ret +'<hr />' + popupString( 'Image from Commons') + ': <a href="' + pg.wiki.commonsbase + '?title=' + encart + '">' + popupString( 'Description page') + '</a>'; mw.loader.load( shared_url ); } showAPIPreview('imagelinks', APIimagelinksPreviewHTML(article,download), navpop.idNumber, download); return ret; } catch (someError) { return 'API imagepage preview failed :('; } } function APIimagelinksPreviewHTML(article, download) { try { var jsobj=getJsObj(download.data); var list=jsobj.query.imageusage; if (list) { var ret=[]; for (var i=0; i < list.length; i++) { ret.push(list[i].title); } if (ret.length === 0) { return popupString('No image links found'); } return '<h2>' + popupString('File links') + '</h2>' + linkList(ret); } else { return popupString('No image links found'); } } catch(someError) { return 'Image links preview generation failed :('; } } function APIcategoryPreviewHTML(article, download) { try{ var jsobj=getJsObj(download.data); var list=jsobj.query.categorymembers; var ret=[]; for (var p=0; p < list.length; p++) { ret.push(list[p].title); } if (ret.length === 0) { return popupString('Empty category'); } ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' +linkList(ret); if (jsobj['continue'] && jsobj['continue'].cmcontinue) { ret += popupString(' and more'); } return ret; } catch(someError) { return 'Category preview failed :('; } } function APIuserInfoPreviewHTML(article, download) { var ret=[]; var queryobj = {}; try{ queryobj=getJsObj(download.data).query; } catch(someError) { return 'Userinfo preview failed :('; } var user=anyChild(queryobj.users); if (user) { var globaluserinfo=queryobj.globaluserinfo; if (user.invalid === '') { ret.push( popupString( 'Invalid user') ); } else if (user.missing === '') { ret.push( popupString( 'Not a registered username') ); } if( user.blockedby ) { if( user.blockpartial ) { ret.push('<b>' + popupString('Has blocks') + '</b>'); } else { ret.push('<b>' + popupString('CẤM') + '</b>'); } } if( globaluserinfo && ( 'locked' in globaluserinfo || 'hidden' in globaluserinfo ) ) { var lockedSulAccountIsAttachedToThis = true; for( var i=0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) { if ( globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname') ) { lockedSulAccountIsAttachedToThis=false; break; } } if (lockedSulAccountIsAttachedToThis) { if ( 'locked' in globaluserinfo ) ret.push('<b><i>' + popupString('LOCKED') + '</i></b>'); if ( 'hidden' in globaluserinfo ) ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>'); } } if( getValueOf('popupShowGender') && user.gender ) { switch( user.gender ) { case "male": ret.push( popupString( "\u2642" ) ); break; case "female": ret.push( popupString( "\u2640" ) ); break; } } if( user.groups ) { for( var j=0; j < user.groups.length; j++) { var currentGroup = user.groups[j]; if( ["*", "user", "autoconfirmed", "extendedconfirmed"].indexOf( currentGroup ) === -1 ) { ret.push( pg.escapeQuotesHTML(user.groups[j]) ); } } } if( globaluserinfo && globaluserinfo.groups ) { for( var k=0; k < globaluserinfo.groups.length; k++) { ret.push( '<i>'+pg.escapeQuotesHTML(globaluserinfo.groups[k])+'</i>' ); } } if( user.registration ) ret.push( pg.escapeQuotesHTML((user.editcount?user.editcount:'0') + popupString(' sửa đổi từ: ') + (user.registration?dayFormat(getDateFromTimestamp(user.registration)):'')) ); } if (queryobj.usercontribs && queryobj.usercontribs.length) { ret.push( popupString('sửa đổi lần cuối vào ') + dayFormat(getDateFromTimestamp(queryobj.usercontribs[0].timestamp)) ); } if (queryobj.blocks) { ret.push( popupString( 'IP user') ); //we only request list=blocks for IPs for (var l=0; l<queryobj.blocks.length; l++) { console.log(queryobj); var rbstr = queryobj.blocks[l].rangestart === queryobj.blocks[l].rangeend ? 'BLOCK' : 'RANGEBLOCK'; rbstr = (!Array.isArray(queryobj.blocks[l].restrictions) ? 'Has ' + rbstr.toLowerCase() + 's' : rbstr + 'ED'); ret.push('<b>' + popupString(rbstr) + '</b>' ); } } ret = '<hr />' + ret.join( ', ' ); return ret; } function APIcontribsPreviewHTML(article, download, navpop) { return APIhistoryPreviewHTML(article, download, navpop, true); } function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) { try { var jsobj=getJsObj(download.data); var edits = []; if( reallyContribs ) { edits=jsobj.query.usercontribs; } else { edits=anyChild(jsobj.query.pages).revisions; } var ret=editPreviewTable(article, edits, reallyContribs, getTimeOffset()); return ret; } catch (someError) { return 'History preview failed :-('; } } //</NOLITE> // ENDFILE: querypreview.js // STARTFILE: debug.js //////////////////////////////////////////////////////////////////// // Debugging functions //////////////////////////////////////////////////////////////////// function setupDebugging() { //<NOLITE> if (window.popupDebug) { // popupDebug is set from .version window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; }; window.console.log(x); }; window.errlog=function(x) { window.console.error(x); }; log('Initializing logger'); } else { //</NOLITE> window.log = function() {}; window.errlog = function() {}; //<NOLITE> } //</NOLITE> } // ENDFILE: debug.js // STARTFILE: images.js // load image of type Title. function loadImage(image, navpop) { if (typeof image.stripNamespace != 'function') { alert('loadImages bad'); } // API call to retrieve image info. if ( !getValueOf('popupImages') ) return; if ( !isValidImageName(image) ) return false; var art=image.urlString(); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query'; url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge'); url += '&titles=' + art; pendingNavpopTask(navpop); var callback=function(d){ popupsInsertImage(navpop.idNumber, navpop, d); }; var go = function(){ getPageWithCaching(url, callback, navpop); return true; }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA'); } } function popupsInsertImage(id, navpop, download) { log( "popupsInsertImage"); var imageinfo; try { var jsObj=getJsObj(download.data); var imagepage=anyChild(jsObj.query.pages); if (typeof imagepage.imageinfo === 'undefined') return; imageinfo = imagepage.imageinfo[0]; } catch (someError) { log( "popupsInsertImage failed :(" ); return; } var popupImage = document.getElementById("popupImg"+id); if (!popupImage) { log( "could not find insertion point for image"); return; } popupImage.width=getValueOf('popupImageSize'); popupImage.style.display='inline'; // Set the source for the image. if( imageinfo.thumburl ) popupImage.src=imageinfo.thumburl; else if( imageinfo.mime.indexOf("image") === 0 ){ popupImage.src=imageinfo.url; log( "a thumb could not be found, using original image" ); } else log( "fullsize imagethumb, but not sure if it's an image"); var a=document.getElementById("popupImageLink"+id); if (a === null) { return null; } // Determine the action of the surrouding imagelink. switch (getValueOf('popupThumbAction')) { case 'imagepage': if (pg.current.article.namespaceId()!=pg.nsImageId) { a.href=imageinfo.descriptionurl; // FIXME: unreliable pg.idNumber popTipsSoonFn('popupImage' + id)(); break; } /* falls through */ case 'sizetoggle': a.onclick=toggleSize; a.title=popupString('Toggle image size'); return; case 'linkfull': a.href = imageinfo.url; a.title=popupString('Open full-size image'); return; } } // Toggles the image between inline small and navpop fullwidth. // It's the same image, no actual sizechange occurs, only display width. function toggleSize() { var imgContainer=this; if (!imgContainer) { alert('imgContainer is null :/'); return; } var img=imgContainer.firstChild; if (!img) { alert('img is null :/'); return; } if (!img.style.width || img.style.width==='') { img.style.width='100%'; } else { img.style.width=''; } } // Returns one title of an image from wikiText. function getValidImageFromWikiText(wikiText) { // nb in pg.re.image we're interested in the second bracketed expression // this may change if the regex changes :-( //var match=pg.re.image.exec(wikiText); var matched=null; var match; // strip html comments, used by evil bots :-( var t = removeMatchesUnless(wikiText, RegExp('(<!--[\\s\\S]*?-->)'), 1, RegExp('^<!--[^[]*popup', 'i')); while ( ( match = pg.re.image.exec(t) ) ) { // now find a sane image name - exclude templates by seeking { var m = match[2] || match[6]; if ( isValidImageName(m) ) { matched=m; break; } } pg.re.image.lastIndex=0; if (!matched) { return null; } return mw.config.get('wgFormattedNamespaces')[pg.nsImageId]+':'+upcaseFirst(matched); } function removeMatchesUnless(str, re1, parencount, re2) { var split=str.parenSplit(re1); var c=parencount + 1; for (var i=0; i<split.length; ++i) { if ( i%c === 0 || re2.test(split[i]) ) { continue; } split[i]=''; } return split.join(''); } //</NOLITE> // ENDFILE: images.js // STARTFILE: namespaces.js // Set up namespaces and other non-strings.js localization // (currently that means redirs too) function setNamespaces() { pg.nsSpecialId = -1; pg.nsMainspaceId = 0; pg.nsImageId = 6; pg.nsUserId = 2; pg.nsUsertalkId = 3; pg.nsCategoryId = 14; pg.nsTemplateId = 10; } function setRedirs() { var r='redirect'; var R='REDIRECT'; var redirLists={ //<NOLITE> 'ar': [ R, 'تحويل' ], 'be': [ r, 'перанакіраваньне' ], 'bg': [ r, 'пренасочване', 'виж' ], 'bs': [ r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI' ], 'cs': [ R, 'PŘESMĚRUJ' ], 'cy': [ r, 'ail-cyfeirio' ], 'de': [ R, 'WEITERLEITUNG' ], 'el': [ R, 'ΑΝΑΚΑΤΕΥΘΥΝΣΗ'], 'eo': [ R, 'ALIDIREKTU', 'ALIDIREKTI' ], 'es': [ R, 'REDIRECCIÓN' ], 'et': [ r, 'suuna' ], 'ga': [ r, 'athsheoladh' ], 'gl': [ r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'], 'he': [ R, 'הפניה' ], 'hu': [ R, 'ÁTIRÁNYÍTÁS' ], 'is': [ r, 'tilvísun', 'TILVÍSUN' ], 'it': [ R, 'RINVIA', 'Rinvia'], 'ja': [ R, '転送' ], 'mk': [ r, 'пренасочување', 'види' ], 'nds': [ r, 'wiederleiden' ], 'nl': [ R, 'DOORVERWIJZING' ], 'nn': [ r, 'omdiriger' ], 'pl': [ R, 'PATRZ', 'PRZEKIERUJ', 'TAM' ], 'pt': [ R, 'redir' ], 'ru': [ R, 'ПЕРЕНАПРАВЛЕНИЕ', 'ПЕРЕНАПР' ], 'sk': [ r, 'presmeruj' ], 'sr': [ r, 'Преусмери', 'преусмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI' ], 'tt': [ R, 'yünältü', 'перенаправление', 'перенапр' ], 'uk': [ R, 'ПЕРЕНАПРАВЛЕННЯ', 'ПЕРЕНАПР' ], 'vi': [ r, 'đổi' ], 'zh': [ R, '重定向'] // no comma //</NOLITE> }; var redirList=redirLists[ pg.wiki.lang ] || [r, R]; // Mediawiki is very tolerant about what comes after the #redirect at the start pg.re.redirect=RegExp('^\\s*[#](' + redirList.join('|') + ').*?\\[{2}([^\\|\\]]*)(|[^\\]]*)?\\]{2}\\s*(.*)', 'i'); } function setInterwiki() { if (pg.wiki.wikimedia) { // From https://meta.wikimedia.org/wiki/List_of_Wikipedias pg.wiki.interwiki='aa|ab|ace|af|ak|als|am|an|ang|ar|arc|arz|as|ast|av|ay|az|ba|bar|bat-smg|bcl|be|be-x-old|bg|bh|bi|bjn|bm|bn|bo|bpy|br|bs|bug|bxr|ca|cbk-zam|cdo|ce|ceb|ch|cho|chr|chy|ckb|co|cr|crh|cs|csb|cu|cv|cy|da|de|diq|dsb|dv|dz|ee|el|eml|en|eo|es|et|eu|ext|fa|ff|fi|fiu-vro|fj|fo|fr|frp|frr|fur|fy|ga|gag|gan|gd|gl|glk|gn|got|gu|gv|ha|hak|haw|he|hi|hif|ho|hr|hsb|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|ilo|io|is|it|iu|ja|jbo|jv|ka|kaa|kab|kbd|kg|ki|kj|kk|kl|km|kn|ko|koi|kr|krc|ks|ksh|ku|kv|kw|ky|la|lad|lb|lbe|lg|li|lij|lmo|ln|lo|lt|ltg|lv|map-bms|mdf|mg|mh|mhr|mi|mk|ml|mn|mo|mr|mrj|ms|mt|mus|mwl|my|myv|mzn|na|nah|nap|nds|nds-nl|ne|new|ng|nl|nn|no|nov|nrm|nv|ny|oc|om|or|os|pa|pag|pam|pap|pcd|pdc|pfl|pi|pih|pl|pms|pnb|pnt|ps|pt|qu|rm|rmy|rn|ro|roa-rup|roa-tara|ru|rue|rw|sa|sah|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|sn|so|sq|sr|srn|ss|st|stq|su|sv|sw|szl|ta|te|tet|tg|th|ti|tk|tl|tn|to|tpi|tr|ts|tt|tum|tw|ty|udm|ug|uk|ur|uz|ve|vec|vi|vls|vo|wa|war|wo|wuu|xal|xh|yi|yo|za|zea|zh|zh-classical|zh-min-nan|zh-yue|zu'; pg.re.interwiki=RegExp('^'+pg.wiki.interwiki+':'); } else { pg.wiki.interwiki=null; pg.re.interwiki=RegExp('^$'); } } // return a regexp pattern matching all variants to write the given namespace function nsRe(namespaceId) { var imageNamespaceVariants = []; jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) { if (_namespaceId!=namespaceId) return; _localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc); imageNamespaceVariants.push(mw.util.escapeRegExp(_localizedNamespaceLc).split(' ').join('[ _]')); imageNamespaceVariants.push(mw.util.escapeRegExp(encodeURI(_localizedNamespaceLc))); }); return '(?:' + imageNamespaceVariants.join('|') + ')'; } function nsReImage() { return nsRe(pg.nsImageId); } // ENDFILE: namespaces.js // STARTFILE: selpop.js //<NOLITE> function getEditboxSelection() { // see http://www.webgurusforum.com/8/12/0 var editbox; try { editbox=document.editform.wpTextbox1; } catch (dang) { return; } // IE, Opera if (document.selection) { return document.selection.createRange().text; } // Mozilla var selStart = editbox.selectionStart; var selEnd = editbox.selectionEnd; return (editbox.value).substring(selStart, selEnd); } function doSelectionPopup() { // popup if the selection looks like [[foo|anything afterwards at all // or [[foo|bar]]text without ']]' // or [[foo|bar]] var sel=getEditboxSelection(); var open=sel.indexOf('[['); var pipe=sel.indexOf('|'); var close=sel.indexOf(']]'); if (open == -1 || ( pipe == -1 && close == -1) ) { return; } if (pipe != -1 && open > pipe || close != -1 && open > close) { return; } if (getValueOf('popupOnEditSelection')=='boxpreview') { return doSeparateSelectionPopup(sel); } var article=new Title(sel.substring(open+2, (pipe < 0) ? close : pipe)).urlString(); if (close > 0 && sel.substring(close+2).indexOf('[[') >= 0) { return; } var a=document.createElement('a'); a.href=pg.wiki.titlebase + article; mouseOverWikiLink2(a); if (a.navpopup) { a.navpopup.addHook(function(){runStopPopupTimer(a.navpopup);}, 'unhide', 'after'); } } function doSeparateSelectionPopup(str) { var div=document.getElementById('selectionPreview'); if (!div) { div = document.createElement('div'); div.id='selectionPreview'; try { var box=document.editform.wpTextbox1; box.parentNode.insertBefore(div, box); } catch (error) { return; } } div.innerHTML=wiki2html(str); div.ranSetupTooltipsAlready = false; popTipsSoonFn('selectionPreview')(); } //</NOLITE> // ENDFILE: selpop.js // STARTFILE: navpopup.js /** @fileoverview Defines two classes: {@link Navpopup} and {@link Mousetracker}. <code>Navpopup</code> describes popups: when they appear, where, what they look like and so on. <code>Mousetracker</code> "captures" the mouse using <code>document.onmousemove</code>. */ /** Creates a new Mousetracker. @constructor @class The Mousetracker class. This monitors mouse movements and manages associated hooks. */ function Mousetracker() { /** Interval to regularly run the hooks anyway, in milliseconds. @type Integer */ this.loopDelay=400; /** Timer for the loop. @type Timer */ this.timer=null; /** Flag - are we switched on? @type Boolean */ this.active=false; /** Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position? */ this.dirty=true; /** Array of hook functions. @private @type Array */ this.hooks=[]; } /** Adds a hook, to be called when we get events. @param {Function} f A function which is called as <code>f(x,y)</code>. It should return <code>true</code> when it wants to be removed, and <code>false</code> otherwise. */ Mousetracker.prototype.addHook = function (f) { this.hooks.push(f); }; /** Runs hooks, passing them the x and y coords of the mouse. Hook functions that return true are passed to {@link Mousetracker#removeHooks} for removal. @private */ Mousetracker.prototype.runHooks = function () { if (!this.hooks || !this.hooks.length) { return; } //log('Mousetracker.runHooks; we got some hooks to run'); var remove=false; var removeObj={}; // this method gets called a LOT - // pre-cache some variables var x=this.x, y=this.y, len = this.hooks.length; for (var i=0; i<len; ++i) { //~ run the hook function, and remove it if it returns true if (this.hooks[i](x, y)===true) { remove=true; removeObj[i]=true; } } if (remove) { this.removeHooks(removeObj); } }; /** Removes hooks. @private @param {Object} removeObj An object whose keys are the index numbers of functions for removal, with values that evaluate to true */ Mousetracker.prototype.removeHooks = function(removeObj) { var newHooks=[]; var len = this.hooks.length; for (var i=0; i<len; ++i) { if (! removeObj[i]) { newHooks.push(this.hooks[i]); } } this.hooks=newHooks; }; /** Event handler for mouse wiggles. We simply grab the event, set x and y and run the hooks. This makes the cpu all hot and bothered :-( @private @param {Event} e Mousemove event */ Mousetracker.prototype.track=function (e) { //~ Apparently this is needed in IE. e = e || window.event; var x, y; if (e) { if (e.pageX) { x=e.pageX; y=e.pageY; } else if (typeof e.clientX!='undefined') { var left, top, docElt = document.documentElement; if (docElt) { left=docElt.scrollLeft; } left = left || document.body.scrollLeft || document.scrollLeft || 0; if (docElt) { top=docElt.scrollTop; } top = top || document.body.scrollTop || document.scrollTop || 0; x=e.clientX + left; y=e.clientY + top; } else { return; } this.setPosition(x,y); } }; /** Sets the x and y coordinates stored and takes appropriate action, running hooks as appropriate. @param {Integer} x, y Screen coordinates to set */ Mousetracker.prototype.setPosition=function(x,y) { this.x = x; this.y = y; if (this.dirty || this.hooks.length === 0) { this.dirty=false; return; } if (typeof this.lastHook_x != 'number') { this.lastHook_x = -100; this.lastHook_y=-100; } var diff = (this.lastHook_x - x)*(this.lastHook_y - y); diff = (diff >= 0) ? diff : -diff; if ( diff > 1 ) { this.lastHook_x=x; this.lastHook_y=y; if (this.dirty) { this.dirty = false; } else { this.runHooks(); } } }; /** Sets things in motion, unless they are already that is, registering an event handler on <code>document.onmousemove</code>. A half-hearted attempt is made to preserve the old event handler if there is one. */ Mousetracker.prototype.enable = function () { if (this.active) { return; } this.active=true; //~ Save the current handler for mousemove events. This isn't too //~ robust, of course. this.savedHandler=document.onmousemove; //~ Gotta save @tt{this} again for the closure, and use apply for //~ the member function. var savedThis=this; document.onmousemove=function (e) {savedThis.track.apply(savedThis, [e]);}; if (this.loopDelay) { this.timer = setInterval(function() { //log('loop delay in mousetracker is working'); savedThis.runHooks();}, this.loopDelay); } }; /** Disables the tracker, removing the event handler. */ Mousetracker.prototype.disable = function () { if (!this.active) { return; } if (typeof this.savedHandler === 'function') { document.onmousemove=this.savedHandler; } else { delete document.onmousemove; } if (this.timer) { clearInterval(this.timer); } this.active=false; }; /** Creates a new Navpopup. Gets a UID for the popup and @param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable. @constructor @class The Navpopup class. This generates popup hints, and does some management of them. */ function Navpopup(/*init*/) { //alert('new Navpopup(init)'); /** UID for each Navpopup instance. Read-only. @type integer */ this.uid=Navpopup.uid++; /** Read-only flag for current visibility of the popup. @type boolean @private */ this.visible=false; /** Flag to be set when we want to cancel a previous request to show the popup in a little while. @private @type boolean */ this.noshow=false; /** Categorised list of hooks. @see #runHooks @see #addHook @private @type Object */ this.hooks={ 'create': [], 'unhide': [], 'hide': [] }; /** list of unique IDs of hook functions, to avoid duplicates @private */ this.hookIds={}; /** List of downloads associated with the popup. @private @type Array */ this.downloads=[]; /** Number of uncompleted downloads. @type integer */ this.pending=null; /** Tolerance in pixels when detecting whether the mouse has left the popup. @type integer */ this.fuzz=5; /** Flag to toggle running {@link #limitHorizontalPosition} to regulate the popup's position. @type boolean */ this.constrained=true; /** The popup width in pixels. @private @type integer */ this.width=0; /** The popup width in pixels. @private @type integer */ this.height=0; /** The main content DIV element. @type HTMLDivElement */ this.mainDiv=null; this.createMainDiv(); // if (!init || typeof init.popups_draggable=='undefined' || init.popups_draggable) { // this.makeDraggable(true); // } } /** A UID for each Navpopup. This constructor property is just a counter. @type integer @private */ Navpopup.uid=0; /** Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible. @type boolean */ Navpopup.prototype.isVisible=function() { return this.visible; }; /** Repositions popup using CSS style. @private @param {integer} x x-coordinate (px) @param {integer} y y-coordinate (px) @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition} */ Navpopup.prototype.reposition= function (x,y, noLimitHor) { log ('reposition('+x+','+y+','+noLimitHor+')'); if (typeof x != 'undefined' && x !== null) { this.left=x; } if (typeof y != 'undefined' && y !== null) { this.top=y; } if (typeof this.left != 'undefined' && typeof this.top != 'undefined') { this.mainDiv.style.left=this.left + 'px'; this.mainDiv.style.top=this.top + 'px'; } if (!noLimitHor) { this.limitHorizontalPosition(); } //console.log('navpop'+this.uid+' - (left,top)=(' + this.left + ',' + this.top + '), css=(' //+ this.mainDiv.style.left + ',' + this.mainDiv.style.top + ')'); }; /** Prevents popups from being in silly locations. Hopefully. Should not be run if {@link #constrained} is true. @private */ Navpopup.prototype.limitHorizontalPosition=function() { if (!this.constrained || this.tooWide) { return; } this.updateDimensions(); var x=this.left; var w=this.width; var cWidth=document.body.clientWidth; // log('limitHorizontalPosition: x='+x+ // ', this.left=' + this.left + // ', this.width=' + this.width + // ', cWidth=' + cWidth); if ( (x+w) >= cWidth || ( x > 0 && this.maxWidth && this.width < this.maxWidth && this.height > this.width && x > cWidth - this.maxWidth ) ) { // This is a very nasty hack. There has to be a better way! // We find the "natural" width of the div by positioning it at the far left // then reset it so that it should be flush right (well, nearly) this.mainDiv.style.left='-10000px'; this.mainDiv.style.width = this.maxWidth + 'px'; var naturalWidth=parseInt(this.mainDiv.offsetWidth, 10); var newLeft=cWidth - naturalWidth - 1; if (newLeft < 0) { newLeft = 0; this.tooWide=true; } // still unstable for really wide popups? log ('limitHorizontalPosition: moving to ('+newLeft + ','+ this.top+');' + ' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth); this.reposition(newLeft, null, true); } }; /** Counter indicating the z-order of the "highest" popup. We start the z-index at 1000 so that popups are above everything else on the screen. @private @type integer */ Navpopup.highest=1000; /** Brings popup to the top of the z-order. We increment the {@link #highest} property of the contructor here. @private */ Navpopup.prototype.raise = function () { this.mainDiv.style.zIndex=Navpopup.highest + 1; ++Navpopup.highest; }; /** Shows the popup provided {@link #noshow} is not true. Updates the position, brings the popup to the top of the z-order and unhides it. */ Navpopup.prototype.show = function () { //document.title+='s'; if (this.noshow) { return; } //document.title+='t'; this.reposition(); this.raise(); this.unhide(); }; /** Checks to see if the mouse pointer has stabilised (checking every <code>time</code>/2 milliseconds) and runs the {@link #show} method if it has. @param {integer} time The minimum time (ms) before the popup may be shown. */ Navpopup.prototype.showSoonIfStable = function (time) { log ('showSoonIfStable, time='+time); if (this.visible) { return; } this.noshow = false; //~ initialize these variables so that we never run @tt{show} after //~ just half the time this.stable_x = -10000; this.stable_y = -10000; var stableShow = function() { log('stableShow called'); var new_x = Navpopup.tracker.x, new_y = Navpopup.tracker.y; var dx = savedThis.stable_x - new_x, dy = savedThis.stable_y - new_y; var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz; //document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] '; if ( dx * dx <= fuzz2 && dy * dy <= fuzz2 ) { log ('mouse is stable'); clearInterval(savedThis.showSoonStableTimer); savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]); savedThis.show.apply(savedThis, []); savedThis.limitHorizontalPosition.apply(savedThis, []); return; } savedThis.stable_x = new_x; savedThis.stable_y = new_y; }; var savedThis = this; this.showSoonStableTimer = setInterval(stableShow, time/2); }; /** Sets the {@link #noshow} flag and hides the popup. This should be called when the mouse leaves the link before (or after) it's actually been displayed. */ Navpopup.prototype.banish = function () { log ('banish called'); // hide and prevent showing with showSoon in the future this.noshow=true; if (this.showSoonStableTimer) { log('clearing showSoonStableTimer'); clearInterval(this.showSoonStableTimer); } this.hide(); }; /** Runs hooks added with {@link #addHook}. @private @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide' @param {String} when Controls exactly when the hook is run: either 'before' or 'after' */ Navpopup.prototype.runHooks = function (key, when) { if (!this.hooks[key]) { return; } var keyHooks=this.hooks[key]; var len=keyHooks.length; for (var i=0; i< len; ++i) { if (keyHooks[i] && keyHooks[i].when == when) { if (keyHooks[i].hook.apply(this, [])) { // remove the hook if (keyHooks[i].hookId) { delete this.hookIds[keyHooks[i].hookId]; } keyHooks[i]=null; } } } }; /** Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the Navpopup instance, and no arguments. @param {Function} hook The hook function. Functions that return true are deleted. @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide' @param {String} when Controls exactly when the hook is run: either 'before' or 'after' @param {String} uid A truthy string identifying the hook function; if it matches another hook in this position, it won't be added again. */ Navpopup.prototype.addHook = function ( hook, key, when, uid ) { when = when || 'after'; if (!this.hooks[key]) { return; } // if uid is specified, don't add duplicates var hookId=null; if (uid) { hookId=[key,when,uid].join('|'); if (this.hookIds[hookId]) { return; } this.hookIds[hookId]=true; } this.hooks[key].push( {hook: hook, when: when, hookId: hookId} ); }; /** Creates the main DIV element, which contains all the actual popup content. Runs hooks with key 'create'. @private */ Navpopup.prototype.createMainDiv = function () { if (this.mainDiv) { return; } this.runHooks('create', 'before'); var mainDiv=document.createElement('div'); var savedThis=this; mainDiv.onclick=function(e) {savedThis.onclickHandler(e);}; mainDiv.className=(this.className) ? this.className : 'navpopup_maindiv'; mainDiv.id=mainDiv.className + this.uid; mainDiv.style.position='absolute'; mainDiv.style.minWidth = '350px'; mainDiv.style.display='none'; mainDiv.className='navpopup'; // easy access to javascript object through DOM functions mainDiv.navpopup=this; this.mainDiv=mainDiv; document.body.appendChild(mainDiv); this.runHooks('create', 'after'); }; /** Calls the {@link #raise} method. @private */ Navpopup.prototype.onclickHandler=function(/*e*/) { this.raise(); }; /** Makes the popup draggable, using a {@link Drag} object. @private */ Navpopup.prototype.makeDraggable=function(handleName) { if (!this.mainDiv) { this.createMainDiv(); } var drag=new Drag(); if (!handleName) { drag.startCondition=function(e) { try { if (!e.shiftKey) { return false; } } catch (err) { return false; } return true; }; } var dragHandle; if (handleName) dragHandle = document.getElementById(handleName); if (!dragHandle) dragHandle = this.mainDiv; var np=this; drag.endHook=function(x,y) { Navpopup.tracker.dirty=true; np.reposition(x,y); }; drag.init(dragHandle,this.mainDiv); }; /** Hides the popup using CSS. Runs hooks with key 'hide'. Sets {@link #visible} appropriately. {@link #banish} should be called externally instead of this method. @private */ Navpopup.prototype.hide = function () { this.runHooks('hide', 'before'); this.abortDownloads(); if (typeof this.visible != 'undefined' && this.visible) { this.mainDiv.style.display='none'; this.visible=false; } this.runHooks('hide', 'after'); }; /** Shows the popup using CSS. Runs hooks with key 'unhide'. Sets {@link #visible} appropriately. {@link #show} should be called externally instead of this method. @private */ Navpopup.prototype.unhide = function () { this.runHooks('unhide', 'before'); if (typeof this.visible != 'undefined' && !this.visible) { this.mainDiv.style.display='inline'; this.visible=true; } this.runHooks('unhide', 'after'); }; /** Sets the <code>innerHTML</code> attribute of the main div containing the popup content. @param {String} html The HTML to set. */ Navpopup.prototype.setInnerHTML = function (html) { this.mainDiv.innerHTML = html; }; /** Updates the {@link #width} and {@link #height} attributes with the CSS properties. @private */ Navpopup.prototype.updateDimensions = function () { this.width=parseInt(this.mainDiv.offsetWidth, 10); this.height=parseInt(this.mainDiv.offsetHeight, 10); }; /** Checks if the point (x,y) is within {@link #fuzz} of the {@link #mainDiv}. @param {integer} x x-coordinate (px) @param {integer} y y-coordinate (px) @type boolean */ Navpopup.prototype.isWithin = function(x,y) { //~ If we're not even visible, no point should be considered as //~ being within the popup. if (!this.visible) { return false; } this.updateDimensions(); var fuzz=this.fuzz || 0; //~ Use a simple box metric here. return (x+fuzz >= this.left && x-fuzz <= this.left + this.width && y+fuzz >= this.top && y-fuzz <= this.top + this.height); }; /** Adds a download to {@link #downloads}. @param {Downloader} download */ Navpopup.prototype.addDownload=function(download) { if (!download) { return; } this.downloads.push(download); }; /** Aborts the downloads listed in {@link #downloads}. @see Downloader#abort */ Navpopup.prototype.abortDownloads=function() { for(var i=0; i<this.downloads.length; ++i) { var d=this.downloads[i]; if (d && d.abort) { d.abort(); } } this.downloads=[]; }; /** A {@link Mousetracker} instance which is a property of the constructor (pseudo-global). */ Navpopup.tracker=new Mousetracker(); // ENDFILE: navpopup.js // STARTFILE: diff.js //<NOLITE> /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) and [[:en:User:Lupin]] * * More Info: * http://ejohn.org/projects/javascript-diff-algorithm/ */ function delFmt(x) { if (!x.length) { return ''; } return "<del class='popupDiff'>" + x.join('') +"</del>"; } function insFmt(x) { if (!x.length) { return ''; } return "<ins class='popupDiff'>" + x.join('') +"</ins>"; } function countCrossings(a, b, i, eject) { // count the crossings on the edge starting at b[i] if (!b[i].row && b[i].row !== 0) { return -1; } var count=0; for (var j=0; j<a.length; ++j) { if (!a[j].row && a[j].row !== 0) { continue; } if ( (j-b[i].row)*(i-a[j].row) > 0) { if(eject) { return true; } count++; } } return count; } function shortenDiffString(str, context) { var re=RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)'); var splitted=str.parenSplit(re); var ret=['']; for (var i=0; i<splitted.length; i+=2) { if (splitted[i].length < 2*context) { ret[ret.length-1] += splitted[i]; if (i+1<splitted.length) { ret[ret.length-1] += splitted[i+1]; } continue; } else { if (i > 0) { ret[ret.length-1] += splitted[i].substring(0,context); } if (i+1 < splitted.length) { ret.push(splitted[i].substring(splitted[i].length-context) + splitted[i+1]); } } } while (ret.length > 0 && !ret[0]) { ret = ret.slice(1); } return ret; } function diffString( o, n, simpleSplit ) { var splitRe=RegExp('([[]{2}|[\\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\\s|\\b)'); // We need to split the strings o and n first, and entify() the parts // individually, so that the HTML entities are never cut apart. (AxelBoldt) var out, i, oSplitted, nSplitted; if (simpleSplit) { oSplitted=o.split(/\b/); nSplitted=n.split(/\b/); } else { oSplitted=o.parenSplit(splitRe); nSplitted=n.parenSplit(splitRe); } for (i=0; i<oSplitted.length; ++i) {oSplitted[i]=oSplitted[i].entify();} for (i=0; i<nSplitted.length; ++i) {nSplitted[i]=nSplitted[i].entify();} out = diff (oSplitted, nSplitted); var str = ""; var acc=[]; // accumulator for prettier output // crossing pairings -- eg 'A B' vs 'B A' -- cause problems, so let's iron them out // this doesn't always do things optimally but it should be fast enough var maxOutputPair=0; for (i=0; i<out.n.length; ++i) { if ( out.n[i].paired ) { if( maxOutputPair > out.n[i].row ) { // tangle - delete pairing out.o[ out.n[i].row ]=out.o[ out.n[i].row ].text; out.n[i]=out.n[i].text; } if (maxOutputPair < out.n[i].row) { maxOutputPair = out.n[i].row; } } } // output the stuff preceding the first paired old line for (i=0; i<out.o.length && !out.o[i].paired; ++i) { acc.push( out.o[i] ); } str += delFmt(acc); acc=[]; // main loop for ( i = 0; i < out.n.length; ++i ) { // output unpaired new "lines" while ( i < out.n.length && !out.n[i].paired ) { acc.push( out.n[i++] ); } str += insFmt(acc); acc=[]; if ( i < out.n.length ) { // this new "line" is paired with the (out.n[i].row)th old "line" str += out.n[i].text; // output unpaired old rows starting after this new line's partner var m = out.n[i].row + 1; while ( m < out.o.length && !out.o[m].paired ) { acc.push ( out.o[m++] ); } str += delFmt(acc); acc=[]; } } return str; } // see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object // FIXME: use obj.hasOwnProperty instead of this kludge! var jsReservedProperties=RegExp('^(constructor|prototype|__((define|lookup)[GS]etter)__' + '|eval|hasOwnProperty|propertyIsEnumerable' + '|to(Source|String|LocaleString)|(un)?watch|valueOf)$'); function diffBugAlert(word) { if (!diffBugAlert.list[word]) { diffBugAlert.list[word]=1; alert('Bad word: '+word+'\n\nPlease report this bug.'); } } diffBugAlert.list={}; function makeDiffHashtable(src) { var ret={}; for ( var i = 0; i < src.length; i++ ) { if ( jsReservedProperties.test(src[i]) ) { src[i] += '<!-- -->'; } if ( !ret[ src[i] ] ) { ret[ src[i] ] = []; } try { ret[ src[i] ].push( i ); } catch (err) { diffBugAlert(src[i]); } } return ret; } function diff( o, n ) { // pass 1: make hashtable ns with new rows as keys var ns = makeDiffHashtable(n); // pass 2: make hashtable os with old rows as keys var os = makeDiffHashtable(o); // pass 3: pair unique new rows and matching unique old rows var i; for ( i in ns ) { if ( ns[i].length == 1 && os[i] && os[i].length == 1 ) { n[ ns[i][0] ] = { text: n[ ns[i][0] ], row: os[i][0], paired: true }; o[ os[i][0] ] = { text: o[ os[i][0] ], row: ns[i][0], paired: true }; } } // pass 4: pair matching rows immediately following paired rows (not necessarily unique) for ( i = 0; i < n.length - 1; i++ ) { if ( n[i].paired && ! n[i+1].paired && n[i].row + 1 < o.length && ! o[ n[i].row + 1 ].paired && n[i+1] == o[ n[i].row + 1 ] ) { n[i+1] = { text: n[i+1], row: n[i].row + 1, paired: true }; o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1, paired: true }; } } // pass 5: pair matching rows immediately preceding paired rows (not necessarily unique) for ( i = n.length - 1; i > 0; i-- ) { if ( n[i].paired && ! n[i-1].paired && n[i].row > 0 && ! o[ n[i].row - 1 ].paired && n[i-1] == o[ n[i].row - 1 ] ) { n[i-1] = { text: n[i-1], row: n[i].row - 1, paired: true }; o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1, paired: true }; } } return { o: o, n: n }; } //</NOLITE> // ENDFILE: diff.js // STARTFILE: init.js function setSiteInfo() { if (window.popupLocalDebug) { pg.wiki.hostname = 'en.wikipedia.org'; } else { pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?) } pg.wiki.wikimedia=RegExp('(wiki([pm]edia|source|books|news|quote|versity)|wiktionary|mediawiki)[.]org').test(pg.wiki.hostname); pg.wiki.wikia=RegExp('[.]wikia[.]com$', 'i').test(pg.wiki.hostname); pg.wiki.isLocal=RegExp('^localhost').test(pg.wiki.hostname); pg.wiki.commons=( pg.wiki.wikimedia && pg.wiki.hostname != 'commons.wikimedia.org') ? 'commons.wikimedia.org' : null; pg.wiki.lang = mw.config.get('wgContentLanguage'); var port = location.port ? ':' + location.port : ''; pg.wiki.sitebase = pg.wiki.hostname + port; } function setUserInfo() { var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'query', list: 'users', ususers: mw.config.get('wgUserName'), usprop: 'rights' }; pg.user.canReview = false; if (getValueOf('popupReview')) { api.get(params).done(function(data){ var rights = data.query.users[0].rights; pg.user.canReview = rights.indexOf('review') !== -1; // TODO: Should it be a getValueOf('ReviewRight') ? }); } } function setTitleBase() { var protocol = ( window.popupLocalDebug ? 'http:' : location.protocol ); pg.wiki.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, ""); // as in http://some.thing.com/wiki/Article pg.wiki.botInterfacePath = mw.config.get('wgScript'); pg.wiki.APIPath = mw.config.get('wgScriptPath') +"/api.php"; // default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo var titletail = pg.wiki.botInterfacePath + '?title='; //var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?title=']); // other sites may need to add code here to set titletail depending on how their urls work pg.wiki.titlebase = protocol + '//' + pg.wiki.sitebase + titletail; //pg.wiki.titlebase2 = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]); pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath; pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath; pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath; pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.botInterfacePath; pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.APIPath; pg.re.basenames = RegExp( '^(' + map( literalizeRegex, [ pg.wiki.titlebase, //pg.wiki.titlebase2, pg.wiki.articlebase ]).join('|') + ')' ); } ////////////////////////////////////////////////// // Global regexps function setMainRegex() { var reStart='[^:]*://'; var preTitles = literalizeRegex( mw.config.get('wgScriptPath') ) + '/(?:index[.]php|wiki[.]phtml)[?]title='; preTitles += '|' + literalizeRegex( pg.wiki.articlePath + '/' ); var reEnd='(' + preTitles + ')([^&?#]*)[^#]*(?:#(.+))?'; pg.re.main = RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd); } function setRegexps() { // TODO: We shoud use an api call to get the aliases for special pages, now it does not work for non-English wikipedias: // E.g., https://ru.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=specialpagealiases&formatversion=2 setMainRegex(); var sp=nsRe(pg.nsSpecialId); pg.re.urlNoPopup=RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)') ; pg.re.contribs =RegExp('(title=|/)' + sp + '(?:%3A|:)Contributions' + '(&target=|/|/' + nsRe(pg.nsUserId)+':)(.*)') ; pg.re.email =RegExp('(title=|/)' + sp + '(?:%3A|:)EmailUser' + '(&target=|/|/(?:' + nsRe(pg.nsUserId)+':)?)(.*)') ; pg.re.backlinks =RegExp('(title=|/)' + sp + '(?:%3A|:)WhatLinksHere' + '(&target=|/)([^&]*)'); pg.re.specialdiff=RegExp('/' + sp + '(?:%3A|:)Diff/([^?#]*)'); //<NOLITE> var im=nsReImage(); // note: tries to get images in infobox templates too, e.g. movie pages, album pages etc // (^|\[\[)image: *([^|\]]*[^|\] ]) * // (^|\[\[)image: *([^|\]]*[^|\] ])([^0-9\]]*([0-9]+) *px)? // $4 = 120 as in 120px pg.re.image = RegExp('(^|\\[\\[)' + im + ': *([^|\\]]*[^|\\] ])' + '([^0-9\\]]*([0-9]+) *px)?|(?:\\n *[|]?|[|]) *' + '(' + getValueOf('popupImageVarsRegexp') + ')' + ' *= *(?:\\[\\[ *)?(?:' + im + ':)?' + '([^|]*?)(?:\\]\\])? *[|]? *\\n', 'img') ; pg.re.imageBracketCount = 6; pg.re.category = RegExp('\\[\\[' +nsRe(pg.nsCategoryId) + ': *([^|\\]]*[^|\\] ]) *', 'i'); pg.re.categoryBracketCount = 1; pg.re.ipUser=RegExp('^' + // IPv6 '(?::(?::|(?::[0-9A-Fa-f]{1,4}){1,7})|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,6}::|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){7})' + // IPv4 '|(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' + '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$'); pg.re.stub= RegExp(getValueOf('popupStubRegexp'), 'im'); pg.re.disambig=RegExp(getValueOf('popupDabRegexp'), 'im'); //</NOLITE> // FIXME replace with general parameter parsing function, this is daft pg.re.oldid=RegExp('[?&]oldid=([^&]*)'); pg.re.diff=RegExp('[?&]diff=([^&]*)'); } ////////////////////////////////////////////////// // miscellany function setupCache() { // page caching pg.cache.pages = []; } function setMisc() { pg.current.link=null; pg.current.links=[]; pg.current.linksHash={}; setupCache(); pg.timer.checkPopupPosition=null; pg.counter.loop=0; // ids change with each popup: popupImage0, popupImage1 etc pg.idNumber=0; // for myDecodeURI pg.misc.decodeExtras = [ {from: '%2C', to: ',' }, {from: '_', to: ' ' }, {from: '%24', to: '$'}, {from: '%26', to: '&' } // no , ]; pg.misc.userAgent = 'Navigation popups/1.0 (' + mw.config.get( 'wgServerName' ) +')'; } // We need a callback since this might end up asynchronous because of // the mw.loader.using() call. function setupPopups( callback ) { if ( setupPopups.completed ) { if ( typeof callback === 'function' ) { callback(); } return; } // These dependencies are also enforced from the gadget, // but not everyone loads this as a gadget, so double check mw.loader.using( ['mediawiki.util', 'mediawiki.user', 'user.options'] ).then( function() { // NB translatable strings should be set up first (strings.js) // basics setupDebugging(); setSiteInfo(); setTitleBase(); setOptions(); // see options.js setUserInfo(); // namespaces etc setNamespaces(); setInterwiki(); // regexps setRegexps(); setRedirs(); // other stuff setMisc(); setupLivePreview(); // main deal here setupTooltips(); log('In setupPopups(), just called setupTooltips()'); Navpopup.tracker.enable(); setupPopups.completed = true; if ( typeof callback === 'function' ) { callback(); } }); } // ENDFILE: init.js // STARTFILE: navlinks.js //<NOLITE> ////////////////////////////////////////////////// // navlinks... let the fun begin // function defaultNavlinkSpec() { var str=''; str += '<b><<mainlink|shortcut= >></b>'; if (getValueOf('popupLastEditLink')) { str += '*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}'; } // user links // contribs - log - count - email - block // count only if applicable; block only if popupAdminLinks str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>'; str+='if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}'; // editing links // talkpage -> edit|new - history - un|watch - article|edit // other page -> edit - history - un|watch - talk|edit|new var editstr='<<edit|shortcut=e>>'; var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'; var historystr='<<history|shortcut=h>>|<<editors|shortcut=E|>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; str+='<br>if(talk){' + editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + // not a talk page editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}'; // misc links str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>'; // admin links str += 'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}'; return str; } function navLinksHTML (article, hint, params) { //oldid, rcid) { var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>'; // BAM return navlinkStringToHTML(str, article, params); } function expandConditionalNavlinkString(s,article,z,recursionCount) { var oldid=z.oldid, rcid=z.rcid, diff=z.diff; // nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out) if (typeof recursionCount!=typeof 0) { recursionCount=0; } var conditionalSplitRegex=RegExp( //(1 if \\( (2 2) \\) {(3 3)} (4 else {(5 5)} 4)1) '(;?\\s*if\\s*\\(\\s*([\\w]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))', 'i'); var splitted=s.parenSplit(conditionalSplitRegex); // $1: whole conditional // $2: test condition // $3: true expansion // $4: else clause (possibly empty) // $5: false expansion (possibly null) var numParens=5; var ret = splitted[0]; for (var i=1; i<splitted.length; i=i+numParens+1) { var testString=splitted[i+2-1]; var trueString=splitted[i+3-1]; var falseString=splitted[i+5-1]; if (typeof falseString=='undefined' || !falseString) { falseString=''; } var testResult=null; switch (testString) { case 'user': testResult=(article.userName())?true:false; break; case 'talk': testResult=(article.talkPage())?false:true; // talkPage converts _articles_ to talkPages break; case 'admin': testResult=getValueOf('popupAdminLinks')?true:false; break; case 'oldid': testResult=(typeof oldid != 'undefined' && oldid)?true:false; break; case 'rcid': testResult=(typeof rcid != 'undefined' && rcid)?true:false; break; case 'ipuser': testResult=(article.isIpUser())?true:false; break; case 'mainspace_en': testResult=isInMainNamespace(article) && pg.wiki.hostname=='en.wikipedia.org'; break; case 'wikimedia': testResult=(pg.wiki.wikimedia) ? true : false; break; case 'diff': testResult=(typeof diff != 'undefined' && diff)?true:false; break; } switch(testResult) { case null: ret+=splitted[i]; break; case true: ret+=trueString; break; case false: ret+=falseString; break; } // append non-conditional string ret += splitted[i+numParens]; } if (conditionalSplitRegex.test(ret) && recursionCount < 10) { return expandConditionalNavlinkString(ret,article,z,recursionCount+1); } return ret; } function navlinkStringToArray(s, article, params) { s=expandConditionalNavlinkString(s,article,params); var splitted=s.parenSplit(RegExp('<<(.*?)>>')); var ret=[]; for (var i=0; i<splitted.length; ++i) { if (i%2) { // i odd, so s is a tag var t=new navlinkTag(); var ss=splitted[i].split('|'); t.id=ss[0]; for (var j=1; j<ss.length; ++j) { var sss=ss[j].split('='); if (sss.length>1) { t[sss[0]]=sss[1]; } else { // no assignment (no "="), so treat this as a title (overwriting the last one) t.text=popupString(sss[0]); } } t.article=article; var oldid=params.oldid, rcid=params.rcid, diff=params.diff; if (typeof oldid !== 'undefined' && oldid !== null) { t.oldid=oldid; } if (typeof rcid !== 'undefined' && rcid !== null) { t.rcid=rcid; } if (typeof diff !== 'undefined' && diff !== null) { t.diff=diff; } if (!t.text && t.id !== 'mainlink') { t.text=popupString(t.id); } ret.push(t); } else { // plain HTML ret.push(splitted[i]); } } return ret; } function navlinkSubstituteHTML(s) { return s.split('*').join(getValueOf('popupNavLinkSeparator')) .split('<menurow>').join('<li class="popup_menu_row">') .split('</menurow>').join('</li>') .split('<menu>').join('<ul class="popup_menu">') .split('</menu>').join('</ul>'); } function navlinkDepth(magic,s) { return s.split('<' + magic + '>').length - s.split('</' + magic + '>').length; } // navlinkString: * becomes the separator // <<foo|bar=baz|fubar>> becomes a foo-link with attribute bar='baz' // and visible text 'fubar' // if(test){...} and if(test){...}else{...} work too (nested ok) function navlinkStringToHTML(s,article,params) { //limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article)); var p=navlinkStringToArray(s,article,params); var html=''; var menudepth = 0; // nested menus not currently allowed, but doesn't do any harm to code for it var menurowdepth = 0; for (var i=0; i<p.length; ++i) { if (typeof p[i] == typeof '') { html+=navlinkSubstituteHTML(p[i]); menudepth += navlinkDepth('menu', p[i]); menurowdepth += navlinkDepth('menurow', p[i]); // if (menudepth === 0) { // tagType='span'; // } else if (menurowdepth === 0) { // tagType='li'; // } else { // tagType = null; // } } else if (typeof p[i].type != 'undefined' && p[i].type=='navlinkTag') { if (menudepth > 0 && menurowdepth === 0) { html += '<li class="popup_menu_item">' + p[i].html() + '</li>'; } else { html+=p[i].html(); } } } return html; } function navlinkTag() { this.type='navlinkTag'; } navlinkTag.prototype.html=function () { this.getNewWin(); this.getPrintFunction(); var html=''; var opening, closing; var tagType='span'; if (!tagType) { opening = ''; closing = ''; } else { opening = '<' + tagType + ' class="popup_' + this.id + '">'; closing = '</' + tagType + '>'; } if (typeof this.print!='function') { errlog ('Oh dear - invalid print function for a navlinkTag, id='+this.id); } else { html=this.print(this); if (typeof html != typeof '') {html='';} else if (typeof this.shortcut!='undefined') html=addPopupShortcut(html, this.shortcut); } return opening + html + closing; }; navlinkTag.prototype.getNewWin=function() { getValueOf('popupLinksNewWindow'); if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') { this.newWin=null; } this.newWin=pg.option.popupLinksNewWindow[this.id]; }; navlinkTag.prototype.getPrintFunction=function() { //think about this some more // this.id and this.article should already be defined if (typeof this.id!=typeof '' || typeof this.article!=typeof {} ) { return; } this.noPopup=1; switch (this.id) { case 'contribs': case 'history': case 'whatLinksHere': case 'userPage': case 'monobook': case 'userTalk': case 'talk': case 'article': case 'lastEdit': this.noPopup=null; } switch (this.id) { case 'email': case 'contribs': case 'block': case 'unblock': case 'userlog': case 'userSpace': case 'deletedContribs': this.article=this.article.userName(); } switch (this.id) { case 'userTalk': case 'newUserTalk': case 'editUserTalk': case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog': this.article=this.article.userName(true); /* fall through */ case 'pagelog': case 'deletelog': case 'protectlog': delete this.oldid; } if (this.id=='editMonobook' || this.id=='monobook') { this.article.append('/monobook.js'); } if (this.id != 'mainlink') { // FIXME anchor handling should be done differently with Title object this.article=this.article.removeAnchor(); // if (typeof this.text=='undefined') this.text=popupString(this.id); } switch (this.id) { case 'undelete': this.print=specialLink; this.specialpage='Undelete'; this.sep='/'; break; case 'whatLinksHere': this.print=specialLink; this.specialpage='Whatlinkshere'; break; case 'relatedChanges': this.print=specialLink; this.specialpage='Recentchangeslinked'; break; case 'move': this.print=specialLink; this.specialpage='Movepage'; break; case 'contribs': this.print=specialLink; this.specialpage='Contributions'; break; case 'deletedContribs':this.print=specialLink; this.specialpage='Deletedcontributions'; break; case 'email': this.print=specialLink; this.specialpage='EmailUser'; this.sep='/'; break; case 'block': this.print=specialLink; this.specialpage='Blockip'; this.sep='&ip='; break; case 'unblock': this.print=specialLink; this.specialpage='Ipblocklist'; this.sep='&action=unblock&ip='; break; case 'userlog': this.print=specialLink; this.specialpage='Log'; this.sep='&user='; break; case 'blocklog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=block&page='; break; case 'pagelog': this.print=specialLink; this.specialpage='Log'; this.sep='&page='; break; case 'protectlog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=protect&page='; break; case 'deletelog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=delete&page='; break; case 'userSpace': this.print=specialLink; this.specialpage='PrefixIndex'; this.sep='&namespace=2&prefix='; break; case 'search': this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break; case 'thank': this.print=specialLink; this.specialpage='Thanks'; this.sep='/'; this.article.value = this.diff; break; case 'unwatch': case 'watch': this.print=magicWatchLink; this.action=this.id+'&autowatchlist=1&autoimpl=' + popupString('autoedit_version') + '&actoken='+autoClickToken(); break; case 'history': case 'historyfeed': case 'unprotect': case 'protect': this.print=wikiLink; this.action=this.id; break; case 'delete': this.print=wikiLink; this.action='delete'; if (this.article.namespaceId()==pg.nsImageId) { var img=this.article.stripNamespace(); this.action+='&image='+img; } break; case 'markpatrolled': case 'edit': // editOld should keep the oldid, but edit should not. delete this.oldid; /* fall through */ case 'view': case 'purge': case 'render': this.print=wikiLink; this.action=this.id; break; case 'raw': this.print=wikiLink; this.action='raw'; break; case 'new': this.print=wikiLink; this.action='edit&section=new'; break; case 'mainlink': if (typeof this.text=='undefined') { this.text=this.article.toString().entify(); } if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) { // only show the /subpage part of the title text var s=this.text.split('/'); this.text=s[s.length-1]; if (this.text==='' && s.length > 1) { this.text=s[s.length-2]; } } this.print=titledWikiLink; if (typeof this.title==='undefined' && pg.current.link && typeof pg.current.link.href !== 'undefined') { this.title=safeDecodeURI((pg.current.link.originalTitle)?pg.current.link.originalTitle:this.article); if (typeof this.oldid !== 'undefined' && this.oldid) { this.title=tprintf('Revision %s of %s', [this.oldid, this.title]); } } this.action='view'; break; case 'userPage': case 'article': case 'monobook': case 'editMonobook': case 'editArticle': delete this.oldid; //alert(this.id+'\n'+this.article + '\n'+ typeof this.article); this.article=this.article.articleFromTalkOrArticle(); //alert(this.id+'\n'+this.article + '\n'+ typeof this.article); this.print=wikiLink; if (this.id.indexOf('edit')===0) { this.action='edit'; } else { this.action='view';} break; case 'userTalk': case 'talk': this.article=this.article.talkPage(); delete this.oldid; this.print=wikiLink; this.action='view'; break; case 'arin': this.print=arinLink; break; case 'count': this.print=editCounterLink; break; case 'google': this.print=googleLink; break; case 'editors': this.print=editorListLink; break; case 'globalsearch': this.print=globalSearchLink; break; case 'lastEdit': this.print=titledDiffLink; this.title=popupString('Show the last edit'); this.from='prev'; this.to='cur'; break; case 'oldEdit': this.print=titledDiffLink; this.title=popupString('Show the edit made to get revision') + ' ' + this.oldid; this.from='prev'; this.to=this.oldid; break; case 'editOld': this.print=wikiLink; this.action='edit'; break; case 'undo': this.print=wikiLink; this.action='edit&undo='; break; case 'revert': this.print=wikiLink; this.action='revert'; break; case 'nullEdit': this.print=wikiLink; this.action='nullEdit'; break; case 'diffCur': this.print=titledDiffLink; this.title=tprintf('Show changes since revision %s', [this.oldid]); this.from=this.oldid; this.to='cur'; break; case 'editUserTalk': case 'editTalk': delete this.oldid; this.article=this.article.talkPage(); this.action='edit'; this.print=wikiLink; break; case 'newUserTalk': case 'newTalk': this.article=this.article.talkPage(); this.action='edit&section=new'; this.print=wikiLink; break; case 'lastContrib': case 'sinceMe': this.print=magicHistoryLink; break; case 'togglePreviews': this.text=popupString(pg.option.simplePopups ? 'enable previews' : 'disable previews'); /* fall through */ case 'disablePopups': case 'purgePopups': this.print=popupMenuLink; break; default: this.print=function () {return 'Unknown navlink type: '+this.id+'';}; } }; // // end navlinks ////////////////////////////////////////////////// //</NOLITE> // ENDFILE: navlinks.js // STARTFILE: shortcutkeys.js //<NOLITE> function popupHandleKeypress(evt) { var keyCode = window.event ? window.event.keyCode : ( evt.keyCode ? evt.keyCode : evt.which); if (!keyCode || !pg.current.link || !pg.current.link.navpopup) { return; } if (keyCode==27) { // escape killPopup(); return false; // swallow keypress } var letter=String.fromCharCode(keyCode); var links=pg.current.link.navpopup.mainDiv.getElementsByTagName('A'); var startLink=0; var i,j; if (popupHandleKeypress.lastPopupLinkSelected) { for (i=0; i<links.length; ++i) { if (links[i]==popupHandleKeypress.lastPopupLinkSelected) { startLink=i; } } } for (j=0; j<links.length; ++j) { i=(startLink + j + 1) % links.length; if (links[i].getAttribute('popupkey')==letter) { if (evt && evt.preventDefault) evt.preventDefault(); links[i].focus(); popupHandleKeypress.lastPopupLinkSelected=links[i]; return false; // swallow keypress } } // pass keypress on if (document.oldPopupOnkeypress) { return document.oldPopupOnkeypress(evt); } return true; } function addPopupShortcuts() { if (document.onkeypress!=popupHandleKeypress) { document.oldPopupOnkeypress=document.onkeypress; } document.onkeypress=popupHandleKeypress; } function rmPopupShortcuts() { popupHandleKeypress.lastPopupLinkSelected=null; try { if (document.oldPopupOnkeypress && document.oldPopupOnkeypress==popupHandleKeypress) { // panic document.onkeypress=null; //function () {}; return; } document.onkeypress=document.oldPopupOnkeypress; } catch (nasties) { /* IE goes here */ } } function addLinkProperty(html, property) { // take "<a href=...>...</a> and add a property // not sophisticated at all, easily broken var i=html.indexOf('>'); if (i<0) { return html; } return html.substring(0,i) + ' ' + property + html.substring(i); } function addPopupShortcut(html, key) { if (!getValueOf('popupShortcutKeys')) { return html; } var ret= addLinkProperty(html, 'popupkey="'+key+'"'); if (key==' ') { key=popupString('spacebar'); } return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'),'$1$2$3 ['+key+']$4'); } //</NOLITE> // ENDFILE: shortcutkeys.js // STARTFILE: diffpreview.js //<NOLITE> //lets jump through hoops to find the rev ids we need to retrieve function loadDiff(article, oldid, diff, navpop) { navpop.diffData={ oldRev: {}, newRev: {} }; mw.loader.using( 'mediawiki.api' ).then( function() { var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'compare', prop: 'ids|title' }; if(article.title){ params.fromtitle = article.title; } switch (diff) { case 'cur': switch ( oldid ) { case null: case '': case 'prev': // this can only work if we have the title // cur -> prev params.torelative = 'prev'; break; default: params.fromrev = oldid; params.torelative = 'cur'; break; } break; case 'prev': if( oldid ) { params.fromrev = oldid; } else { params.fromtitle; } params.torelative = 'prev'; break; case 'next': params.fromrev = oldid || 0; params.torelative = 'next'; break; default: params.fromrev = oldid || 0; params.torev = diff || 0; break; } api.get( params ).then( function( data ) { navpop.diffData.oldRev.revid = data.compare.fromrevid; navpop.diffData.newRev.revid = data.compare.torevid; addReviewLink(navpop, 'popupMiscTools'); var go = function() { pendingNavpopTask(navpop); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&'; url += 'revids=' + navpop.diffData.oldRev.revid + '|' + navpop.diffData.newRev.revid; url += '&prop=revisions&rvprop=ids|timestamp|content'; getPageWithCaching(url, doneDiff, navpop); return true; // remove hook once run }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS'); } } ); } ); } // Put a "mark patrolled" link to an element target // TODO: Allow patrol a revision, as well as a diff function addReviewLink (navpop, target) { if (! pg.user.canReview) return; // If 'newRev' is older than 'oldRev' than it could be confusing, so we do not show the review link. if (navpop.diffData.newRev.revid <= navpop.diffData.oldRev.revid) return; var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'query', prop: 'info|flagged', revids: navpop.diffData.oldRev.revid, formatversion : 2 }; api.get (params).then(function(data){ var stable_revid = data.query.pages[0].flagged && data.query.pages[0].flagged.stable_revid || 0; // The diff can be reviewed if the old version is the last reviewed version // TODO: Other possible conditions that we may want to implement instead of this one: // * old version is patrolled and the new version is not patrolled // * old version is patrolled and the new version is more recent than the last reviewed version if (stable_revid == navpop.diffData.oldRev.revid) { var a = document.createElement('a'); a.innerHTML = popupString('mark patrolled'); a.title=popupString('markpatrolledHint'); a.onclick = function() { var params = { action: 'review', revid: navpop.diffData.newRev.revid, comment: tprintf('defaultpopupReviewedSummary', [navpop.diffData.oldRev.revid, navpop.diffData.newRev.revid]) }; api.postWithToken('csrf',params).done(function(){ a.style.display = "none"; // TODO: Update current page and other already constructed popups } ).fail(function(){ alert(popupString('Could not marked this edit as patrolled')); }); }; setPopupHTML(a, target, navpop.idNumber,null,true); } }); } function doneDiff(download) { if (!download.owner || !download.owner.diffData) { return; } var navpop=download.owner; completedNavpopTask(navpop); var pages, revisions=[]; try{ // Process the downloads pages = getJsObj(download.data).query.pages; for(var i=0; i < pages.length; i++ ) { revisions = revisions.concat(pages[i].revisions); } for(i=0; i< revisions.length; i++){ if(revisions[i].revid == navpop.diffData.oldRev.revid) { navpop.diffData.oldRev.revision = revisions[i]; } else if (revisions[i].revid == navpop.diffData.newRev.revid) { navpop.diffData.newRev.revision = revisions[i]; } } } catch(someError) { errlog( 'Could not get diff' ); } insertDiff(navpop); } function rmBoringLines(a,b,context) { if (typeof context == 'undefined') { context=2; } // this is fairly slow... i think it's quicker than doing a word-based diff from the off, though var aa=[], aaa=[]; var bb=[], bbb=[]; var i, j; // first, gather all disconnected nodes in a and all crossing nodes in a and b for (i=0; i<a.length; ++i ) { if(!a[i].paired) { aa[i]=1; } else if (countCrossings(b,a,i, true)) { aa[i]=1; bb[ a[i].row ] = 1; } } // pick up remaining disconnected nodes in b for (i=0; i<b.length; ++i ) { if (bb[i]==1) { continue; } if(!b[i].paired) { bb[i]=1; } } // another pass to gather context: we want the neighbours of included nodes which are not yet included // we have to add in partners of these nodes, but we don't want to add context for *those* nodes in the next pass for (i=0; i<b.length; ++i) { if ( bb[i] == 1 ) { for (j=Math.max(0,i-context); j < Math.min(b.length, i+context); ++j) { if ( !bb[j] ) { bb[j] = 1; aa[ b[j].row ] = 0.5; } } } } for (i=0; i<a.length; ++i) { if ( aa[i] == 1 ) { for (j=Math.max(0,i-context); j < Math.min(a.length, i+context); ++j) { if ( !aa[j] ) { aa[j] = 1; bb[ a[j].row ] = 0.5; } } } } for (i=0; i<bb.length; ++i) { if (bb[i] > 0) { // it's a row we need if (b[i].paired) { bbb.push(b[i].text); } // joined; partner should be in aa else { bbb.push(b[i]); } } } for (i=0; i<aa.length; ++i) { if (aa[i] > 0) { // it's a row we need if (a[i].paired) { aaa.push(a[i].text); } // joined; partner should be in aa else { aaa.push(a[i]); } } } return { a: aaa, b: bbb}; } function stripOuterCommonLines(a,b,context) { var i=0; while (i<a.length && i < b.length && a[i]==b[i]) { ++i; } var j=a.length-1; var k=b.length-1; while ( j>=0 && k>=0 && a[j]==b[k] ) { --j; --k; } return { a: a.slice(Math.max(0,i - 1 - context), Math.min(a.length+1, j + context+1)), b: b.slice(Math.max(0,i - 1 - context), Math.min(b.length+1, k + context+1)) }; } function insertDiff(navpop) { // for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff // FIXME: sometimes this gives misleading diffs as distant chunks are squashed together var oldlines = navpop.diffData.oldRev.revision.content.split('\n'); var newlines = navpop.diffData.newRev.revision.content.split('\n'); var inner=stripOuterCommonLines(oldlines,newlines,getValueOf('popupDiffContextLines')); oldlines=inner.a; newlines=inner.b; var truncated=false; getValueOf('popupDiffMaxLines'); if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) { // truncate truncated=true; inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines), newlines.slice(0,pg.option.popupDiffMaxLines), pg.option.popupDiffContextLines); oldlines=inner.a; newlines=inner.b; } var lineDiff=diff(oldlines, newlines); var lines2=rmBoringLines(lineDiff.o, lineDiff.n); var oldlines2=lines2.a; var newlines2=lines2.b; var simpleSplit = !String.prototype.parenSplit.isNative; var html='<hr />'; if (getValueOf('popupDiffDates')) { html += diffDatesTable(navpop); html += '<hr />'; } html += shortenDiffString( diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit), getValueOf('popupDiffContextCharacters') ).join('<hr />'); setPopupTipsAndHTML(html.split('\n').join('<br>') + (truncated ? '<hr /><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') , 'popupPreview', navpop.idNumber); } function diffDatesTable( navpop ) { var html='<table class="popup_diff_dates">'; html += diffDatesTableRow( navpop.diffData.newRev.revision, tprintf('New revision')); html += diffDatesTableRow( navpop.diffData.oldRev.revision, tprintf('Old revision')); html += '</table>'; return html; } function diffDatesTableRow( revision, label ) { var txt=''; var lastModifiedDate = new Date(revision.timestamp); var datePrint=getValueOf('popupDiffDatePrinter'); if (typeof lastModifiedDate[datePrint] == 'function') { var d2 = adjustDate(lastModifiedDate, getTimeOffset()); txt = dayFormat(d2, true) + ' ' + timeFormat(d2, true); } else { txt = tprintf('Invalid %s %s', ['popupDiffDatePrinter', datePrint]); } var revlink = generalLink({url: mw.config.get('wgScript') + '?oldid='+revision.revid, text: label, title: label}); return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [ revlink, txt ]); } //</NOLITE> // ENDFILE: diffpreview.js // STARTFILE: links.js //<NOLITE> ///////////////////// // LINK GENERATION // ///////////////////// // titledDiffLink --> titledWikiLink --> generalLink // wikiLink --> titledWikiLink --> generalLink // editCounterLink --> generalLink // TODO Make these functions return Element objects, not just raw HTML strings. function titledDiffLink(l) { // article, text, title, from, to) { return titledWikiLink({article: l.article, action: l.to + '&oldid=' + l.from, newWin: l.newWin, noPopup: l.noPopup, text: l.text, title: l.title, /* hack: no oldid here */ actionName: 'diff'}); } function wikiLink(l) { //{article:article, action:action, text:text, oldid, newid}) { if (! (typeof l.article == typeof {} && typeof l.action == typeof '' && typeof l.text==typeof '')) return null; if (typeof l.oldid == 'undefined') { l.oldid=null; } var savedOldid = l.oldid; if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) { l.oldid=null; } var hint=popupString(l.action + 'Hint'); // revertHint etc etc etc var oldidData=[l.oldid, safeDecodeURI(l.article)]; var revisionString = tprintf('revision %s of %s', oldidData); log('revisionString='+revisionString); switch (l.action) { case 'edit&section=new': hint = popupString('newSectionHint'); break; case 'edit&undo=': if (l.diff && l.diff != 'prev' && savedOldid ) { l.action += l.diff + '&undoafter=' + savedOldid; } else if (savedOldid) { l.action += savedOldid; } hint = popupString('undoHint'); break; case 'raw&ctype=text/css': hint=popupString('rawHint'); break; case 'revert': var p=parseParams(pg.current.link.href); l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=' + revertSummary(l.oldid, p.diff); if (p.diff=='prev') { l.action += '&direction=prev'; revisionString = tprintf('the revision prior to revision %s of %s', oldidData); } if (getValueOf('popupRevertSummaryPrompt')) { l.action += '&autosummaryprompt=true'; } if (getValueOf('popupMinorReverts')) { l.action += '&autominor=true'; } log('revisionString is now '+revisionString); break; case 'nullEdit': l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=null'; break; case 'historyfeed': l.action='history&feed=rss'; break; case 'markpatrolled': l.action='markpatrolled&rcid='+l.rcid; } if (hint) { if (l.oldid) { hint = simplePrintf(hint, [revisionString]); } else { hint = simplePrintf(hint, [safeDecodeURI(l.article)]); } } else { hint = safeDecodeURI(l.article + '&action=' + l.action) + (l.oldid) ? '&oldid='+l.oldid : ''; } return titledWikiLink({article: l.article, action: l.action, text: l.text, newWin:l.newWin, title: hint, oldid: l.oldid, noPopup: l.noPopup, onclick: l.onclick}); } function revertSummary(oldid, diff) { var ret=''; if (diff == 'prev') { ret=getValueOf('popupQueriedRevertToPreviousSummary'); } else { ret = getValueOf('popupQueriedRevertSummary'); } return ret + '&autorv=' + oldid; } function titledWikiLink(l) { // possible properties of argument: // article, action, text, title, oldid, actionName, className, noPopup // oldid = null is fine here // article and action are mandatory args if (typeof l.article == 'undefined' || typeof l.action=='undefined') { errlog('got undefined article or action in titledWikiLink'); return null; } var base = pg.wiki.titlebase + l.article.urlString(); var url=base; if (typeof l.actionName=='undefined' || !l.actionName) { l.actionName='action'; } // no need to add &action=view, and this confuses anchors if (l.action != 'view') { url = base + '&' + l.actionName + '=' + l.action; } if (typeof l.oldid!='undefined' && l.oldid) { url+='&oldid='+l.oldid; } var cssClass=pg.misc.defaultNavlinkClassname; if (typeof l.className!='undefined' && l.className) { cssClass=l.className; } return generalNavLink({url: url, newWin: l.newWin, title: (typeof l.title != 'undefined') ? l.title : null, text: (typeof l.text!='undefined')?l.text:null, className: cssClass, noPopup:l.noPopup, onclick:l.onclick}); } pg.fn.getLastContrib = function getLastContrib(wikipage, newWin) { getHistoryInfo(wikipage, function(x) { processLastContribInfo(x, {page: wikipage, newWin: newWin}); }); }; function processLastContribInfo(info, stuff) { if(!info.edits || !info.edits.length) { alert('Popups: an odd thing happened. Please retry.'); return; } if(!info.firstNewEditor) { alert(tprintf('Only found one editor: %s made %s edits', [info.edits[0].editor,info.edits.length])); return; } var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+info.firstNewEditor.oldid; displayUrl(newUrl, stuff.newWin); } pg.fn.getDiffSinceMyEdit = function getDiffSinceMyEdit(wikipage, newWin) { getHistoryInfo(wikipage, function(x){ processDiffSinceMyEdit(x, {page: wikipage, newWin: newWin}); }); }; function processDiffSinceMyEdit(info, stuff) { if(!info.edits || !info.edits.length) { alert('Popups: something fishy happened. Please try again.'); return; } var friendlyName=stuff.page.split('_').join(' '); if(!info.myLastEdit) { alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s', [info.userName, getValueOf('popupHistoryLimit'), friendlyName])); return; } if(info.myLastEdit.index === 0) { alert(tprintf("%s seems to be the last editor to the page %s", [info.userName, friendlyName])); return; } var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+ info.myLastEdit.oldid; displayUrl(newUrl, stuff.newWin); } function displayUrl(url, newWin){ if(newWin) { window.open(url); } else { document.location=url; } } pg.fn.purgePopups = function purgePopups() { processAllPopups(true); setupCache(); // deletes all cached items (not browser cached, though...) pg.option={}; abortAllDownloads(); }; function processAllPopups(nullify, banish) { for (var i=0; pg.current.links && i<pg.current.links.length; ++i) { if (!pg.current.links[i].navpopup) { continue; } if (nullify || banish) pg.current.links[i].navpopup.banish(); pg.current.links[i].simpleNoMore=false; if (nullify) pg.current.links[i].navpopup=null; } } pg.fn.disablePopups = function disablePopups(){ processAllPopups(false, true); setupTooltips(null, true); }; pg.fn.togglePreviews = function togglePreviews() { processAllPopups(true, true); pg.option.simplePopups=!pg.option.simplePopups; abortAllDownloads(); }; function magicWatchLink(l) { //Yuck!! Would require a thorough redesign to add this as a click event though ... l.onclick = simplePrintf( 'pg.fn.modifyWatchlist(\'%s\',\'%s\');return false;', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), this.id] ); return wikiLink(l); } pg.fn.modifyWatchlist = function modifyWatchlist(title, action) { var reqData = { 'action': 'watch', 'formatversion': 2, 'titles': title, 'uselang': mw.config.get('wgUserLanguage') }; if ( action === 'unwatch' ) reqData.unwatch = true; var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); // Load the Addedwatchtext or Removedwatchtext message and show it var mwTitle = mw.Title.newFromText( title ); var messageName; if ( mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1 ) { messageName = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk'; } else { messageName = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext'; } $.when( api.postWithToken( 'watch', reqData ), mw.loader.using( [ 'mediawiki.api', 'mediawiki.jqueryMsg' ] ).then( function () { return api.loadMessagesIfMissing( [ messageName ] ); } ) ).done( function () { mw.notify( mw.message( messageName, title ).parseDom() ); } ); }; function magicHistoryLink(l) { // FIXME use onclick change href trick to sort this out instead of window.open var jsUrl='', title='', onClick=''; switch(l.id) { case 'lastContrib': onClick=simplePrintf('pg.fn.getLastContrib(\'%s\',%s)', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]); title=popupString('lastContribHint'); break; case 'sinceMe': onClick=simplePrintf('pg.fn.getDiffSinceMyEdit(\'%s\',%s)', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]); title=popupString('sinceMeHint'); break; } jsUrl = 'javascript:' + onClick; // jshint ignore:line onClick += ';return false;'; return generalNavLink({url: jsUrl, newWin: false, // can't have new windows with JS links, I think title: title, text: l.text, noPopup: l.noPopup, onclick: onClick }); } function popupMenuLink(l) { var jsUrl=simplePrintf('javascript:pg.fn.%s()', [l.id]); // jshint ignore:line var title=popupString(simplePrintf('%sHint', [l.id])); var onClick=simplePrintf('pg.fn.%s();return false;', [l.id]); return generalNavLink({url: jsUrl, newWin:false, title:title, text:l.text, noPopup:l.noPopup, onclick: onClick}); } function specialLink(l) { // properties: article, specialpage, text, sep if (typeof l.specialpage=='undefined'||!l.specialpage) return null; var base = pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId]+':'+l.specialpage; if (typeof l.sep == 'undefined' || l.sep === null) l.sep='&target='; var article=l.article.urlString({keepSpaces: l.specialpage=='Search'}); var hint=popupString(l.specialpage+'Hint'); switch (l.specialpage) { case 'Log': switch (l.sep) { case '&user=': hint=popupString('userLogHint'); break; case '&type=block&page=': hint=popupString('blockLogHint'); break; case '&page=': hint=popupString('pageLogHint'); break; case '&type=protect&page=': hint=popupString('protectLogHint'); break; case '&type=delete&page=': hint=popupString('deleteLogHint'); break; default: log('Unknown log type, sep=' + l.sep); hint='Missing hint (FIXME)'; } break; case 'PrefixIndex': article += '/'; break; } if (hint) hint = simplePrintf(hint, [safeDecodeURI(l.article)]); else hint = safeDecodeURI(l.specialpage+':'+l.article) ; var url = base + l.sep + article; return generalNavLink({url: url, title: hint, text: l.text, newWin:l.newWin, noPopup:l.noPopup}); } function generalLink(l) { // l.url, l.text, l.title, l.newWin, l.className, l.noPopup, l.onclick if (typeof l.url=='undefined') return null; // only quotation marks in the url can screw us up now... I think var url=l.url.split('"').join('%22'); var ret='<a href="' + url + '"'; if (typeof l.title!='undefined' && l.title) { ret += ' title="' + pg.escapeQuotesHTML(l.title) + '"'; } if (typeof l.onclick!='undefined' && l.onclick) { ret += ' onclick="' + pg.escapeQuotesHTML(l.onclick) + '"'; } if (l.noPopup) { ret += ' noPopup=1'; } var newWin; if (typeof l.newWin=='undefined' || l.newWin === null) { newWin=getValueOf('popupNewWindows'); } else { newWin=l.newWin; } if (newWin) { ret += ' target="_blank"'; } if (typeof l.className!='undefined'&&l.className) { ret+=' class="'+l.className+'"'; } ret += '>'; if (typeof l.text==typeof '') { ret+= l.text; } ret +='</a>'; return ret; } function appendParamsToLink(linkstr, params) { var sp=linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i')); if (sp.length<2) return null; var ret=sp.shift() + sp.shift(); ret += '&' + params + '"'; ret += sp.join(''); return ret; } function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor, title (optional), alsoChangeLabel { if (x.newTarget) { log ('changeLinkTargetLink: newTarget=' + x.newTarget); } if (x.oldTarget !== decodeURIComponent( x.oldTarget ) ) { log ('This might be an input problem: ' + x.oldTarget ); } // FIXME: first character of page title as well as namespace should be case insensitive // eg [[category:X1]] and [[Category:X1]] are equivalent // this'll break if charAt(0) is nasty var cA = mw.util.escapeRegExp(x.oldTarget); var chs = cA.charAt(0).toUpperCase(); chs='['+chs + chs.toLowerCase()+']'; var currentArticleRegexBit=chs+cA.substring(1); currentArticleRegexBit=currentArticleRegexBit .split(RegExp('(?:[_ ]+|%20)', 'g')).join('(?:[_ ]+|%20)') .split('\\(').join('(?:%28|\\()') .split('\\)').join('(?:%29|\\))'); // why does this need to match encoded strings ? links in the document ? // leading and trailing space should be ignored, and anchor bits optional: currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + '(?:#[^\\[\\|]*)?)\\s*'; // e.g. Computer (archaic) -> \s*([Cc]omputer[_ ](?:%2528|\()archaic(?:%2528|\)))\s* // autoedit=s~\[\[([Cc]ad)\]\]~[[Computer-aided%20design|$1]]~g;s~\[\[([Cc]AD)[|]~[[Computer-aided%20design|~g var title=x.title || mw.config.get('wgPageName').split('_').join(' '); var lk=titledWikiLink({article: new Title(title), newWin:x.newWin, action: 'edit', text: x.text, title: x.hint, className: 'popup_change_title_link' }); var cmd=''; if (x.newTarget) { // escape '&' and other nasties var t = x.newTarget; var s = mw.util.escapeRegExp(x.newTarget); if (x.alsoChangeLabel) { cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + ']]~g;'; cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;'; cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g'; } else { cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + '|$1]]~g;'; cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;'; cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g'; } } else { cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~$1~g;'; cmd += 's~\\[\\['+currentArticleRegexBit+'[|](.*?)\\]\\]~$2~g'; } // Build query cmd = 'autoedit=' + encodeURIComponent ( cmd ); cmd += '&autoclick='+ encodeURIComponent( x.clickButton ) + '&actoken=' + encodeURIComponent( autoClickToken() ); cmd += ( x.minor === null ) ? '' : '&autominor='+ encodeURIComponent( x.minor ); cmd += ( x.watch === null ) ? '' : '&autowatch='+ encodeURIComponent( x.watch ); cmd += '&autosummary='+encodeURIComponent(x.summary); cmd += '&autoimpl='+encodeURIComponent( popupString('autoedit_version') ); return appendParamsToLink(lk, cmd); } function redirLink(redirMatch, article) { // NB redirMatch is in wikiText var ret=''; if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) { ret += '<hr />'; if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) { ret += popupString('Redirects to: (Fix '); log('redirLink: newTarget=' + redirMatch); ret += addPopupShortcut(changeLinkTargetLink({ newTarget: redirMatch, text: popupString('target'), hint: popupString('Fix this redirect, changing just the link target'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]), oldTarget: article.toString(), clickButton: getValueOf('popupRedirAutoClick'), minor: true, watch: getValueOf('popupWatchRedirredPages') }), 'R'); ret += popupString(' or '); ret += addPopupShortcut(changeLinkTargetLink({ newTarget: redirMatch, text: popupString('target & label'), hint: popupString('Fix this redirect, changing the link target and label'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]), oldTarget: article.toString(), clickButton: getValueOf('popupRedirAutoClick'), minor: true, watch: getValueOf('popupWatchRedirredPages'), alsoChangeLabel: true }), 'R'); ret += popupString(')'); } else ret += popupString('Redirects') + popupString(' to '); return ret; } else return '<br> ' + popupString('Redirects') + popupString(' to ') + titledWikiLink({article: new Title().fromWikiText(redirMatch), action: 'view', /* FIXME: newWin */ text: safeDecodeURI(redirMatch), title: popupString('Bypass redirect')}); } function arinLink(l) { if (!saneLinkCheck(l)) { return null; } if ( ! l.article.isIpUser() || ! pg.wiki.wikimedia) return null; var uN=l.article.userName(); return generalNavLink({url:'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + encodeURIComponent(uN), newWin:l.newWin, title: tprintf('Look up %s in ARIN whois database', [uN]), text: l.text, noPopup:1}); } function toolDbName(cookieStyle) { var ret = mw.config.get('wgDBname'); if (!cookieStyle) { ret+= '_p'; } return ret; } function saneLinkCheck(l) { if (typeof l.article != typeof {} || typeof l.text != typeof '') { return false; } return true; } function editCounterLink(l) { if(!saneLinkCheck(l)) return null; if (! pg.wiki.wikimedia) return null; var uN=l.article.userName(); var tool=getValueOf('popupEditCounterTool'); var url; var defaultToolUrl='//tools.wmflabs.org/supercount/index.php?user=$1&project=$2.$3'; switch(tool) { case 'custom': url=simplePrintf(getValueOf('popupEditCounterUrl'), [ encodeURIComponent(uN), toolDbName() ]); break; case 'soxred': // no longer available case 'kate': // no longer available case 'interiot':// no longer available /* fall through */ case 'supercount': default: var theWiki=pg.wiki.hostname.split('.'); url=simplePrintf(defaultToolUrl, [ encodeURIComponent(uN), theWiki[0], theWiki[1] ]); } return generalNavLink({url:url, title: tprintf('editCounterLinkHint', [uN]), newWin:l.newWin, text: l.text, noPopup:1}); } function globalSearchLink(l) { if(!saneLinkCheck(l)) return null; var base='http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search='; var article=l.article.urlString({keepSpaces:true}); return generalNavLink({url:base + article, newWin:l.newWin, title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]), text: l.text, noPopup:1}); } function googleLink(l) { if(!saneLinkCheck(l)) return null; var base='https://www.google.com/search?q='; var article=l.article.urlString({keepSpaces:true}); return generalNavLink({url:base + '%22' + article + '%22', newWin:l.newWin, title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]), text: l.text, noPopup:1}); } function editorListLink(l) { if(!saneLinkCheck(l)) return null; var article= l.article.articleFromTalkPage() || l.article; var url='https://xtools.wmflabs.org/articleinfo/' + encodeURI( pg.wiki.hostname ) + '/' + article.urlString() + '?uselang=' + mw.config.get('wgUserLanguage'); return generalNavLink({url:url, title: tprintf('editorListHint', [article]), newWin:l.newWin, text: l.text, noPopup:1}); } function generalNavLink(l) { l.className = (l.className === null) ? 'popupNavLink' : l.className; return generalLink(l); } ////////////////////////////////////////////////// // magic history links // function getHistoryInfo(wikipage, whatNext) { log('getHistoryInfo'); getHistory(wikipage, whatNext ? function(d){whatNext(processHistory(d));} : processHistory); } // FIXME eliminate pg.idNumber ... how? :-( function getHistory(wikipage, onComplete) { log('getHistory'); var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&prop=revisions&titles=' + new Title(wikipage).urlString() + '&rvlimit=' + getValueOf('popupHistoryLimit'); log('getHistory: url='+url); return startDownload(url, pg.idNumber+'history', onComplete); } function processHistory(download) { var jsobj = getJsObj(download.data); try { var revisions = anyChild(jsobj.query.pages).revisions; var edits=[]; for (var i=0; i<revisions.length; ++i) { edits.push({ oldid: revisions[i].revid, editor: revisions[i].user }); } log('processed ' + edits.length + ' edits'); return finishProcessHistory( edits, mw.config.get('wgUserName') ); } catch (someError) { log('Something went wrong with JSON business'); return finishProcessHistory([]); } } function finishProcessHistory(edits, userName) { var histInfo={}; histInfo.edits=edits; histInfo.userName=userName; for (var i=0; i<edits.length; ++i) { if (typeof histInfo.myLastEdit === 'undefined' && userName && edits[i].editor==userName) { histInfo.myLastEdit={index: i, oldid: edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)}; } if (typeof histInfo.firstNewEditor === 'undefined' && edits[i].editor != edits[0].editor) { histInfo.firstNewEditor={index:i, oldid:edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)}; } } //pg.misc.historyInfo=histInfo; return histInfo; } //</NOLITE> // ENDFILE: links.js // STARTFILE: options.js ////////////////////////////////////////////////// // options // check for existing value, else use default function defaultize(x) { if (pg.option[x]===null || typeof pg.option[x]=='undefined') { if (typeof window[x] != 'undefined' ) pg.option[x]=window[x]; else pg.option[x]=pg.optionDefault[x]; } } function newOption(x, def) { pg.optionDefault[x]=def; } function setDefault(x, def) { return newOption(x, def); } function getValueOf(varName) { defaultize(varName); return pg.option[varName]; } /*eslint-disable */ function useDefaultOptions() { // for testing for (var p in pg.optionDefault) { pg.option[p]=pg.optionDefault[p]; if (typeof window[p]!='undefined') { delete window[p]; } } } /*eslint-enable */ function setOptions() { // user-settable parameters and defaults var userIsSysop = false; if ( mw.config.get('wgUserGroups') ) { for ( var g = 0; g < mw.config.get('wgUserGroups').length; ++g ) { if ( mw.config.get('wgUserGroups')[g] == "sysop" ) userIsSysop = true; } } // Basic options newOption('popupDelay', 0.5); newOption('popupHideDelay', 0.5); newOption('simplePopups', false); newOption('popupStructure', 'shortmenus'); // see later - default for popupStructure is 'original' if simplePopups is true newOption('popupActionsMenu', true); newOption('popupSetupMenu', true); newOption('popupAdminLinks', userIsSysop); newOption('popupShortcutKeys', false); newOption('popupHistoricalLinks', true); newOption('popupOnlyArticleLinks', true); newOption('removeTitles', true); newOption('popupMaxWidth', 350); newOption('popupSimplifyMainLink', true); newOption('popupAppendRedirNavLinks', true); newOption('popupTocLinks', false); newOption('popupSubpopups', true); newOption('popupDragHandle', false /* 'popupTopLinks'*/); newOption('popupLazyPreviews', true); newOption('popupLazyDownloads', true); newOption('popupAllDabsStubs', false); newOption('popupDebugging', false); newOption('popupActiveNavlinks', true); newOption('popupModifier', false); // ctrl, shift, alt or meta newOption('popupModifierAction', 'enable'); // or 'disable' newOption('popupDraggable', true); newOption('popupReview', false); //<NOLITE> // images newOption('popupImages', true); newOption('imagePopupsForImages', true); newOption('popupNeverGetThumbs', false); //newOption('popupImagesToggleSize', true); newOption('popupThumbAction', 'imagepage'); //'sizetoggle'); newOption('popupImageSize', 60); newOption('popupImageSizeLarge', 200); // redirs, dabs, reversion newOption('popupFixRedirs', false); newOption('popupRedirAutoClick', 'wpDiff'); newOption('popupFixDabs', false); newOption('popupDabsAutoClick', 'wpDiff'); newOption('popupRevertSummaryPrompt', false); newOption('popupMinorReverts', false); newOption('popupRedlinkRemoval', false); newOption('popupRedlinkAutoClick', 'wpDiff'); newOption('popupWatchDisambiggedPages', null); newOption('popupWatchRedirredPages', null); newOption('popupDabWiktionary', 'last'); // navlinks newOption('popupNavLinks', true); newOption('popupNavLinkSeparator', ' &sdot; '); newOption('popupLastEditLink', true); newOption('popupEditCounterTool', 'supercount'); newOption('popupEditCounterUrl', ''); //</NOLITE> // previews etc newOption('popupPreviews', true); newOption('popupSummaryData', true); newOption('popupMaxPreviewSentences', 9); newOption('popupMaxPreviewCharacters', 900); newOption('popupLastModified', true); newOption('popupPreviewKillTemplates', true); newOption('popupPreviewRawTemplates', true); newOption('popupPreviewFirstParOnly', false); newOption('popupPreviewCutHeadings', false); newOption('popupPreviewButton', false); newOption('popupPreviewButtonEvent', 'click'); //<NOLITE> // diffs newOption('popupPreviewDiffs', true); newOption('popupDiffMaxLines', 100); newOption('popupDiffContextLines', 2); newOption('popupDiffContextCharacters', 40); newOption('popupDiffDates', true); newOption('popupDiffDatePrinter', 'toLocaleString'); // edit summaries. God, these are ugly. newOption('popupReviewedSummary', popupString('defaultpopupReviewedSummary') ); newOption('popupFixDabsSummary', popupString('defaultpopupFixDabsSummary') ); newOption('popupExtendedRevertSummary', popupString('defaultpopupExtendedRevertSummary') ); newOption('popupRevertSummary', popupString('defaultpopupRevertSummary') ); newOption('popupRevertToPreviousSummary', popupString('defaultpopupRevertToPreviousSummary') ); newOption('popupQueriedRevertSummary', popupString('defaultpopupQueriedRevertSummary') ); newOption('popupQueriedRevertToPreviousSummary', popupString('defaultpopupQueriedRevertToPreviousSummary') ); newOption('popupFixRedirsSummary', popupString('defaultpopupFixRedirsSummary') ); newOption('popupRedlinkSummary', popupString('defaultpopupRedlinkSummary') ); newOption('popupRmDabLinkSummary', popupString('defaultpopupRmDabLinkSummary') ); //</NOLITE> // misc newOption('popupHistoryLimit', 50); //<NOLITE> newOption('popupFilters', [popupFilterStubDetect, popupFilterDisambigDetect, popupFilterPageSize, popupFilterCountLinks, popupFilterCountImages, popupFilterCountCategories, popupFilterLastModified]); newOption('extraPopupFilters', []); newOption('popupOnEditSelection', 'cursor'); newOption('popupPreviewHistory', true); newOption('popupImageLinks', true); newOption('popupCategoryMembers', true); newOption('popupUserInfo', true); newOption('popupHistoryPreviewLimit', 25); newOption('popupContribsPreviewLimit',25); newOption('popupRevDelUrl', '//en.wikipedia.org/wiki/Wikipedia:Revision_deletion'); newOption('popupShowGender', true); //</NOLITE> // new windows newOption('popupNewWindows', false); newOption('popupLinksNewWindow', {'lastContrib': true, 'sinceMe': true}); // regexps newOption('popupDabRegexp', '(\\{\\{\\s*disambig(?!uation needed)|disambig(uation|)\\s*\\}\\}|disamb\\s*\\}\\}|dab\\s*\\}\\})|\\{\\{\\s*(((geo|hn|road?|school|number)dis)|[234][lc][acw]|(road|ship)index)(\\s*[|][^}]*)?\\s*[}][}]|is a .*disambiguation.*page'); newOption('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub'); newOption('popupImageVarsRegexp', 'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo'); } // ENDFILE: options.js // STARTFILE: strings.js //<NOLITE> ////////////////////////////////////////////////// // Translatable strings ////////////////////////////////////////////////// // // See instructions at // https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation pg.string = { ///////////////////////////////////// // summary data, searching etc. ///////////////////////////////////// 'article': 'article', 'category': 'category', 'categories': 'categories', 'image': 'image', 'images': 'images', 'stub': 'stub', 'section stub': 'section stub', 'Empty page': 'Empty page', 'kB': 'kB', 'bytes': 'bytes', 'day': 'day', 'days': 'days', 'hour': 'hour', 'hours': 'hours', 'minute': 'minute', 'minutes': 'minutes', 'second': 'second', 'seconds': 'seconds', 'week': 'week', 'weeks': 'weeks', 'search': 'search', 'SearchHint': 'Find English Wikipedia articles containing %s', 'web': 'web', 'global': 'global', 'globalSearchHint': 'Search across Wikipedias in different languages for %s', 'googleSearchHint': 'Google for %s', ///////////////////////////////////// // article-related actions and info // (some actions also apply to user pages) ///////////////////////////////////// 'tác vụ': 'tác vụ', ///// view articles and view talk 'popupsMenu': 'popups', 'togglePreviewsHint': 'Toggle preview generation in popups on this page', 'enable previews': 'enable previews', 'disable previews': 'disable previews', 'toggle previews': 'toggle previews', 'show preview': 'show preview', 'reset': 'reset', 'more...': 'more...', 'disable': 'disable popups', 'disablePopupsHint': 'Disable popups on this page. Reload page to re-enable.', 'historyfeedHint': 'RSS feed of recent changes to this page', 'purgePopupsHint': 'Reset popups, clearing all cached popup data.', 'PopupsHint': 'Reset popups, clearing all cached popup data.', 'spacebar': 'space', 'view': 'view', 'view article': 'view article', 'viewHint': 'Go to %s', 'talk': 'talk', 'talk page': 'talk page', 'this&nbsp;revision': 'this&nbsp;revision', 'revision %s of %s': 'revision %s of %s', 'Revision %s of %s': 'Revision %s of %s', 'the revision prior to revision %s of %s': 'the revision prior to revision %s of %s', 'Toggle image size': 'Click to toggle image size', 'del': 'del', ///// delete, protect, move 'delete': 'delete', 'deleteHint': 'Delete %s', 'undeleteShort': 'un', 'UndeleteHint': 'Show the deletion history for %s', 'protect': 'protect', 'protectHint': 'Restrict editing rights to %s', 'unprotectShort': 'un', 'unprotectHint': 'Allow %s to be edited by anyone again', 'send thanks': 'send thanks', 'ThanksHint': 'Send a thank you notification to this user', 'move': 'move', 'move page': 'move page', 'MovepageHint': 'Change the title of %s', 'sửa': 'sửa', ///// edit articles and talk 'edit article': 'edit article', 'editHint': 'Change the content of %s', 'edit talk': 'edit talk', 'new': 'new', 'new topic': 'new topic', 'newSectionHint': 'Start a new section on %s', 'null edit': 'null edit', 'nullEditHint': 'Submit an edit to %s, making no changes ', 'hist': 'hist', ///// history, diffs, editors, related 'lịch sử': 'lịch sử', 'historyHint': 'List the changes made to %s', 'last': 'prev', // For labelling the previous revision in history pages; the key is "last" for backwards compatibility 'lastEdit': 'lastEdit', 'mark patrolled': 'đánh dấu tuần tra', 'markpatrolledHint': 'Mark this edit as patrolled', 'Could not marked this edit as patrolled': 'Could not marked this edit as patrolled', 'show last edit': 'lần sửa đổi gần nhất', 'Show the last edit': 'Show the effects of the most recent change', 'lastContrib': 'lastContrib', 'last set of edits': 'latest edits', 'lastContribHint': 'Show the net effect of changes made by the last editor', 'cur': 'cur', 'diffCur': 'diffCur', 'Show changes since revision %s': 'Show changes since revision %s', '%s old': '%s old', // as in 4 weeks old 'oldEdit': 'oldEdit', 'purge': 'purge', 'purgeHint': 'Demand a fresh copy of %s', 'raw': 'source', 'rawHint': 'Download the source of %s', 'render': 'simple', 'renderHint': 'Show a plain HTML version of %s', 'Show the edit made to get revision': 'Show the edit made to get revision', 'sinceMe': 'sinceMe', 'changes since mine': 'diff my edit', 'sinceMeHint': 'Show changes since my last edit', 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s': 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s', 'eds': 'eds', 'editors': 'editors', 'editorListHint': 'List the users who have edited %s', 'related': 'related', 'relatedChanges': 'relatedChanges', 'related changes': 'related changes', 'RecentchangeslinkedHint': 'Show changes in articles related to %s', 'editOld': 'editOld', ///// edit old version, or revert 'rv': 'rv', 'revert': 'revert', 'revertHint': 'Revert to %s', 'defaultpopupReviewedSummary': 'Accepted by reviewing the [[Special:diff/%s/%s|difference]] between this version and previously accepted version using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRedlinkSummary': 'Removing link to empty page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupFixDabsSummary': 'Disambiguate [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupFixRedirsSummary': 'Redirect bypass from [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupExtendedRevertSummary': 'Revert to revision dated %s by %s, oldid %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRevertToPreviousSummary': 'Revert to the revision prior to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRevertSummary': 'Revert to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupQueriedRevertToPreviousSummary': 'Revert to the revision prior to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupQueriedRevertSummary': 'Revert to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRmDabLinkSummary': 'Remove link to dab page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'Redirects': 'Redirects', // as in Redirects to ... ' to ': ' to ', // as in Redirects to ... 'Bypass redirect': 'Bypass redirect', 'Fix this redirect': 'Fix this redirect', 'disambig': 'disambig', ///// add or remove dab etc. 'disambigHint': 'Disambiguate this link to [[%s]]', 'Click to disambiguate this link to:': 'Click to disambiguate this link to:', 'remove this link': 'remove this link', 'remove all links to this page from this article': 'remove all links to this page from this article', 'remove all links to this disambig page from this article': 'remove all links to this disambig page from this article', 'mainlink': 'mainlink', ///// links, watch, unwatch 'wikiLink': 'wikiLink', 'wikiLinks': 'wikiLinks', 'links here': 'links here', 'whatLinksHere': 'whatLinksHere', 'what links here': 'what links here', 'WhatlinkshereHint': 'List the pages that are hyperlinked to %s', 'unwatchShort': 'un', 'watchThingy': 'watch', // called watchThingy because {}.watch is a function 'watchHint': 'Add %s to my watchlist', 'unwatchHint': 'Remove %s from my watchlist', 'Only found one editor: %s made %s edits': 'Only found one editor: %s made %s edits', '%s seems to be the last editor to the page %s': '%s seems to be the last editor to the page %s', 'rss': 'rss', ///////////////////////////////////// // diff previews ///////////////////////////////////// 'Diff truncated for performance reasons': 'Diff truncated for performance reasons', 'Old revision': 'Old revision', 'New revision': 'New revision', 'Something went wrong :-(': 'Something went wrong :-(', 'Empty revision, maybe non-existent': 'Empty revision, maybe non-existent', 'Unknown date': 'Unknown date', ///////////////////////////////////// // other special previews ///////////////////////////////////// 'Empty category': 'Empty category', 'Category members (%s shown)': 'Category members (%s shown)', 'No image links found': 'No image links found', 'File links': 'File links', 'No image found': 'No image found', 'Image from Commons': 'Image from Commons', 'Description page': 'Description page', 'Alt text:': 'Alt text:', 'revdel':'Hidden revision', ///////////////////////////////////// // user-related actions and info ///////////////////////////////////// 'user': 'user', ///// user page, talk, email, space 'user&nbsp;page': 'user&nbsp;page', 'user talk': 'user talk', 'edit user talk': 'edit user talk', 'leave comment': 'leave comment', 'email': 'email', 'email user': 'email user', 'EmailuserHint': 'Send an email to %s', 'space': 'space', // short form for userSpace link 'PrefixIndexHint': 'Show pages in the userspace of %s', 'count': 'count', ///// contributions, log 'edit counter': 'edit counter', 'editCounterLinkHint': 'Count the contributions made by %s', 'contribs': 'contribs', 'contributions': 'contributions', 'deletedContribs': 'deleted contributions', 'DeletedcontributionsHint': 'List deleted edits made by %s', 'ContributionsHint': 'List the contributions made by %s', 'log': 'log', 'user log': 'user log', 'userLogHint': 'Show %s\'s user log', 'arin': 'ARIN lookup', ///// ARIN lookup, block user or IP 'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database', 'unblockShort': 'un', 'block': 'block', 'block user': 'block user', 'IpblocklistHint': 'Unblock %s', 'BlockipHint': 'Prevent %s from editing', 'block log': 'block log', 'blockLogHint': 'Show the block log for %s', 'protectLogHint': 'Show the protection log for %s', 'pageLogHint': 'Show the page log for %s', 'deleteLogHint': 'Show the deletion log for %s', 'Invalid %s %s': 'The option %s is invalid: %s', 'No backlinks found': 'No backlinks found', ' and more': ' and more', 'undo': 'undo', 'undoHint': 'undo this edit', 'Download preview data': 'Download preview data', 'Invalid or IP user': 'Invalid or IP user', 'Not a registered username': 'Not a registered username', 'CẤM': 'CẤM', 'Has blocks': 'Has blocks', ' sửa đổi từ: ': ' sửa đổi từ: ', 'sửa đổi lần cuối vào ': 'sửa đổi lần cuối vào ', ///////////////////////////////////// // Autoediting ///////////////////////////////////// 'Enter a non-empty edit summary or press cancel to abort': 'Enter a non-empty edit summary or press cancel to abort', 'Failed to get revision information, please edit manually.\n\n': 'Failed to get revision information, please edit manually.\n\n', 'The %s button has been automatically clicked. Please wait for the next page to load.': 'The %s button has been automatically clicked. Please wait for the next page to load.', 'Could not find button %s. Please check the settings in your javascript file.': 'Could not find button %s. Please check the settings in your javascript file.', ///////////////////////////////////// // Popups setup ///////////////////////////////////// 'Open full-size image': 'Open full-size image', 'zxy': 'zxy', 'autoedit_version': 'np20140416' }; function popupString(str) { if (typeof popupStrings != 'undefined' && popupStrings && popupStrings[str]) { return popupStrings[str]; } if (pg.string[str]) { return pg.string[str]; } return str; } function tprintf(str,subs) { if (typeof subs != typeof []) { subs = [subs]; } return simplePrintf(popupString(str), subs); } //</NOLITE> // ENDFILE: strings.js // STARTFILE: run.js //////////////////////////////////////////////////////////////////// // Run things //////////////////////////////////////////////////////////////////// // For some reason popups requires a fully loaded page jQuery.ready(...) causes problems for some. // The old addOnloadHook did something similar to the below if (document.readyState=="complete") autoEdit(); //will setup popups else $( window ).on( 'load', autoEdit ); // Support for MediaWiki's live preview, VisualEditor's saves and Echo's flyout. ( function () { var once = true; function dynamicContentHandler( $content ) { // Try to detect the hook fired on initial page load and disregard // it, we already hook to onload (possibly to different parts of // page - it's configurable) and running twice might be bad. Ugly… if ( $content.attr( 'id' ) == 'mw-content-text' ) { if ( once ) { once = false; return; } } function registerHooksForVisibleNavpops () { for (var i=0; pg.current.links && i<pg.current.links.length; ++i) { var navpop = pg.current.links[i].navpopup; if (!navpop || !navpop.isVisible()) { continue; } Navpopup.tracker.addHook(posCheckerHook(navpop)); } } function doIt () { registerHooksForVisibleNavpops(); $content.each( function () { this.ranSetupTooltipsAlready = false; setupTooltips( this ); } ); } setupPopups( doIt ); } // This hook is also fired after page load. mw.hook( 'wikipage.content' ).add( dynamicContentHandler ); mw.hook( 'ext.echo.overlay.beforeShowingOverlay' ).add( function($overlay){ dynamicContentHandler( $overlay.find(".mw-echo-state") ); }); } )(); // Forked from User:Connel MacKenzie/mess-with-popups.js Previewmaker.prototype.firstBit = function() { try{ if (mw.config.get("wgNamespaceNumber") !== 0){ // dont't be givin' me no subsequent paragraphs, you hear me? /// first we "normalize" section headings, removing whitespace after, adding before var d = this.data; if (getValueOf('popupPreviewCutHeadings')) { this.data = this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 '); /// then we want to get rid of paragraph breaks whose text ends badly this.data = this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n'); this.data = this.data.replace(RegExp('^[\\s\\n]*'), ''); stuff = (RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data); if (stuff) { d = stuff[0]; } if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; } /// now put \n\n after sections so that bullets and numbered lists work d = d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n'); } // superfluous sentences are RIGHT OUT. // note: exactly 1 set of parens here needed to make the slice work d = d.parenSplit(RegExp('([!?.]+["' + "'" + ']*\\s)', 'g')); // leading space is bad, mmkay? d[0] = d[0].replace(RegExp('^\\s*'), ''); var notSentenceEnds = RegExp('([^.][a-z][.][a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|\\[[^\\]]*|\\s[A-Zvclm])$', 'i'); d = this.fixSentenceEnds(d, notSentenceEnds); var maxChars=getValueOf('popupMaxPreviewCharacters'); var n = getValueOf('popupMaxPreviewSentences'); var dd; do { dd = this.firstSentences(d, n); --n; } while (dd.length > maxChars && n > 0); this.data = dd; } else { //TODO: Limit this override to urls that start with http://*.wiktionary.org/ //TODO: extend the above section to also preview Wikipedia links var charsWikt = 0; var linesWikt = this.data.split('\n'); for (jWikt = 0; jWikt < linesWikt.length; jWikt++) { linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{[in]transitive}}/g, ''); linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{[un]countable}}/g, ''); linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{cattag\|(.*?)}}/g, ""); linesWikt[jWikt] = linesWikt[jWikt].replace(/[\{\}]/g, "'"); if (linesWikt[jWikt].search(/^[#]/) == -1) { if (linesWikt[jWikt].search(/^==[A-Z]/) == -1) { linesWikt[jWikt] = ''; } } charsWikt += linesWikt[jWikt].length; } if (charsWikt < 2) { linesWikt[0] = "No properly formatted content"; } var maxChars = getValueOf('popupMaxPreviewCharacters'); var n = getValueOf('popupMaxPreviewSentences'); var dd = ''; for (jWikt = 0; jWikt < linesWikt.length; jWikt++) { if (linesWikt[jWikt] !== '') { dd += linesWikt[jWikt] + '\n'; } if (dd.length < maxChars) { this.data = dd; } } } } catch (e) { console.log("error from mess with popups: ", e); } }; }); hcl6pbrsz9x6mz7w8z8unth5ejrnzb8 2513983 2513982 2026-08-14T08:14:47Z Hiyuune 50834 2513983 javascript text/javascript // {{documentation}} // _________________________________________________________________________________________ // | | // | === WARNING: GLOBAL GADGET FILE === | // | Changes to this page affect many users. | // | Please discuss changes on the [[Wiktionary:Grease Pit]] before editing. | // |_________________________________________________________________________________________| mw.loader.load('//en.wikipedia.org/w/index.php?action=raw&title=MediaWiki:Gadget-navpop.css&ctype=text/css', "text/css"); // Forked from [[:w:MediaWiki:Gadget-popups.js]] $(function () { ////////////////////////////////////////////////// // Globals // // Trying to shove as many of these as possible into the pg (popup globals) object var pg = { re: {}, // regexps ns: {}, // namespaces string: {}, // translatable strings wiki: {}, // local site info user: {}, // current user info misc: {}, // YUCK PHOOEY option: {}, // options, see newOption etc optionDefault: {}, // default option values flag: {}, // misc flags cache: {}, // page and image cache structures: {}, // navlink structures timer: {}, // all sorts of timers (too damn many) counter: {}, // .. and all sorts of counters current: {}, // state info fn: {}, // functions endoflist: null }; /* Bail if the gadget/script is being loaded twice */ if( window.pg ) { return; } /* Export to global context */ window.pg = pg; /// Local Variables: /// /// mode:c /// /// End: /// // ENDFILE: main.js // STARTFILE: actions.js function setupTooltips(container, remove, force, popData) { log('setupTooltips, container='+container+', remove='+remove); if (!container) { //<NOLITE> // the main initial call if (getValueOf('popupOnEditSelection') && document && document.editform && document.editform.wpTextbox1) { document.editform.wpTextbox1.onmouseup=doSelectionPopup; } //</NOLITE> // article/content is a structure-dependent thing container = defaultPopupsContainer(); } if (!remove && !force && container.ranSetupTooltipsAlready) { return; } container.ranSetupTooltipsAlready = !remove; var anchors; anchors=container.getElementsByTagName('A'); setupTooltipsLoop(anchors, 0, 250, 100, remove, popData); } function defaultPopupsContainer() { if (getValueOf('popupOnlyArticleLinks')) { return document.getElementById('mw_content') || document.getElementById('content') || document.getElementById('article') || document; } return document; } function setupTooltipsLoop(anchors,begin,howmany,sleep, remove, popData) { log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments)); var finish=begin+howmany; var loopend = Math.min(finish, anchors.length); var j=loopend - begin; log ('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin + ', howmany=' + howmany + ', loopend=' + loopend + ', remove=' + remove); var doTooltip= remove ? removeTooltip : addTooltip; // try a faster (?) loop construct if (j > 0) { do { var a=anchors[loopend - j]; if (typeof a==='undefined' || !a || !a.href) { log('got null anchor at index ' + loopend - j); continue; } doTooltip(a, popData); } while (--j); } if (finish < anchors.length) { setTimeout(function() { setupTooltipsLoop(anchors,finish,howmany,sleep,remove,popData);}, sleep); } else { if ( !remove && ! getValueOf('popupTocLinks')) { rmTocTooltips(); } pg.flag.finishedLoading=true; } } // eliminate popups from the TOC // This also kills any onclick stuff that used to be going on in the toc function rmTocTooltips() { var toc=document.getElementById('toc'); if (toc) { var tocLinks=toc.getElementsByTagName('A'); var tocLen = tocLinks.length; for (var j=0; j<tocLen; ++j) { removeTooltip(tocLinks[j], true); } } } function addTooltip(a, popData) { if ( !isPopupLink(a) ) { return; } a.onmouseover=mouseOverWikiLink; a.onmouseout= mouseOutWikiLink; a.onmousedown = killPopup; a.hasPopup = true; a.popData = popData; } function removeTooltip(a) { if ( !a.hasPopup ) { return; } a.onmouseover = null; a.onmouseout = null; if (a.originalTitle) { a.title = a.originalTitle; } a.hasPopup=false; } function removeTitle(a) { if (!a.originalTitle) { a.originalTitle=a.title; } a.title=''; } function restoreTitle(a) { if ( a.title || !a.originalTitle ) { return; } a.title = a.originalTitle; } function registerHooks(np) { var popupMaxWidth=getValueOf('popupMaxWidth'); if (typeof popupMaxWidth === 'number') { var setMaxWidth = function () { np.mainDiv.style.maxWidth = popupMaxWidth + 'px'; np.maxWidth = popupMaxWidth; }; np.addHook(setMaxWidth, 'unhide', 'before'); } //<NOLITE> np.addHook(addPopupShortcuts, 'unhide', 'after'); np.addHook(rmPopupShortcuts, 'hide', 'before'); //</NOLITE> } function removeModifierKeyHandler(a) { //remove listeners for modifier key if any that were added in mouseOverWikiLink document.removeEventListener('keydown', a.modifierKeyHandler, false); document.removeEventListener('keyup', a.modifierKeyHandler, false); } function mouseOverWikiLink(evt) { if (!evt && window.event) {evt=window.event;} // if the modifier is needed, listen for it, // we will remove the listener when we mouseout of this link or kill popup. if (getValueOf('popupModifier')) { // if popupModifierAction = enable, we should popup when the modifier is pressed // if popupModifierAction = disable, we should popup unless the modifier is pressed var action = getValueOf('popupModifierAction'); var key = action=='disable' ? 'keyup' : 'keydown'; var a = this; a.modifierKeyHandler = function(evt) { mouseOverWikiLink2(a, evt); }; document.addEventListener(key, a.modifierKeyHandler, false); } return mouseOverWikiLink2(this, evt); } /** * Gets the references list item that the provided footnote link targets. This * is typically a li element within the ol.references element inside the reflist. * @param {Element} a - A footnote link. * @returns {Element|boolean} The targeted element, or false if one can't be found. */ function footnoteTarget(a) { var aTitle=Title.fromAnchor(a); // We want ".3A" rather than "%3A" or "?" here, so use the anchor property directly var anch = aTitle.anchor; if ( ! /^(cite_note-|_note-|endnote)/.test(anch) ) { return false; } var lTitle=Title.fromURL(location.href); if ( lTitle.toString(true) !== aTitle.toString(true) ) { return false; } var el=document.getElementById(anch); while ( el && typeof el.nodeName === 'string') { var nt = el.nodeName.toLowerCase(); if ( nt === 'li' ) { return el; } else if ( nt === 'body' ) { return false; } else if ( el.parentNode ) { el=el.parentNode; } else { return false; } } return false; } function footnotePreview(x, navpop) { setPopupHTML('<hr />' + x.innerHTML, 'popupPreview', navpop.idNumber); } function modifierPressed(evt) { var mod=getValueOf('popupModifier'); if (!mod) { return false; } if (!evt && window.event) {evt=window.event;} return ( evt && mod && evt[mod.toLowerCase() + 'Key'] ); } // Checks if the correct modifier pressed/unpressed if needed function isCorrectModifier(a,evt) { if (!getValueOf('popupModifier')) { return true; } // if popupModifierAction = enable, we should popup when the modifier is pressed // if popupModifierAction = disable, we should popup unless the modifier is pressed var action = getValueOf('popupModifierAction'); return ( action == 'enable' && modifierPressed(evt) || action == 'disable' && !modifierPressed(evt) ); } function mouseOverWikiLink2(a, evt) { if (!isCorrectModifier(a,evt)) { return; } if ( getValueOf('removeTitles') ) { removeTitle(a); } if ( a==pg.current.link && a.navpopup && a.navpopup.isVisible() ) { return; } pg.current.link=a; if (getValueOf('simplePopups') && !pg.option.popupStructure) { // reset *default value* of popupStructure setDefault('popupStructure', 'original'); } var article=(new Title()).fromAnchor(a); // set global variable (ugh) to hold article (wikipage) pg.current.article = article; if (!a.navpopup) { a.navpopup=newNavpopup(a, article); pg.current.linksHash[a.href] = a.navpopup; pg.current.links.push(a); } if (a.navpopup.pending === null || a.navpopup.pending !== 0) { // either fresh popups or those with unfinshed business are redone from scratch simplePopupContent(a, article); } a.navpopup.showSoonIfStable(a.navpopup.delay); clearInterval(pg.timer.checkPopupPosition); pg.timer.checkPopupPosition=setInterval(checkPopupPosition, 600); if(getValueOf('simplePopups')) { if (getValueOf('popupPreviewButton') && !a.simpleNoMore) { var d=document.createElement('div'); d.className='popupPreviewButtonDiv'; var s=document.createElement('span'); d.appendChild(s); s.className='popupPreviewButton'; s['on' + getValueOf('popupPreviewButtonEvent')] = function() { a.simpleNoMore=true; d.style.display = "none"; nonsimplePopupContent(a,article); }; s.innerHTML=popupString('show preview'); setPopupHTML(d, 'popupPreview', a.navpopup.idNumber); } } if (a.navpopup.pending !== 0 ) { nonsimplePopupContent(a, article); } } // simplePopupContent: the content that do not require additional download // (it is shown even when simplePopups is true) function simplePopupContent(a, article) { /* FIXME hack */ a.navpopup.hasPopupMenu=false; a.navpopup.setInnerHTML(popupHTML(a)); fillEmptySpans({navpopup:a.navpopup}); if (getValueOf('popupDraggable')) { var dragHandle = getValueOf('popupDragHandle') || null; if (dragHandle && dragHandle != 'all') { dragHandle += a.navpopup.idNumber; } setTimeout(function(){a.navpopup.makeDraggable(dragHandle);}, 150); } //<NOLITE> if (getValueOf('popupRedlinkRemoval') && a.className=='new') { setPopupHTML('<br>'+popupRedlinkHTML(article), 'popupRedlink', a.navpopup.idNumber); } //</NOLITE> } function debugData(navpopup) { if(getValueOf('popupDebugging') && navpopup.idNumber) { setPopupHTML('idNumber='+navpopup.idNumber + ', pending=' + navpopup.pending, 'popupError', navpopup.idNumber); } } function newNavpopup(a, article) { var navpopup = new Navpopup(); navpopup.fuzz=5; navpopup.delay=getValueOf('popupDelay')*1000; // increment global counter now navpopup.idNumber = ++pg.idNumber; navpopup.parentAnchor = a; navpopup.parentPopup = (a.popData && a.popData.owner); navpopup.article = article; registerHooks(navpopup); return navpopup; } // Should we show nonsimple context? // If simplePopups is set to true, then we do not show nonsimple context, // but if a bottom "show preview" was clicked we do show nonsimple context function shouldShowNonSimple(a) { return !getValueOf('simplePopups') || a.simpleNoMore; } // Should we show nonsimple context govern by the option (e.g. popupUserInfo)? // If the user explicitly asked for nonsimple context by setting the option to true, // then we show it even in nonsimple mode. function shouldShow(a,option) { if (shouldShowNonSimple(a)) { return getValueOf(option); } else { return (typeof window[option] != 'undefined' ) && window[option]; } } function nonsimplePopupContent(a, article) { var diff=null, history=null; var params=parseParams(a.href); var oldid=(typeof params.oldid=='undefined' ? null : params.oldid); //<NOLITE> if(shouldShow(a,'popupPreviewDiffs')) { diff=params.diff; } if(shouldShow(a,'popupPreviewHistory')) { history=(params.action=='history'); } //</NOLITE> a.navpopup.pending=0; var referenceElement = footnoteTarget(a); if (referenceElement) { footnotePreview(referenceElement, a.navpopup); //<NOLITE> } else if ( diff || diff === 0 ) { loadDiff(article, oldid, diff, a.navpopup); } else if ( history ) { loadAPIPreview('history', article, a.navpopup); } else if ( shouldShowNonSimple(a) && pg.re.contribs.test(a.href) ) { loadAPIPreview('contribs', article, a.navpopup); } else if ( shouldShowNonSimple(a) && pg.re.backlinks.test(a.href) ) { loadAPIPreview('backlinks', article, a.navpopup); } else if ( // FIXME should be able to get all preview combinations with options article.namespaceId()==pg.nsImageId && ( shouldShow(a,'imagePopupsForImages') || ! anchorContainsImage(a) ) ) { loadAPIPreview('imagepagepreview', article, a.navpopup); loadImage(article, a.navpopup); //</NOLITE> } else { if (article.namespaceId() == pg.nsCategoryId && shouldShow(a,'popupCategoryMembers')) { loadAPIPreview('category', article, a.navpopup); } else if ((article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) && shouldShow(a,'popupUserInfo')) { loadAPIPreview('userinfo', article, a.navpopup); } if (shouldShowNonSimple(a)) startArticlePreview(article, oldid, a.navpopup); } } function pendingNavpopTask(navpop) { if (navpop && navpop.pending === null) { navpop.pending=0; } ++navpop.pending; debugData(navpop); } function completedNavpopTask(navpop) { if (navpop && navpop.pending) { --navpop.pending; } debugData(navpop); } function startArticlePreview(article, oldid, navpop) { navpop.redir=0; loadPreview(article, oldid, navpop); } function loadPreview(article, oldid, navpop) { if (!navpop.redir) { navpop.originalArticle=article; } article.oldid = oldid; loadAPIPreview('revision', article, navpop); } function loadPreviewFromRedir(redirMatch, navpop) { // redirMatch is a regex match var target = new Title().fromWikiText(redirMatch[2]); // overwrite (or add) anchor from original target // mediawiki does overwrite; eg User:Lupin/foo3#Done if ( navpop.article.anchor ) { target.anchor = navpop.article.anchor; } navpop.redir++; navpop.redirTarget=target; //<NOLITE> var warnRedir = redirLink(target, navpop.article); setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber); //</NOLITE> navpop.article=target; fillEmptySpans({redir: true, redirTarget: target, navpopup:navpop}); return loadPreview(target, null, navpop); } function insertPreview(download) { if (!download.owner) { return; } var redirMatch = pg.re.redirect.exec(download.data); if (download.owner.redir === 0 && redirMatch) { loadPreviewFromRedir(redirMatch, download.owner); return; } if (download.owner.visible || !getValueOf('popupLazyPreviews')) { insertPreviewNow(download); } else { var id=(download.owner.redir) ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK'; download.owner.addHook( function(){insertPreviewNow(download); return true;}, 'unhide', 'after', id ); } } function insertPreviewNow(download) { if (!download.owner) { return; } var wikiText=download.data; var navpop=download.owner; var art=navpop.redirTarget || navpop.originalArticle; //<NOLITE> makeFixDabs(wikiText, navpop); if (getValueOf('popupSummaryData')) { getPageInfo(wikiText, download); setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber); } var imagePage=''; if (art.namespaceId()==pg.nsImageId) { imagePage=art.toString(); } else { imagePage=getValidImageFromWikiText(wikiText); } if(imagePage) { loadImage(Title.fromWikiText(imagePage), navpop); } //</NOLITE> if (getValueOf('popupPreviews')) { insertArticlePreview(download, art, navpop); } } function insertArticlePreview(download, art, navpop) { if (download && typeof download.data == typeof ''){ if (art.namespaceId()==pg.nsTemplateId && getValueOf('popupPreviewRawTemplates')) { // FIXME compare/consolidate with diff escaping code for wikitext var h='<hr /><span style="font-family: monospace;">' + download.data.entify().split('\\n').join('<br />\\n') + '</span>'; setPopupHTML(h, 'popupPreview', navpop.idNumber); } else { var p=prepPreviewmaker(download.data, art, navpop); p.showPreview(); } } } function prepPreviewmaker(data, article, navpop) { // deal with tricksy anchors var d=anchorize(data, article.anchorString()); var urlBase=joinPath([pg.wiki.articlebase, article.urlString()]); var p=new Previewmaker(d, urlBase, navpop); return p; } // Try to imitate the way mediawiki generates HTML anchors from section titles function anchorize(d, anch) { if (!anch) { return d; } var anchRe=RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*'+getValueOf('popupAnchorRegexp')+'\\s*(?:\\|[^|}]*)*?\\s*'+literalizeRegex(anch)+'\\s*(?:\\|[^}]*)?}})'); var match=d.match(anchRe); if(match && match.length > 0 && match[0]) { return d.substring(d.indexOf(match[0])); } // now try to deal with == foo [[bar|baz]] boom == -> #foo_baz_boom var lines=d.split('\n'); for (var i=0; i<lines.length; ++i) { lines[i]=lines[i].replace(RegExp('[[]{2}([^|\\]]*?[|])?(.*?)[\\]]{2}', 'g'), '$2') .replace(/'''([^'])/g, '$1').replace(RegExp("''([^'])", 'g'), '$1'); if (lines[i].match(anchRe)) { return d.split('\n').slice(i).join('\n').replace(RegExp('^[^=]*'), ''); } } return d; } function killPopup() { removeModifierKeyHandler(this); if (getValueOf('popupShortcutKeys')) { rmPopupShortcuts(); } if (!pg) { return; } if (pg.current.link && pg.current.link.navpopup) { pg.current.link.navpopup.banish(); } pg.current.link=null; abortAllDownloads(); if (pg.timer.checkPopupPosition) { clearInterval(pg.timer.checkPopupPosition); pg.timer.checkPopupPosition=null; } return true; // preserve default action } // ENDFILE: actions.js // STARTFILE: domdrag.js /** @fileoverview The {@link Drag} object, which enables objects to be dragged around. <pre> ************************************************* dom-drag.js 09.25.2001 www.youngpup.net ************************************************** 10.28.2001 - fixed minor bug where events sometimes fired off the handle, not the root. ************************************************* Pared down, some hooks added by User:Lupin Copyright Aaron Boodman. Saying stupid things daily since March 2001. </pre> */ /** Creates a new Drag object. This is used to make various DOM elements draggable. @constructor */ function Drag () { /** Condition to determine whether or not to drag. This function should take one parameter, an Event. To disable this, set it to <code>null</code>. @type Function */ this.startCondition = null; /** Hook to be run when the drag finishes. This is passed the final coordinates of the dragged object (two integers, x and y). To disables this, set it to <code>null</code>. @type Function */ this.endHook = null; } /** Gets an event in a cross-browser manner. @param {Event} e @private */ Drag.prototype.fixE = function(e) { if (typeof e == 'undefined') { e = window.event; } if (typeof e.layerX == 'undefined') { e.layerX = e.offsetX; } if (typeof e.layerY == 'undefined') { e.layerY = e.offsetY; } return e; }; /** Initialises the Drag instance by telling it which object you want to be draggable, and what you want to drag it by. @param {DOMElement} o The "handle" by which <code>oRoot</code> is dragged. @param {DOMElement} oRoot The object which moves when <code>o</code> is dragged, or <code>o</code> if omitted. */ Drag.prototype.init = function(o, oRoot) { var dragObj = this; this.obj = o; o.onmousedown = function(e) { dragObj.start.apply( dragObj, [e]); }; o.dragging = false; o.popups_draggable = true; o.hmode = true; o.vmode = true; o.root = oRoot ? oRoot : o ; if (isNaN(parseInt(o.root.style.left, 10))) { o.root.style.left = "0px"; } if (isNaN(parseInt(o.root.style.top, 10))) { o.root.style.top = "0px"; } o.root.onthisStart = function(){}; o.root.onthisEnd = function(){}; o.root.onthis = function(){}; }; /** Starts the drag. @private @param {Event} e */ Drag.prototype.start = function(e) { var o = this.obj; // = this; e = this.fixE(e); if (this.startCondition && !this.startCondition(e)) { return; } var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10); o.root.onthisStart(x, y); o.lastMouseX = e.clientX; o.lastMouseY = e.clientY; var dragObj = this; o.onmousemoveDefault = document.onmousemove; o.dragging = true; document.onmousemove = function(e) { dragObj.drag.apply( dragObj, [e] ); }; document.onmouseup = function(e) { dragObj.end.apply( dragObj, [e] ); }; return false; }; /** Does the drag. @param {Event} e @private */ Drag.prototype.drag = function(e) { e = this.fixE(e); var o = this.obj; var ey = e.clientY; var ex = e.clientX; var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10 ); var nx, ny; nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1)); ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1)); this.obj.root.style[o.hmode ? "left" : "right"] = nx + "px"; this.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px"; this.obj.lastMouseX = ex; this.obj.lastMouseY = ey; this.obj.root.onthis(nx, ny); return false; }; /** Ends the drag. @private */ Drag.prototype.end = function() { document.onmousemove=this.obj.onmousemoveDefault; document.onmouseup = null; this.obj.dragging = false; if (this.endHook) { this.endHook( parseInt(this.obj.root.style[this.obj.hmode ? "left" : "right"], 10), parseInt(this.obj.root.style[this.obj.vmode ? "top" : "bottom"], 10)); } }; // ENDFILE: domdrag.js // STARTFILE: structures.js //<NOLITE> pg.structures.original={}; pg.structures.original.popupLayout=function () { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupUserData', 'popupData', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; pg.structures.original.popupRedirSpans=function () { return ['popupRedir', 'popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks']; }; pg.structures.original.popupTitle=function (x) { log ('defaultstructure.popupTitle'); if (!getValueOf('popupNavLinks')) { return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params); } return ''; }; pg.structures.original.popupTopLinks=function (x) { log ('defaultstructure.popupTopLinks'); if (getValueOf('popupNavLinks')) { return navLinksHTML(x.article, x.hint, x.params); } return ''; }; pg.structures.original.popupImage=function(x) { log ('original.popupImage, x.article='+x.article+', x.navpop.idNumber='+x.navpop.idNumber); return imageHTML(x.article, x.navpop.idNumber); }; pg.structures.original.popupRedirTitle=pg.structures.original.popupTitle; pg.structures.original.popupRedirTopLinks=pg.structures.original.popupTopLinks; function copyStructure(oldStructure, newStructure) { pg.structures[newStructure]={}; for (var prop in pg.structures[oldStructure]) { pg.structures[newStructure][prop]=pg.structures[oldStructure][prop]; } } copyStructure('original', 'nostalgia'); pg.structures.nostalgia.popupTopLinks=function(x) { var str=''; str += '<b><<mainlink|shortcut= >></b>'; // user links // contribs - log - count - email - block // count only if applicable; block only if popupAdminLinks str += 'if(user){<br><<contribs|shortcut=c>>'; str+='if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}'; // editing links // talkpage -> edit|new - history - un|watch - article|edit // other page -> edit - history - un|watch - talk|edit|new var editstr='<<edit|shortcut=e>>'; var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'; var historystr='<<history|shortcut=h>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; str += '<br>if(talk){' + editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + // not a talk page editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}'; // misc links str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>'; str += 'if(admin){<br>}else{*}<<move|shortcut=m>>'; // admin links str += 'if(admin){*<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>}'; return navlinkStringToHTML(str, x.article, x.params); }; pg.structures.nostalgia.popupRedirTopLinks=pg.structures.nostalgia.popupTopLinks; /** -- fancy -- **/ copyStructure('original', 'fancy'); pg.structures.fancy.popupTitle=function (x) { return navlinkStringToHTML('<font size=+0><<mainlink>></font>',x.article,x.params); }; pg.structures.fancy.popupTopLinks=function(x) { var hist='<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>|<<editors|shortcut=E|eds>>'; var watch='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; var move='<<move|shortcut=m|move>>'; return navlinkStringToHTML('if(talk){' + '<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' + hist + '*' + '<<article|shortcut=a>>|<<editArticle|edit>>' + '*' + watch + '*' + move + '}else{<<edit|shortcut=e>>*' + hist + '*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' + '*' + watch + '*' + move+'}<br>', x.article, x.params); }; pg.structures.fancy.popupOtherLinks=function(x) { var admin='<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*<<undelete|undeleteShort>>|<<delete|shortcut=d|del>>'; var user='<<contribs|shortcut=c>>if(wikimedia){|<<count|shortcut=#|#>>}'; user+='if(ipuser){|<<arin>>}else{*<<email|shortcut=E|'+ popupString('email')+'>>}if(admin){*<<block|shortcut=b>>}'; var normal='<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>'; return navlinkStringToHTML('<br>if(user){' + user + '*}if(admin){'+admin+'if(user){<br>}else{*}}' + normal, x.article, x.params); }; pg.structures.fancy.popupRedirTitle=pg.structures.fancy.popupTitle; pg.structures.fancy.popupRedirTopLinks=pg.structures.fancy.popupTopLinks; pg.structures.fancy.popupRedirOtherLinks=pg.structures.fancy.popupOtherLinks; /** -- fancy2 -- **/ // hack for User:MacGyverMagic copyStructure('fancy', 'fancy2'); pg.structures.fancy2.popupTopLinks=function(x) { // hack out the <br> at the end and put one at the beginning return '<br>'+pg.structures.fancy.popupTopLinks(x).replace(RegExp('<br>$','i'),''); }; pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title return ['popupError', 'popupImage', 'popupTitle', 'popupUserData', 'popupData', 'popupTopLinks', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; /** -- menus -- **/ copyStructure('original', 'menus'); pg.structures.menus.popupLayout=function () { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupUserData', 'popupData', 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; pg.structures.menus.popupTopLinks = function (x, shorter) { // FIXME maybe this stuff should be cached var s=[]; var dropdiv='<div class="popup_drop">'; var enddiv='</div>'; var hist='<<history|shortcut=h>>'; if (!shorter) { hist = '<menurow>' + hist + '|<<historyfeed|rss>>|<<editors|shortcut=E>></menurow>'; } var lastedit='<<lastEdit|shortcut=/|show last edit>>'; var thank='if(diff){<<thank|send thanks>>}'; var jsHistory='<<lastContrib|last set of edits>><<sinceMe|changes since mine>>'; var linkshere='<<whatLinksHere|shortcut=l|what links here>>'; var related='<<relatedChanges|shortcut=r|related changes>>'; var search='<menurow><<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}' + '|<<google|shortcut=G|web>></menurow>'; var watch='<menurow><<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>></menurow>'; var protect='<menurow><<unprotect|unprotectShort>>|' + '<<protect|shortcut=p>>|<<protectlog|log>></menurow>'; var del='<menurow><<undelete|undeleteShort>>|<<delete|shortcut=d>>|' + '<<deletelog|log>></menurow>'; var move='<<move|shortcut=m|move page>>'; var nullPurge='<menurow><<nullEdit|shortcut=n|null edit>>|<<purge|shortcut=P>></menurow>'; var viewOptions='<menurow><<view|shortcut=v>>|<<render|shortcut=S>>|<<raw>></menurow>'; var editRow='if(oldid){' + '<menurow><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></menurow>' + '<menurow><<revert|shortcut=v>>|<<undo>></menurow>' + '}else{<<edit|shortcut=e>>}'; var markPatrolled='if(rcid){<<markpatrolled|mark patrolled>>}'; var newTopic='if(talk){<<new|shortcut=+|new topic>>}'; var protectDelete='if(admin){' + protect + del + '}'; if (getValueOf('popupActionsMenu')) { s.push( '<<mainlink>>*' + dropdiv + menuTitle('tác vụ')); } else { s.push( dropdiv + '<<mainlink>>'); } s.push( '<menu>'); s.push( editRow + markPatrolled + newTopic + hist + lastedit + thank ); if (!shorter) { s.push(jsHistory); } s.push( move + linkshere + related); if (!shorter) { s.push(nullPurge + search); } if (!shorter) { s.push(viewOptions); } s.push('<hr />' + watch + protectDelete); s.push('<hr />' + 'if(talk){<<article|shortcut=a|view article>><<editArticle|edit article>>}' + 'else{<<talk|shortcut=t|talk page>><<editTalk|edit talk>>' + '<<newTalk|shortcut=+|new topic>>}</menu>' + enddiv); // user menu starts here var email='<<email|shortcut=E|email user>>'; var contribs= 'if(wikimedia){<menurow>}<<contribs|shortcut=c|contributions>>if(wikimedia){</menurow>}' + 'if(admin){<menurow><<deletedContribs>></menurow>}'; s.push('if(user){*' + dropdiv + menuTitle('user')); s.push('<menu>'); s.push('<menurow><<userPage|shortcut=u|user&nbsp;page>>|<<userSpace|space>></menurow>'); s.push('<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' + '<<newUserTalk|shortcut=+|leave comment>>'); if(!shorter) { s.push( 'if(ipuser){<<arin>>}else{' + email + '}' ); } else { s.push( 'if(ipuser){}else{' + email + '}' ); } s.push('<hr />' + contribs + '<<userlog|shortcut=L|user log>>'); s.push('if(wikimedia){<<count|shortcut=#|edit counter>>}'); s.push('if(admin){<menurow><<unblock|unblockShort>>|<<block|shortcut=b|block user>></menurow>}'); s.push('<<blocklog|shortcut=B|block log>>'); s.push('</menu>' + enddiv + '}'); // popups menu starts here if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */) { x.navpop.hasPopupMenu=true; s.push('*' + dropdiv + menuTitle('popupsMenu') + '<menu>'); s.push('<<togglePreviews|toggle previews>>'); s.push('<<purgePopups|reset>>'); s.push('<<disablePopups|disable>>'); s.push('</menu>'+enddiv); } return navlinkStringToHTML(s.join(''), x.article, x.params); }; function menuTitle(s) { return '<a href="#" noPopup=1>' + popupString(s) + '</a>'; } pg.structures.menus.popupRedirTitle=pg.structures.menus.popupTitle; pg.structures.menus.popupRedirTopLinks=pg.structures.menus.popupTopLinks; copyStructure('menus', 'shortmenus'); pg.structures.shortmenus.popupTopLinks=function(x) { return pg.structures.menus.popupTopLinks(x,true); }; pg.structures.shortmenus.popupRedirTopLinks=pg.structures.shortmenus.popupTopLinks; //</NOLITE> pg.structures.lite={}; pg.structures.lite.popupLayout=function () { return ['popupTitle', 'popupPreview' ]; }; pg.structures.lite.popupTitle=function (x) { log (x.article + ': structures.lite.popupTitle'); //return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params); return '<div><span class="popup_mainlink"><b>' + x.article.toString() + '</b></span></div>'; }; // ENDFILE: structures.js // STARTFILE: autoedit.js //<NOLITE> function substitute(data,cmdBody) { // alert('sub\nfrom: '+cmdBody.from+'\nto: '+cmdBody.to+'\nflags: '+cmdBody.flags); var fromRe=RegExp(cmdBody.from, cmdBody.flags); return data.replace(fromRe, cmdBody.to); } function execCmds(data, cmdList) { for (var i=0; i<cmdList.length; ++i) { data=cmdList[i].action(data, cmdList[i]); } return data; } function parseCmd(str) { // returns a list of commands if (!str.length) { return []; } var p=false; switch (str.charAt(0)) { case 's': p=parseSubstitute(str); break; default: return false; } if (p) { return [p].concat(parseCmd(p.remainder)); } return false; } function unEscape(str, sep) { return str.split('\\\\').join('\\').split('\\'+sep).join(sep).split('\\n').join('\n'); } function parseSubstitute(str) { // takes a string like s/a/b/flags;othercmds and parses it var from,to,flags,tmp; if (str.length<4) { return false; } var sep=str.charAt(1); str=str.substring(2); tmp=skipOver(str,sep); if (tmp) { from=tmp.segment; str=tmp.remainder; } else { return false; } tmp=skipOver(str,sep); if (tmp) { to=tmp.segment; str=tmp.remainder; } else { return false; } flags=''; if (str.length) { tmp=skipOver(str,';') || skipToEnd(str, ';'); if (tmp) {flags=tmp.segment; str=tmp.remainder; } } return {action: substitute, from: from, to: to, flags: flags, remainder: str}; } function skipOver(str,sep) { var endSegment=findNext(str,sep); if (endSegment<0) { return false; } var segment=unEscape(str.substring(0,endSegment), sep); return {segment: segment, remainder: str.substring(endSegment+1)}; } /*eslint-disable*/ function skipToEnd(str,sep) { return {segment: str, remainder: ''}; } /*eslint-enable */ function findNext(str, ch) { for (var i=0; i<str.length; ++i) { if (str.charAt(i)=='\\') { i+=2; } if (str.charAt(i)==ch) { return i; } } return -1; } function setCheckbox(param, box) { var val=mw.util.getParamValue(param); if (val) { switch (val) { case '1': case 'yes': case 'true': box.checked=true; break; case '0': case 'no': case 'false': box.checked=false; } } } function autoEdit() { setupPopups( function () { if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version') ) { return false; } if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken')===autoClickToken()) { pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action')); } if (!document.editform) { return false; } if (autoEdit.alreadyRan) { return false; } autoEdit.alreadyRan=true; var cmdString=mw.util.getParamValue('autoedit'); if (cmdString) { try { var editbox=document.editform.wpTextbox1; var cmdList=parseCmd(cmdString); var input=editbox.value; var output=execCmds(input, cmdList); editbox.value=output; } catch (dang) { return; } // wikEd user script compatibility if (typeof(wikEdUseWikEd) != 'undefined') { if (wikEdUseWikEd === true) { WikEdUpdateFrame(); } } } setCheckbox('autominor', document.editform.wpMinoredit); setCheckbox('autowatch', document.editform.wpWatchthis); var rvid = mw.util.getParamValue('autorv'); if (rvid) { var url=pg.wiki.apiwikibase + '?action=query&format=json&formatversion=2&prop=revisions&revids='+rvid; startDownload(url, null, autoEdit2); } else { autoEdit2(); } } ); } function autoEdit2(d) { var summary=mw.util.getParamValue('autosummary'); var summaryprompt=mw.util.getParamValue('autosummaryprompt'); var summarynotice=''; if (d && d.data && mw.util.getParamValue('autorv')) { var s = getRvSummary(summary, d.data); if (s === false) { summaryprompt=true; summarynotice=popupString('Failed to get revision information, please edit manually.\n\n'); summary = simplePrintf(summary, [mw.util.getParamValue('autorv'), '(unknown)', '(unknown)']); } else { summary = s; } } if (summaryprompt) { var txt= summarynotice + popupString('Enter a non-empty edit summary or press cancel to abort'); var response=prompt(txt, summary); if (response) { summary=response; } else { return; } } if (summary) { document.editform.wpSummary.value=summary; } // Attempt to avoid possible premature clicking of the save button // (maybe delays in updates to the DOM are to blame?? or a red herring) setTimeout(autoEdit3, 100); } function autoClickToken() { return mw.user.sessionId(); } function autoEdit3() { if( mw.util.getParamValue('actoken') != autoClickToken()) { return; } var btn=mw.util.getParamValue('autoclick'); if (btn) { if (document.editform && document.editform[btn]) { var button=document.editform[btn]; var msg=tprintf('The %s button has been automatically clicked. Please wait for the next page to load.', [ button.value ]); bannerMessage(msg); document.title='('+document.title+')'; button.click(); } else { alert(tprintf('Could not find button %s. Please check the settings in your javascript file.', [ btn ])); } } } function bannerMessage(s) { var headings=document.getElementsByTagName('h1'); if (headings) { var div=document.createElement('div'); div.innerHTML='<font size=+1><b>' + s + '</b></font>'; headings[0].parentNode.insertBefore(div, headings[0]); } } function getRvSummary(template, json) { try { var o=getJsObj(json); var edit = anyChild(o.query.pages).revisions[0]; var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, ''); return simplePrintf(template, [edit.revid, timestamp, edit.userhidden ? '(hidden)' : edit.user ]); } catch (badness) { return false; } } //</NOLITE> // ENDFILE: autoedit.js // STARTFILE: downloader.js /** @fileoverview {@link Downloader}, a xmlhttprequest wrapper, and helper functions. */ /** Creates a new Downloader @constructor @class The Downloader class. Create a new instance of this class to download stuff. @param {String} url The url to download. This can be omitted and supplied later. */ function Downloader(url) { if (typeof XMLHttpRequest!='undefined') { this.http = new XMLHttpRequest(); } /** The url to download @type String */ this.url = url; /** A universally unique ID number @type integer */ this.id=null; /** Modification date, to be culled from the incoming headers @type Date @private */ this.lastModified = null; /** What to do when the download completes successfully @type Function @private */ this.callbackFunction = null; /** What to do on failure @type Function @private */ this.onFailure = null; /** Flag set on <code>abort</code> @type boolean */ this.aborted = false; /** HTTP method. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for possibilities. @type String */ this.method='GET'; /** Async flag. @type boolean */ this.async=true; } new Downloader(); /** Submits the http request. */ Downloader.prototype.send = function (x) { if (!this.http) { return null; } return this.http.send(x); }; /** Aborts the download, setting the <code>aborted</code> field to true. */ Downloader.prototype.abort = function () { if (!this.http) { return null; } this.aborted=true; return this.http.abort(); }; /** Returns the downloaded data. */ Downloader.prototype.getData = function () {if (!this.http) { return null; } return this.http.responseText;}; /** Prepares the download. */ Downloader.prototype.setTarget = function () { if (!this.http) { return null; } this.http.open(this.method, this.url, this.async); this.http.setRequestHeader( 'Api-User-Agent', pg.misc.userAgent ); }; /** Gets the state of the download. */ Downloader.prototype.getReadyState=function () {if (!this.http) { return null; } return this.http.readyState;}; pg.misc.downloadsInProgress = { }; /** Starts the download. Note that setTarget {@link Downloader#setTarget} must be run first */ Downloader.prototype.start=function () { if (!this.http) { return; } pg.misc.downloadsInProgress[this.id] = this; this.http.send(null); }; /** Gets the 'Last-Modified' date from the download headers. Should be run after the download completes. Returns <code>null</code> on failure. @return {Date} */ Downloader.prototype.getLastModifiedDate=function () { if(!this.http) { return null; } var lastmod=null; try { lastmod=this.http.getResponseHeader('Last-Modified'); } catch (err) {} if (lastmod) { return new Date(lastmod); } return null; }; /** Sets the callback function. @param {Function} f callback function, called as <code>f(this)</code> on success */ Downloader.prototype.setCallback = function (f) { if(!this.http) { return; } this.http.onreadystatechange = f; }; Downloader.prototype.getStatus = function() { if (!this.http) { return null; } return this.http.status; }; ////////////////////////////////////////////////// // helper functions /** Creates a new {@link Downloader} and prepares it for action. @param {String} url The url to download @param {integer} id The ID of the {@link Downloader} object @param {Function} callback The callback function invoked on success @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser */ function newDownload(url, id, callback, onfailure) { var d=new Downloader(url); if (!d.http) { return 'ohdear'; } d.id=id; d.setTarget(); if (!onfailure) { onfailure=2; } var f = function () { if (d.getReadyState() == 4) { delete pg.misc.downloadsInProgress[this.id]; try { if ( d.getStatus() == 200 ) { d.data=d.getData(); d.lastModified=d.getLastModifiedDate(); callback(d); } else if (typeof onfailure == typeof 1) { if (onfailure > 0) { // retry newDownload(url, id, callback, onfailure - 1); } } else if (typeof onfailure === 'function') { onfailure(d,url,id,callback); } } catch (somerr) { /* ignore it */ } } }; d.setCallback(f); return d; } /** Simulates a download from cached data. The supplied data is put into a {@link Downloader} as if it had downloaded it. @param {String} url The url. @param {integer} id The ID. @param {Function} callback The callback, which is invoked immediately as <code>callback(d)</code>, where <code>d</code> is the new {@link Downloader}. @param {String} data The (cached) data. @param {Date} lastModified The (cached) last modified date. */ function fakeDownload(url, id, callback, data, lastModified, owner) { var d=newDownload(url,callback); d.owner=owner; d.id=id; d.data=data; d.lastModified=lastModified; return callback(d); } /** Starts a download. @param {String} url The url to download @param {integer} id The ID of the {@link Downloader} object @param {Function} callback The callback function invoked on success @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser */ function startDownload(url, id, callback) { var d=newDownload(url, id, callback); if (typeof d == typeof '' ) { return d; } d.start(); return d; } /** Aborts all downloads which have been started. */ function abortAllDownloads() { for ( var x in pg.misc.downloadsInProgress ) { try { pg.misc.downloadsInProgress[x].aborted=true; pg.misc.downloadsInProgress[x].abort(); delete pg.misc.downloadsInProgress[x]; } catch (e) {} } } // ENDFILE: downloader.js // STARTFILE: livepreview.js // TODO: location is often not correct (eg relative links in previews) // NOTE: removed md5 and image and math parsing. was broken, lots of bytes. /** * InstaView - a Mediawiki to HTML converter in JavaScript * Version 0.6.1 * Copyright (C) Pedro Fayolle 2005-2006 * https://en.wikipedia.org/wiki/User:Pilaf * Distributed under the BSD license * * Changelog: * * 0.6.1 * - Fixed problem caused by \r characters * - Improved inline formatting parser * * 0.6 * - Changed name to InstaView * - Some major code reorganizations and factored out some common functions * - Handled conversion of relative links (i.e. [[/foo]]) * - Fixed misrendering of adjacent definition list items * - Fixed bug in table headings handling * - Changed date format in signatures to reflect Mediawiki's * - Fixed handling of [[:Image:...]] * - Updated MD5 function (hopefully it will work with UTF-8) * - Fixed bug in handling of links inside images * * To do: * - Better support for math tags * - Full support for <nowiki> * - Parser-based (as opposed to RegExp-based) inline wikicode handling (make it one-pass and bullet-proof) * - Support for templates (through AJAX) * - Support for coloured links (AJAX) */ var Insta = {}; function setupLivePreview() { // options Insta.conf = { baseUrl: '', user: {}, wiki: { lang: pg.wiki.lang, interwiki: pg.wiki.interwiki, default_thumb_width: 180 }, paths: { articles: pg.wiki.articlePath + '/', // Only used for Insta previews with images. (not in popups) math: '/math/', images: '//upload.wikimedia.org/wikipedia/en/', // FIXME getImageUrlStart(pg.wiki.hostname), images_fallback: '//upload.wikimedia.org/wikipedia/commons/', }, locale: { user: mw.config.get('wgFormattedNamespaces')[pg.nsUserId], image: mw.config.get('wgFormattedNamespaces')[pg.nsImageId], category: mw.config.get('wgFormattedNamespaces')[pg.nsCategoryId], // shouldn't be used in popup previews, i think months: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'] } }; // options with default values or backreferences Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian'; Insta.conf.user.signature = '[['+Insta.conf.locale.user+':'+Insta.conf.user.name+'|'+Insta.conf.user.name+']]'; //Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/'; // define constants Insta.BLOCK_IMAGE = new RegExp('^\\[\\[(?:File|Image|'+Insta.conf.locale.image+ '):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)', 'i'); } Insta.dump = function(from, to) { if (typeof from == 'string') { from = document.getElementById(from); } if (typeof to == 'string') { to = document.getElementById(to); } to.innerHTML = this.convert(from.value); }; Insta.convert = function(wiki) { var ll = (typeof wiki == 'string')? wiki.replace(/\r/g,'').split(/\n/): wiki, // lines of wikicode o = '', // output p = 0, // para flag $r; // result of passing a regexp to $() // some shorthands function remain() { return ll.length; } function sh() { return ll.shift(); } // shift function ps(s) { o += s; } // push // similar to C's printf, uses ? as placeholders, ?? to escape question marks function f() { var i=1, a=arguments, f=a[0], o='', c, p; for (; i<a.length; i++) { if ((p=f.indexOf('?'))+1) { // allow character escaping i -= c = f.charAt(p+1)=='?' ? 1 : 0; o += f.substring(0,p) + (c ? '?' : a[i]); f = f.substr(p+1+c); } else { break; } } return o+f; } function html_entities(s) { return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); } // Wiki text parsing to html is a nightmare. // The below functions deliberately don't escape the ampersand since this would make it more difficult, // and we don't absolutely need to for how we need it. // This means that any unescaped ampersands in wikitext will remain unescaped and can cause invalid HTML. // Browsers should all be able to handle it though. // We also escape significant wikimarkup characters to prevent further matching on the processed text function htmlescape_text(s) { return s.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/:/g,"&#58;").replace(/\[/g,"&#91;").replace(/]/g,"&#93;"); } function htmlescape_attr(s) { return htmlescape_text(s).replace(/'/g,"&#39;").replace(/"/g,"&quot;"); } // return the first non matching character position between two strings function str_imatch(a, b) { for (var i=0, l=Math.min(a.length, b.length); i<l; i++) { if (a.charAt(i)!=b.charAt(i)) { break; } } return i; } // compare current line against a string or regexp // if passed a string it will compare only the first string.length characters // if passed a regexp the result is stored in $r function $(c) { return (typeof c == 'string') ? (ll[0].substr(0,c.length)==c) : ($r = ll[0].match(c)); } function $$(c) { return ll[0]==c; } // compare current line against a string function _(p) { return ll[0].charAt(p); } // return char at pos p function endl(s) { ps(s); sh(); } function parse_list() { var prev=''; while (remain() && $(/^([*#:;]+)(.*)$/)) { var l_match = $r; sh(); var ipos = str_imatch(prev, l_match[1]); // close uncontinued lists for (var prevPos=prev.length-1; prevPos >= ipos; prevPos--) { var pi = prev.charAt(prevPos); if (pi=='*') { ps('</ul>'); } else if (pi=='#') { ps('</ol>'); } // close a dl only if the new item is not a dl item (:, ; or empty) else if($.inArray(l_match[1].charAt(prevPos), ['','*','#'])) { ps('</dl>'); } } // open new lists for (var matchPos=ipos; matchPos<l_match[1].length; matchPos++) { var li = l_match[1].charAt(matchPos); if (li=='*') { ps('<ul>'); } else if (li=='#') { ps('<ol>'); } // open a new dl only if the prev item is not a dl item (:, ; or empty) else if ($.inArray(prev.charAt(matchPos), ['','*','#'])) { ps('<dl>'); } } switch (l_match[1].charAt(l_match[1].length-1)) { case '*': case '#': ps('<li>' + parse_inline_nowiki(l_match[2])); break; case ';': ps('<dt>'); var dt_match = l_match[2].match(/(.*?)(:.*?)$/); // handle ;dt :dd format if (dt_match) { ps(parse_inline_nowiki(dt_match[1])); ll.unshift(dt_match[2]); } else ps(parse_inline_nowiki(l_match[2])); break; case ':': ps('<dd>' + parse_inline_nowiki(l_match[2])); } prev=l_match[1]; } // close remaining lists for (var i=prev.length-1; i>=0; i--) { ps(f('</?>', (prev.charAt(i)=='*')? 'ul': ((prev.charAt(i)=='#')? 'ol': 'dl'))); } } function parse_table() { endl(f('<table>', $(/^\{\|( .*)$/)? $r[1]: '')); for (;remain();) if ($('|')) switch (_(1)) { case '}': endl('</table>'); return; case '-': endl(f('<tr>', $(/\|-*(.*)/)[1])); break; default: parse_table_data(); } else if ($('!')) { parse_table_data(); } else { sh(); } } function parse_table_data() { var td_line, match_i; // 1: "|+", '|' or '+' // 2: ?? // 3: attributes ?? // TODO: finish commenting this regexp var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/); if (td_match[1] == '|+') ps('<caption'); else ps('<t' + ((td_match[1]=='|')?'d':'h')); if (typeof td_match[3] != 'undefined') { //ps(' ' + td_match[3]) match_i = 4; } else match_i = 2; ps('>'); if (td_match[1] != '|+') { // use || or !! as a cell separator depending on context // NOTE: when split() is passed a regexp make sure to use non-capturing brackets td_line = td_match[match_i].split((td_match[1] == '|')? '||': /(?:\|\||!!)/); ps(parse_inline_nowiki(td_line.shift())); while (td_line.length) ll.unshift(td_match[1] + td_line.pop()); } else ps(td_match[match_i]); var tc = 0, td = []; while (remain()) { td.push(sh()); if ($('|')) { if (!tc) break; // we're at the outer-most level (no nested tables), skip to td parse else if (_(1)=='}') tc--; } else if (!tc && $('!')) break; else if ($('{|')) tc++; } if (td.length) ps(Insta.convert(td)); } function parse_pre() { ps('<pre>'); do { endl(parse_inline_nowiki(ll[0].substring(1)) + "\n"); } while (remain() && $(' ')); ps('</pre>'); } function parse_block_image() { ps(parse_image(sh())); } function parse_image(str) { //<NOLITE> // get what's in between "[[Image:" and "]]" var tag = str.substring(str.indexOf(':') + 1, str.length - 2); /* eslint-disable no-unused-vars */ var width; var attr = [], filename, caption = ''; var thumb=0, frame=0, center=0; var align=''; /* eslint-enable no-unused-vars */ if (tag.match(/\|/)) { // manage nested links var nesting = 0; var last_attr; for (var i = tag.length-1; i > 0; i--) { if (tag.charAt(i) == '|' && !nesting) { last_attr = tag.substr(i+1); tag = tag.substring(0, i); break; } else switch (tag.substr(i-1, 2)) { case ']]': nesting++; i--; break; case '[[': nesting--; i--; } } attr = tag.split(/\s*\|\s*/); attr.push(last_attr); filename = attr.shift(); var w_match; for (;attr.length; attr.shift()) { w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/); if (w_match) width = w_match[1]; else switch(attr[0]) { case 'thumb': case 'thumbnail': thumb=true; frame=true; break; case 'frame': frame=true; break; case 'none': case 'right': case 'left': center=false; align=attr[0]; break; case 'center': center=true; align='none'; break; default: if (attr.length == 1) caption = attr[0]; } } } else filename = tag; return ''; //</NOLITE> } function parse_inline_nowiki(str) { var start, lastend=0; var substart=0, nestlev=0, open, close, subloop; var html=''; while (-1 != (start = str.indexOf('<nowiki>', substart))) { html += parse_inline_wiki(str.substring(lastend, start)); start += 8; substart = start; subloop = true; do { open = str.indexOf('<nowiki>', substart); close = str.indexOf('</nowiki>', substart); if (close<=open || open==-1) { if (close==-1) { return html + html_entities(str.substr(start)); } substart = close+9; if (nestlev) { nestlev--; } else { lastend = substart; html += html_entities(str.substring(start, lastend-9)); subloop = false; } } else { substart = open+8; nestlev++; } } while (subloop); } return html + parse_inline_wiki(str.substr(lastend)); } function parse_inline_images(str) { //<NOLITE> var start, substart=0, nestlev=0; var loop, close, open, wiki, html; while (-1 != (start=str.indexOf('[[', substart))) { if(str.substr(start+2).match(RegExp('^(Image|File|' + Insta.conf.locale.image + '):','i'))) { loop=true; substart=start; do { substart+=2; close=str.indexOf(']]',substart); open=str.indexOf('[[',substart); if (close<=open||open==-1) { if (close==-1) return str; substart=close; if (nestlev) { nestlev--; } else { wiki=str.substring(start,close+2); html=parse_image(wiki); str=str.replace(wiki,html); substart=start+html.length; loop=false; } } else { substart=open; nestlev++; } } while (loop); } else break; } //</NOLITE> return str; } // the output of this function doesn't respect the FILO structure of HTML // but since most browsers can handle it I'll save myself the hassle function parse_inline_formatting(str) { var em,st,i,li,o=''; while ((i=str.indexOf("''",li))+1) { o += str.substring(li,i); li=i+2; if (str.charAt(i+2)=="'") { li++; st=!st; o+=st?'<strong>':'</strong>'; } else { em=!em; o+=em?'<em>':'</em>'; } } return o+str.substr(li); } function parse_inline_wiki(str) { str = parse_inline_images(str); str = parse_inline_formatting(str); // math str = str.replace(/<(?:)math>(.*?)<\/math>/ig, ''); // Build a Mediawiki-formatted date string var date = new Date(); var minutes = date.getUTCMinutes(); if (minutes < 10) minutes = '0' + minutes; date = f("?:?, ? ? ? (UTC)", date.getUTCHours(), minutes, date.getUTCDate(), Insta.conf.locale.months[date.getUTCMonth()], date.getUTCFullYear()); // text formatting return str. // signatures replace(/~{5}(?!~)/g, date). replace(/~{4}(?!~)/g, Insta.conf.user.name+' '+date). replace(/~{3}(?!~)/g, Insta.conf.user.name). // [[:Category:...]], [[:Image:...]], etc... replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):[^|]*?)\\]\\](\\w*)','gi'), function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2));}). // remove straight category and interwiki tags replace(RegExp('\\[\\[(?:'+Insta.conf.locale.category+'|'+Insta.conf.wiki.interwiki+'):.*?\\]\\]','gi'),''). // [[:Category:...|Links]], [[:Image:...|Links]], etc... replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):.*?)\\|([^\\]]+?)\\]\\](\\w*)','gi'), function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));}). // [[/Relative links]] replace(/\[\[(\/[^|]*?)\]\]/g, function($0,$1){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($1)); }). // [[/Replaced|Relative links]] replace(/\[\[(\/.*?)\|(.+?)\]\]/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($2)); }). // [[Common links]] replace(/\[\[([^[|]*?)\]\](\w*)/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2)); }). // [[Replaced|Links]] replace(/\[\[([^[]*?)\|([^\]]+?)\]\](\w*)/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3)); }). // [[Stripped:Namespace|Namespace]] replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1) + htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($2)); }). // External links replace(/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g, function($0,$1,$2,$3,$4){return f("<a class='external' href='?:?'>?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($4)); }). replace(/\[http:\/\/(.*?)\]/g, function($0,$1){return f("<a class='external' href='http://?'>[#]</a>", htmlescape_attr($1)); }). replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, function($0,$1,$2,$3){return f("<a class='external' href='?:?'>?:?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($1), htmlescape_text($2) + htmlescape_text($3)); }). replace(/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*[^.,!?;: $])/g, function($0,$1,$2,$3,$4){return f("?<a class='external' href='?:?'>?:?</a>", htmlescape_text($1), htmlescape_attr($2), htmlescape_attr($3) + htmlescape_attr($4), htmlescape_text($2), htmlescape_text($3) + htmlescape_text($4)); }). replace('__NOTOC__',''). replace('__NOEDITSECTION__',''); } // begin parsing for (;remain();) if ($(/^(={1,6})(.*)\1(.*)$/)) { p=0; endl(f('<h?>?</h?>?', $r[1].length, parse_inline_nowiki($r[2]), $r[1].length, $r[3])); } else if ($(/^[*#:;]/)) { p=0; parse_list(); } else if ($(' ')) { p=0; parse_pre(); } else if ($('{|')) { p=0; parse_table(); } else if ($(/^----+$/)) { p=0; endl('<hr />'); } else if ($(Insta.BLOCK_IMAGE)) { p=0; parse_block_image(); } else { // handle paragraphs if ($$('')) { p = (remain()>1 && ll[1]===('')); if (p) endl('<p><br>'); } else { if(!p) { ps('<p>'); p=1; } ps(parse_inline_nowiki(ll[0]) + ' '); } sh(); } return o; }; function wiki2html(txt,baseurl) { Insta.conf.baseUrl=baseurl; return Insta.convert(txt); } // ENDFILE: livepreview.js // STARTFILE: pageinfo.js //<NOLITE> function popupFilterPageSize(data) { return formatBytes(data.length); } function popupFilterCountLinks(data) { var num=countLinks(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('wikiLinks'):popupString('wikiLink')); } function popupFilterCountImages(data) { var num=countImages(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('images'):popupString('image')); } function popupFilterCountCategories(data) { var num=countCategories(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('categories'):popupString('category')); } function popupFilterLastModified(data,download) { var lastmod=download.lastModified; var now=new Date(); var age=now-lastmod; if (lastmod && getValueOf('popupLastModified')) { return (tprintf('%s old', [formatAge(age)])).replace(RegExp(' ','g'), '&nbsp;'); } return ''; } function formatAge(age) { // coerce into a number var a=0+age, aa=a; var seclen = 1000; var minlen = 60*seclen; var hourlen = 60*minlen; var daylen = 24*hourlen; var weeklen = 7*daylen; var numweeks = (a-a%weeklen)/weeklen; a = a-numweeks*weeklen; var sweeks = addunit(numweeks, 'week'); var numdays = (a-a%daylen)/daylen; a = a-numdays*daylen; var sdays = addunit(numdays, 'day'); var numhours = (a-a%hourlen)/hourlen; a = a-numhours*hourlen; var shours = addunit(numhours,'hour'); var nummins = (a-a%minlen)/minlen; a = a-nummins*minlen; var smins = addunit(nummins, 'minute'); var numsecs = (a-a%seclen)/seclen; a = a-numsecs*seclen; var ssecs = addunit(numsecs, 'second'); if (aa > 4*weeklen) { return sweeks; } if (aa > weeklen) { return sweeks + ' ' + sdays; } if (aa > daylen) { return sdays + ' ' + shours; } if (aa > 6*hourlen) { return shours; } if (aa > hourlen) { return shours + ' ' + smins; } if (aa > 10*minlen) { return smins; } if (aa > minlen) { return smins + ' ' + ssecs; } return ssecs; } function addunit(num,str) { return '' + num + ' ' + ((num!=1) ? popupString(str+'s') : popupString(str)) ;} function runPopupFilters(list, data, download) { var ret=[]; for (var i=0; i<list.length; ++i) { if (list[i] && typeof list[i] == 'function') { var s=list[i](data, download, download.owner.article); if (s) { ret.push(s); } } } return ret; } function getPageInfo(data, download) { if (!data || data.length === 0) { return popupString('Empty page'); } var popupFilters=getValueOf('popupFilters') || []; var extraPopupFilters = getValueOf('extraPopupFilters') || []; var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download); var pageInfo=pageInfoArray.join(', '); if (pageInfo !== '' ) { pageInfo = upcaseFirst(pageInfo); } return pageInfo; } // this could be improved! function countLinks(wikiText) { return wikiText.split('[[').length - 1; } // if N = # matches, n = # brackets, then // String.parenSplit(regex) intersperses the N+1 split elements // with Nn other elements. So total length is // L= N+1 + Nn = N(n+1)+1. So N=(L-1)/(n+1). function countImages(wikiText) { return (wikiText.parenSplit(pg.re.image).length - 1) / (pg.re.imageBracketCount + 1); } function countCategories(wikiText) { return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1); } function popupFilterStubDetect(data, download, article) { var counts=stubCount(data, article); if (counts.real) { return popupString('stub'); } if (counts.sect) { return popupString('section stub'); } return ''; } function popupFilterDisambigDetect(data, download, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return ''; } return (isDisambig(data, article)) ? popupString('disambig') : ''; } function formatBytes(num) { return (num > 949) ? (Math.round(num/100)/10+popupString('kB')) : (num +'&nbsp;' + popupString('bytes')) ; } //</NOLITE> // ENDFILE: pageinfo.js // STARTFILE: titles.js /** @fileoverview Defines the {@link Title} class, and associated crufty functions. <code>Title</code> deals with article titles and their various forms. {@link Stringwrapper} is the parent class of <code>Title</code>, which exists simply to make things a little neater. */ /** Creates a new Stringwrapper. @constructor @class the Stringwrapper class. This base class is not really useful on its own; it just wraps various common string operations. */ function Stringwrapper() { /** Wrapper for this.toString().indexOf() @param {String} x @type integer */ this.indexOf=function(x){return this.toString().indexOf(x);}; /** Returns this.value. @type String */ this.toString=function(){return this.value;}; /** Wrapper for {@link String#parenSplit} applied to this.toString() @param {RegExp} x @type Array */ this.parenSplit=function(x){return this.toString().parenSplit(x);}; /** Wrapper for this.toString().substring() @param {String} x @param {String} y (optional) @type String */ this.substring=function(x,y){ if (typeof y=='undefined') { return this.toString().substring(x); } return this.toString().substring(x,y); }; /** Wrapper for this.toString().split() @param {String} x @type Array */ this.split=function(x){return this.toString().split(x);}; /** Wrapper for this.toString().replace() @param {String} x @param {String} y @type String */ this.replace=function(x,y){ return this.toString().replace(x,y); }; } /** Creates a new <code>Title</code>. @constructor @class The Title class. Holds article titles and converts them into various forms. Also deals with anchors, by which we mean the bits of the article URL after a # character, representing locations within an article. @param {String} value The initial value to assign to the article. This must be the canonical title (see {@link Title#value}. Omit this in the constructor and use another function to set the title if this is unavailable. */ function Title(val) { /** The canonical article title. This must be in UTF-8 with no entities, escaping or nasties. Also, underscores should be replaced with spaces. @type String @private */ this.value=null; /** The canonical form of the anchor. This should be exactly as it appears in the URL, i.e. with the .C3.0A bits in. @type String */ this.anchor=''; this.setUtf(val); } Title.prototype=new Stringwrapper(); /** Returns the canonical representation of the article title, optionally without anchor. @param {boolean} omitAnchor @fixme Decide specs for anchor @return String The article title and the anchor. */ Title.prototype.toString=function(omitAnchor) { return this.value + ( (!omitAnchor && this.anchor) ? '#' + this.anchorString() : '' ); }; Title.prototype.anchorString=function() { if (!this.anchor) { return ''; } var split=this.anchor.parenSplit(/((?:[.][0-9A-F]{2})+)/); var len=split.length; for (var j=1; j<len; j+=2) { // FIXME s/decodeURI/decodeURIComponent/g ? split[j]=decodeURIComponent(split[j].split('.').join('%')).split('_').join(' '); } return split.join(''); }; Title.prototype.urlAnchor=function() { var split=this.anchor.parenSplit('/((?:[%][0-9A-F]{2})+)/'); var len=split.length; for (var j=1; j<len; j+=2) { split[j]=split[j].split('%').join('.'); } return split.join(''); }; Title.prototype.anchorFromUtf=function(str) { this.anchor=encodeURIComponent(str.split(' ').join('_')) .split('%3A').join(':').split("'").join('%27').split('%').join('.'); }; Title.fromURL=function(h) { return new Title().fromURL(h); }; Title.prototype.fromURL=function(h) { if (typeof h != 'string') { this.value=null; return this; } // NOTE : playing with decodeURI, encodeURI, escape, unescape, // we seem to be able to replicate the IE borked encoding // IE doesn't do this new-fangled utf-8 thing. // and it's worse than that. // IE seems to treat the query string differently to the rest of the url // the query is treated as bona-fide utf8, but the first bit of the url is pissed around with // we fix up & for all browsers, just in case. var splitted=h.split('?'); splitted[0]=splitted[0].split('&').join('%26'); h=splitted.join('?'); var contribs=pg.re.contribs.exec(h); if (contribs) { if (contribs[1]=='title=') { contribs[3]=contribs[3].split('+').join(' '); } var u=new Title(contribs[3]); this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + u.stripNamespace())); return this; } var email=pg.re.email.exec(h); if (email) { this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(email[3]).stripNamespace())); return this; } var backlinks=pg.re.backlinks.exec(h); if (backlinks) { this.setUtf(this.decodeNasties(new Title(backlinks[3]))); return this; } //A dummy title object for a Special:Diff link. var specialdiff=pg.re.specialdiff.exec(h); if (specialdiff) { this.setUtf(this.decodeNasties(new Title(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Diff'))); return this; } // no more special cases to check -- // hopefully it's not a disguised user-related or specially treated special page var m=pg.re.main.exec(h); if(m === null) { this.value=null; } else { var fromBotInterface = /[?](.+[&])?title=/.test(h); if (fromBotInterface) { m[2]=m[2].split('+').join('_'); } var extracted = m[2] + (m[3] ? '#' + m[3] : ''); if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) { // Fix Safari issue // Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3. this.setUtf(decodeURIComponent(unescape(extracted))); } else { this.setUtf(this.decodeNasties(extracted)); } } return this; }; Title.prototype.decodeNasties=function(txt) { var ret= this.decodeEscapes(decodeURI(txt)); ret = ret.replace(/[_ ]*$/, ''); return ret; }; Title.prototype.decodeEscapes=function(txt) { var split=txt.parenSplit(/((?:[%][0-9A-Fa-f]{2})+)/); var len=split.length; for (var i=1; i<len; i=i+2) { // FIXME is decodeURIComponent better? split[i]=unescape(split[i]); } return split.join(''); }; Title.fromAnchor=function(a) { return new Title().fromAnchor(a); }; Title.prototype.fromAnchor=function(a) { if (!a) { this.value=null; return this; } return this.fromURL(a.href); }; Title.fromWikiText=function(txt) { return new Title().fromWikiText(txt); }; Title.prototype.fromWikiText=function(txt) { // FIXME - testing needed txt=myDecodeURI(txt); this.setUtf(txt); return this; }; Title.prototype.hintValue=function(){ if(!this.value) { return ''; } return safeDecodeURI(this.value); }; //<NOLITE> Title.prototype.toUserName=function(withNs) { if (this.namespaceId() != pg.nsUserId && this.namespaceId() != pg.nsUsertalkId) { this.value=null; return; } this.value = (withNs ? mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' : '') + this.stripNamespace().split('/')[0]; }; Title.prototype.userName=function(withNs) { var t=(new Title(this.value)); t.toUserName(withNs); if (t.value) { return t; } return null; }; Title.prototype.toTalkPage=function() { // convert article to a talk page, or if we can't, return null // In other words: return null if this ALREADY IS a talk page // and return the corresponding talk page otherwise // // Per https://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces // * All discussion namespaces have odd-integer indices // * The discussion namespace index for a specific namespace with index n is n + 1 if (this.value === null) { return null; } var namespaceId = this.namespaceId(); if (namespaceId>=0 && namespaceId % 2 === 0) //non-special and subject namespace { var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId+1]; if (typeof localizedNamespace!=='undefined') { if (localizedNamespace === '') { this.value = this.stripNamespace(); } else { this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace(); } return this.value; } } this.value=null; return null; }; //</NOLITE> // Return canonical, localized namespace Title.prototype.namespace=function() { return mw.config.get('wgFormattedNamespaces')[this.namespaceId()]; }; Title.prototype.namespaceId=function() { var n=this.value.indexOf(':'); if (n<0) { return 0; } //mainspace var namespaceId = mw.config.get('wgNamespaceIds')[this.value.substring(0,n).split(' ').join('_').toLowerCase()]; if (typeof namespaceId=='undefined') return 0; //mainspace return namespaceId; }; //<NOLITE> Title.prototype.talkPage=function() { var t=new Title(this.value); t.toTalkPage(); if (t.value) { return t; } return null; }; Title.prototype.isTalkPage=function() { if (this.talkPage()===null) { return true; } return false; }; Title.prototype.toArticleFromTalkPage=function() { //largely copy/paste from toTalkPage above. if (this.value === null) { return null; } var namespaceId = this.namespaceId(); if (namespaceId >= 0 && namespaceId % 2 == 1) //non-special and talk namespace { var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId-1]; if (typeof localizedNamespace!=='undefined') { if (localizedNamespace === '') { this.value = this.stripNamespace(); } else { this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace(); } return this.value; } } this.value=null; return null; }; Title.prototype.articleFromTalkPage=function() { var t=new Title(this.value); t.toArticleFromTalkPage(); if (t.value) { return t; } return null; }; Title.prototype.articleFromTalkOrArticle=function() { var t=new Title(this.value); if ( t.toArticleFromTalkPage() ) { return t; } return this; }; Title.prototype.isIpUser=function() { return pg.re.ipUser.test(this.userName()); }; //</NOLITE> Title.prototype.stripNamespace=function(){ // returns a string, not a Title var n=this.value.indexOf(':'); if (n<0) { return this.value; } var namespaceId = this.namespaceId(); if (namespaceId === pg.nsMainspaceId) return this.value; return this.value.substring(n+1); }; Title.prototype.setUtf=function(value){ if (!value) { this.value=''; return; } var anch=value.indexOf('#'); if(anch < 0) { this.value=value.split('_').join(' '); this.anchor=''; return; } this.value=value.substring(0,anch).split('_').join(' '); this.anchor=value.substring(anch+1); this.ns=null; // wait until namespace() is called }; Title.prototype.setUrl=function(urlfrag) { var anch=urlfrag.indexOf('#'); this.value=safeDecodeURI(urlfrag.substring(0,anch)); this.anchor=this.value.substring(anch+1); }; Title.prototype.append=function(x){ this.setUtf(this.value + x); }; Title.prototype.urlString=function(x) { if(!x) { x={}; } var v=this.toString(true); if (!x.omitAnchor && this.anchor) { v+= '#' + this.urlAnchor(); } if (!x.keepSpaces) { v=v.split(' ').join('_'); } return encodeURI(v).split('&').join('%26').split('?').join('%3F').split('+').join('%2B'); }; Title.prototype.removeAnchor=function() { return new Title(this.toString(true)); }; Title.prototype.toUrl=function() { return pg.wiki.titlebase + this.urlString(); }; function parseParams(url) { var specialDiff = pg.re.specialdiff.exec(url); if (specialDiff) { var split= specialDiff[1].split('/'); if (split.length==1) return {oldid:split[0], diff: 'prev'}; else if (split.length==2) return {oldid: split[0], diff: split[1]}; } var ret={}; if (url.indexOf('?')==-1) { return ret; } url = url.split('#')[0]; var s=url.split('?').slice(1).join(); var t=s.split('&'); for (var i=0; i<t.length; ++i) { var z=t[i].split('='); z.push(null); ret[z[0]]=z[1]; } //Diff revision with no oldid is interpreted as a diff to the previous revision by MediaWiki if (ret.diff && typeof(ret.oldid)==='undefined') { ret.oldid = "prev"; } //Documentation seems to say something different, but oldid can also accept prev/next, and Echo is emitting such URLs. Simple fixup during parameter decoding: if (ret.oldid && (ret.oldid==='prev' || ret.oldid==='next' || ret.oldid==='cur')) { var helper = ret.diff; ret.diff = ret.oldid; ret.oldid = helper; } return ret; } // (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup) // (b) change spaces to underscores // (c) encodeURI (just the straight one, no pg.re.urlNoPopup) function myDecodeURI (str) { var ret; // FIXME decodeURIComponent?? try { ret=decodeURI(str.toString()); } catch (summat) { return str; } for (var i=0; i<pg.misc.decodeExtras.length; ++i) { var from=pg.misc.decodeExtras[i].from; var to=pg.misc.decodeExtras[i].to; ret=ret.split(from).join(to); } return ret; } function safeDecodeURI(str) { var ret=myDecodeURI(str); return ret || str; } /////////// // TESTS // /////////// //<NOLITE> function isDisambig(data, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; } return ! article.isTalkPage() && pg.re.disambig.test(data); } function stubCount(data, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; } var sectStub=0; var realStub=0; if (pg.re.stub.test(data)) { var s=data.parenSplit(pg.re.stub); for (var i=1; i<s.length; i=i+2) { if (s[i]) { ++sectStub; } else { ++realStub; } } } return { real: realStub, sect: sectStub }; } function isValidImageName(str){ // extend as needed... return ( str.indexOf('{') == -1 ); } function isInStrippableNamespace(article) { // Does the namespace allow subpages // Note, would be better if we had access to wgNamespacesWithSubpages return ( article.namespaceId() !== 0 ); } function isInMainNamespace(article) { return article.namespaceId() === 0; } function anchorContainsImage(a) { // iterate over children of anchor a // see if any are images if (a === null) { return false; } var kids=a.childNodes; for (var i=0; i<kids.length; ++i) { if (kids[i].nodeName=='IMG') { return true; } } return false; } //</NOLITE> function isPopupLink(a) { // NB for performance reasons, TOC links generally return true // they should be stripped out later if (!markNopopupSpanLinks.done) { markNopopupSpanLinks(); } if (a.inNopopupSpan) { return false; } // FIXME is this faster inline? if (a.onmousedown || a.getAttribute('nopopup')) { return false; } var h=a.href; if (h === document.location.href+'#') { return false; } if (!pg.re.basenames.test(h)) { return false; } if (!pg.re.urlNoPopup.test(h)) { return true; } return ( (pg.re.email.test(h) || pg.re.contribs.test(h) || pg.re.backlinks.test(h) || pg.re.specialdiff.test(h)) && h.indexOf('&limit=') == -1 ); } function markNopopupSpanLinks() { if( !getValueOf('popupOnlyArticleLinks')) fixVectorMenuPopups(); var s = $('.nopopups').toArray(); for (var i=0; i<s.length; ++i) { var as=s[i].getElementsByTagName('a'); for (var j=0; j<as.length; ++j) { as[j].inNopopupSpan=true; } } markNopopupSpanLinks.done=true; } function fixVectorMenuPopups() { $('div.vectorMenu h3:first a:first').prop('inNopopupSpan', true); } // ENDFILE: titles.js // STARTFILE: getpage.js ////////////////////////////////////////////////// // Wiki-specific downloading // // Schematic for a getWiki call // // getPageWithCaching // | // false | true // getPage<-[findPictureInCache]->-onComplete(a fake download) // \. // (async)->addPageToCache(download)->-onComplete(download) // check cache to see if page exists function getPageWithCaching(url, onComplete, owner) { log('getPageWithCaching, url='+url); var i=findInPageCache(url); var d; if (i > -1) { d=fakeDownload(url, owner.idNumber, onComplete, pg.cache.pages[i].data, pg.cache.pages[i].lastModified, owner); } else { d=getPage(url, onComplete, owner); if (d && owner && owner.addDownload) { owner.addDownload(d); d.owner=owner; } } } function getPage(url, onComplete, owner) { log('getPage'); var callback= function (d) { if (!d.aborted) {addPageToCache(d); onComplete(d);} }; return startDownload(url, owner.idNumber, callback); } function findInPageCache(url) { for (var i=0; i<pg.cache.pages.length; ++i) { if (url==pg.cache.pages[i].url) { return i; } } return -1; } function addPageToCache(download) { log('addPageToCache '+download.url); var page = {url: download.url, data: download.data, lastModified: download.lastModified}; return pg.cache.pages.push(page); } // ENDFILE: getpage.js // STARTFILE: parensplit.js ////////////////////////////////////////////////// // parenSplit // String.prototype.parenSplit should do what ECMAscript says String.prototype.split does, // interspersing paren matches (regex capturing groups) between the split elements. // i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c'] if (String('abc'.split(/(b)/))!='a,b,c') { // broken String.split, e.g. konq, IE < 10 String.prototype.parenSplit=function (re) { re=nonGlobalRegex(re); var s=this; var m=re.exec(s); var ret=[]; while (m && s) { // without the following loop, we have // 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/) for(var i=0; i<m.length; ++i) { if (typeof m[i]=='undefined') m[i]=''; } ret.push(s.substring(0,m.index)); ret = ret.concat(m.slice(1)); s=s.substring(m.index + m[0].length); m=re.exec(s); } ret.push(s); return ret; }; } else { String.prototype.parenSplit=function (re) { return this.split(re); }; String.prototype.parenSplit.isNative=true; } function nonGlobalRegex(re) { var s=re.toString(); var flags=''; for (var j=s.length; s.charAt(j) != '/'; --j) { if (s.charAt(j) != 'g') { flags += s.charAt(j); } } var t=s.substring(1,j); return RegExp(t,flags); } // ENDFILE: parensplit.js // STARTFILE: tools.js // IE madness with encoding // ======================== // // suppose throughout that the page is in utf8, like wikipedia // // if a is an anchor DOM element and a.href should consist of // // http://host.name.here/wiki/foo?bar=baz // // then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie)) // but IE gives bar=baz correctly as plain utf8 // // --------------------------------- // // IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here. // // --------------------------------- // // summat else // Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm //<NOLITE> function getJsObj(json) { try { var json_ret = JSON.parse(json); if( json_ret.warnings ) { for( var w=0; w < json_ret.warnings.length; w++ ) { if( json_ret.warnings[w]['*'] ) { log( json_ret.warnings[w]['*'] ); } else { log( json_ret.warnings[w]['warnings'] ); } } } else if ( json_ret.error ) { errlog( json_ret.error.code + ': ' + json_ret.error.info ); } return json_ret; } catch (someError) { errlog('Something went wrong with getJsObj, json='+json); return 1; } } function anyChild(obj) { for (var p in obj) { return obj[p]; } return null; } //</NOLITE> function upcaseFirst(str) { if (typeof str != typeof '' || str === '') return ''; return str.charAt(0).toUpperCase() + str.substring(1); } function findInArray(arr, foo) { if (!arr || !arr.length) { return -1; } var len=arr.length; for (var i=0; i<len; ++i) { if (arr[i]==foo) { return i; } } return -1; } /* eslint-disable no-unused-vars */ function nextOne (array, value) { // NB if the array has two consecutive entries equal // then this will loop on successive calls var i=findInArray(array, value); if (i<0) { return null; } return array[i+1]; } /* eslint-enable no-unused-vars */ function literalizeRegex(str){ return mw.util.escapeRegExp(str); } String.prototype.entify=function() { //var shy='&shy;'; return this.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;'/*+shy*/).split('"').join('&quot;'); }; // Array filter function function removeNulls(val) { return val !== null; } function joinPath(list) { return list.filter(removeNulls).join('/'); } function simplePrintf(str, subs) { if (!str || !subs) { return str; } var ret=[]; var s=str.parenSplit(/(%s|\$[0-9]+)/); var i=0; do { ret.push(s.shift()); if ( !s.length ) { break; } var cmd=s.shift(); if (cmd == '%s') { if ( i < subs.length ) { ret.push(subs[i]); } else { ret.push(cmd); } ++i; } else { var j=parseInt( cmd.replace('$', ''), 10 ) - 1; if ( j > -1 && j < subs.length ) { ret.push(subs[j]); } else { ret.push(cmd); } } } while (s.length > 0); return ret.join(''); } /* eslint-disable no-unused-vars */ function isString(x) { return (typeof x === 'string' || x instanceof String); } function isNumber(x) { return (typeof x === 'number' || x instanceof Number); } function isRegExp(x) { return x instanceof RegExp; } function isArray (x) { return x instanceof Array; } function isObject(x) { return x instanceof Object; } function isFunction(x) { return !isRegExp(x) && (typeof x === 'function' || x instanceof Function); } /* eslint-enable no-unused-vars */ function repeatString(s,mult) { var ret=''; for (var i=0; i<mult; ++i) { ret += s; } return ret; } function zeroFill(s, min) { min = min || 2; var t=s.toString(); return repeatString('0', min - t.length) + t; } function map(f, o) { if (isArray(o)) { return map_array(f,o); } return map_object(f,o); } function map_array(f,o) { var ret=[]; for (var i=0; i<o.length; ++i) { ret.push(f(o[i])); } return ret; } function map_object(f,o) { var ret={}; for (var i in o) { ret[o]=f(o[i]); } return ret; } pg.escapeQuotesHTML = function ( text ) { return text .replace(/&/g, "&amp;") .replace(/"/g, "&quot;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;"); }; // ENDFILE: tools.js // STARTFILE: dab.js //<NOLITE> ////////////////////////////////////////////////// // Dab-fixing code // function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) { log('retargetDab: newTarget='+newTarget + ' oldTarget=' + oldTarget); return changeLinkTargetLink( {newTarget: newTarget, text: newTarget.split(' ').join('&nbsp;'), hint: tprintf('disambigHint', [newTarget]), summary: simplePrintf( getValueOf('popupFixDabsSummary'), [friendlyCurrentArticleName, newTarget ]), clickButton: getValueOf('popupDabsAutoClick'), minor: true, oldTarget: oldTarget, watch: getValueOf('popupWatchDisambiggedPages'), title: titleToEdit}); } function listLinks(wikitext, oldTarget, titleToEdit) { // mediawiki strips trailing spaces, so we do the same // testcase: https://en.wikipedia.org/w/index.php?title=Radial&oldid=97365633 var reg=RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi'); var ret=[]; var splitted=wikitext.parenSplit(reg); // ^[a-z]+ should match interwiki links, hopefully (case-insensitive) // and ^[a-z]* should match those and [[:Category...]] style links too var omitRegex=RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory'); var friendlyCurrentArticleName= oldTarget.toString(); var wikPos = getValueOf('popupDabWiktionary'); for (var i=1; i<splitted.length; i=i+3) { if (typeof splitted[i] == typeof 'string' && splitted[i].length>0 && !omitRegex.test(splitted[i])) { ret.push( retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit) ); } /* if */ } /* for loop */ ret = rmDupesFromSortedList(ret.sort()); if (wikPos) { var wikTarget='wiktionary:' + friendlyCurrentArticleName.replace( RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1' ); var meth; if (wikPos.toLowerCase() == 'first') { meth = 'unshift'; } else { meth = 'push'; } ret[meth]( retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) ); } ret.push(changeLinkTargetLink( { newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'), hint: popupString("remove all links to this disambig page from this article"), clickButton: getValueOf('popupDabsAutoClick'), oldTarget: oldTarget, summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]), watch: getValueOf('popupWatchDisambiggedPages'), title: titleToEdit })); return ret; } function rmDupesFromSortedList(list) { var ret=[]; for (var i=0; i<list.length; ++i) { if (ret.length === 0 || list[i]!=ret[ret.length-1]) { ret.push(list[i]); } } return ret; } function makeFixDab(data, navpop) { // grab title from parent popup if there is one; default exists in changeLinkTargetLink var titleToEdit=(navpop.parentPopup && navpop.parentPopup.article.toString()); var list=listLinks(data, navpop.originalArticle, titleToEdit); if (list.length === 0) { log('listLinks returned empty list'); return null; } var html='<hr />' + popupString('Click to disambiguate this link to:') + '<br>'; html+=list.join(', '); return html; } function makeFixDabs(wikiText, navpop) { if (getValueOf('popupFixDabs') && isDisambig(wikiText, navpop.article) && Title.fromURL(location.href).namespaceId() != pg.nsSpecialId && navpop.article.talkPage() ) { setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber); } } function popupRedlinkHTML(article) { return changeLinkTargetLink( { newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'), hint: popupString("remove all links to this page from this article"), clickButton: getValueOf('popupRedlinkAutoClick'), oldTarget: article.toString(), summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()])}); } //</NOLITE> // ENDFILE: dab.js // STARTFILE: htmloutput.js // this has to use a timer loop as we don't know if the DOM element exists when we want to set the text function setPopupHTML (str, elementId, popupId, onSuccess, append) { if (typeof popupId === 'undefined') { //console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100)); popupId = pg.idNumber; } var popupElement=document.getElementById(elementId+popupId); if (popupElement) { if (!append) { popupElement.innerHTML=''; } if (isString(str)) { popupElement.innerHTML+=str; } else { popupElement.appendChild(str); } if (onSuccess) { onSuccess(); } setTimeout(checkPopupPosition, 100); return true; } else { // call this function again in a little while... setTimeout(function(){ setPopupHTML(str,elementId,popupId,onSuccess); }, 600); } return null; } //<NOLITE> function setPopupTrailer(str,id) {return setPopupHTML(str, 'popupData', id);} //</NOLITE> // args.navpopup is mandatory // optional: args.redir, args.redirTarget // FIXME: ye gods, this is ugly stuff function fillEmptySpans(args) { // if redir is present and true then redirTarget is mandatory var redir=true; var rcid; if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) { redir=false; } var a=args.navpopup.parentAnchor; var article, hint=null, oldid=null, params={}; if (redir && typeof args.redirTarget == typeof {}) { article=args.redirTarget; //hint=article.hintValue(); } else { article=(new Title()).fromAnchor(a); hint=a.originalTitle || article.hintValue(); params=parseParams(a.href); oldid=(getValueOf('popupHistoricalLinks')) ? params.oldid : null; rcid=params.rcid; } var x={ article:article, hint: hint, oldid: oldid, rcid: rcid, navpop:args.navpopup, params:params }; var structure=pg.structures[getValueOf('popupStructure')]; if (typeof structure != 'object') { setPopupHTML('popupError', 'Unknown structure (this should never happen): '+ pg.option.popupStructure, args.navpopup.idNumber); return; } var spans=flatten(pg.misc.layout); var numspans = spans.length; var redirs=pg.misc.redirSpans; for (var i=0; i<numspans; ++i) { var found = redirs && (redirs.indexOf( spans[i] ) !== -1); //log('redir='+redir+', found='+found+', spans[i]='+spans[i]); if ( (found && !redir) || (!found && redir) ) { //log('skipping this set of the loop'); continue; } var structurefn=structure[spans[i]]; var setfn = setPopupHTML; if (getValueOf('popupActiveNavlinks') && (spans[i].indexOf('popupTopLinks')===0 || spans[i].indexOf('popupRedirTopLinks')===0) ) { setfn = setPopupTipsAndHTML; } switch (typeof structurefn) { case 'function': log('running '+spans[i]+'({article:'+x.article+', hint:'+x.hint+', oldid: '+x.oldid+'})'); setfn(structurefn(x), spans[i], args.navpopup.idNumber); break; case 'string': setfn(structurefn, spans[i], args.navpopup.idNumber); break; default: errlog('unknown thing with label '+spans[i] + ' (span index was ' + i + ')'); break; } } } // flatten an array function flatten(list, start) { var ret=[]; if (typeof start == 'undefined') { start=0; } for (var i=start; i<list.length; ++i) { if (typeof list[i] == typeof []) { return ret.concat(flatten(list[i])).concat(flatten(list, i+1)); } else { ret.push(list[i]); } } return ret; } // Generate html for whole popup function popupHTML (a) { getValueOf('popupStructure'); var structure=pg.structures[pg.option.popupStructure]; if (typeof structure != 'object') { //return 'Unknown structure: '+pg.option.popupStructure; // override user choice pg.option.popupStructure=pg.optionDefault.popupStructure; return popupHTML(a); } if (typeof structure.popupLayout != 'function') { return 'Bad layout'; } pg.misc.layout=structure.popupLayout(); if (typeof structure.popupRedirSpans === 'function') { pg.misc.redirSpans=structure.popupRedirSpans(); } else { pg.misc.redirSpans=[]; } return makeEmptySpans(pg.misc.layout, a.navpopup); } function makeEmptySpans (list, navpop) { var ret=''; for (var i=0; i<list.length; ++i) { if (typeof list[i] == typeof '') { ret += emptySpanHTML(list[i], navpop.idNumber, 'div'); } else if (typeof list[i] == typeof [] && list[i].length > 0 ) { ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop)); } else if (typeof list[i] == typeof {} && list[i].nodeType ) { ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType); } } return ret; } function emptySpanHTML(name, id, tag, classname) { tag = tag || 'span'; if (!classname) { classname = emptySpanHTML.classAliases[name]; } classname = classname || name; if (name == getValueOf('popupDragHandle')) { classname += ' popupDragHandle'; } return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]); } emptySpanHTML.classAliases={ 'popupSecondPreview': 'popupPreview' }; // generate html for popup image // <a id="popupImageLinkn"><img id="popupImagen"> // where n=idNumber function imageHTML(article, idNumber) { return simplePrintf('<a id="popupImageLink$1">' + '<img align="right" valign="top" id="popupImg$1" style="display: none;"></img>' + '</a>', [ idNumber ]); } function popTipsSoonFn(id, when, popData) { if (!when) { when=250; } var popTips=function(){ setupTooltips(document.getElementById(id), false, true, popData); }; return function() { setTimeout( popTips, when, popData ); }; } function setPopupTipsAndHTML(html, divname, idnumber, popData) { setPopupHTML(html, divname, idnumber, getValueOf('popupSubpopups') ? popTipsSoonFn(divname + idnumber, null, popData) : null); } // ENDFILE: htmloutput.js // STARTFILE: mouseout.js ////////////////////////////////////////////////// // fuzzy checks function fuzzyCursorOffMenus(x,y, fuzz, parent) { if (!parent) { return null; } var uls=parent.getElementsByTagName('ul'); for (var i=0; i<uls.length; ++i) { if (uls[i].className=='popup_menu') { if (uls[i].offsetWidth > 0) return false; } // else {document.title+='.';} } return true; } function checkPopupPosition () { // stop the popup running off the right of the screen // FIXME avoid pg.current.link if (pg.current.link && pg.current.link.navpopup) pg.current.link.navpopup.limitHorizontalPosition(); } function mouseOutWikiLink () { //console ('mouseOutWikiLink'); var a=this; removeModifierKeyHandler(a); if (a.navpopup === null || typeof a.navpopup === 'undefined') return; if ( ! a.navpopup.isVisible() ) { a.navpopup.banish(); return; } restoreTitle(a); Navpopup.tracker.addHook(posCheckerHook(a.navpopup)); } function posCheckerHook(navpop) { return function() { if (!navpop.isVisible()) { return true; /* remove this hook */ } if (Navpopup.tracker.dirty) { return false; } var x=Navpopup.tracker.x, y=Navpopup.tracker.y; var mouseOverNavpop = navpop.isWithin(x,y,navpop.fuzz, navpop.mainDiv) || !fuzzyCursorOffMenus(x,y,navpop.fuzz, navpop.mainDiv); // FIXME it'd be prettier to do this internal to the Navpopup objects var t=getValueOf('popupHideDelay'); if (t) { t = t * 1000; } if (!t) { if(!mouseOverNavpop) { if(navpop.parentAnchor) { restoreTitle( navpop.parentAnchor ); } navpop.banish(); return true; /* remove this hook */ } return false; } // we have a hide delay set var d=+(new Date()); if ( !navpop.mouseLeavingTime ) { navpop.mouseLeavingTime = d; return false; } if ( mouseOverNavpop ) { navpop.mouseLeavingTime=null; return false; } if (d - navpop.mouseLeavingTime > t) { navpop.mouseLeavingTime=null; navpop.banish(); return true; /* remove this hook */ } return false; }; } function runStopPopupTimer(navpop) { // at this point, we should have left the link but remain within the popup // so we call this function again until we leave the popup. if (!navpop.stopPopupTimer) { navpop.stopPopupTimer=setInterval(posCheckerHook(navpop), 500); navpop.addHook(function(){clearInterval(navpop.stopPopupTimer);}, 'hide', 'before'); } } // ENDFILE: mouseout.js // STARTFILE: previewmaker.js /** @fileoverview Defines the {@link Previewmaker} object, which generates short previews from wiki markup. */ /** Creates a new Previewmaker @constructor @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext. @param {String} wikiText The Wikitext source of the page we wish to preview. @param {String} baseUrl The url we should prepend when creating relative urls. @param {Navpopup} owner The navpop associated to this preview generator */ function Previewmaker(wikiText, baseUrl, owner) { /** The wikitext which is manipulated to generate the preview. */ this.originalData=wikiText; this.baseUrl=baseUrl; this.owner=owner; this.maxCharacters=getValueOf('popupMaxPreviewCharacters'); this.maxSentences=getValueOf('popupMaxPreviewSentences'); this.setData(); } Previewmaker.prototype.setData=function() { var maxSize=Math.max(10000, 2*this.maxCharacters); this.data=this.originalData.substring(0,maxSize); }; /** Remove HTML comments @private */ Previewmaker.prototype.killComments = function () { // this also kills one trailing newline, eg [[diamyo]] this.data=this.data.replace(RegExp('^<!--[^$]*?-->\\n|\\n<!--[^$]*?-->(?=\\n)|<!--[^$]*?-->', 'g'), ''); }; /** @private */ Previewmaker.prototype.killDivs = function () { // say goodbye, divs (can be nested, so use * not *?) this.data=this.data.replace(RegExp('< *div[^>]* *>[\\s\\S]*?< */ *div *>', 'gi'), ''); }; /** @private */ Previewmaker.prototype.killGalleries = function () { this.data=this.data.replace(RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>', 'gi'), ''); }; /** @private */ Previewmaker.prototype.kill = function(opening, closing, subopening, subclosing, repl) { var oldk=this.data; var k=this.killStuff(this.data, opening, closing, subopening, subclosing, repl); while (k.length < oldk.length) { oldk=k; k=this.killStuff(k, opening, closing, subopening, subclosing, repl); } this.data=k; }; /** @private */ Previewmaker.prototype.killStuff = function (txt, opening, closing, subopening, subclosing, repl) { var op=this.makeRegexp(opening); var cl=this.makeRegexp(closing, '^'); var sb=subopening ? this.makeRegexp(subopening, '^') : null; var sc=subclosing ? this.makeRegexp(subclosing, '^') : cl; if (!op || !cl) { alert('Navigation Popups error: op or cl is null! something is wrong.'); return; } if (!op.test(txt)) { return txt; } var ret=''; var opResult = op.exec(txt); ret = txt.substring(0,opResult.index); txt=txt.substring(opResult.index+opResult[0].length); var depth = 1; while (txt.length > 0) { var removal=0; if (depth==1 && cl.test(txt)) { depth--; removal=cl.exec(txt)[0].length; } else if (depth > 1 && sc.test(txt)) { depth--; removal=sc.exec(txt)[0].length; }else if (sb && sb.test(txt)) { depth++; removal=sb.exec(txt)[0].length; } if ( !removal ) { removal = 1; } txt=txt.substring(removal); if (depth === 0) { break; } } return ret + (repl || '') + txt; }; /** @private */ Previewmaker.prototype.makeRegexp = function (x, prefix, suffix) { prefix = prefix || ''; suffix = suffix || ''; var reStr=''; var flags=''; if (isString(x)) { reStr=prefix + literalizeRegex(x) + suffix; } else if (isRegExp(x)) { var s=x.toString().substring(1); var sp=s.split('/'); flags=sp[sp.length-1]; sp[sp.length-1]=''; s=sp.join('/'); s=s.substring(0,s.length-1); reStr= prefix + s + suffix; } else { log ('makeRegexp failed'); } log ('makeRegexp: got reStr=' + reStr + ', flags=' + flags); return RegExp(reStr, flags); }; /** @private */ Previewmaker.prototype.killBoxTemplates = function () { // taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general // also, have float_begin, ... float_end this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /[}][}]\s*/, '{{'); // infoboxes etc // from User:Zyxw/popups.js: kill frames too this.kill(RegExp('[{][{][^{}\\s|]*?(infobox|elementbox|frame)[_ ]', 'i'), /[}][}]\s*/, '{{'); }; /** @private */ Previewmaker.prototype.killTemplates = function () { // the plus sign is to avoid transcluding [[Template:', ']] this.kill('{'+'{', '}'+'}', '{', '}', ' '); }; /** @private */ Previewmaker.prototype.killTables = function () { // tables are bad, too // this can be slow, but it's an inprovement over a browser hang // torture test: [[Comparison_of_Intel_Central_Processing_Units]] this.kill('{|', /[|]}\s*/, '{|'); this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i); // remove lines starting with a pipe for the hell of it (?) this.data=this.data.replace(RegExp('^[|].*$', 'mg'), ''); }; /** @private */ Previewmaker.prototype.killImages = function () { var forbiddenNamespaceAliases = []; jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) { if (_namespaceId!=pg.nsImageId && _namespaceId!=pg.nsCategoryId) return; forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments! }); // images and categories are a nono this.kill(RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'), /\]\]\s*/, '[', ']'); }; /** @private */ Previewmaker.prototype.killHTML = function () { // kill <ref ...>...</ref> this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i); // let's also delete entire lines starting with <. it's worth a try. this.data=this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n'); // and those pesky html tags, but not <nowiki> or <blockquote> var splitted=this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/); var len=splitted.length; for (var i=1; i<len; i=i+2) { switch (splitted[i]) { case '<nowiki>': case '</nowiki>': case '<blockquote>': case '</blockquote>': break; default: splitted[i]=''; } } this.data=splitted.join(''); }; /** @private */ Previewmaker.prototype.killChunks = function() { // heuristics alert // chunks of italic text? you crazy, man? var italicChunkRegex=new RegExp ("((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+", 'g'); // keep stuff separated, though, so stick in \n (fixes [[Union Jack]]? this.data=this.data.replace(italicChunkRegex, '\n'); }; /** @private */ Previewmaker.prototype.mopup = function () { // we simply *can't* be doing with horizontal rules right now this.data=this.data.replace(RegExp('^-{4,}','mg'),''); // no indented lines this.data=this.data.replace(RegExp('(^|\\n) *:[^\\n]*','g'), ''); // replace __TOC__, __NOTOC__ and whatever else there is // this'll probably do this.data=this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'),''); }; /** @private */ Previewmaker.prototype.firstBit = function () { // dont't be givin' me no subsequent paragraphs, you hear me? /// first we "normalize" section headings, removing whitespace after, adding before var d=this.data; if (getValueOf('popupPreviewCutHeadings')) { this.data=this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 '); /// then we want to get rid of paragraph breaks whose text ends badly this.data=this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n'); this.data=this.data.replace(RegExp('^[\\s\\n]*'), ''); var stuff=(RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data); if (stuff) { d = stuff[0]; } if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; } /// now put \n\n after sections so that bullets and numbered lists work d=d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n'); } // Split sentences. Superfluous sentences are RIGHT OUT. // note: exactly 1 set of parens here needed to make the slice work d = d.parenSplit(RegExp('([!?.]+["'+"'"+']*\\s)','g')); // leading space is bad, mmkay? d[0]=d[0].replace(RegExp('^\\s*'), ''); var notSentenceEnds=RegExp('([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$', 'i'); d = this.fixSentenceEnds(d, notSentenceEnds); this.fullLength=d.join('').length; var n=this.maxSentences; var dd=this.firstSentences(d,n); do { dd=this.firstSentences(d,n); --n; } while ( dd.length > this.maxCharacters && n !== 0 ); this.data = dd; }; /** @private */ Previewmaker.prototype.fixSentenceEnds = function(strs, reg) { // take an array of strings, strs // join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg for (var i=0; i<strs.length-2; ++i) { if (reg.test(strs[i])) { var a=[]; for (var j=0; j<strs.length; ++j) { if (j<i) a[j]=strs[j]; if (j==i) a[i]=strs[i]+strs[i+1]+strs[i+2]; if (j>i+2) a[j-2]=strs[j]; } return this.fixSentenceEnds(a,reg); } } return strs; }; /** @private */ Previewmaker.prototype.firstSentences = function(strs, howmany) { var t=strs.slice(0, 2*howmany); return t.join(''); }; /** @private */ Previewmaker.prototype.killBadWhitespace = function() { // also cleans up isolated '''', eg [[Suntory Sungoliath]] this.data=this.data.replace(RegExp('^ *\'+ *$', 'gm'), ''); }; /** Runs the various methods to generate the preview. The preview is stored in the <code>html</html> field. @private */ Previewmaker.prototype.makePreview = function() { if (this.owner.article.namespaceId()!=pg.nsTemplateId && this.owner.article.namespaceId()!=pg.nsImageId ) { this.killComments(); this.killDivs(); this.killGalleries(); this.killBoxTemplates(); if (getValueOf('popupPreviewKillTemplates')) { this.killTemplates(); } else { this.killMultilineTemplates(); } this.killTables(); this.killImages(); this.killHTML(); this.killChunks(); this.mopup(); this.firstBit(); this.killBadWhitespace(); } else { this.killHTML(); } this.html=wiki2html(this.data, this.baseUrl); // needs livepreview this.fixHTML(); this.stripLongTemplates(); }; /** @private */ Previewmaker.prototype.esWiki2HtmlPart = function(data) { var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink reLinks.lastIndex = 0; //reset regex var match; var result = ""; var postfixIndex = 0; while ((match = reLinks.exec(data))) //match all wikilinks { //FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases. result += pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) + '<a href="'+Insta.conf.paths.articles+pg.escapeQuotesHTML(match[1])+'">'+pg.escapeQuotesHTML((match[2]?match[2]:match[1])+match[3])+"</a>"; postfixIndex = reLinks.lastIndex; } //append the rest result += pg.escapeQuotesHTML(data.substring(postfixIndex)); return result; }; Previewmaker.prototype.editSummaryPreview=function() { var reAes = /\/\* *(.*?) *\*\//g; //match the first section marker reAes.lastIndex = 0; //reset regex var match; match = reAes.exec(this.data); if (match) { //we have a section link. Split it, process it, combine it. var prefix = this.data.substring(0,match.index-1); var section = match[1]; var postfix = this.data.substring(reAes.lastIndex); var start = "<span class='autocomment'>"; var end = "</span>"; if (prefix.length>0) start = this.esWiki2HtmlPart(prefix) + " " + start + "- "; if (postfix.length>0) end = ": " + end + this.esWiki2HtmlPart(postfix); var t=new Title().fromURL(this.baseUrl); t.anchorFromUtf(section); var sectionLink = Insta.conf.paths.articles + pg.escapeQuotesHTML(t.toString(true)) + '#' + pg.escapeQuotesHTML(t.anchor); return start + '<a href="'+sectionLink+'">&rarr;</a> '+pg.escapeQuotesHTML(section) + end; } //else there's no section link, htmlify the whole thing. return this.esWiki2HtmlPart(this.data); }; //<NOLITE> /** Test function for debugging preview problems one step at a time. */ /*eslint-disable */ function previewSteps(txt) { try { txt=txt || document.editform.wpTextbox1.value; } catch (err) { if (pg.cache.pages.length > 0) { txt=pg.cache.pages[pg.cache.pages.length-1].data; } else { alert('provide text or use an edit page'); } } txt=txt.substring(0,10000); var base=pg.wiki.articlebase + Title.fromURL(document.location.href).urlString(); var p=new Previewmaker(txt, base, pg.current.link.navpopup); if (this.owner.article.namespaceId() != pg.nsTemplateId) { p.killComments(); if (!confirm('done killComments(). Continue?\n---\n' + p.data)) { return; } p.killDivs(); if (!confirm('done killDivs(). Continue?\n---\n' + p.data)) { return; } p.killGalleries(); if (!confirm('done killGalleries(). Continue?\n---\n' + p.data)) { return; } p.killBoxTemplates(); if (!confirm('done killBoxTemplates(). Continue?\n---\n' + p.data)) { return; } if (getValueOf('popupPreviewKillTemplates')) { p.killTemplates(); if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) { return; } } else { p.killMultilineTemplates(); if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) { return; } } p.killTables(); if (!confirm('done killTables(). Continue?\n---\n' + p.data)) { return; } p.killImages(); if (!confirm('done killImages(). Continue?\n---\n' + p.data)) { return; } p.killHTML(); if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) { return; } p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; } p.mopup(); if (!confirm('done mopup(). Continue?\n---\n' + p.data)) { return; } p.firstBit(); if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) { return; } p.killBadWhitespace(); if (!confirm('done killBadWhitespace(). Continue?\n---\n' + p.data)) { return; } } p.html=wiki2html(p.data, base); // needs livepreview p.fixHTML(); if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) { return; } p.stripLongTemplates(); if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) { return; } alert('finished preview - end result follows.\n---\n' + p.html); } /*eslint-enable */ //</NOLITE> /** Works around livepreview bugs. @private */ Previewmaker.prototype.fixHTML = function() { if(!this.html) return; var ret = this.html; // fix question marks in wiki links // maybe this'll break some stuff :-( ret=ret.replace(RegExp('(<a href="' + pg.wiki.articlePath + '/[^"]*)[?](.*?")', 'g'), '$1%3F$2'); ret=ret.replace(RegExp('(<a href=\'' + pg.wiki.articlePath + '/[^\']*)[?](.*?\')', 'g'), '$1%3F$2'); // FIXME fix up % too this.html=ret; }; /** Generates the preview and displays it in the current popup. Does nothing if the generated preview is invalid or consists of whitespace only. Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true. */ Previewmaker.prototype.showPreview = function () { this.makePreview(); if (typeof this.html != typeof '') return; if (RegExp('^\\s*$').test(this.html)) return; setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber); setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, { owner: this.owner }); var more = (this.fullLength > this.data.length) ? this.moreLink() : ''; setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber); }; /** @private */ Previewmaker.prototype.moreLink=function() { var a=document.createElement('a'); a.className='popupMoreLink'; a.innerHTML=popupString('more...'); var savedThis=this; a.onclick=function() { savedThis.maxCharacters+=2000; savedThis.maxSentences+=20; savedThis.setData(); savedThis.showPreview(); }; return a; }; /** @private */ Previewmaker.prototype.stripLongTemplates = function() { // operates on the HTML! this.html=this.html.replace(RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'), ''); this.html=this.html.split('\n').join(' '); // workaround for <pre> templates this.html=this.html.replace(RegExp('[{][{][^}]*<pre>[^}]*[}][}]','gi'), ''); }; /** @private */ Previewmaker.prototype.killMultilineTemplates = function() { this.kill('{{{', '}}}'); this.kill(RegExp('\\s*[{][{][^{}]*\\n'), '}}', '{{'); }; // ENDFILE: previewmaker.js // STARTFILE: querypreview.js function loadAPIPreview(queryType, article, navpop) { var art=new Title(article).urlString(); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&'; var htmlGenerator=function(/*a, d*/){alert('invalid html generator');}; var usernameart = ''; switch (queryType) { case 'history': url += 'titles=' + art + '&prop=revisions&rvlimit=' + getValueOf('popupHistoryPreviewLimit'); htmlGenerator=APIhistoryPreviewHTML; break; case 'category': url += 'list=categorymembers&cmtitle=' + art; htmlGenerator=APIcategoryPreviewHTML; break; case 'userinfo': var username = new Title( article ).userName(); usernameart = encodeURIComponent( username ); if (pg.re.ipUser.test(username)) { url += 'list=blocks&bkprop=range|restrictions&bkip=' + usernameart; } else { url += 'list=users|usercontribs&usprop=blockinfo|groups|editcount|registration|gender&ususers=' + usernameart + "&meta=globaluserinfo&guiprop=groups|unattached&guiuser="+ usernameart + "&uclimit=1&ucprop=timestamp&ucuser=" + usernameart; } htmlGenerator=APIuserInfoPreviewHTML; break; case 'contribs': usernameart = encodeURIComponent( new Title( article ).userName() ); url += 'list=usercontribs&ucuser=' + usernameart + '&uclimit=' + getValueOf('popupContribsPreviewLimit'); htmlGenerator=APIcontribsPreviewHTML; break; case 'imagepagepreview': var trail=''; if (getValueOf('popupImageLinks')) { trail = '&list=imageusage&iutitle=' + art; } url += 'titles=' + art + '&prop=revisions|imageinfo&rvprop=content' + trail; htmlGenerator=APIimagepagePreviewHTML; break; case 'backlinks': url += 'list=backlinks&bltitle=' + art; htmlGenerator=APIbacklinksPreviewHTML; break; case 'revision': if (article.oldid) { url += 'revids=' + article.oldid; } else { url += 'titles=' + article.removeAnchor().urlString(); } url += '&prop=revisions|pageprops|info|images|categories&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max'; htmlGenerator=APIrevisionPreviewHTML; break; } pendingNavpopTask(navpop); var callback=function(d){ log( "callback of API functions was hit" ); showAPIPreview(queryType, htmlGenerator(article,d,navpop), navpop.idNumber, navpop, d); }; var go = function(){ getPageWithCaching(url, callback, navpop); return true; }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_'+queryType+'_QUERY_DATA'); } } function linkList(list) { list.sort(function(x,y) { return (x==y ? 0 : (x<y ? -1 : 1)); }); var buf=[]; for (var i=0; i<list.length; ++i) { buf.push(wikiLink({article: new Title(list[i]), text: list[i].split(' ').join('&nbsp;'), action: 'view'})); } return buf.join(', '); } function getTimeOffset() { var tz = mw.user.options.get('timecorrection'); if(tz) { if( tz.indexOf('|') > -1 ) { // New format return parseInt(tz.split('|')[1],10); } else if ( tz.indexOf(':') > -1 ) { // Old format return( parseInt(tz,10)*60 + parseInt(tz.split(':')[1],10) ); } } return 0; } /* * Creates a HTML table that's shown in the history and user-contribs popups. * @param {Object[]} h - a list of revisions, returned from the API * @param {boolean} reallyContribs - true only if we're displaying user contributions */ function editPreviewTable(article, h, reallyContribs, timeOffset) { var html=['<table>']; var day=null; var curart=article; var page=null; var makeFirstColumnLinks; if(reallyContribs) { // We're showing user contributions, so make (diff | hist) links makeFirstColumnLinks = function(currentRevision) { var result = '('; result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&diff=prev' + '&oldid=' + currentRevision.revid + '">' + popupString('diff') + '</a>'; result += '&nbsp;|&nbsp;'; result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&action=history">' + popupString('hist') + '</a>'; result += ')'; return result; }; } else { // It's a regular history page, so make (cur | last) links var firstRevid = h[0].revid; makeFirstColumnLinks = function(currentRevision) { var result = '('; result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=' + firstRevid + '&oldid=' + currentRevision.revid + '">' + popupString('cur') + '</a>'; result += '&nbsp;|&nbsp;'; result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=prev&oldid=' + currentRevision.revid + '">' + popupString('last') + '</a>'; result += ')'; return result; }; } for (var i=0; i<h.length; ++i) { if (reallyContribs) { page = h[i].title; curart = new Title(page); } var minor = h[i].minor ? '<b>m </b>' : ''; var editDate = adjustDate(getDateFromTimestamp(h[i].timestamp), timeOffset); var thisDay = dayFormat(editDate); var thisTime = timeFormat(editDate); if (thisDay == day) { thisDay = ''; } else { day = thisDay; } if (thisDay) { html.push( '<tr><td colspan=3><span class="popup_history_date">' + thisDay+'</span></td></tr>' ); } html.push('<tr class="popup_history_row_' + ( (i%2) ? 'odd' : 'even') + '">'); html.push('<td>' + makeFirstColumnLinks(h[i]) + '</td>'); html.push('<td>' + '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&oldid=' + h[i].revid + '">' + thisTime + '</a></td>'); var col3url='', col3txt=''; if (!reallyContribs) { var user=h[i].user; if( !h[i].userhidden ) { if( pg.re.ipUser.test(user) ) { col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Contributions&target=' + new Title(user).urlString(); } else { col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(user).urlString(); } col3txt=pg.escapeQuotesHTML(user); } else { col3url=getValueOf('popupRevDelUrl'); col3txt=pg.escapeQuotesHTML( popupString('revdel')); } } else { col3url=pg.wiki.titlebase + curart.urlString(); col3txt=pg.escapeQuotesHTML(page); } html.push('<td>' + (reallyContribs ? minor : '') + '<a href="' + col3url + '">' + col3txt + '</a></td>'); var comment=''; var c=h[i].comment || h[i].content; if (c) { comment=new Previewmaker(c, new Title(curart).toUrl()).editSummaryPreview(); } else if ( h[i].commenthidden ) { comment=popupString('revdel'); } html.push('<td>' + (!reallyContribs ? minor : '') + comment + '</td>'); html.push('</tr>'); html=[html.join('')]; } html.push('</table>'); return html.join(''); } function getDateFromTimestamp(t) { var s=t.split(/[^0-9]/); switch(s.length) { case 0: return null; case 1: return new Date(s[0]); case 2: return new Date(s[0], s[1]-1); case 3: return new Date(s[0], s[1]-1, s[2]); case 4: return new Date(s[0], s[1]-1, s[2], s[3]); case 5: return new Date(s[0], s[1]-1, s[2], s[3], s[4]); case 6: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5]); default: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5], s[6]); } } function adjustDate(d, offset) { // offset is in minutes var o=offset * 60 * 1000; return new Date( +d + o); } function dayFormat(editDate, utc) { if (utc) { return map(zeroFill, [editDate.getUTCFullYear(), editDate.getUTCMonth()+1, editDate.getUTCDate()]).join('-'); } return map(zeroFill, [editDate.getFullYear(), editDate.getMonth()+1, editDate.getDate()]).join('-'); } function timeFormat(editDate, utc) { if (utc) { return map(zeroFill, [editDate.getUTCHours(), editDate.getUTCMinutes(), editDate.getUTCSeconds()]).join(':'); } return map(zeroFill, [editDate.getHours(), editDate.getMinutes(), editDate.getSeconds()]).join(':'); } function showAPIPreview(queryType, html, id, navpop, download) { // DJ: done var target='popupPreview'; completedNavpopTask(navpop); switch (queryType) { case 'imagelinks': case 'category': target='popupPostPreview'; break; case 'userinfo': target='popupUserData'; break; case 'revision': insertPreview(download); return; } setPopupTipsAndHTML(html, target, id); } function APIrevisionPreviewHTML(article, download) { try{ var jsObj=getJsObj(download.data); var page=anyChild(jsObj.query.pages); if( page.missing ) { // TODO we need to fix this proper later on download.owner = null; return; } var content = ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; if( typeof content === 'string' ) { download.data = content; download.lastModified = new Date(page.revisions[0].timestamp); } } catch(someError) { return 'Revision preview failed :('; } } function APIbacklinksPreviewHTML(article, download/*, navpop*/ ) { try { var jsObj=getJsObj(download.data); var list=jsObj.query.backlinks; var html=[]; if (!list) { return popupString('No backlinks found'); } for ( var i=0; i < list.length; i++ ) { var t=new Title(list[i].title); html.push('<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t + '</a>'); } html=html.join(', '); if (jsObj['continue'] && jsObj['continue'].blcontinue) { html += popupString(' and more'); } return html; } catch (someError) { return 'backlinksPreviewHTML went wonky'; } } pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) { log( "APIsharedImagePagePreviewHTML" ); var popupid = obj.requestid; if( obj.query && obj.query.pages ) { var page=anyChild(obj.query.pages ); var content = ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; if( typeof content === 'string' ) { /* Not entirely safe, but the best we can do */ var p=new Previewmaker(content, pg.current.link.navpopup.article, pg.current.link.navpopup); p.makePreview(); setPopupHTML( p.html, "popupSecondPreview", popupid ); } } }; function APIimagepagePreviewHTML(article, download, navpop) { try { var jsObj=getJsObj(download.data); var page=anyChild(jsObj.query.pages); var content= ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; var ret=''; var alt=''; try{alt=navpop.parentAnchor.childNodes[0].alt;} catch(e){} if (alt) { ret = ret + '<hr /><b>' + popupString('Alt text:') + '</b> ' + pg.escapeQuotesHTML(alt); } if (typeof content === 'string') { var p=prepPreviewmaker(content, article, navpop); p.makePreview(); if (p.html) { ret += '<hr />' + p.html; } if (getValueOf('popupSummaryData')) { var info=getPageInfo(content, download); log(info); setPopupTrailer(info, navpop.idNumber); } } if (page && page.imagerepository == "shared" ) { var art=new Title(article); var encart = encodeURIComponent( "File:" + art.stripNamespace() ); var shared_url = pg.wiki.apicommonsbase + '?format=json&formatversion=2' + '&callback=pg.fn.APIsharedImagePagePreviewHTML' + '&requestid=' + navpop.idNumber + '&action=query&prop=revisions&rvprop=content&titles=' + encart; ret = ret +'<hr />' + popupString( 'Image from Commons') + ': <a href="' + pg.wiki.commonsbase + '?title=' + encart + '">' + popupString( 'Description page') + '</a>'; mw.loader.load( shared_url ); } showAPIPreview('imagelinks', APIimagelinksPreviewHTML(article,download), navpop.idNumber, download); return ret; } catch (someError) { return 'API imagepage preview failed :('; } } function APIimagelinksPreviewHTML(article, download) { try { var jsobj=getJsObj(download.data); var list=jsobj.query.imageusage; if (list) { var ret=[]; for (var i=0; i < list.length; i++) { ret.push(list[i].title); } if (ret.length === 0) { return popupString('No image links found'); } return '<h2>' + popupString('File links') + '</h2>' + linkList(ret); } else { return popupString('No image links found'); } } catch(someError) { return 'Image links preview generation failed :('; } } function APIcategoryPreviewHTML(article, download) { try{ var jsobj=getJsObj(download.data); var list=jsobj.query.categorymembers; var ret=[]; for (var p=0; p < list.length; p++) { ret.push(list[p].title); } if (ret.length === 0) { return popupString('Empty category'); } ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' +linkList(ret); if (jsobj['continue'] && jsobj['continue'].cmcontinue) { ret += popupString(' and more'); } return ret; } catch(someError) { return 'Category preview failed :('; } } function APIuserInfoPreviewHTML(article, download) { var ret=[]; var queryobj = {}; try{ queryobj=getJsObj(download.data).query; } catch(someError) { return 'Userinfo preview failed :('; } var user=anyChild(queryobj.users); if (user) { var globaluserinfo=queryobj.globaluserinfo; if (user.invalid === '') { ret.push( popupString( 'Invalid user') ); } else if (user.missing === '') { ret.push( popupString( 'Not a registered username') ); } if( user.blockedby ) { if( user.blockpartial ) { ret.push('<b>' + popupString('Has blocks') + '</b>'); } else { ret.push('<b>' + popupString('CẤM') + '</b>'); } } if( globaluserinfo && ( 'locked' in globaluserinfo || 'hidden' in globaluserinfo ) ) { var lockedSulAccountIsAttachedToThis = true; for( var i=0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) { if ( globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname') ) { lockedSulAccountIsAttachedToThis=false; break; } } if (lockedSulAccountIsAttachedToThis) { if ( 'locked' in globaluserinfo ) ret.push('<b><i>' + popupString('LOCKED') + '</i></b>'); if ( 'hidden' in globaluserinfo ) ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>'); } } if( getValueOf('popupShowGender') && user.gender ) { switch( user.gender ) { case "male": ret.push( popupString( "\u2642" ) ); break; case "female": ret.push( popupString( "\u2640" ) ); break; } } if( user.groups ) { for( var j=0; j < user.groups.length; j++) { var currentGroup = user.groups[j]; if( ["*", "user", "autoconfirmed", "extendedconfirmed"].indexOf( currentGroup ) === -1 ) { ret.push( pg.escapeQuotesHTML(user.groups[j]) ); } } } if( globaluserinfo && globaluserinfo.groups ) { for( var k=0; k < globaluserinfo.groups.length; k++) { ret.push( '<i>'+pg.escapeQuotesHTML(globaluserinfo.groups[k])+'</i>' ); } } if( user.registration ) ret.push( pg.escapeQuotesHTML((user.editcount?user.editcount:'0') + popupString(' sửa đổi từ: ') + (user.registration?dayFormat(getDateFromTimestamp(user.registration)):'')) ); } if (queryobj.usercontribs && queryobj.usercontribs.length) { ret.push( popupString('sửa đổi lần cuối vào ') + dayFormat(getDateFromTimestamp(queryobj.usercontribs[0].timestamp)) ); } if (queryobj.blocks) { ret.push( popupString( 'IP user') ); //we only request list=blocks for IPs for (var l=0; l<queryobj.blocks.length; l++) { console.log(queryobj); var rbstr = queryobj.blocks[l].rangestart === queryobj.blocks[l].rangeend ? 'BLOCK' : 'RANGEBLOCK'; rbstr = (!Array.isArray(queryobj.blocks[l].restrictions) ? 'Has ' + rbstr.toLowerCase() + 's' : rbstr + 'ED'); ret.push('<b>' + popupString(rbstr) + '</b>' ); } } ret = '<hr />' + ret.join( ', ' ); return ret; } function APIcontribsPreviewHTML(article, download, navpop) { return APIhistoryPreviewHTML(article, download, navpop, true); } function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) { try { var jsobj=getJsObj(download.data); var edits = []; if( reallyContribs ) { edits=jsobj.query.usercontribs; } else { edits=anyChild(jsobj.query.pages).revisions; } var ret=editPreviewTable(article, edits, reallyContribs, getTimeOffset()); return ret; } catch (someError) { return 'History preview failed :-('; } } //</NOLITE> // ENDFILE: querypreview.js // STARTFILE: debug.js //////////////////////////////////////////////////////////////////// // Debugging functions //////////////////////////////////////////////////////////////////// function setupDebugging() { //<NOLITE> if (window.popupDebug) { // popupDebug is set from .version window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; }; window.console.log(x); }; window.errlog=function(x) { window.console.error(x); }; log('Initializing logger'); } else { //</NOLITE> window.log = function() {}; window.errlog = function() {}; //<NOLITE> } //</NOLITE> } // ENDFILE: debug.js // STARTFILE: images.js // load image of type Title. function loadImage(image, navpop) { if (typeof image.stripNamespace != 'function') { alert('loadImages bad'); } // API call to retrieve image info. if ( !getValueOf('popupImages') ) return; if ( !isValidImageName(image) ) return false; var art=image.urlString(); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query'; url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge'); url += '&titles=' + art; pendingNavpopTask(navpop); var callback=function(d){ popupsInsertImage(navpop.idNumber, navpop, d); }; var go = function(){ getPageWithCaching(url, callback, navpop); return true; }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA'); } } function popupsInsertImage(id, navpop, download) { log( "popupsInsertImage"); var imageinfo; try { var jsObj=getJsObj(download.data); var imagepage=anyChild(jsObj.query.pages); if (typeof imagepage.imageinfo === 'undefined') return; imageinfo = imagepage.imageinfo[0]; } catch (someError) { log( "popupsInsertImage failed :(" ); return; } var popupImage = document.getElementById("popupImg"+id); if (!popupImage) { log( "could not find insertion point for image"); return; } popupImage.width=getValueOf('popupImageSize'); popupImage.style.display='inline'; // Set the source for the image. if( imageinfo.thumburl ) popupImage.src=imageinfo.thumburl; else if( imageinfo.mime.indexOf("image") === 0 ){ popupImage.src=imageinfo.url; log( "a thumb could not be found, using original image" ); } else log( "fullsize imagethumb, but not sure if it's an image"); var a=document.getElementById("popupImageLink"+id); if (a === null) { return null; } // Determine the action of the surrouding imagelink. switch (getValueOf('popupThumbAction')) { case 'imagepage': if (pg.current.article.namespaceId()!=pg.nsImageId) { a.href=imageinfo.descriptionurl; // FIXME: unreliable pg.idNumber popTipsSoonFn('popupImage' + id)(); break; } /* falls through */ case 'sizetoggle': a.onclick=toggleSize; a.title=popupString('Toggle image size'); return; case 'linkfull': a.href = imageinfo.url; a.title=popupString('Open full-size image'); return; } } // Toggles the image between inline small and navpop fullwidth. // It's the same image, no actual sizechange occurs, only display width. function toggleSize() { var imgContainer=this; if (!imgContainer) { alert('imgContainer is null :/'); return; } var img=imgContainer.firstChild; if (!img) { alert('img is null :/'); return; } if (!img.style.width || img.style.width==='') { img.style.width='100%'; } else { img.style.width=''; } } // Returns one title of an image from wikiText. function getValidImageFromWikiText(wikiText) { // nb in pg.re.image we're interested in the second bracketed expression // this may change if the regex changes :-( //var match=pg.re.image.exec(wikiText); var matched=null; var match; // strip html comments, used by evil bots :-( var t = removeMatchesUnless(wikiText, RegExp('(<!--[\\s\\S]*?-->)'), 1, RegExp('^<!--[^[]*popup', 'i')); while ( ( match = pg.re.image.exec(t) ) ) { // now find a sane image name - exclude templates by seeking { var m = match[2] || match[6]; if ( isValidImageName(m) ) { matched=m; break; } } pg.re.image.lastIndex=0; if (!matched) { return null; } return mw.config.get('wgFormattedNamespaces')[pg.nsImageId]+':'+upcaseFirst(matched); } function removeMatchesUnless(str, re1, parencount, re2) { var split=str.parenSplit(re1); var c=parencount + 1; for (var i=0; i<split.length; ++i) { if ( i%c === 0 || re2.test(split[i]) ) { continue; } split[i]=''; } return split.join(''); } //</NOLITE> // ENDFILE: images.js // STARTFILE: namespaces.js // Set up namespaces and other non-strings.js localization // (currently that means redirs too) function setNamespaces() { pg.nsSpecialId = -1; pg.nsMainspaceId = 0; pg.nsImageId = 6; pg.nsUserId = 2; pg.nsUsertalkId = 3; pg.nsCategoryId = 14; pg.nsTemplateId = 10; } function setRedirs() { var r='redirect'; var R='REDIRECT'; var redirLists={ //<NOLITE> 'ar': [ R, 'تحويل' ], 'be': [ r, 'перанакіраваньне' ], 'bg': [ r, 'пренасочване', 'виж' ], 'bs': [ r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI' ], 'cs': [ R, 'PŘESMĚRUJ' ], 'cy': [ r, 'ail-cyfeirio' ], 'de': [ R, 'WEITERLEITUNG' ], 'el': [ R, 'ΑΝΑΚΑΤΕΥΘΥΝΣΗ'], 'eo': [ R, 'ALIDIREKTU', 'ALIDIREKTI' ], 'es': [ R, 'REDIRECCIÓN' ], 'et': [ r, 'suuna' ], 'ga': [ r, 'athsheoladh' ], 'gl': [ r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'], 'he': [ R, 'הפניה' ], 'hu': [ R, 'ÁTIRÁNYÍTÁS' ], 'is': [ r, 'tilvísun', 'TILVÍSUN' ], 'it': [ R, 'RINVIA', 'Rinvia'], 'ja': [ R, '転送' ], 'mk': [ r, 'пренасочување', 'види' ], 'nds': [ r, 'wiederleiden' ], 'nl': [ R, 'DOORVERWIJZING' ], 'nn': [ r, 'omdiriger' ], 'pl': [ R, 'PATRZ', 'PRZEKIERUJ', 'TAM' ], 'pt': [ R, 'redir' ], 'ru': [ R, 'ПЕРЕНАПРАВЛЕНИЕ', 'ПЕРЕНАПР' ], 'sk': [ r, 'presmeruj' ], 'sr': [ r, 'Преусмери', 'преусмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI' ], 'tt': [ R, 'yünältü', 'перенаправление', 'перенапр' ], 'uk': [ R, 'ПЕРЕНАПРАВЛЕННЯ', 'ПЕРЕНАПР' ], 'vi': [ r, 'đổi' ], 'zh': [ R, '重定向'] // no comma //</NOLITE> }; var redirList=redirLists[ pg.wiki.lang ] || [r, R]; // Mediawiki is very tolerant about what comes after the #redirect at the start pg.re.redirect=RegExp('^\\s*[#](' + redirList.join('|') + ').*?\\[{2}([^\\|\\]]*)(|[^\\]]*)?\\]{2}\\s*(.*)', 'i'); } function setInterwiki() { if (pg.wiki.wikimedia) { // From https://meta.wikimedia.org/wiki/List_of_Wikipedias pg.wiki.interwiki='aa|ab|ace|af|ak|als|am|an|ang|ar|arc|arz|as|ast|av|ay|az|ba|bar|bat-smg|bcl|be|be-x-old|bg|bh|bi|bjn|bm|bn|bo|bpy|br|bs|bug|bxr|ca|cbk-zam|cdo|ce|ceb|ch|cho|chr|chy|ckb|co|cr|crh|cs|csb|cu|cv|cy|da|de|diq|dsb|dv|dz|ee|el|eml|en|eo|es|et|eu|ext|fa|ff|fi|fiu-vro|fj|fo|fr|frp|frr|fur|fy|ga|gag|gan|gd|gl|glk|gn|got|gu|gv|ha|hak|haw|he|hi|hif|ho|hr|hsb|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|ilo|io|is|it|iu|ja|jbo|jv|ka|kaa|kab|kbd|kg|ki|kj|kk|kl|km|kn|ko|koi|kr|krc|ks|ksh|ku|kv|kw|ky|la|lad|lb|lbe|lg|li|lij|lmo|ln|lo|lt|ltg|lv|map-bms|mdf|mg|mh|mhr|mi|mk|ml|mn|mo|mr|mrj|ms|mt|mus|mwl|my|myv|mzn|na|nah|nap|nds|nds-nl|ne|new|ng|nl|nn|no|nov|nrm|nv|ny|oc|om|or|os|pa|pag|pam|pap|pcd|pdc|pfl|pi|pih|pl|pms|pnb|pnt|ps|pt|qu|rm|rmy|rn|ro|roa-rup|roa-tara|ru|rue|rw|sa|sah|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|sn|so|sq|sr|srn|ss|st|stq|su|sv|sw|szl|ta|te|tet|tg|th|ti|tk|tl|tn|to|tpi|tr|ts|tt|tum|tw|ty|udm|ug|uk|ur|uz|ve|vec|vi|vls|vo|wa|war|wo|wuu|xal|xh|yi|yo|za|zea|zh|zh-classical|zh-min-nan|zh-yue|zu'; pg.re.interwiki=RegExp('^'+pg.wiki.interwiki+':'); } else { pg.wiki.interwiki=null; pg.re.interwiki=RegExp('^$'); } } // return a regexp pattern matching all variants to write the given namespace function nsRe(namespaceId) { var imageNamespaceVariants = []; jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) { if (_namespaceId!=namespaceId) return; _localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc); imageNamespaceVariants.push(mw.util.escapeRegExp(_localizedNamespaceLc).split(' ').join('[ _]')); imageNamespaceVariants.push(mw.util.escapeRegExp(encodeURI(_localizedNamespaceLc))); }); return '(?:' + imageNamespaceVariants.join('|') + ')'; } function nsReImage() { return nsRe(pg.nsImageId); } // ENDFILE: namespaces.js // STARTFILE: selpop.js //<NOLITE> function getEditboxSelection() { // see http://www.webgurusforum.com/8/12/0 var editbox; try { editbox=document.editform.wpTextbox1; } catch (dang) { return; } // IE, Opera if (document.selection) { return document.selection.createRange().text; } // Mozilla var selStart = editbox.selectionStart; var selEnd = editbox.selectionEnd; return (editbox.value).substring(selStart, selEnd); } function doSelectionPopup() { // popup if the selection looks like [[foo|anything afterwards at all // or [[foo|bar]]text without ']]' // or [[foo|bar]] var sel=getEditboxSelection(); var open=sel.indexOf('[['); var pipe=sel.indexOf('|'); var close=sel.indexOf(']]'); if (open == -1 || ( pipe == -1 && close == -1) ) { return; } if (pipe != -1 && open > pipe || close != -1 && open > close) { return; } if (getValueOf('popupOnEditSelection')=='boxpreview') { return doSeparateSelectionPopup(sel); } var article=new Title(sel.substring(open+2, (pipe < 0) ? close : pipe)).urlString(); if (close > 0 && sel.substring(close+2).indexOf('[[') >= 0) { return; } var a=document.createElement('a'); a.href=pg.wiki.titlebase + article; mouseOverWikiLink2(a); if (a.navpopup) { a.navpopup.addHook(function(){runStopPopupTimer(a.navpopup);}, 'unhide', 'after'); } } function doSeparateSelectionPopup(str) { var div=document.getElementById('selectionPreview'); if (!div) { div = document.createElement('div'); div.id='selectionPreview'; try { var box=document.editform.wpTextbox1; box.parentNode.insertBefore(div, box); } catch (error) { return; } } div.innerHTML=wiki2html(str); div.ranSetupTooltipsAlready = false; popTipsSoonFn('selectionPreview')(); } //</NOLITE> // ENDFILE: selpop.js // STARTFILE: navpopup.js /** @fileoverview Defines two classes: {@link Navpopup} and {@link Mousetracker}. <code>Navpopup</code> describes popups: when they appear, where, what they look like and so on. <code>Mousetracker</code> "captures" the mouse using <code>document.onmousemove</code>. */ /** Creates a new Mousetracker. @constructor @class The Mousetracker class. This monitors mouse movements and manages associated hooks. */ function Mousetracker() { /** Interval to regularly run the hooks anyway, in milliseconds. @type Integer */ this.loopDelay=400; /** Timer for the loop. @type Timer */ this.timer=null; /** Flag - are we switched on? @type Boolean */ this.active=false; /** Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position? */ this.dirty=true; /** Array of hook functions. @private @type Array */ this.hooks=[]; } /** Adds a hook, to be called when we get events. @param {Function} f A function which is called as <code>f(x,y)</code>. It should return <code>true</code> when it wants to be removed, and <code>false</code> otherwise. */ Mousetracker.prototype.addHook = function (f) { this.hooks.push(f); }; /** Runs hooks, passing them the x and y coords of the mouse. Hook functions that return true are passed to {@link Mousetracker#removeHooks} for removal. @private */ Mousetracker.prototype.runHooks = function () { if (!this.hooks || !this.hooks.length) { return; } //log('Mousetracker.runHooks; we got some hooks to run'); var remove=false; var removeObj={}; // this method gets called a LOT - // pre-cache some variables var x=this.x, y=this.y, len = this.hooks.length; for (var i=0; i<len; ++i) { //~ run the hook function, and remove it if it returns true if (this.hooks[i](x, y)===true) { remove=true; removeObj[i]=true; } } if (remove) { this.removeHooks(removeObj); } }; /** Removes hooks. @private @param {Object} removeObj An object whose keys are the index numbers of functions for removal, with values that evaluate to true */ Mousetracker.prototype.removeHooks = function(removeObj) { var newHooks=[]; var len = this.hooks.length; for (var i=0; i<len; ++i) { if (! removeObj[i]) { newHooks.push(this.hooks[i]); } } this.hooks=newHooks; }; /** Event handler for mouse wiggles. We simply grab the event, set x and y and run the hooks. This makes the cpu all hot and bothered :-( @private @param {Event} e Mousemove event */ Mousetracker.prototype.track=function (e) { //~ Apparently this is needed in IE. e = e || window.event; var x, y; if (e) { if (e.pageX) { x=e.pageX; y=e.pageY; } else if (typeof e.clientX!='undefined') { var left, top, docElt = document.documentElement; if (docElt) { left=docElt.scrollLeft; } left = left || document.body.scrollLeft || document.scrollLeft || 0; if (docElt) { top=docElt.scrollTop; } top = top || document.body.scrollTop || document.scrollTop || 0; x=e.clientX + left; y=e.clientY + top; } else { return; } this.setPosition(x,y); } }; /** Sets the x and y coordinates stored and takes appropriate action, running hooks as appropriate. @param {Integer} x, y Screen coordinates to set */ Mousetracker.prototype.setPosition=function(x,y) { this.x = x; this.y = y; if (this.dirty || this.hooks.length === 0) { this.dirty=false; return; } if (typeof this.lastHook_x != 'number') { this.lastHook_x = -100; this.lastHook_y=-100; } var diff = (this.lastHook_x - x)*(this.lastHook_y - y); diff = (diff >= 0) ? diff : -diff; if ( diff > 1 ) { this.lastHook_x=x; this.lastHook_y=y; if (this.dirty) { this.dirty = false; } else { this.runHooks(); } } }; /** Sets things in motion, unless they are already that is, registering an event handler on <code>document.onmousemove</code>. A half-hearted attempt is made to preserve the old event handler if there is one. */ Mousetracker.prototype.enable = function () { if (this.active) { return; } this.active=true; //~ Save the current handler for mousemove events. This isn't too //~ robust, of course. this.savedHandler=document.onmousemove; //~ Gotta save @tt{this} again for the closure, and use apply for //~ the member function. var savedThis=this; document.onmousemove=function (e) {savedThis.track.apply(savedThis, [e]);}; if (this.loopDelay) { this.timer = setInterval(function() { //log('loop delay in mousetracker is working'); savedThis.runHooks();}, this.loopDelay); } }; /** Disables the tracker, removing the event handler. */ Mousetracker.prototype.disable = function () { if (!this.active) { return; } if (typeof this.savedHandler === 'function') { document.onmousemove=this.savedHandler; } else { delete document.onmousemove; } if (this.timer) { clearInterval(this.timer); } this.active=false; }; /** Creates a new Navpopup. Gets a UID for the popup and @param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable. @constructor @class The Navpopup class. This generates popup hints, and does some management of them. */ function Navpopup(/*init*/) { //alert('new Navpopup(init)'); /** UID for each Navpopup instance. Read-only. @type integer */ this.uid=Navpopup.uid++; /** Read-only flag for current visibility of the popup. @type boolean @private */ this.visible=false; /** Flag to be set when we want to cancel a previous request to show the popup in a little while. @private @type boolean */ this.noshow=false; /** Categorised list of hooks. @see #runHooks @see #addHook @private @type Object */ this.hooks={ 'create': [], 'unhide': [], 'hide': [] }; /** list of unique IDs of hook functions, to avoid duplicates @private */ this.hookIds={}; /** List of downloads associated with the popup. @private @type Array */ this.downloads=[]; /** Number of uncompleted downloads. @type integer */ this.pending=null; /** Tolerance in pixels when detecting whether the mouse has left the popup. @type integer */ this.fuzz=5; /** Flag to toggle running {@link #limitHorizontalPosition} to regulate the popup's position. @type boolean */ this.constrained=true; /** The popup width in pixels. @private @type integer */ this.width=0; /** The popup width in pixels. @private @type integer */ this.height=0; /** The main content DIV element. @type HTMLDivElement */ this.mainDiv=null; this.createMainDiv(); // if (!init || typeof init.popups_draggable=='undefined' || init.popups_draggable) { // this.makeDraggable(true); // } } /** A UID for each Navpopup. This constructor property is just a counter. @type integer @private */ Navpopup.uid=0; /** Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible. @type boolean */ Navpopup.prototype.isVisible=function() { return this.visible; }; /** Repositions popup using CSS style. @private @param {integer} x x-coordinate (px) @param {integer} y y-coordinate (px) @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition} */ Navpopup.prototype.reposition= function (x,y, noLimitHor) { log ('reposition('+x+','+y+','+noLimitHor+')'); if (typeof x != 'undefined' && x !== null) { this.left=x; } if (typeof y != 'undefined' && y !== null) { this.top=y; } if (typeof this.left != 'undefined' && typeof this.top != 'undefined') { this.mainDiv.style.left=this.left + 'px'; this.mainDiv.style.top=this.top + 'px'; } if (!noLimitHor) { this.limitHorizontalPosition(); } //console.log('navpop'+this.uid+' - (left,top)=(' + this.left + ',' + this.top + '), css=(' //+ this.mainDiv.style.left + ',' + this.mainDiv.style.top + ')'); }; /** Prevents popups from being in silly locations. Hopefully. Should not be run if {@link #constrained} is true. @private */ Navpopup.prototype.limitHorizontalPosition=function() { if (!this.constrained || this.tooWide) { return; } this.updateDimensions(); var x=this.left; var w=this.width; var cWidth=document.body.clientWidth; // log('limitHorizontalPosition: x='+x+ // ', this.left=' + this.left + // ', this.width=' + this.width + // ', cWidth=' + cWidth); if ( (x+w) >= cWidth || ( x > 0 && this.maxWidth && this.width < this.maxWidth && this.height > this.width && x > cWidth - this.maxWidth ) ) { // This is a very nasty hack. There has to be a better way! // We find the "natural" width of the div by positioning it at the far left // then reset it so that it should be flush right (well, nearly) this.mainDiv.style.left='-10000px'; this.mainDiv.style.width = this.maxWidth + 'px'; var naturalWidth=parseInt(this.mainDiv.offsetWidth, 10); var newLeft=cWidth - naturalWidth - 1; if (newLeft < 0) { newLeft = 0; this.tooWide=true; } // still unstable for really wide popups? log ('limitHorizontalPosition: moving to ('+newLeft + ','+ this.top+');' + ' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth); this.reposition(newLeft, null, true); } }; /** Counter indicating the z-order of the "highest" popup. We start the z-index at 1000 so that popups are above everything else on the screen. @private @type integer */ Navpopup.highest=1000; /** Brings popup to the top of the z-order. We increment the {@link #highest} property of the contructor here. @private */ Navpopup.prototype.raise = function () { this.mainDiv.style.zIndex=Navpopup.highest + 1; ++Navpopup.highest; }; /** Shows the popup provided {@link #noshow} is not true. Updates the position, brings the popup to the top of the z-order and unhides it. */ Navpopup.prototype.show = function () { //document.title+='s'; if (this.noshow) { return; } //document.title+='t'; this.reposition(); this.raise(); this.unhide(); }; /** Checks to see if the mouse pointer has stabilised (checking every <code>time</code>/2 milliseconds) and runs the {@link #show} method if it has. @param {integer} time The minimum time (ms) before the popup may be shown. */ Navpopup.prototype.showSoonIfStable = function (time) { log ('showSoonIfStable, time='+time); if (this.visible) { return; } this.noshow = false; //~ initialize these variables so that we never run @tt{show} after //~ just half the time this.stable_x = -10000; this.stable_y = -10000; var stableShow = function() { log('stableShow called'); var new_x = Navpopup.tracker.x, new_y = Navpopup.tracker.y; var dx = savedThis.stable_x - new_x, dy = savedThis.stable_y - new_y; var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz; //document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] '; if ( dx * dx <= fuzz2 && dy * dy <= fuzz2 ) { log ('mouse is stable'); clearInterval(savedThis.showSoonStableTimer); savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]); savedThis.show.apply(savedThis, []); savedThis.limitHorizontalPosition.apply(savedThis, []); return; } savedThis.stable_x = new_x; savedThis.stable_y = new_y; }; var savedThis = this; this.showSoonStableTimer = setInterval(stableShow, time/2); }; /** Sets the {@link #noshow} flag and hides the popup. This should be called when the mouse leaves the link before (or after) it's actually been displayed. */ Navpopup.prototype.banish = function () { log ('banish called'); // hide and prevent showing with showSoon in the future this.noshow=true; if (this.showSoonStableTimer) { log('clearing showSoonStableTimer'); clearInterval(this.showSoonStableTimer); } this.hide(); }; /** Runs hooks added with {@link #addHook}. @private @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide' @param {String} when Controls exactly when the hook is run: either 'before' or 'after' */ Navpopup.prototype.runHooks = function (key, when) { if (!this.hooks[key]) { return; } var keyHooks=this.hooks[key]; var len=keyHooks.length; for (var i=0; i< len; ++i) { if (keyHooks[i] && keyHooks[i].when == when) { if (keyHooks[i].hook.apply(this, [])) { // remove the hook if (keyHooks[i].hookId) { delete this.hookIds[keyHooks[i].hookId]; } keyHooks[i]=null; } } } }; /** Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the Navpopup instance, and no arguments. @param {Function} hook The hook function. Functions that return true are deleted. @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide' @param {String} when Controls exactly when the hook is run: either 'before' or 'after' @param {String} uid A truthy string identifying the hook function; if it matches another hook in this position, it won't be added again. */ Navpopup.prototype.addHook = function ( hook, key, when, uid ) { when = when || 'after'; if (!this.hooks[key]) { return; } // if uid is specified, don't add duplicates var hookId=null; if (uid) { hookId=[key,when,uid].join('|'); if (this.hookIds[hookId]) { return; } this.hookIds[hookId]=true; } this.hooks[key].push( {hook: hook, when: when, hookId: hookId} ); }; /** Creates the main DIV element, which contains all the actual popup content. Runs hooks with key 'create'. @private */ Navpopup.prototype.createMainDiv = function () { if (this.mainDiv) { return; } this.runHooks('create', 'before'); var mainDiv=document.createElement('div'); var savedThis=this; mainDiv.onclick=function(e) {savedThis.onclickHandler(e);}; mainDiv.className=(this.className) ? this.className : 'navpopup_maindiv'; mainDiv.id=mainDiv.className + this.uid; mainDiv.style.position='absolute'; mainDiv.style.minWidth = '350px'; mainDiv.style.display='none'; mainDiv.className='navpopup'; // easy access to javascript object through DOM functions mainDiv.navpopup=this; this.mainDiv=mainDiv; document.body.appendChild(mainDiv); this.runHooks('create', 'after'); }; /** Calls the {@link #raise} method. @private */ Navpopup.prototype.onclickHandler=function(/*e*/) { this.raise(); }; /** Makes the popup draggable, using a {@link Drag} object. @private */ Navpopup.prototype.makeDraggable=function(handleName) { if (!this.mainDiv) { this.createMainDiv(); } var drag=new Drag(); if (!handleName) { drag.startCondition=function(e) { try { if (!e.shiftKey) { return false; } } catch (err) { return false; } return true; }; } var dragHandle; if (handleName) dragHandle = document.getElementById(handleName); if (!dragHandle) dragHandle = this.mainDiv; var np=this; drag.endHook=function(x,y) { Navpopup.tracker.dirty=true; np.reposition(x,y); }; drag.init(dragHandle,this.mainDiv); }; /** Hides the popup using CSS. Runs hooks with key 'hide'. Sets {@link #visible} appropriately. {@link #banish} should be called externally instead of this method. @private */ Navpopup.prototype.hide = function () { this.runHooks('hide', 'before'); this.abortDownloads(); if (typeof this.visible != 'undefined' && this.visible) { this.mainDiv.style.display='none'; this.visible=false; } this.runHooks('hide', 'after'); }; /** Shows the popup using CSS. Runs hooks with key 'unhide'. Sets {@link #visible} appropriately. {@link #show} should be called externally instead of this method. @private */ Navpopup.prototype.unhide = function () { this.runHooks('unhide', 'before'); if (typeof this.visible != 'undefined' && !this.visible) { this.mainDiv.style.display='inline'; this.visible=true; } this.runHooks('unhide', 'after'); }; /** Sets the <code>innerHTML</code> attribute of the main div containing the popup content. @param {String} html The HTML to set. */ Navpopup.prototype.setInnerHTML = function (html) { this.mainDiv.innerHTML = html; }; /** Updates the {@link #width} and {@link #height} attributes with the CSS properties. @private */ Navpopup.prototype.updateDimensions = function () { this.width=parseInt(this.mainDiv.offsetWidth, 10); this.height=parseInt(this.mainDiv.offsetHeight, 10); }; /** Checks if the point (x,y) is within {@link #fuzz} of the {@link #mainDiv}. @param {integer} x x-coordinate (px) @param {integer} y y-coordinate (px) @type boolean */ Navpopup.prototype.isWithin = function(x,y) { //~ If we're not even visible, no point should be considered as //~ being within the popup. if (!this.visible) { return false; } this.updateDimensions(); var fuzz=this.fuzz || 0; //~ Use a simple box metric here. return (x+fuzz >= this.left && x-fuzz <= this.left + this.width && y+fuzz >= this.top && y-fuzz <= this.top + this.height); }; /** Adds a download to {@link #downloads}. @param {Downloader} download */ Navpopup.prototype.addDownload=function(download) { if (!download) { return; } this.downloads.push(download); }; /** Aborts the downloads listed in {@link #downloads}. @see Downloader#abort */ Navpopup.prototype.abortDownloads=function() { for(var i=0; i<this.downloads.length; ++i) { var d=this.downloads[i]; if (d && d.abort) { d.abort(); } } this.downloads=[]; }; /** A {@link Mousetracker} instance which is a property of the constructor (pseudo-global). */ Navpopup.tracker=new Mousetracker(); // ENDFILE: navpopup.js // STARTFILE: diff.js //<NOLITE> /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) and [[:en:User:Lupin]] * * More Info: * http://ejohn.org/projects/javascript-diff-algorithm/ */ function delFmt(x) { if (!x.length) { return ''; } return "<del class='popupDiff'>" + x.join('') +"</del>"; } function insFmt(x) { if (!x.length) { return ''; } return "<ins class='popupDiff'>" + x.join('') +"</ins>"; } function countCrossings(a, b, i, eject) { // count the crossings on the edge starting at b[i] if (!b[i].row && b[i].row !== 0) { return -1; } var count=0; for (var j=0; j<a.length; ++j) { if (!a[j].row && a[j].row !== 0) { continue; } if ( (j-b[i].row)*(i-a[j].row) > 0) { if(eject) { return true; } count++; } } return count; } function shortenDiffString(str, context) { var re=RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)'); var splitted=str.parenSplit(re); var ret=['']; for (var i=0; i<splitted.length; i+=2) { if (splitted[i].length < 2*context) { ret[ret.length-1] += splitted[i]; if (i+1<splitted.length) { ret[ret.length-1] += splitted[i+1]; } continue; } else { if (i > 0) { ret[ret.length-1] += splitted[i].substring(0,context); } if (i+1 < splitted.length) { ret.push(splitted[i].substring(splitted[i].length-context) + splitted[i+1]); } } } while (ret.length > 0 && !ret[0]) { ret = ret.slice(1); } return ret; } function diffString( o, n, simpleSplit ) { var splitRe=RegExp('([[]{2}|[\\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\\s|\\b)'); // We need to split the strings o and n first, and entify() the parts // individually, so that the HTML entities are never cut apart. (AxelBoldt) var out, i, oSplitted, nSplitted; if (simpleSplit) { oSplitted=o.split(/\b/); nSplitted=n.split(/\b/); } else { oSplitted=o.parenSplit(splitRe); nSplitted=n.parenSplit(splitRe); } for (i=0; i<oSplitted.length; ++i) {oSplitted[i]=oSplitted[i].entify();} for (i=0; i<nSplitted.length; ++i) {nSplitted[i]=nSplitted[i].entify();} out = diff (oSplitted, nSplitted); var str = ""; var acc=[]; // accumulator for prettier output // crossing pairings -- eg 'A B' vs 'B A' -- cause problems, so let's iron them out // this doesn't always do things optimally but it should be fast enough var maxOutputPair=0; for (i=0; i<out.n.length; ++i) { if ( out.n[i].paired ) { if( maxOutputPair > out.n[i].row ) { // tangle - delete pairing out.o[ out.n[i].row ]=out.o[ out.n[i].row ].text; out.n[i]=out.n[i].text; } if (maxOutputPair < out.n[i].row) { maxOutputPair = out.n[i].row; } } } // output the stuff preceding the first paired old line for (i=0; i<out.o.length && !out.o[i].paired; ++i) { acc.push( out.o[i] ); } str += delFmt(acc); acc=[]; // main loop for ( i = 0; i < out.n.length; ++i ) { // output unpaired new "lines" while ( i < out.n.length && !out.n[i].paired ) { acc.push( out.n[i++] ); } str += insFmt(acc); acc=[]; if ( i < out.n.length ) { // this new "line" is paired with the (out.n[i].row)th old "line" str += out.n[i].text; // output unpaired old rows starting after this new line's partner var m = out.n[i].row + 1; while ( m < out.o.length && !out.o[m].paired ) { acc.push ( out.o[m++] ); } str += delFmt(acc); acc=[]; } } return str; } // see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object // FIXME: use obj.hasOwnProperty instead of this kludge! var jsReservedProperties=RegExp('^(constructor|prototype|__((define|lookup)[GS]etter)__' + '|eval|hasOwnProperty|propertyIsEnumerable' + '|to(Source|String|LocaleString)|(un)?watch|valueOf)$'); function diffBugAlert(word) { if (!diffBugAlert.list[word]) { diffBugAlert.list[word]=1; alert('Bad word: '+word+'\n\nPlease report this bug.'); } } diffBugAlert.list={}; function makeDiffHashtable(src) { var ret={}; for ( var i = 0; i < src.length; i++ ) { if ( jsReservedProperties.test(src[i]) ) { src[i] += '<!-- -->'; } if ( !ret[ src[i] ] ) { ret[ src[i] ] = []; } try { ret[ src[i] ].push( i ); } catch (err) { diffBugAlert(src[i]); } } return ret; } function diff( o, n ) { // pass 1: make hashtable ns with new rows as keys var ns = makeDiffHashtable(n); // pass 2: make hashtable os with old rows as keys var os = makeDiffHashtable(o); // pass 3: pair unique new rows and matching unique old rows var i; for ( i in ns ) { if ( ns[i].length == 1 && os[i] && os[i].length == 1 ) { n[ ns[i][0] ] = { text: n[ ns[i][0] ], row: os[i][0], paired: true }; o[ os[i][0] ] = { text: o[ os[i][0] ], row: ns[i][0], paired: true }; } } // pass 4: pair matching rows immediately following paired rows (not necessarily unique) for ( i = 0; i < n.length - 1; i++ ) { if ( n[i].paired && ! n[i+1].paired && n[i].row + 1 < o.length && ! o[ n[i].row + 1 ].paired && n[i+1] == o[ n[i].row + 1 ] ) { n[i+1] = { text: n[i+1], row: n[i].row + 1, paired: true }; o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1, paired: true }; } } // pass 5: pair matching rows immediately preceding paired rows (not necessarily unique) for ( i = n.length - 1; i > 0; i-- ) { if ( n[i].paired && ! n[i-1].paired && n[i].row > 0 && ! o[ n[i].row - 1 ].paired && n[i-1] == o[ n[i].row - 1 ] ) { n[i-1] = { text: n[i-1], row: n[i].row - 1, paired: true }; o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1, paired: true }; } } return { o: o, n: n }; } //</NOLITE> // ENDFILE: diff.js // STARTFILE: init.js function setSiteInfo() { if (window.popupLocalDebug) { pg.wiki.hostname = 'en.wikipedia.org'; } else { pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?) } pg.wiki.wikimedia=RegExp('(wiki([pm]edia|source|books|news|quote|versity)|wiktionary|mediawiki)[.]org').test(pg.wiki.hostname); pg.wiki.wikia=RegExp('[.]wikia[.]com$', 'i').test(pg.wiki.hostname); pg.wiki.isLocal=RegExp('^localhost').test(pg.wiki.hostname); pg.wiki.commons=( pg.wiki.wikimedia && pg.wiki.hostname != 'commons.wikimedia.org') ? 'commons.wikimedia.org' : null; pg.wiki.lang = mw.config.get('wgContentLanguage'); var port = location.port ? ':' + location.port : ''; pg.wiki.sitebase = pg.wiki.hostname + port; } function setUserInfo() { var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'query', list: 'users', ususers: mw.config.get('wgUserName'), usprop: 'rights' }; pg.user.canReview = false; if (getValueOf('popupReview')) { api.get(params).done(function(data){ var rights = data.query.users[0].rights; pg.user.canReview = rights.indexOf('review') !== -1; // TODO: Should it be a getValueOf('ReviewRight') ? }); } } function setTitleBase() { var protocol = ( window.popupLocalDebug ? 'http:' : location.protocol ); pg.wiki.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, ""); // as in http://some.thing.com/wiki/Article pg.wiki.botInterfacePath = mw.config.get('wgScript'); pg.wiki.APIPath = mw.config.get('wgScriptPath') +"/api.php"; // default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo var titletail = pg.wiki.botInterfacePath + '?title='; //var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?title=']); // other sites may need to add code here to set titletail depending on how their urls work pg.wiki.titlebase = protocol + '//' + pg.wiki.sitebase + titletail; //pg.wiki.titlebase2 = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]); pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath; pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath; pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath; pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.botInterfacePath; pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.APIPath; pg.re.basenames = RegExp( '^(' + map( literalizeRegex, [ pg.wiki.titlebase, //pg.wiki.titlebase2, pg.wiki.articlebase ]).join('|') + ')' ); } ////////////////////////////////////////////////// // Global regexps function setMainRegex() { var reStart='[^:]*://'; var preTitles = literalizeRegex( mw.config.get('wgScriptPath') ) + '/(?:index[.]php|wiki[.]phtml)[?]title='; preTitles += '|' + literalizeRegex( pg.wiki.articlePath + '/' ); var reEnd='(' + preTitles + ')([^&?#]*)[^#]*(?:#(.+))?'; pg.re.main = RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd); } function setRegexps() { // TODO: We shoud use an api call to get the aliases for special pages, now it does not work for non-English wikipedias: // E.g., https://ru.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=specialpagealiases&formatversion=2 setMainRegex(); var sp=nsRe(pg.nsSpecialId); pg.re.urlNoPopup=RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)') ; pg.re.contribs =RegExp('(title=|/)' + sp + '(?:%3A|:)Contributions' + '(&target=|/|/' + nsRe(pg.nsUserId)+':)(.*)') ; pg.re.email =RegExp('(title=|/)' + sp + '(?:%3A|:)EmailUser' + '(&target=|/|/(?:' + nsRe(pg.nsUserId)+':)?)(.*)') ; pg.re.backlinks =RegExp('(title=|/)' + sp + '(?:%3A|:)WhatLinksHere' + '(&target=|/)([^&]*)'); pg.re.specialdiff=RegExp('/' + sp + '(?:%3A|:)Diff/([^?#]*)'); //<NOLITE> var im=nsReImage(); // note: tries to get images in infobox templates too, e.g. movie pages, album pages etc // (^|\[\[)image: *([^|\]]*[^|\] ]) * // (^|\[\[)image: *([^|\]]*[^|\] ])([^0-9\]]*([0-9]+) *px)? // $4 = 120 as in 120px pg.re.image = RegExp('(^|\\[\\[)' + im + ': *([^|\\]]*[^|\\] ])' + '([^0-9\\]]*([0-9]+) *px)?|(?:\\n *[|]?|[|]) *' + '(' + getValueOf('popupImageVarsRegexp') + ')' + ' *= *(?:\\[\\[ *)?(?:' + im + ':)?' + '([^|]*?)(?:\\]\\])? *[|]? *\\n', 'img') ; pg.re.imageBracketCount = 6; pg.re.category = RegExp('\\[\\[' +nsRe(pg.nsCategoryId) + ': *([^|\\]]*[^|\\] ]) *', 'i'); pg.re.categoryBracketCount = 1; pg.re.ipUser=RegExp('^' + // IPv6 '(?::(?::|(?::[0-9A-Fa-f]{1,4}){1,7})|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,6}::|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){7})' + // IPv4 '|(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' + '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$'); pg.re.stub= RegExp(getValueOf('popupStubRegexp'), 'im'); pg.re.disambig=RegExp(getValueOf('popupDabRegexp'), 'im'); //</NOLITE> // FIXME replace with general parameter parsing function, this is daft pg.re.oldid=RegExp('[?&]oldid=([^&]*)'); pg.re.diff=RegExp('[?&]diff=([^&]*)'); } ////////////////////////////////////////////////// // miscellany function setupCache() { // page caching pg.cache.pages = []; } function setMisc() { pg.current.link=null; pg.current.links=[]; pg.current.linksHash={}; setupCache(); pg.timer.checkPopupPosition=null; pg.counter.loop=0; // ids change with each popup: popupImage0, popupImage1 etc pg.idNumber=0; // for myDecodeURI pg.misc.decodeExtras = [ {from: '%2C', to: ',' }, {from: '_', to: ' ' }, {from: '%24', to: '$'}, {from: '%26', to: '&' } // no , ]; pg.misc.userAgent = 'Navigation popups/1.0 (' + mw.config.get( 'wgServerName' ) +')'; } // We need a callback since this might end up asynchronous because of // the mw.loader.using() call. function setupPopups( callback ) { if ( setupPopups.completed ) { if ( typeof callback === 'function' ) { callback(); } return; } // These dependencies are also enforced from the gadget, // but not everyone loads this as a gadget, so double check mw.loader.using( ['mediawiki.util', 'mediawiki.user', 'user.options'] ).then( function() { // NB translatable strings should be set up first (strings.js) // basics setupDebugging(); setSiteInfo(); setTitleBase(); setOptions(); // see options.js setUserInfo(); // namespaces etc setNamespaces(); setInterwiki(); // regexps setRegexps(); setRedirs(); // other stuff setMisc(); setupLivePreview(); // main deal here setupTooltips(); log('In setupPopups(), just called setupTooltips()'); Navpopup.tracker.enable(); setupPopups.completed = true; if ( typeof callback === 'function' ) { callback(); } }); } // ENDFILE: init.js // STARTFILE: navlinks.js //<NOLITE> ////////////////////////////////////////////////// // navlinks... let the fun begin // function defaultNavlinkSpec() { var str=''; str += '<b><<mainlink|shortcut= >></b>'; if (getValueOf('popupLastEditLink')) { str += '*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}'; } // user links // contribs - log - count - email - block // count only if applicable; block only if popupAdminLinks str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>'; str+='if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}'; // editing links // talkpage -> edit|new - history - un|watch - article|edit // other page -> edit - history - un|watch - talk|edit|new var editstr='<<edit|shortcut=e>>'; var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'; var historystr='<<history|shortcut=h>>|<<editors|shortcut=E|>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; str+='<br>if(talk){' + editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + // not a talk page editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}'; // misc links str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>'; // admin links str += 'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}'; return str; } function navLinksHTML (article, hint, params) { //oldid, rcid) { var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>'; // BAM return navlinkStringToHTML(str, article, params); } function expandConditionalNavlinkString(s,article,z,recursionCount) { var oldid=z.oldid, rcid=z.rcid, diff=z.diff; // nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out) if (typeof recursionCount!=typeof 0) { recursionCount=0; } var conditionalSplitRegex=RegExp( //(1 if \\( (2 2) \\) {(3 3)} (4 else {(5 5)} 4)1) '(;?\\s*if\\s*\\(\\s*([\\w]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))', 'i'); var splitted=s.parenSplit(conditionalSplitRegex); // $1: whole conditional // $2: test condition // $3: true expansion // $4: else clause (possibly empty) // $5: false expansion (possibly null) var numParens=5; var ret = splitted[0]; for (var i=1; i<splitted.length; i=i+numParens+1) { var testString=splitted[i+2-1]; var trueString=splitted[i+3-1]; var falseString=splitted[i+5-1]; if (typeof falseString=='undefined' || !falseString) { falseString=''; } var testResult=null; switch (testString) { case 'user': testResult=(article.userName())?true:false; break; case 'talk': testResult=(article.talkPage())?false:true; // talkPage converts _articles_ to talkPages break; case 'admin': testResult=getValueOf('popupAdminLinks')?true:false; break; case 'oldid': testResult=(typeof oldid != 'undefined' && oldid)?true:false; break; case 'rcid': testResult=(typeof rcid != 'undefined' && rcid)?true:false; break; case 'ipuser': testResult=(article.isIpUser())?true:false; break; case 'mainspace_en': testResult=isInMainNamespace(article) && pg.wiki.hostname=='en.wikipedia.org'; break; case 'wikimedia': testResult=(pg.wiki.wikimedia) ? true : false; break; case 'diff': testResult=(typeof diff != 'undefined' && diff)?true:false; break; } switch(testResult) { case null: ret+=splitted[i]; break; case true: ret+=trueString; break; case false: ret+=falseString; break; } // append non-conditional string ret += splitted[i+numParens]; } if (conditionalSplitRegex.test(ret) && recursionCount < 10) { return expandConditionalNavlinkString(ret,article,z,recursionCount+1); } return ret; } function navlinkStringToArray(s, article, params) { s=expandConditionalNavlinkString(s,article,params); var splitted=s.parenSplit(RegExp('<<(.*?)>>')); var ret=[]; for (var i=0; i<splitted.length; ++i) { if (i%2) { // i odd, so s is a tag var t=new navlinkTag(); var ss=splitted[i].split('|'); t.id=ss[0]; for (var j=1; j<ss.length; ++j) { var sss=ss[j].split('='); if (sss.length>1) { t[sss[0]]=sss[1]; } else { // no assignment (no "="), so treat this as a title (overwriting the last one) t.text=popupString(sss[0]); } } t.article=article; var oldid=params.oldid, rcid=params.rcid, diff=params.diff; if (typeof oldid !== 'undefined' && oldid !== null) { t.oldid=oldid; } if (typeof rcid !== 'undefined' && rcid !== null) { t.rcid=rcid; } if (typeof diff !== 'undefined' && diff !== null) { t.diff=diff; } if (!t.text && t.id !== 'mainlink') { t.text=popupString(t.id); } ret.push(t); } else { // plain HTML ret.push(splitted[i]); } } return ret; } function navlinkSubstituteHTML(s) { return s.split('*').join(getValueOf('popupNavLinkSeparator')) .split('<menurow>').join('<li class="popup_menu_row">') .split('</menurow>').join('</li>') .split('<menu>').join('<ul class="popup_menu">') .split('</menu>').join('</ul>'); } function navlinkDepth(magic,s) { return s.split('<' + magic + '>').length - s.split('</' + magic + '>').length; } // navlinkString: * becomes the separator // <<foo|bar=baz|fubar>> becomes a foo-link with attribute bar='baz' // and visible text 'fubar' // if(test){...} and if(test){...}else{...} work too (nested ok) function navlinkStringToHTML(s,article,params) { //limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article)); var p=navlinkStringToArray(s,article,params); var html=''; var menudepth = 0; // nested menus not currently allowed, but doesn't do any harm to code for it var menurowdepth = 0; for (var i=0; i<p.length; ++i) { if (typeof p[i] == typeof '') { html+=navlinkSubstituteHTML(p[i]); menudepth += navlinkDepth('menu', p[i]); menurowdepth += navlinkDepth('menurow', p[i]); // if (menudepth === 0) { // tagType='span'; // } else if (menurowdepth === 0) { // tagType='li'; // } else { // tagType = null; // } } else if (typeof p[i].type != 'undefined' && p[i].type=='navlinkTag') { if (menudepth > 0 && menurowdepth === 0) { html += '<li class="popup_menu_item">' + p[i].html() + '</li>'; } else { html+=p[i].html(); } } } return html; } function navlinkTag() { this.type='navlinkTag'; } navlinkTag.prototype.html=function () { this.getNewWin(); this.getPrintFunction(); var html=''; var opening, closing; var tagType='span'; if (!tagType) { opening = ''; closing = ''; } else { opening = '<' + tagType + ' class="popup_' + this.id + '">'; closing = '</' + tagType + '>'; } if (typeof this.print!='function') { errlog ('Oh dear - invalid print function for a navlinkTag, id='+this.id); } else { html=this.print(this); if (typeof html != typeof '') {html='';} else if (typeof this.shortcut!='undefined') html=addPopupShortcut(html, this.shortcut); } return opening + html + closing; }; navlinkTag.prototype.getNewWin=function() { getValueOf('popupLinksNewWindow'); if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') { this.newWin=null; } this.newWin=pg.option.popupLinksNewWindow[this.id]; }; navlinkTag.prototype.getPrintFunction=function() { //think about this some more // this.id and this.article should already be defined if (typeof this.id!=typeof '' || typeof this.article!=typeof {} ) { return; } this.noPopup=1; switch (this.id) { case 'contribs': case 'history': case 'whatLinksHere': case 'userPage': case 'monobook': case 'userTalk': case 'talk': case 'article': case 'lastEdit': this.noPopup=null; } switch (this.id) { case 'email': case 'contribs': case 'block': case 'unblock': case 'userlog': case 'userSpace': case 'deletedContribs': this.article=this.article.userName(); } switch (this.id) { case 'userTalk': case 'newUserTalk': case 'editUserTalk': case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog': this.article=this.article.userName(true); /* fall through */ case 'pagelog': case 'deletelog': case 'protectlog': delete this.oldid; } if (this.id=='editMonobook' || this.id=='monobook') { this.article.append('/monobook.js'); } if (this.id != 'mainlink') { // FIXME anchor handling should be done differently with Title object this.article=this.article.removeAnchor(); // if (typeof this.text=='undefined') this.text=popupString(this.id); } switch (this.id) { case 'undelete': this.print=specialLink; this.specialpage='Undelete'; this.sep='/'; break; case 'whatLinksHere': this.print=specialLink; this.specialpage='Whatlinkshere'; break; case 'relatedChanges': this.print=specialLink; this.specialpage='Recentchangeslinked'; break; case 'move': this.print=specialLink; this.specialpage='Movepage'; break; case 'contribs': this.print=specialLink; this.specialpage='Contributions'; break; case 'deletedContribs':this.print=specialLink; this.specialpage='Deletedcontributions'; break; case 'email': this.print=specialLink; this.specialpage='EmailUser'; this.sep='/'; break; case 'block': this.print=specialLink; this.specialpage='Blockip'; this.sep='&ip='; break; case 'unblock': this.print=specialLink; this.specialpage='Ipblocklist'; this.sep='&action=unblock&ip='; break; case 'userlog': this.print=specialLink; this.specialpage='Log'; this.sep='&user='; break; case 'blocklog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=block&page='; break; case 'pagelog': this.print=specialLink; this.specialpage='Log'; this.sep='&page='; break; case 'protectlog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=protect&page='; break; case 'deletelog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=delete&page='; break; case 'userSpace': this.print=specialLink; this.specialpage='PrefixIndex'; this.sep='&namespace=2&prefix='; break; case 'search': this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break; case 'thank': this.print=specialLink; this.specialpage='Thanks'; this.sep='/'; this.article.value = this.diff; break; case 'unwatch': case 'watch': this.print=magicWatchLink; this.action=this.id+'&autowatchlist=1&autoimpl=' + popupString('autoedit_version') + '&actoken='+autoClickToken(); break; case 'history': case 'historyfeed': case 'unprotect': case 'protect': this.print=wikiLink; this.action=this.id; break; case 'delete': this.print=wikiLink; this.action='delete'; if (this.article.namespaceId()==pg.nsImageId) { var img=this.article.stripNamespace(); this.action+='&image='+img; } break; case 'markpatrolled': case 'edit': // editOld should keep the oldid, but edit should not. delete this.oldid; /* fall through */ case 'view': case 'purge': case 'render': this.print=wikiLink; this.action=this.id; break; case 'raw': this.print=wikiLink; this.action='raw'; break; case 'new': this.print=wikiLink; this.action='edit&section=new'; break; case 'mainlink': if (typeof this.text=='undefined') { this.text=this.article.toString().entify(); } if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) { // only show the /subpage part of the title text var s=this.text.split('/'); this.text=s[s.length-1]; if (this.text==='' && s.length > 1) { this.text=s[s.length-2]; } } this.print=titledWikiLink; if (typeof this.title==='undefined' && pg.current.link && typeof pg.current.link.href !== 'undefined') { this.title=safeDecodeURI((pg.current.link.originalTitle)?pg.current.link.originalTitle:this.article); if (typeof this.oldid !== 'undefined' && this.oldid) { this.title=tprintf('Revision %s of %s', [this.oldid, this.title]); } } this.action='view'; break; case 'userPage': case 'article': case 'monobook': case 'editMonobook': case 'editArticle': delete this.oldid; //alert(this.id+'\n'+this.article + '\n'+ typeof this.article); this.article=this.article.articleFromTalkOrArticle(); //alert(this.id+'\n'+this.article + '\n'+ typeof this.article); this.print=wikiLink; if (this.id.indexOf('edit')===0) { this.action='edit'; } else { this.action='view';} break; case 'userTalk': case 'talk': this.article=this.article.talkPage(); delete this.oldid; this.print=wikiLink; this.action='view'; break; case 'arin': this.print=arinLink; break; case 'count': this.print=editCounterLink; break; case 'google': this.print=googleLink; break; case 'editors': this.print=editorListLink; break; case 'globalsearch': this.print=globalSearchLink; break; case 'lastEdit': this.print=titledDiffLink; this.title=popupString('Show the last edit'); this.from='prev'; this.to='cur'; break; case 'oldEdit': this.print=titledDiffLink; this.title=popupString('Show the edit made to get revision') + ' ' + this.oldid; this.from='prev'; this.to=this.oldid; break; case 'editOld': this.print=wikiLink; this.action='edit'; break; case 'undo': this.print=wikiLink; this.action='edit&undo='; break; case 'revert': this.print=wikiLink; this.action='revert'; break; case 'nullEdit': this.print=wikiLink; this.action='nullEdit'; break; case 'diffCur': this.print=titledDiffLink; this.title=tprintf('Show changes since revision %s', [this.oldid]); this.from=this.oldid; this.to='cur'; break; case 'editUserTalk': case 'editTalk': delete this.oldid; this.article=this.article.talkPage(); this.action='edit'; this.print=wikiLink; break; case 'newUserTalk': case 'newTalk': this.article=this.article.talkPage(); this.action='edit&section=new'; this.print=wikiLink; break; case 'lastContrib': case 'sinceMe': this.print=magicHistoryLink; break; case 'togglePreviews': this.text=popupString(pg.option.simplePopups ? 'enable previews' : 'disable previews'); /* fall through */ case 'disablePopups': case 'purgePopups': this.print=popupMenuLink; break; default: this.print=function () {return 'Unknown navlink type: '+this.id+'';}; } }; // // end navlinks ////////////////////////////////////////////////// //</NOLITE> // ENDFILE: navlinks.js // STARTFILE: shortcutkeys.js //<NOLITE> function popupHandleKeypress(evt) { var keyCode = window.event ? window.event.keyCode : ( evt.keyCode ? evt.keyCode : evt.which); if (!keyCode || !pg.current.link || !pg.current.link.navpopup) { return; } if (keyCode==27) { // escape killPopup(); return false; // swallow keypress } var letter=String.fromCharCode(keyCode); var links=pg.current.link.navpopup.mainDiv.getElementsByTagName('A'); var startLink=0; var i,j; if (popupHandleKeypress.lastPopupLinkSelected) { for (i=0; i<links.length; ++i) { if (links[i]==popupHandleKeypress.lastPopupLinkSelected) { startLink=i; } } } for (j=0; j<links.length; ++j) { i=(startLink + j + 1) % links.length; if (links[i].getAttribute('popupkey')==letter) { if (evt && evt.preventDefault) evt.preventDefault(); links[i].focus(); popupHandleKeypress.lastPopupLinkSelected=links[i]; return false; // swallow keypress } } // pass keypress on if (document.oldPopupOnkeypress) { return document.oldPopupOnkeypress(evt); } return true; } function addPopupShortcuts() { if (document.onkeypress!=popupHandleKeypress) { document.oldPopupOnkeypress=document.onkeypress; } document.onkeypress=popupHandleKeypress; } function rmPopupShortcuts() { popupHandleKeypress.lastPopupLinkSelected=null; try { if (document.oldPopupOnkeypress && document.oldPopupOnkeypress==popupHandleKeypress) { // panic document.onkeypress=null; //function () {}; return; } document.onkeypress=document.oldPopupOnkeypress; } catch (nasties) { /* IE goes here */ } } function addLinkProperty(html, property) { // take "<a href=...>...</a> and add a property // not sophisticated at all, easily broken var i=html.indexOf('>'); if (i<0) { return html; } return html.substring(0,i) + ' ' + property + html.substring(i); } function addPopupShortcut(html, key) { if (!getValueOf('popupShortcutKeys')) { return html; } var ret= addLinkProperty(html, 'popupkey="'+key+'"'); if (key==' ') { key=popupString('spacebar'); } return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'),'$1$2$3 ['+key+']$4'); } //</NOLITE> // ENDFILE: shortcutkeys.js // STARTFILE: diffpreview.js //<NOLITE> //lets jump through hoops to find the rev ids we need to retrieve function loadDiff(article, oldid, diff, navpop) { navpop.diffData={ oldRev: {}, newRev: {} }; mw.loader.using( 'mediawiki.api' ).then( function() { var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'compare', prop: 'ids|title' }; if(article.title){ params.fromtitle = article.title; } switch (diff) { case 'cur': switch ( oldid ) { case null: case '': case 'prev': // this can only work if we have the title // cur -> prev params.torelative = 'prev'; break; default: params.fromrev = oldid; params.torelative = 'cur'; break; } break; case 'prev': if( oldid ) { params.fromrev = oldid; } else { params.fromtitle; } params.torelative = 'prev'; break; case 'next': params.fromrev = oldid || 0; params.torelative = 'next'; break; default: params.fromrev = oldid || 0; params.torev = diff || 0; break; } api.get( params ).then( function( data ) { navpop.diffData.oldRev.revid = data.compare.fromrevid; navpop.diffData.newRev.revid = data.compare.torevid; addReviewLink(navpop, 'popupMiscTools'); var go = function() { pendingNavpopTask(navpop); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&'; url += 'revids=' + navpop.diffData.oldRev.revid + '|' + navpop.diffData.newRev.revid; url += '&prop=revisions&rvprop=ids|timestamp|content'; getPageWithCaching(url, doneDiff, navpop); return true; // remove hook once run }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS'); } } ); } ); } // Put a "mark patrolled" link to an element target // TODO: Allow patrol a revision, as well as a diff function addReviewLink (navpop, target) { if (! pg.user.canReview) return; // If 'newRev' is older than 'oldRev' than it could be confusing, so we do not show the review link. if (navpop.diffData.newRev.revid <= navpop.diffData.oldRev.revid) return; var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'query', prop: 'info|flagged', revids: navpop.diffData.oldRev.revid, formatversion : 2 }; api.get (params).then(function(data){ var stable_revid = data.query.pages[0].flagged && data.query.pages[0].flagged.stable_revid || 0; // The diff can be reviewed if the old version is the last reviewed version // TODO: Other possible conditions that we may want to implement instead of this one: // * old version is patrolled and the new version is not patrolled // * old version is patrolled and the new version is more recent than the last reviewed version if (stable_revid == navpop.diffData.oldRev.revid) { var a = document.createElement('a'); a.innerHTML = popupString('mark patrolled'); a.title=popupString('markpatrolledHint'); a.onclick = function() { var params = { action: 'review', revid: navpop.diffData.newRev.revid, comment: tprintf('defaultpopupReviewedSummary', [navpop.diffData.oldRev.revid, navpop.diffData.newRev.revid]) }; api.postWithToken('csrf',params).done(function(){ a.style.display = "none"; // TODO: Update current page and other already constructed popups } ).fail(function(){ alert(popupString('Could not marked this edit as patrolled')); }); }; setPopupHTML(a, target, navpop.idNumber,null,true); } }); } function doneDiff(download) { if (!download.owner || !download.owner.diffData) { return; } var navpop=download.owner; completedNavpopTask(navpop); var pages, revisions=[]; try{ // Process the downloads pages = getJsObj(download.data).query.pages; for(var i=0; i < pages.length; i++ ) { revisions = revisions.concat(pages[i].revisions); } for(i=0; i< revisions.length; i++){ if(revisions[i].revid == navpop.diffData.oldRev.revid) { navpop.diffData.oldRev.revision = revisions[i]; } else if (revisions[i].revid == navpop.diffData.newRev.revid) { navpop.diffData.newRev.revision = revisions[i]; } } } catch(someError) { errlog( 'Could not get diff' ); } insertDiff(navpop); } function rmBoringLines(a,b,context) { if (typeof context == 'undefined') { context=2; } // this is fairly slow... i think it's quicker than doing a word-based diff from the off, though var aa=[], aaa=[]; var bb=[], bbb=[]; var i, j; // first, gather all disconnected nodes in a and all crossing nodes in a and b for (i=0; i<a.length; ++i ) { if(!a[i].paired) { aa[i]=1; } else if (countCrossings(b,a,i, true)) { aa[i]=1; bb[ a[i].row ] = 1; } } // pick up remaining disconnected nodes in b for (i=0; i<b.length; ++i ) { if (bb[i]==1) { continue; } if(!b[i].paired) { bb[i]=1; } } // another pass to gather context: we want the neighbours of included nodes which are not yet included // we have to add in partners of these nodes, but we don't want to add context for *those* nodes in the next pass for (i=0; i<b.length; ++i) { if ( bb[i] == 1 ) { for (j=Math.max(0,i-context); j < Math.min(b.length, i+context); ++j) { if ( !bb[j] ) { bb[j] = 1; aa[ b[j].row ] = 0.5; } } } } for (i=0; i<a.length; ++i) { if ( aa[i] == 1 ) { for (j=Math.max(0,i-context); j < Math.min(a.length, i+context); ++j) { if ( !aa[j] ) { aa[j] = 1; bb[ a[j].row ] = 0.5; } } } } for (i=0; i<bb.length; ++i) { if (bb[i] > 0) { // it's a row we need if (b[i].paired) { bbb.push(b[i].text); } // joined; partner should be in aa else { bbb.push(b[i]); } } } for (i=0; i<aa.length; ++i) { if (aa[i] > 0) { // it's a row we need if (a[i].paired) { aaa.push(a[i].text); } // joined; partner should be in aa else { aaa.push(a[i]); } } } return { a: aaa, b: bbb}; } function stripOuterCommonLines(a,b,context) { var i=0; while (i<a.length && i < b.length && a[i]==b[i]) { ++i; } var j=a.length-1; var k=b.length-1; while ( j>=0 && k>=0 && a[j]==b[k] ) { --j; --k; } return { a: a.slice(Math.max(0,i - 1 - context), Math.min(a.length+1, j + context+1)), b: b.slice(Math.max(0,i - 1 - context), Math.min(b.length+1, k + context+1)) }; } function insertDiff(navpop) { // for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff // FIXME: sometimes this gives misleading diffs as distant chunks are squashed together var oldlines = navpop.diffData.oldRev.revision.content.split('\n'); var newlines = navpop.diffData.newRev.revision.content.split('\n'); var inner=stripOuterCommonLines(oldlines,newlines,getValueOf('popupDiffContextLines')); oldlines=inner.a; newlines=inner.b; var truncated=false; getValueOf('popupDiffMaxLines'); if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) { // truncate truncated=true; inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines), newlines.slice(0,pg.option.popupDiffMaxLines), pg.option.popupDiffContextLines); oldlines=inner.a; newlines=inner.b; } var lineDiff=diff(oldlines, newlines); var lines2=rmBoringLines(lineDiff.o, lineDiff.n); var oldlines2=lines2.a; var newlines2=lines2.b; var simpleSplit = !String.prototype.parenSplit.isNative; var html='<hr />'; if (getValueOf('popupDiffDates')) { html += diffDatesTable(navpop); html += '<hr />'; } html += shortenDiffString( diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit), getValueOf('popupDiffContextCharacters') ).join('<hr />'); setPopupTipsAndHTML(html.split('\n').join('<br>') + (truncated ? '<hr /><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') , 'popupPreview', navpop.idNumber); } function diffDatesTable( navpop ) { var html='<table class="popup_diff_dates">'; html += diffDatesTableRow( navpop.diffData.newRev.revision, tprintf('New revision')); html += diffDatesTableRow( navpop.diffData.oldRev.revision, tprintf('Old revision')); html += '</table>'; return html; } function diffDatesTableRow( revision, label ) { var txt=''; var lastModifiedDate = new Date(revision.timestamp); var datePrint=getValueOf('popupDiffDatePrinter'); if (typeof lastModifiedDate[datePrint] == 'function') { var d2 = adjustDate(lastModifiedDate, getTimeOffset()); txt = dayFormat(d2, true) + ' ' + timeFormat(d2, true); } else { txt = tprintf('Invalid %s %s', ['popupDiffDatePrinter', datePrint]); } var revlink = generalLink({url: mw.config.get('wgScript') + '?oldid='+revision.revid, text: label, title: label}); return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [ revlink, txt ]); } //</NOLITE> // ENDFILE: diffpreview.js // STARTFILE: links.js //<NOLITE> ///////////////////// // LINK GENERATION // ///////////////////// // titledDiffLink --> titledWikiLink --> generalLink // wikiLink --> titledWikiLink --> generalLink // editCounterLink --> generalLink // TODO Make these functions return Element objects, not just raw HTML strings. function titledDiffLink(l) { // article, text, title, from, to) { return titledWikiLink({article: l.article, action: l.to + '&oldid=' + l.from, newWin: l.newWin, noPopup: l.noPopup, text: l.text, title: l.title, /* hack: no oldid here */ actionName: 'diff'}); } function wikiLink(l) { //{article:article, action:action, text:text, oldid, newid}) { if (! (typeof l.article == typeof {} && typeof l.action == typeof '' && typeof l.text==typeof '')) return null; if (typeof l.oldid == 'undefined') { l.oldid=null; } var savedOldid = l.oldid; if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) { l.oldid=null; } var hint=popupString(l.action + 'Hint'); // revertHint etc etc etc var oldidData=[l.oldid, safeDecodeURI(l.article)]; var revisionString = tprintf('revision %s of %s', oldidData); log('revisionString='+revisionString); switch (l.action) { case 'edit&section=new': hint = popupString('newSectionHint'); break; case 'edit&undo=': if (l.diff && l.diff != 'prev' && savedOldid ) { l.action += l.diff + '&undoafter=' + savedOldid; } else if (savedOldid) { l.action += savedOldid; } hint = popupString('undoHint'); break; case 'raw&ctype=text/css': hint=popupString('rawHint'); break; case 'revert': var p=parseParams(pg.current.link.href); l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=' + revertSummary(l.oldid, p.diff); if (p.diff=='prev') { l.action += '&direction=prev'; revisionString = tprintf('the revision prior to revision %s of %s', oldidData); } if (getValueOf('popupRevertSummaryPrompt')) { l.action += '&autosummaryprompt=true'; } if (getValueOf('popupMinorReverts')) { l.action += '&autominor=true'; } log('revisionString is now '+revisionString); break; case 'nullEdit': l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=null'; break; case 'historyfeed': l.action='history&feed=rss'; break; case 'markpatrolled': l.action='markpatrolled&rcid='+l.rcid; } if (hint) { if (l.oldid) { hint = simplePrintf(hint, [revisionString]); } else { hint = simplePrintf(hint, [safeDecodeURI(l.article)]); } } else { hint = safeDecodeURI(l.article + '&action=' + l.action) + (l.oldid) ? '&oldid='+l.oldid : ''; } return titledWikiLink({article: l.article, action: l.action, text: l.text, newWin:l.newWin, title: hint, oldid: l.oldid, noPopup: l.noPopup, onclick: l.onclick}); } function revertSummary(oldid, diff) { var ret=''; if (diff == 'prev') { ret=getValueOf('popupQueriedRevertToPreviousSummary'); } else { ret = getValueOf('popupQueriedRevertSummary'); } return ret + '&autorv=' + oldid; } function titledWikiLink(l) { // possible properties of argument: // article, action, text, title, oldid, actionName, className, noPopup // oldid = null is fine here // article and action are mandatory args if (typeof l.article == 'undefined' || typeof l.action=='undefined') { errlog('got undefined article or action in titledWikiLink'); return null; } var base = pg.wiki.titlebase + l.article.urlString(); var url=base; if (typeof l.actionName=='undefined' || !l.actionName) { l.actionName='action'; } // no need to add &action=view, and this confuses anchors if (l.action != 'view') { url = base + '&' + l.actionName + '=' + l.action; } if (typeof l.oldid!='undefined' && l.oldid) { url+='&oldid='+l.oldid; } var cssClass=pg.misc.defaultNavlinkClassname; if (typeof l.className!='undefined' && l.className) { cssClass=l.className; } return generalNavLink({url: url, newWin: l.newWin, title: (typeof l.title != 'undefined') ? l.title : null, text: (typeof l.text!='undefined')?l.text:null, className: cssClass, noPopup:l.noPopup, onclick:l.onclick}); } pg.fn.getLastContrib = function getLastContrib(wikipage, newWin) { getHistoryInfo(wikipage, function(x) { processLastContribInfo(x, {page: wikipage, newWin: newWin}); }); }; function processLastContribInfo(info, stuff) { if(!info.edits || !info.edits.length) { alert('Popups: an odd thing happened. Please retry.'); return; } if(!info.firstNewEditor) { alert(tprintf('Only found one editor: %s made %s edits', [info.edits[0].editor,info.edits.length])); return; } var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+info.firstNewEditor.oldid; displayUrl(newUrl, stuff.newWin); } pg.fn.getDiffSinceMyEdit = function getDiffSinceMyEdit(wikipage, newWin) { getHistoryInfo(wikipage, function(x){ processDiffSinceMyEdit(x, {page: wikipage, newWin: newWin}); }); }; function processDiffSinceMyEdit(info, stuff) { if(!info.edits || !info.edits.length) { alert('Popups: something fishy happened. Please try again.'); return; } var friendlyName=stuff.page.split('_').join(' '); if(!info.myLastEdit) { alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s', [info.userName, getValueOf('popupHistoryLimit'), friendlyName])); return; } if(info.myLastEdit.index === 0) { alert(tprintf("%s seems to be the last editor to the page %s", [info.userName, friendlyName])); return; } var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+ info.myLastEdit.oldid; displayUrl(newUrl, stuff.newWin); } function displayUrl(url, newWin){ if(newWin) { window.open(url); } else { document.location=url; } } pg.fn.purgePopups = function purgePopups() { processAllPopups(true); setupCache(); // deletes all cached items (not browser cached, though...) pg.option={}; abortAllDownloads(); }; function processAllPopups(nullify, banish) { for (var i=0; pg.current.links && i<pg.current.links.length; ++i) { if (!pg.current.links[i].navpopup) { continue; } if (nullify || banish) pg.current.links[i].navpopup.banish(); pg.current.links[i].simpleNoMore=false; if (nullify) pg.current.links[i].navpopup=null; } } pg.fn.disablePopups = function disablePopups(){ processAllPopups(false, true); setupTooltips(null, true); }; pg.fn.togglePreviews = function togglePreviews() { processAllPopups(true, true); pg.option.simplePopups=!pg.option.simplePopups; abortAllDownloads(); }; function magicWatchLink(l) { //Yuck!! Would require a thorough redesign to add this as a click event though ... l.onclick = simplePrintf( 'pg.fn.modifyWatchlist(\'%s\',\'%s\');return false;', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), this.id] ); return wikiLink(l); } pg.fn.modifyWatchlist = function modifyWatchlist(title, action) { var reqData = { 'action': 'watch', 'formatversion': 2, 'titles': title, 'uselang': mw.config.get('wgUserLanguage') }; if ( action === 'unwatch' ) reqData.unwatch = true; var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); // Load the Addedwatchtext or Removedwatchtext message and show it var mwTitle = mw.Title.newFromText( title ); var messageName; if ( mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1 ) { messageName = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk'; } else { messageName = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext'; } $.when( api.postWithToken( 'watch', reqData ), mw.loader.using( [ 'mediawiki.api', 'mediawiki.jqueryMsg' ] ).then( function () { return api.loadMessagesIfMissing( [ messageName ] ); } ) ).done( function () { mw.notify( mw.message( messageName, title ).parseDom() ); } ); }; function magicHistoryLink(l) { // FIXME use onclick change href trick to sort this out instead of window.open var jsUrl='', title='', onClick=''; switch(l.id) { case 'lastContrib': onClick=simplePrintf('pg.fn.getLastContrib(\'%s\',%s)', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]); title=popupString('lastContribHint'); break; case 'sinceMe': onClick=simplePrintf('pg.fn.getDiffSinceMyEdit(\'%s\',%s)', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]); title=popupString('sinceMeHint'); break; } jsUrl = 'javascript:' + onClick; // jshint ignore:line onClick += ';return false;'; return generalNavLink({url: jsUrl, newWin: false, // can't have new windows with JS links, I think title: title, text: l.text, noPopup: l.noPopup, onclick: onClick }); } function popupMenuLink(l) { var jsUrl=simplePrintf('javascript:pg.fn.%s()', [l.id]); // jshint ignore:line var title=popupString(simplePrintf('%sHint', [l.id])); var onClick=simplePrintf('pg.fn.%s();return false;', [l.id]); return generalNavLink({url: jsUrl, newWin:false, title:title, text:l.text, noPopup:l.noPopup, onclick: onClick}); } function specialLink(l) { // properties: article, specialpage, text, sep if (typeof l.specialpage=='undefined'||!l.specialpage) return null; var base = pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId]+':'+l.specialpage; if (typeof l.sep == 'undefined' || l.sep === null) l.sep='&target='; var article=l.article.urlString({keepSpaces: l.specialpage=='Search'}); var hint=popupString(l.specialpage+'Hint'); switch (l.specialpage) { case 'Log': switch (l.sep) { case '&user=': hint=popupString('userLogHint'); break; case '&type=block&page=': hint=popupString('blockLogHint'); break; case '&page=': hint=popupString('pageLogHint'); break; case '&type=protect&page=': hint=popupString('protectLogHint'); break; case '&type=delete&page=': hint=popupString('deleteLogHint'); break; default: log('Unknown log type, sep=' + l.sep); hint='Missing hint (FIXME)'; } break; case 'PrefixIndex': article += '/'; break; } if (hint) hint = simplePrintf(hint, [safeDecodeURI(l.article)]); else hint = safeDecodeURI(l.specialpage+':'+l.article) ; var url = base + l.sep + article; return generalNavLink({url: url, title: hint, text: l.text, newWin:l.newWin, noPopup:l.noPopup}); } function generalLink(l) { // l.url, l.text, l.title, l.newWin, l.className, l.noPopup, l.onclick if (typeof l.url=='undefined') return null; // only quotation marks in the url can screw us up now... I think var url=l.url.split('"').join('%22'); var ret='<a href="' + url + '"'; if (typeof l.title!='undefined' && l.title) { ret += ' title="' + pg.escapeQuotesHTML(l.title) + '"'; } if (typeof l.onclick!='undefined' && l.onclick) { ret += ' onclick="' + pg.escapeQuotesHTML(l.onclick) + '"'; } if (l.noPopup) { ret += ' noPopup=1'; } var newWin; if (typeof l.newWin=='undefined' || l.newWin === null) { newWin=getValueOf('popupNewWindows'); } else { newWin=l.newWin; } if (newWin) { ret += ' target="_blank"'; } if (typeof l.className!='undefined'&&l.className) { ret+=' class="'+l.className+'"'; } ret += '>'; if (typeof l.text==typeof '') { ret+= l.text; } ret +='</a>'; return ret; } function appendParamsToLink(linkstr, params) { var sp=linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i')); if (sp.length<2) return null; var ret=sp.shift() + sp.shift(); ret += '&' + params + '"'; ret += sp.join(''); return ret; } function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor, title (optional), alsoChangeLabel { if (x.newTarget) { log ('changeLinkTargetLink: newTarget=' + x.newTarget); } if (x.oldTarget !== decodeURIComponent( x.oldTarget ) ) { log ('This might be an input problem: ' + x.oldTarget ); } // FIXME: first character of page title as well as namespace should be case insensitive // eg [[category:X1]] and [[Category:X1]] are equivalent // this'll break if charAt(0) is nasty var cA = mw.util.escapeRegExp(x.oldTarget); var chs = cA.charAt(0).toUpperCase(); chs='['+chs + chs.toLowerCase()+']'; var currentArticleRegexBit=chs+cA.substring(1); currentArticleRegexBit=currentArticleRegexBit .split(RegExp('(?:[_ ]+|%20)', 'g')).join('(?:[_ ]+|%20)') .split('\\(').join('(?:%28|\\()') .split('\\)').join('(?:%29|\\))'); // why does this need to match encoded strings ? links in the document ? // leading and trailing space should be ignored, and anchor bits optional: currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + '(?:#[^\\[\\|]*)?)\\s*'; // e.g. Computer (archaic) -> \s*([Cc]omputer[_ ](?:%2528|\()archaic(?:%2528|\)))\s* // autoedit=s~\[\[([Cc]ad)\]\]~[[Computer-aided%20design|$1]]~g;s~\[\[([Cc]AD)[|]~[[Computer-aided%20design|~g var title=x.title || mw.config.get('wgPageName').split('_').join(' '); var lk=titledWikiLink({article: new Title(title), newWin:x.newWin, action: 'edit', text: x.text, title: x.hint, className: 'popup_change_title_link' }); var cmd=''; if (x.newTarget) { // escape '&' and other nasties var t = x.newTarget; var s = mw.util.escapeRegExp(x.newTarget); if (x.alsoChangeLabel) { cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + ']]~g;'; cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;'; cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g'; } else { cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + '|$1]]~g;'; cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;'; cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g'; } } else { cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~$1~g;'; cmd += 's~\\[\\['+currentArticleRegexBit+'[|](.*?)\\]\\]~$2~g'; } // Build query cmd = 'autoedit=' + encodeURIComponent ( cmd ); cmd += '&autoclick='+ encodeURIComponent( x.clickButton ) + '&actoken=' + encodeURIComponent( autoClickToken() ); cmd += ( x.minor === null ) ? '' : '&autominor='+ encodeURIComponent( x.minor ); cmd += ( x.watch === null ) ? '' : '&autowatch='+ encodeURIComponent( x.watch ); cmd += '&autosummary='+encodeURIComponent(x.summary); cmd += '&autoimpl='+encodeURIComponent( popupString('autoedit_version') ); return appendParamsToLink(lk, cmd); } function redirLink(redirMatch, article) { // NB redirMatch is in wikiText var ret=''; if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) { ret += '<hr />'; if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) { ret += popupString('Redirects to: (Fix '); log('redirLink: newTarget=' + redirMatch); ret += addPopupShortcut(changeLinkTargetLink({ newTarget: redirMatch, text: popupString('target'), hint: popupString('Fix this redirect, changing just the link target'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]), oldTarget: article.toString(), clickButton: getValueOf('popupRedirAutoClick'), minor: true, watch: getValueOf('popupWatchRedirredPages') }), 'R'); ret += popupString(' or '); ret += addPopupShortcut(changeLinkTargetLink({ newTarget: redirMatch, text: popupString('target & label'), hint: popupString('Fix this redirect, changing the link target and label'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]), oldTarget: article.toString(), clickButton: getValueOf('popupRedirAutoClick'), minor: true, watch: getValueOf('popupWatchRedirredPages'), alsoChangeLabel: true }), 'R'); ret += popupString(')'); } else ret += popupString('Redirects') + popupString(' to '); return ret; } else return '<br> ' + popupString('Redirects') + popupString(' to ') + titledWikiLink({article: new Title().fromWikiText(redirMatch), action: 'view', /* FIXME: newWin */ text: safeDecodeURI(redirMatch), title: popupString('Bypass redirect')}); } function arinLink(l) { if (!saneLinkCheck(l)) { return null; } if ( ! l.article.isIpUser() || ! pg.wiki.wikimedia) return null; var uN=l.article.userName(); return generalNavLink({url:'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + encodeURIComponent(uN), newWin:l.newWin, title: tprintf('Look up %s in ARIN whois database', [uN]), text: l.text, noPopup:1}); } function toolDbName(cookieStyle) { var ret = mw.config.get('wgDBname'); if (!cookieStyle) { ret+= '_p'; } return ret; } function saneLinkCheck(l) { if (typeof l.article != typeof {} || typeof l.text != typeof '') { return false; } return true; } function editCounterLink(l) { if(!saneLinkCheck(l)) return null; if (! pg.wiki.wikimedia) return null; var uN=l.article.userName(); var tool=getValueOf('popupEditCounterTool'); var url; var defaultToolUrl='//tools.wmflabs.org/supercount/index.php?user=$1&project=$2.$3'; switch(tool) { case 'custom': url=simplePrintf(getValueOf('popupEditCounterUrl'), [ encodeURIComponent(uN), toolDbName() ]); break; case 'soxred': // no longer available case 'kate': // no longer available case 'interiot':// no longer available /* fall through */ case 'supercount': default: var theWiki=pg.wiki.hostname.split('.'); url=simplePrintf(defaultToolUrl, [ encodeURIComponent(uN), theWiki[0], theWiki[1] ]); } return generalNavLink({url:url, title: tprintf('editCounterLinkHint', [uN]), newWin:l.newWin, text: l.text, noPopup:1}); } function globalSearchLink(l) { if(!saneLinkCheck(l)) return null; var base='http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search='; var article=l.article.urlString({keepSpaces:true}); return generalNavLink({url:base + article, newWin:l.newWin, title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]), text: l.text, noPopup:1}); } function googleLink(l) { if(!saneLinkCheck(l)) return null; var base='https://www.google.com/search?q='; var article=l.article.urlString({keepSpaces:true}); return generalNavLink({url:base + '%22' + article + '%22', newWin:l.newWin, title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]), text: l.text, noPopup:1}); } function editorListLink(l) { if(!saneLinkCheck(l)) return null; var article= l.article.articleFromTalkPage() || l.article; var url='https://xtools.wmflabs.org/articleinfo/' + encodeURI( pg.wiki.hostname ) + '/' + article.urlString() + '?uselang=' + mw.config.get('wgUserLanguage'); return generalNavLink({url:url, title: tprintf('editorListHint', [article]), newWin:l.newWin, text: l.text, noPopup:1}); } function generalNavLink(l) { l.className = (l.className === null) ? 'popupNavLink' : l.className; return generalLink(l); } ////////////////////////////////////////////////// // magic history links // function getHistoryInfo(wikipage, whatNext) { log('getHistoryInfo'); getHistory(wikipage, whatNext ? function(d){whatNext(processHistory(d));} : processHistory); } // FIXME eliminate pg.idNumber ... how? :-( function getHistory(wikipage, onComplete) { log('getHistory'); var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&prop=revisions&titles=' + new Title(wikipage).urlString() + '&rvlimit=' + getValueOf('popupHistoryLimit'); log('getHistory: url='+url); return startDownload(url, pg.idNumber+'history', onComplete); } function processHistory(download) { var jsobj = getJsObj(download.data); try { var revisions = anyChild(jsobj.query.pages).revisions; var edits=[]; for (var i=0; i<revisions.length; ++i) { edits.push({ oldid: revisions[i].revid, editor: revisions[i].user }); } log('processed ' + edits.length + ' edits'); return finishProcessHistory( edits, mw.config.get('wgUserName') ); } catch (someError) { log('Something went wrong with JSON business'); return finishProcessHistory([]); } } function finishProcessHistory(edits, userName) { var histInfo={}; histInfo.edits=edits; histInfo.userName=userName; for (var i=0; i<edits.length; ++i) { if (typeof histInfo.myLastEdit === 'undefined' && userName && edits[i].editor==userName) { histInfo.myLastEdit={index: i, oldid: edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)}; } if (typeof histInfo.firstNewEditor === 'undefined' && edits[i].editor != edits[0].editor) { histInfo.firstNewEditor={index:i, oldid:edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)}; } } //pg.misc.historyInfo=histInfo; return histInfo; } //</NOLITE> // ENDFILE: links.js // STARTFILE: options.js ////////////////////////////////////////////////// // options // check for existing value, else use default function defaultize(x) { if (pg.option[x]===null || typeof pg.option[x]=='undefined') { if (typeof window[x] != 'undefined' ) pg.option[x]=window[x]; else pg.option[x]=pg.optionDefault[x]; } } function newOption(x, def) { pg.optionDefault[x]=def; } function setDefault(x, def) { return newOption(x, def); } function getValueOf(varName) { defaultize(varName); return pg.option[varName]; } /*eslint-disable */ function useDefaultOptions() { // for testing for (var p in pg.optionDefault) { pg.option[p]=pg.optionDefault[p]; if (typeof window[p]!='undefined') { delete window[p]; } } } /*eslint-enable */ function setOptions() { // user-settable parameters and defaults var userIsSysop = false; if ( mw.config.get('wgUserGroups') ) { for ( var g = 0; g < mw.config.get('wgUserGroups').length; ++g ) { if ( mw.config.get('wgUserGroups')[g] == "bảo quản viên" ) userIsSysop = true; } } // Basic options newOption('popupDelay', 0.5); newOption('popupHideDelay', 0.5); newOption('simplePopups', false); newOption('popupStructure', 'shortmenus'); // see later - default for popupStructure is 'original' if simplePopups is true newOption('popupActionsMenu', true); newOption('popupSetupMenu', true); newOption('popupAdminLinks', userIsSysop); newOption('popupShortcutKeys', false); newOption('popupHistoricalLinks', true); newOption('popupOnlyArticleLinks', true); newOption('removeTitles', true); newOption('popupMaxWidth', 350); newOption('popupSimplifyMainLink', true); newOption('popupAppendRedirNavLinks', true); newOption('popupTocLinks', false); newOption('popupSubpopups', true); newOption('popupDragHandle', false /* 'popupTopLinks'*/); newOption('popupLazyPreviews', true); newOption('popupLazyDownloads', true); newOption('popupAllDabsStubs', false); newOption('popupDebugging', false); newOption('popupActiveNavlinks', true); newOption('popupModifier', false); // ctrl, shift, alt or meta newOption('popupModifierAction', 'enable'); // or 'disable' newOption('popupDraggable', true); newOption('popupReview', false); //<NOLITE> // images newOption('popupImages', true); newOption('imagePopupsForImages', true); newOption('popupNeverGetThumbs', false); //newOption('popupImagesToggleSize', true); newOption('popupThumbAction', 'imagepage'); //'sizetoggle'); newOption('popupImageSize', 60); newOption('popupImageSizeLarge', 200); // redirs, dabs, reversion newOption('popupFixRedirs', false); newOption('popupRedirAutoClick', 'wpDiff'); newOption('popupFixDabs', false); newOption('popupDabsAutoClick', 'wpDiff'); newOption('popupRevertSummaryPrompt', false); newOption('popupMinorReverts', false); newOption('popupRedlinkRemoval', false); newOption('popupRedlinkAutoClick', 'wpDiff'); newOption('popupWatchDisambiggedPages', null); newOption('popupWatchRedirredPages', null); newOption('popupDabWiktionary', 'last'); // navlinks newOption('popupNavLinks', true); newOption('popupNavLinkSeparator', ' &sdot; '); newOption('popupLastEditLink', true); newOption('popupEditCounterTool', 'supercount'); newOption('popupEditCounterUrl', ''); //</NOLITE> // previews etc newOption('popupPreviews', true); newOption('popupSummaryData', true); newOption('popupMaxPreviewSentences', 9); newOption('popupMaxPreviewCharacters', 900); newOption('popupLastModified', true); newOption('popupPreviewKillTemplates', true); newOption('popupPreviewRawTemplates', true); newOption('popupPreviewFirstParOnly', false); newOption('popupPreviewCutHeadings', false); newOption('popupPreviewButton', false); newOption('popupPreviewButtonEvent', 'click'); //<NOLITE> // diffs newOption('popupPreviewDiffs', true); newOption('popupDiffMaxLines', 100); newOption('popupDiffContextLines', 2); newOption('popupDiffContextCharacters', 40); newOption('popupDiffDates', true); newOption('popupDiffDatePrinter', 'toLocaleString'); // edit summaries. God, these are ugly. newOption('popupReviewedSummary', popupString('defaultpopupReviewedSummary') ); newOption('popupFixDabsSummary', popupString('defaultpopupFixDabsSummary') ); newOption('popupExtendedRevertSummary', popupString('defaultpopupExtendedRevertSummary') ); newOption('popupRevertSummary', popupString('defaultpopupRevertSummary') ); newOption('popupRevertToPreviousSummary', popupString('defaultpopupRevertToPreviousSummary') ); newOption('popupQueriedRevertSummary', popupString('defaultpopupQueriedRevertSummary') ); newOption('popupQueriedRevertToPreviousSummary', popupString('defaultpopupQueriedRevertToPreviousSummary') ); newOption('popupFixRedirsSummary', popupString('defaultpopupFixRedirsSummary') ); newOption('popupRedlinkSummary', popupString('defaultpopupRedlinkSummary') ); newOption('popupRmDabLinkSummary', popupString('defaultpopupRmDabLinkSummary') ); //</NOLITE> // misc newOption('popupHistoryLimit', 50); //<NOLITE> newOption('popupFilters', [popupFilterStubDetect, popupFilterDisambigDetect, popupFilterPageSize, popupFilterCountLinks, popupFilterCountImages, popupFilterCountCategories, popupFilterLastModified]); newOption('extraPopupFilters', []); newOption('popupOnEditSelection', 'cursor'); newOption('popupPreviewHistory', true); newOption('popupImageLinks', true); newOption('popupCategoryMembers', true); newOption('popupUserInfo', true); newOption('popupHistoryPreviewLimit', 25); newOption('popupContribsPreviewLimit',25); newOption('popupRevDelUrl', '//en.wikipedia.org/wiki/Wikipedia:Revision_deletion'); newOption('popupShowGender', true); //</NOLITE> // new windows newOption('popupNewWindows', false); newOption('popupLinksNewWindow', {'lastContrib': true, 'sinceMe': true}); // regexps newOption('popupDabRegexp', '(\\{\\{\\s*disambig(?!uation needed)|disambig(uation|)\\s*\\}\\}|disamb\\s*\\}\\}|dab\\s*\\}\\})|\\{\\{\\s*(((geo|hn|road?|school|number)dis)|[234][lc][acw]|(road|ship)index)(\\s*[|][^}]*)?\\s*[}][}]|is a .*disambiguation.*page'); newOption('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub'); newOption('popupImageVarsRegexp', 'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo'); } // ENDFILE: options.js // STARTFILE: strings.js //<NOLITE> ////////////////////////////////////////////////// // Translatable strings ////////////////////////////////////////////////// // // See instructions at // https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation pg.string = { ///////////////////////////////////// // summary data, searching etc. ///////////////////////////////////// 'article': 'article', 'category': 'category', 'categories': 'categories', 'image': 'image', 'images': 'images', 'stub': 'stub', 'section stub': 'section stub', 'Empty page': 'Empty page', 'kB': 'kB', 'bytes': 'bytes', 'day': 'day', 'days': 'days', 'hour': 'hour', 'hours': 'hours', 'minute': 'minute', 'minutes': 'minutes', 'second': 'second', 'seconds': 'seconds', 'week': 'week', 'weeks': 'weeks', 'search': 'search', 'SearchHint': 'Find English Wikipedia articles containing %s', 'web': 'web', 'global': 'global', 'globalSearchHint': 'Search across Wikipedias in different languages for %s', 'googleSearchHint': 'Google for %s', ///////////////////////////////////// // article-related actions and info // (some actions also apply to user pages) ///////////////////////////////////// 'tác vụ': 'tác vụ', ///// view articles and view talk 'popupsMenu': 'popups', 'togglePreviewsHint': 'Toggle preview generation in popups on this page', 'enable previews': 'enable previews', 'disable previews': 'disable previews', 'toggle previews': 'toggle previews', 'show preview': 'show preview', 'reset': 'reset', 'more...': 'more...', 'disable': 'disable popups', 'disablePopupsHint': 'Disable popups on this page. Reload page to re-enable.', 'historyfeedHint': 'RSS feed of recent changes to this page', 'purgePopupsHint': 'Reset popups, clearing all cached popup data.', 'PopupsHint': 'Reset popups, clearing all cached popup data.', 'spacebar': 'space', 'view': 'view', 'view article': 'view article', 'viewHint': 'Go to %s', 'talk': 'talk', 'talk page': 'talk page', 'this&nbsp;revision': 'this&nbsp;revision', 'revision %s of %s': 'revision %s of %s', 'Revision %s of %s': 'Revision %s of %s', 'the revision prior to revision %s of %s': 'the revision prior to revision %s of %s', 'Toggle image size': 'Click to toggle image size', 'del': 'del', ///// delete, protect, move 'delete': 'delete', 'deleteHint': 'Delete %s', 'undeleteShort': 'un', 'UndeleteHint': 'Show the deletion history for %s', 'protect': 'protect', 'protectHint': 'Restrict editing rights to %s', 'unprotectShort': 'un', 'unprotectHint': 'Allow %s to be edited by anyone again', 'send thanks': 'send thanks', 'ThanksHint': 'Send a thank you notification to this user', 'move': 'move', 'move page': 'move page', 'MovepageHint': 'Change the title of %s', 'sửa': 'sửa', ///// edit articles and talk 'edit article': 'edit article', 'editHint': 'Change the content of %s', 'edit talk': 'edit talk', 'new': 'new', 'new topic': 'new topic', 'newSectionHint': 'Start a new section on %s', 'null edit': 'null edit', 'nullEditHint': 'Submit an edit to %s, making no changes ', 'hist': 'hist', ///// history, diffs, editors, related 'lịch sử': 'lịch sử', 'historyHint': 'List the changes made to %s', 'last': 'prev', // For labelling the previous revision in history pages; the key is "last" for backwards compatibility 'lastEdit': 'lastEdit', 'mark patrolled': 'đánh dấu tuần tra', 'markpatrolledHint': 'Mark this edit as patrolled', 'Could not marked this edit as patrolled': 'Could not marked this edit as patrolled', 'show last edit': 'lần sửa đổi gần nhất', 'Show the last edit': 'Show the effects of the most recent change', 'lastContrib': 'lastContrib', 'last set of edits': 'latest edits', 'lastContribHint': 'Show the net effect of changes made by the last editor', 'cur': 'cur', 'diffCur': 'diffCur', 'Show changes since revision %s': 'Show changes since revision %s', '%s old': '%s old', // as in 4 weeks old 'oldEdit': 'oldEdit', 'purge': 'purge', 'purgeHint': 'Demand a fresh copy of %s', 'raw': 'source', 'rawHint': 'Download the source of %s', 'render': 'simple', 'renderHint': 'Show a plain HTML version of %s', 'Show the edit made to get revision': 'Show the edit made to get revision', 'sinceMe': 'sinceMe', 'changes since mine': 'diff my edit', 'sinceMeHint': 'Show changes since my last edit', 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s': 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s', 'eds': 'eds', 'editors': 'editors', 'editorListHint': 'List the users who have edited %s', 'related': 'related', 'relatedChanges': 'relatedChanges', 'related changes': 'related changes', 'RecentchangeslinkedHint': 'Show changes in articles related to %s', 'editOld': 'editOld', ///// edit old version, or revert 'rv': 'rv', 'revert': 'revert', 'revertHint': 'Revert to %s', 'defaultpopupReviewedSummary': 'Accepted by reviewing the [[Special:diff/%s/%s|difference]] between this version and previously accepted version using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRedlinkSummary': 'Removing link to empty page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupFixDabsSummary': 'Disambiguate [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupFixRedirsSummary': 'Redirect bypass from [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupExtendedRevertSummary': 'Revert to revision dated %s by %s, oldid %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRevertToPreviousSummary': 'Revert to the revision prior to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRevertSummary': 'Revert to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupQueriedRevertToPreviousSummary': 'Revert to the revision prior to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupQueriedRevertSummary': 'Revert to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRmDabLinkSummary': 'Remove link to dab page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'Redirects': 'Redirects', // as in Redirects to ... ' to ': ' to ', // as in Redirects to ... 'Bypass redirect': 'Bypass redirect', 'Fix this redirect': 'Fix this redirect', 'disambig': 'disambig', ///// add or remove dab etc. 'disambigHint': 'Disambiguate this link to [[%s]]', 'Click to disambiguate this link to:': 'Click to disambiguate this link to:', 'remove this link': 'remove this link', 'remove all links to this page from this article': 'remove all links to this page from this article', 'remove all links to this disambig page from this article': 'remove all links to this disambig page from this article', 'mainlink': 'mainlink', ///// links, watch, unwatch 'wikiLink': 'wikiLink', 'wikiLinks': 'wikiLinks', 'links here': 'links here', 'whatLinksHere': 'whatLinksHere', 'what links here': 'what links here', 'WhatlinkshereHint': 'List the pages that are hyperlinked to %s', 'unwatchShort': 'un', 'watchThingy': 'watch', // called watchThingy because {}.watch is a function 'watchHint': 'Add %s to my watchlist', 'unwatchHint': 'Remove %s from my watchlist', 'Only found one editor: %s made %s edits': 'Only found one editor: %s made %s edits', '%s seems to be the last editor to the page %s': '%s seems to be the last editor to the page %s', 'rss': 'rss', ///////////////////////////////////// // diff previews ///////////////////////////////////// 'Diff truncated for performance reasons': 'Diff truncated for performance reasons', 'Old revision': 'Old revision', 'New revision': 'New revision', 'Something went wrong :-(': 'Something went wrong :-(', 'Empty revision, maybe non-existent': 'Empty revision, maybe non-existent', 'Unknown date': 'Unknown date', ///////////////////////////////////// // other special previews ///////////////////////////////////// 'Empty category': 'Empty category', 'Category members (%s shown)': 'Category members (%s shown)', 'No image links found': 'No image links found', 'File links': 'File links', 'No image found': 'No image found', 'Image from Commons': 'Image from Commons', 'Description page': 'Description page', 'Alt text:': 'Alt text:', 'revdel':'Hidden revision', ///////////////////////////////////// // user-related actions and info ///////////////////////////////////// 'user': 'user', ///// user page, talk, email, space 'user&nbsp;page': 'user&nbsp;page', 'user talk': 'user talk', 'edit user talk': 'edit user talk', 'leave comment': 'leave comment', 'email': 'email', 'email user': 'email user', 'EmailuserHint': 'Send an email to %s', 'space': 'space', // short form for userSpace link 'PrefixIndexHint': 'Show pages in the userspace of %s', 'count': 'count', ///// contributions, log 'edit counter': 'edit counter', 'editCounterLinkHint': 'Count the contributions made by %s', 'contribs': 'contribs', 'contributions': 'contributions', 'deletedContribs': 'deleted contributions', 'DeletedcontributionsHint': 'List deleted edits made by %s', 'ContributionsHint': 'List the contributions made by %s', 'log': 'log', 'user log': 'user log', 'userLogHint': 'Show %s\'s user log', 'arin': 'ARIN lookup', ///// ARIN lookup, block user or IP 'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database', 'unblockShort': 'un', 'block': 'block', 'block user': 'block user', 'IpblocklistHint': 'Unblock %s', 'BlockipHint': 'Prevent %s from editing', 'block log': 'block log', 'blockLogHint': 'Show the block log for %s', 'protectLogHint': 'Show the protection log for %s', 'pageLogHint': 'Show the page log for %s', 'deleteLogHint': 'Show the deletion log for %s', 'Invalid %s %s': 'The option %s is invalid: %s', 'No backlinks found': 'No backlinks found', ' and more': ' and more', 'undo': 'undo', 'undoHint': 'undo this edit', 'Download preview data': 'Download preview data', 'Invalid or IP user': 'Invalid or IP user', 'Not a registered username': 'Not a registered username', 'CẤM': 'CẤM', 'Has blocks': 'Has blocks', ' sửa đổi từ: ': ' sửa đổi từ: ', 'sửa đổi lần cuối vào ': 'sửa đổi lần cuối vào ', ///////////////////////////////////// // Autoediting ///////////////////////////////////// 'Enter a non-empty edit summary or press cancel to abort': 'Enter a non-empty edit summary or press cancel to abort', 'Failed to get revision information, please edit manually.\n\n': 'Failed to get revision information, please edit manually.\n\n', 'The %s button has been automatically clicked. Please wait for the next page to load.': 'The %s button has been automatically clicked. Please wait for the next page to load.', 'Could not find button %s. Please check the settings in your javascript file.': 'Could not find button %s. Please check the settings in your javascript file.', ///////////////////////////////////// // Popups setup ///////////////////////////////////// 'Open full-size image': 'Open full-size image', 'zxy': 'zxy', 'autoedit_version': 'np20140416' }; function popupString(str) { if (typeof popupStrings != 'undefined' && popupStrings && popupStrings[str]) { return popupStrings[str]; } if (pg.string[str]) { return pg.string[str]; } return str; } function tprintf(str,subs) { if (typeof subs != typeof []) { subs = [subs]; } return simplePrintf(popupString(str), subs); } //</NOLITE> // ENDFILE: strings.js // STARTFILE: run.js //////////////////////////////////////////////////////////////////// // Run things //////////////////////////////////////////////////////////////////// // For some reason popups requires a fully loaded page jQuery.ready(...) causes problems for some. // The old addOnloadHook did something similar to the below if (document.readyState=="complete") autoEdit(); //will setup popups else $( window ).on( 'load', autoEdit ); // Support for MediaWiki's live preview, VisualEditor's saves and Echo's flyout. ( function () { var once = true; function dynamicContentHandler( $content ) { // Try to detect the hook fired on initial page load and disregard // it, we already hook to onload (possibly to different parts of // page - it's configurable) and running twice might be bad. Ugly… if ( $content.attr( 'id' ) == 'mw-content-text' ) { if ( once ) { once = false; return; } } function registerHooksForVisibleNavpops () { for (var i=0; pg.current.links && i<pg.current.links.length; ++i) { var navpop = pg.current.links[i].navpopup; if (!navpop || !navpop.isVisible()) { continue; } Navpopup.tracker.addHook(posCheckerHook(navpop)); } } function doIt () { registerHooksForVisibleNavpops(); $content.each( function () { this.ranSetupTooltipsAlready = false; setupTooltips( this ); } ); } setupPopups( doIt ); } // This hook is also fired after page load. mw.hook( 'wikipage.content' ).add( dynamicContentHandler ); mw.hook( 'ext.echo.overlay.beforeShowingOverlay' ).add( function($overlay){ dynamicContentHandler( $overlay.find(".mw-echo-state") ); }); } )(); // Forked from User:Connel MacKenzie/mess-with-popups.js Previewmaker.prototype.firstBit = function() { try{ if (mw.config.get("wgNamespaceNumber") !== 0){ // dont't be givin' me no subsequent paragraphs, you hear me? /// first we "normalize" section headings, removing whitespace after, adding before var d = this.data; if (getValueOf('popupPreviewCutHeadings')) { this.data = this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 '); /// then we want to get rid of paragraph breaks whose text ends badly this.data = this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n'); this.data = this.data.replace(RegExp('^[\\s\\n]*'), ''); stuff = (RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data); if (stuff) { d = stuff[0]; } if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; } /// now put \n\n after sections so that bullets and numbered lists work d = d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n'); } // superfluous sentences are RIGHT OUT. // note: exactly 1 set of parens here needed to make the slice work d = d.parenSplit(RegExp('([!?.]+["' + "'" + ']*\\s)', 'g')); // leading space is bad, mmkay? d[0] = d[0].replace(RegExp('^\\s*'), ''); var notSentenceEnds = RegExp('([^.][a-z][.][a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|\\[[^\\]]*|\\s[A-Zvclm])$', 'i'); d = this.fixSentenceEnds(d, notSentenceEnds); var maxChars=getValueOf('popupMaxPreviewCharacters'); var n = getValueOf('popupMaxPreviewSentences'); var dd; do { dd = this.firstSentences(d, n); --n; } while (dd.length > maxChars && n > 0); this.data = dd; } else { //TODO: Limit this override to urls that start with http://*.wiktionary.org/ //TODO: extend the above section to also preview Wikipedia links var charsWikt = 0; var linesWikt = this.data.split('\n'); for (jWikt = 0; jWikt < linesWikt.length; jWikt++) { linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{[in]transitive}}/g, ''); linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{[un]countable}}/g, ''); linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{cattag\|(.*?)}}/g, ""); linesWikt[jWikt] = linesWikt[jWikt].replace(/[\{\}]/g, "'"); if (linesWikt[jWikt].search(/^[#]/) == -1) { if (linesWikt[jWikt].search(/^==[A-Z]/) == -1) { linesWikt[jWikt] = ''; } } charsWikt += linesWikt[jWikt].length; } if (charsWikt < 2) { linesWikt[0] = "No properly formatted content"; } var maxChars = getValueOf('popupMaxPreviewCharacters'); var n = getValueOf('popupMaxPreviewSentences'); var dd = ''; for (jWikt = 0; jWikt < linesWikt.length; jWikt++) { if (linesWikt[jWikt] !== '') { dd += linesWikt[jWikt] + '\n'; } if (dd.length < maxChars) { this.data = dd; } } } } catch (e) { console.log("error from mess with popups: ", e); } }; }); q2ui3o84dl6smwhwh2kkvmiz7o8qxis 2513984 2513983 2026-08-14T08:20:58Z Hiyuune 50834 2513984 javascript text/javascript // {{documentation}} // _________________________________________________________________________________________ // | | // | === WARNING: GLOBAL GADGET FILE === | // | Changes to this page affect many users. | // | Please discuss changes on the [[Wiktionary:Grease Pit]] before editing. | // |_________________________________________________________________________________________| mw.loader.load('//en.wikipedia.org/w/index.php?action=raw&title=MediaWiki:Gadget-navpop.css&ctype=text/css', "text/css"); // Forked from [[:w:MediaWiki:Gadget-popups.js]] $(function () { ////////////////////////////////////////////////// // Globals // // Trying to shove as many of these as possible into the pg (popup globals) object var pg = { re: {}, // regexps ns: {}, // namespaces string: {}, // translatable strings wiki: {}, // local site info user: {}, // current user info misc: {}, // YUCK PHOOEY option: {}, // options, see newOption etc optionDefault: {}, // default option values flag: {}, // misc flags cache: {}, // page and image cache structures: {}, // navlink structures timer: {}, // all sorts of timers (too damn many) counter: {}, // .. and all sorts of counters current: {}, // state info fn: {}, // functions endoflist: null }; /* Bail if the gadget/script is being loaded twice */ if( window.pg ) { return; } /* Export to global context */ window.pg = pg; /// Local Variables: /// /// mode:c /// /// End: /// // ENDFILE: main.js // STARTFILE: actions.js function setupTooltips(container, remove, force, popData) { log('setupTooltips, container='+container+', remove='+remove); if (!container) { //<NOLITE> // the main initial call if (getValueOf('popupOnEditSelection') && document && document.editform && document.editform.wpTextbox1) { document.editform.wpTextbox1.onmouseup=doSelectionPopup; } //</NOLITE> // article/content is a structure-dependent thing container = defaultPopupsContainer(); } if (!remove && !force && container.ranSetupTooltipsAlready) { return; } container.ranSetupTooltipsAlready = !remove; var anchors; anchors=container.getElementsByTagName('A'); setupTooltipsLoop(anchors, 0, 250, 100, remove, popData); } function defaultPopupsContainer() { if (getValueOf('popupOnlyArticleLinks')) { return document.getElementById('mw_content') || document.getElementById('content') || document.getElementById('article') || document; } return document; } function setupTooltipsLoop(anchors,begin,howmany,sleep, remove, popData) { log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments)); var finish=begin+howmany; var loopend = Math.min(finish, anchors.length); var j=loopend - begin; log ('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin + ', howmany=' + howmany + ', loopend=' + loopend + ', remove=' + remove); var doTooltip= remove ? removeTooltip : addTooltip; // try a faster (?) loop construct if (j > 0) { do { var a=anchors[loopend - j]; if (typeof a==='undefined' || !a || !a.href) { log('got null anchor at index ' + loopend - j); continue; } doTooltip(a, popData); } while (--j); } if (finish < anchors.length) { setTimeout(function() { setupTooltipsLoop(anchors,finish,howmany,sleep,remove,popData);}, sleep); } else { if ( !remove && ! getValueOf('popupTocLinks')) { rmTocTooltips(); } pg.flag.finishedLoading=true; } } // eliminate popups from the TOC // This also kills any onclick stuff that used to be going on in the toc function rmTocTooltips() { var toc=document.getElementById('toc'); if (toc) { var tocLinks=toc.getElementsByTagName('A'); var tocLen = tocLinks.length; for (var j=0; j<tocLen; ++j) { removeTooltip(tocLinks[j], true); } } } function addTooltip(a, popData) { if ( !isPopupLink(a) ) { return; } a.onmouseover=mouseOverWikiLink; a.onmouseout= mouseOutWikiLink; a.onmousedown = killPopup; a.hasPopup = true; a.popData = popData; } function removeTooltip(a) { if ( !a.hasPopup ) { return; } a.onmouseover = null; a.onmouseout = null; if (a.originalTitle) { a.title = a.originalTitle; } a.hasPopup=false; } function removeTitle(a) { if (!a.originalTitle) { a.originalTitle=a.title; } a.title=''; } function restoreTitle(a) { if ( a.title || !a.originalTitle ) { return; } a.title = a.originalTitle; } function registerHooks(np) { var popupMaxWidth=getValueOf('popupMaxWidth'); if (typeof popupMaxWidth === 'number') { var setMaxWidth = function () { np.mainDiv.style.maxWidth = popupMaxWidth + 'px'; np.maxWidth = popupMaxWidth; }; np.addHook(setMaxWidth, 'unhide', 'before'); } //<NOLITE> np.addHook(addPopupShortcuts, 'unhide', 'after'); np.addHook(rmPopupShortcuts, 'hide', 'before'); //</NOLITE> } function removeModifierKeyHandler(a) { //remove listeners for modifier key if any that were added in mouseOverWikiLink document.removeEventListener('keydown', a.modifierKeyHandler, false); document.removeEventListener('keyup', a.modifierKeyHandler, false); } function mouseOverWikiLink(evt) { if (!evt && window.event) {evt=window.event;} // if the modifier is needed, listen for it, // we will remove the listener when we mouseout of this link or kill popup. if (getValueOf('popupModifier')) { // if popupModifierAction = enable, we should popup when the modifier is pressed // if popupModifierAction = disable, we should popup unless the modifier is pressed var action = getValueOf('popupModifierAction'); var key = action=='disable' ? 'keyup' : 'keydown'; var a = this; a.modifierKeyHandler = function(evt) { mouseOverWikiLink2(a, evt); }; document.addEventListener(key, a.modifierKeyHandler, false); } return mouseOverWikiLink2(this, evt); } /** * Gets the references list item that the provided footnote link targets. This * is typically a li element within the ol.references element inside the reflist. * @param {Element} a - A footnote link. * @returns {Element|boolean} The targeted element, or false if one can't be found. */ function footnoteTarget(a) { var aTitle=Title.fromAnchor(a); // We want ".3A" rather than "%3A" or "?" here, so use the anchor property directly var anch = aTitle.anchor; if ( ! /^(cite_note-|_note-|endnote)/.test(anch) ) { return false; } var lTitle=Title.fromURL(location.href); if ( lTitle.toString(true) !== aTitle.toString(true) ) { return false; } var el=document.getElementById(anch); while ( el && typeof el.nodeName === 'string') { var nt = el.nodeName.toLowerCase(); if ( nt === 'li' ) { return el; } else if ( nt === 'body' ) { return false; } else if ( el.parentNode ) { el=el.parentNode; } else { return false; } } return false; } function footnotePreview(x, navpop) { setPopupHTML('<hr />' + x.innerHTML, 'popupPreview', navpop.idNumber); } function modifierPressed(evt) { var mod=getValueOf('popupModifier'); if (!mod) { return false; } if (!evt && window.event) {evt=window.event;} return ( evt && mod && evt[mod.toLowerCase() + 'Key'] ); } // Checks if the correct modifier pressed/unpressed if needed function isCorrectModifier(a,evt) { if (!getValueOf('popupModifier')) { return true; } // if popupModifierAction = enable, we should popup when the modifier is pressed // if popupModifierAction = disable, we should popup unless the modifier is pressed var action = getValueOf('popupModifierAction'); return ( action == 'enable' && modifierPressed(evt) || action == 'disable' && !modifierPressed(evt) ); } function mouseOverWikiLink2(a, evt) { if (!isCorrectModifier(a,evt)) { return; } if ( getValueOf('removeTitles') ) { removeTitle(a); } if ( a==pg.current.link && a.navpopup && a.navpopup.isVisible() ) { return; } pg.current.link=a; if (getValueOf('simplePopups') && !pg.option.popupStructure) { // reset *default value* of popupStructure setDefault('popupStructure', 'original'); } var article=(new Title()).fromAnchor(a); // set global variable (ugh) to hold article (wikipage) pg.current.article = article; if (!a.navpopup) { a.navpopup=newNavpopup(a, article); pg.current.linksHash[a.href] = a.navpopup; pg.current.links.push(a); } if (a.navpopup.pending === null || a.navpopup.pending !== 0) { // either fresh popups or those with unfinshed business are redone from scratch simplePopupContent(a, article); } a.navpopup.showSoonIfStable(a.navpopup.delay); clearInterval(pg.timer.checkPopupPosition); pg.timer.checkPopupPosition=setInterval(checkPopupPosition, 600); if(getValueOf('simplePopups')) { if (getValueOf('popupPreviewButton') && !a.simpleNoMore) { var d=document.createElement('div'); d.className='popupPreviewButtonDiv'; var s=document.createElement('span'); d.appendChild(s); s.className='popupPreviewButton'; s['on' + getValueOf('popupPreviewButtonEvent')] = function() { a.simpleNoMore=true; d.style.display = "none"; nonsimplePopupContent(a,article); }; s.innerHTML=popupString('show preview'); setPopupHTML(d, 'popupPreview', a.navpopup.idNumber); } } if (a.navpopup.pending !== 0 ) { nonsimplePopupContent(a, article); } } // simplePopupContent: the content that do not require additional download // (it is shown even when simplePopups is true) function simplePopupContent(a, article) { /* FIXME hack */ a.navpopup.hasPopupMenu=false; a.navpopup.setInnerHTML(popupHTML(a)); fillEmptySpans({navpopup:a.navpopup}); if (getValueOf('popupDraggable')) { var dragHandle = getValueOf('popupDragHandle') || null; if (dragHandle && dragHandle != 'all') { dragHandle += a.navpopup.idNumber; } setTimeout(function(){a.navpopup.makeDraggable(dragHandle);}, 150); } //<NOLITE> if (getValueOf('popupRedlinkRemoval') && a.className=='new') { setPopupHTML('<br>'+popupRedlinkHTML(article), 'popupRedlink', a.navpopup.idNumber); } //</NOLITE> } function debugData(navpopup) { if(getValueOf('popupDebugging') && navpopup.idNumber) { setPopupHTML('idNumber='+navpopup.idNumber + ', pending=' + navpopup.pending, 'popupError', navpopup.idNumber); } } function newNavpopup(a, article) { var navpopup = new Navpopup(); navpopup.fuzz=5; navpopup.delay=getValueOf('popupDelay')*1000; // increment global counter now navpopup.idNumber = ++pg.idNumber; navpopup.parentAnchor = a; navpopup.parentPopup = (a.popData && a.popData.owner); navpopup.article = article; registerHooks(navpopup); return navpopup; } // Should we show nonsimple context? // If simplePopups is set to true, then we do not show nonsimple context, // but if a bottom "show preview" was clicked we do show nonsimple context function shouldShowNonSimple(a) { return !getValueOf('simplePopups') || a.simpleNoMore; } // Should we show nonsimple context govern by the option (e.g. popupUserInfo)? // If the user explicitly asked for nonsimple context by setting the option to true, // then we show it even in nonsimple mode. function shouldShow(a,option) { if (shouldShowNonSimple(a)) { return getValueOf(option); } else { return (typeof window[option] != 'undefined' ) && window[option]; } } function nonsimplePopupContent(a, article) { var diff=null, history=null; var params=parseParams(a.href); var oldid=(typeof params.oldid=='undefined' ? null : params.oldid); //<NOLITE> if(shouldShow(a,'popupPreviewDiffs')) { diff=params.diff; } if(shouldShow(a,'popupPreviewHistory')) { history=(params.action=='history'); } //</NOLITE> a.navpopup.pending=0; var referenceElement = footnoteTarget(a); if (referenceElement) { footnotePreview(referenceElement, a.navpopup); //<NOLITE> } else if ( diff || diff === 0 ) { loadDiff(article, oldid, diff, a.navpopup); } else if ( history ) { loadAPIPreview('history', article, a.navpopup); } else if ( shouldShowNonSimple(a) && pg.re.contribs.test(a.href) ) { loadAPIPreview('contribs', article, a.navpopup); } else if ( shouldShowNonSimple(a) && pg.re.backlinks.test(a.href) ) { loadAPIPreview('backlinks', article, a.navpopup); } else if ( // FIXME should be able to get all preview combinations with options article.namespaceId()==pg.nsImageId && ( shouldShow(a,'imagePopupsForImages') || ! anchorContainsImage(a) ) ) { loadAPIPreview('imagepagepreview', article, a.navpopup); loadImage(article, a.navpopup); //</NOLITE> } else { if (article.namespaceId() == pg.nsCategoryId && shouldShow(a,'popupCategoryMembers')) { loadAPIPreview('category', article, a.navpopup); } else if ((article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) && shouldShow(a,'popupUserInfo')) { loadAPIPreview('userinfo', article, a.navpopup); } if (shouldShowNonSimple(a)) startArticlePreview(article, oldid, a.navpopup); } } function pendingNavpopTask(navpop) { if (navpop && navpop.pending === null) { navpop.pending=0; } ++navpop.pending; debugData(navpop); } function completedNavpopTask(navpop) { if (navpop && navpop.pending) { --navpop.pending; } debugData(navpop); } function startArticlePreview(article, oldid, navpop) { navpop.redir=0; loadPreview(article, oldid, navpop); } function loadPreview(article, oldid, navpop) { if (!navpop.redir) { navpop.originalArticle=article; } article.oldid = oldid; loadAPIPreview('revision', article, navpop); } function loadPreviewFromRedir(redirMatch, navpop) { // redirMatch is a regex match var target = new Title().fromWikiText(redirMatch[2]); // overwrite (or add) anchor from original target // mediawiki does overwrite; eg User:Lupin/foo3#Done if ( navpop.article.anchor ) { target.anchor = navpop.article.anchor; } navpop.redir++; navpop.redirTarget=target; //<NOLITE> var warnRedir = redirLink(target, navpop.article); setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber); //</NOLITE> navpop.article=target; fillEmptySpans({redir: true, redirTarget: target, navpopup:navpop}); return loadPreview(target, null, navpop); } function insertPreview(download) { if (!download.owner) { return; } var redirMatch = pg.re.redirect.exec(download.data); if (download.owner.redir === 0 && redirMatch) { loadPreviewFromRedir(redirMatch, download.owner); return; } if (download.owner.visible || !getValueOf('popupLazyPreviews')) { insertPreviewNow(download); } else { var id=(download.owner.redir) ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK'; download.owner.addHook( function(){insertPreviewNow(download); return true;}, 'unhide', 'after', id ); } } function insertPreviewNow(download) { if (!download.owner) { return; } var wikiText=download.data; var navpop=download.owner; var art=navpop.redirTarget || navpop.originalArticle; //<NOLITE> makeFixDabs(wikiText, navpop); if (getValueOf('popupSummaryData')) { getPageInfo(wikiText, download); setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber); } var imagePage=''; if (art.namespaceId()==pg.nsImageId) { imagePage=art.toString(); } else { imagePage=getValidImageFromWikiText(wikiText); } if(imagePage) { loadImage(Title.fromWikiText(imagePage), navpop); } //</NOLITE> if (getValueOf('popupPreviews')) { insertArticlePreview(download, art, navpop); } } function insertArticlePreview(download, art, navpop) { if (download && typeof download.data == typeof ''){ if (art.namespaceId()==pg.nsTemplateId && getValueOf('popupPreviewRawTemplates')) { // FIXME compare/consolidate with diff escaping code for wikitext var h='<hr /><span style="font-family: monospace;">' + download.data.entify().split('\\n').join('<br />\\n') + '</span>'; setPopupHTML(h, 'popupPreview', navpop.idNumber); } else { var p=prepPreviewmaker(download.data, art, navpop); p.showPreview(); } } } function prepPreviewmaker(data, article, navpop) { // deal with tricksy anchors var d=anchorize(data, article.anchorString()); var urlBase=joinPath([pg.wiki.articlebase, article.urlString()]); var p=new Previewmaker(d, urlBase, navpop); return p; } // Try to imitate the way mediawiki generates HTML anchors from section titles function anchorize(d, anch) { if (!anch) { return d; } var anchRe=RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*'+getValueOf('popupAnchorRegexp')+'\\s*(?:\\|[^|}]*)*?\\s*'+literalizeRegex(anch)+'\\s*(?:\\|[^}]*)?}})'); var match=d.match(anchRe); if(match && match.length > 0 && match[0]) { return d.substring(d.indexOf(match[0])); } // now try to deal with == foo [[bar|baz]] boom == -> #foo_baz_boom var lines=d.split('\n'); for (var i=0; i<lines.length; ++i) { lines[i]=lines[i].replace(RegExp('[[]{2}([^|\\]]*?[|])?(.*?)[\\]]{2}', 'g'), '$2') .replace(/'''([^'])/g, '$1').replace(RegExp("''([^'])", 'g'), '$1'); if (lines[i].match(anchRe)) { return d.split('\n').slice(i).join('\n').replace(RegExp('^[^=]*'), ''); } } return d; } function killPopup() { removeModifierKeyHandler(this); if (getValueOf('popupShortcutKeys')) { rmPopupShortcuts(); } if (!pg) { return; } if (pg.current.link && pg.current.link.navpopup) { pg.current.link.navpopup.banish(); } pg.current.link=null; abortAllDownloads(); if (pg.timer.checkPopupPosition) { clearInterval(pg.timer.checkPopupPosition); pg.timer.checkPopupPosition=null; } return true; // preserve default action } // ENDFILE: actions.js // STARTFILE: domdrag.js /** @fileoverview The {@link Drag} object, which enables objects to be dragged around. <pre> ************************************************* dom-drag.js 09.25.2001 www.youngpup.net ************************************************** 10.28.2001 - fixed minor bug where events sometimes fired off the handle, not the root. ************************************************* Pared down, some hooks added by User:Lupin Copyright Aaron Boodman. Saying stupid things daily since March 2001. </pre> */ /** Creates a new Drag object. This is used to make various DOM elements draggable. @constructor */ function Drag () { /** Condition to determine whether or not to drag. This function should take one parameter, an Event. To disable this, set it to <code>null</code>. @type Function */ this.startCondition = null; /** Hook to be run when the drag finishes. This is passed the final coordinates of the dragged object (two integers, x and y). To disables this, set it to <code>null</code>. @type Function */ this.endHook = null; } /** Gets an event in a cross-browser manner. @param {Event} e @private */ Drag.prototype.fixE = function(e) { if (typeof e == 'undefined') { e = window.event; } if (typeof e.layerX == 'undefined') { e.layerX = e.offsetX; } if (typeof e.layerY == 'undefined') { e.layerY = e.offsetY; } return e; }; /** Initialises the Drag instance by telling it which object you want to be draggable, and what you want to drag it by. @param {DOMElement} o The "handle" by which <code>oRoot</code> is dragged. @param {DOMElement} oRoot The object which moves when <code>o</code> is dragged, or <code>o</code> if omitted. */ Drag.prototype.init = function(o, oRoot) { var dragObj = this; this.obj = o; o.onmousedown = function(e) { dragObj.start.apply( dragObj, [e]); }; o.dragging = false; o.popups_draggable = true; o.hmode = true; o.vmode = true; o.root = oRoot ? oRoot : o ; if (isNaN(parseInt(o.root.style.left, 10))) { o.root.style.left = "0px"; } if (isNaN(parseInt(o.root.style.top, 10))) { o.root.style.top = "0px"; } o.root.onthisStart = function(){}; o.root.onthisEnd = function(){}; o.root.onthis = function(){}; }; /** Starts the drag. @private @param {Event} e */ Drag.prototype.start = function(e) { var o = this.obj; // = this; e = this.fixE(e); if (this.startCondition && !this.startCondition(e)) { return; } var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10); o.root.onthisStart(x, y); o.lastMouseX = e.clientX; o.lastMouseY = e.clientY; var dragObj = this; o.onmousemoveDefault = document.onmousemove; o.dragging = true; document.onmousemove = function(e) { dragObj.drag.apply( dragObj, [e] ); }; document.onmouseup = function(e) { dragObj.end.apply( dragObj, [e] ); }; return false; }; /** Does the drag. @param {Event} e @private */ Drag.prototype.drag = function(e) { e = this.fixE(e); var o = this.obj; var ey = e.clientY; var ex = e.clientX; var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10 ); var nx, ny; nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1)); ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1)); this.obj.root.style[o.hmode ? "left" : "right"] = nx + "px"; this.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px"; this.obj.lastMouseX = ex; this.obj.lastMouseY = ey; this.obj.root.onthis(nx, ny); return false; }; /** Ends the drag. @private */ Drag.prototype.end = function() { document.onmousemove=this.obj.onmousemoveDefault; document.onmouseup = null; this.obj.dragging = false; if (this.endHook) { this.endHook( parseInt(this.obj.root.style[this.obj.hmode ? "left" : "right"], 10), parseInt(this.obj.root.style[this.obj.vmode ? "top" : "bottom"], 10)); } }; // ENDFILE: domdrag.js // STARTFILE: structures.js //<NOLITE> pg.structures.original={}; pg.structures.original.popupLayout=function () { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupUserData', 'popupData', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; pg.structures.original.popupRedirSpans=function () { return ['popupRedir', 'popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks']; }; pg.structures.original.popupTitle=function (x) { log ('defaultstructure.popupTitle'); if (!getValueOf('popupNavLinks')) { return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params); } return ''; }; pg.structures.original.popupTopLinks=function (x) { log ('defaultstructure.popupTopLinks'); if (getValueOf('popupNavLinks')) { return navLinksHTML(x.article, x.hint, x.params); } return ''; }; pg.structures.original.popupImage=function(x) { log ('original.popupImage, x.article='+x.article+', x.navpop.idNumber='+x.navpop.idNumber); return imageHTML(x.article, x.navpop.idNumber); }; pg.structures.original.popupRedirTitle=pg.structures.original.popupTitle; pg.structures.original.popupRedirTopLinks=pg.structures.original.popupTopLinks; function copyStructure(oldStructure, newStructure) { pg.structures[newStructure]={}; for (var prop in pg.structures[oldStructure]) { pg.structures[newStructure][prop]=pg.structures[oldStructure][prop]; } } copyStructure('original', 'nostalgia'); pg.structures.nostalgia.popupTopLinks=function(x) { var str=''; str += '<b><<mainlink|shortcut= >></b>'; // user links // contribs - log - count - email - block // count only if applicable; block only if popupAdminLinks str += 'if(user){<br><<contribs|shortcut=c>>'; str+='if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}'; // editing links // talkpage -> edit|new - history - un|watch - article|edit // other page -> edit - history - un|watch - talk|edit|new var editstr='<<edit|shortcut=e>>'; var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'; var historystr='<<history|shortcut=h>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; str += '<br>if(talk){' + editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + // not a talk page editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}'; // misc links str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>'; str += 'if(admin){<br>}else{*}<<move|shortcut=m>>'; // admin links str += 'if(admin){*<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>}'; return navlinkStringToHTML(str, x.article, x.params); }; pg.structures.nostalgia.popupRedirTopLinks=pg.structures.nostalgia.popupTopLinks; /** -- fancy -- **/ copyStructure('original', 'fancy'); pg.structures.fancy.popupTitle=function (x) { return navlinkStringToHTML('<font size=+0><<mainlink>></font>',x.article,x.params); }; pg.structures.fancy.popupTopLinks=function(x) { var hist='<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>|<<editors|shortcut=E|eds>>'; var watch='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; var move='<<move|shortcut=m|move>>'; return navlinkStringToHTML('if(talk){' + '<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' + hist + '*' + '<<article|shortcut=a>>|<<editArticle|edit>>' + '*' + watch + '*' + move + '}else{<<edit|shortcut=e>>*' + hist + '*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' + '*' + watch + '*' + move+'}<br>', x.article, x.params); }; pg.structures.fancy.popupOtherLinks=function(x) { var admin='<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*<<undelete|undeleteShort>>|<<delete|shortcut=d|del>>'; var user='<<contribs|shortcut=c>>if(wikimedia){|<<count|shortcut=#|#>>}'; user+='if(ipuser){|<<arin>>}else{*<<email|shortcut=E|'+ popupString('email')+'>>}if(admin){*<<block|shortcut=b>>}'; var normal='<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>'; return navlinkStringToHTML('<br>if(user){' + user + '*}if(admin){'+admin+'if(user){<br>}else{*}}' + normal, x.article, x.params); }; pg.structures.fancy.popupRedirTitle=pg.structures.fancy.popupTitle; pg.structures.fancy.popupRedirTopLinks=pg.structures.fancy.popupTopLinks; pg.structures.fancy.popupRedirOtherLinks=pg.structures.fancy.popupOtherLinks; /** -- fancy2 -- **/ // hack for User:MacGyverMagic copyStructure('fancy', 'fancy2'); pg.structures.fancy2.popupTopLinks=function(x) { // hack out the <br> at the end and put one at the beginning return '<br>'+pg.structures.fancy.popupTopLinks(x).replace(RegExp('<br>$','i'),''); }; pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title return ['popupError', 'popupImage', 'popupTitle', 'popupUserData', 'popupData', 'popupTopLinks', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; /** -- menus -- **/ copyStructure('original', 'menus'); pg.structures.menus.popupLayout=function () { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupUserData', 'popupData', 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']; }; pg.structures.menus.popupTopLinks = function (x, shorter) { // FIXME maybe this stuff should be cached var s=[]; var dropdiv='<div class="popup_drop">'; var enddiv='</div>'; var hist='<<history|shortcut=h>>'; if (!shorter) { hist = '<menurow>' + hist + '|<<historyfeed|rss>>|<<editors|shortcut=E>></menurow>'; } var lastedit='<<lastEdit|shortcut=/|show last edit>>'; var thank='if(diff){<<thank|send thanks>>}'; var jsHistory='<<lastContrib|last set of edits>><<sinceMe|changes since mine>>'; var linkshere='<<whatLinksHere|shortcut=l|what links here>>'; var related='<<relatedChanges|shortcut=r|related changes>>'; var search='<menurow><<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}' + '|<<google|shortcut=G|web>></menurow>'; var watch='<menurow><<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>></menurow>'; var protect='<menurow><<unprotect|unprotectShort>>|' + '<<protect|shortcut=p>>|<<protectlog|log>></menurow>'; var del='<menurow><<undelete|undeleteShort>>|<<delete|shortcut=d>>|' + '<<deletelog|log>></menurow>'; var move='<<move|shortcut=m|move page>>'; var nullPurge='<menurow><<nullEdit|shortcut=n|null edit>>|<<purge|shortcut=P>></menurow>'; var viewOptions='<menurow><<view|shortcut=v>>|<<render|shortcut=S>>|<<raw>></menurow>'; var editRow='if(oldid){' + '<menurow><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></menurow>' + '<menurow><<revert|shortcut=v>>|<<undo>></menurow>' + '}else{<<edit|shortcut=e>>}'; var markPatrolled='if(rcid){<<markpatrolled|mark patrolled>>}'; var newTopic='if(talk){<<new|shortcut=+|new topic>>}'; var protectDelete='if(admin){' + protect + del + '}'; if (getValueOf('popupActionsMenu')) { s.push( '<<mainlink>>*' + dropdiv + menuTitle('tác vụ')); } else { s.push( dropdiv + '<<mainlink>>'); } s.push( '<menu>'); s.push( editRow + markPatrolled + newTopic + hist + lastedit + thank ); if (!shorter) { s.push(jsHistory); } s.push( move + linkshere + related); if (!shorter) { s.push(nullPurge + search); } if (!shorter) { s.push(viewOptions); } s.push('<hr />' + watch + protectDelete); s.push('<hr />' + 'if(talk){<<article|shortcut=a|view article>><<editArticle|edit article>>}' + 'else{<<talk|shortcut=t|talk page>><<editTalk|edit talk>>' + '<<newTalk|shortcut=+|new topic>>}</menu>' + enddiv); // user menu starts here var email='<<email|shortcut=E|email user>>'; var contribs= 'if(wikimedia){<menurow>}<<contribs|shortcut=c|contributions>>if(wikimedia){</menurow>}' + 'if(admin){<menurow><<deletedContribs>></menurow>}'; s.push('if(user){*' + dropdiv + menuTitle('user')); s.push('<menu>'); s.push('<menurow><<userPage|shortcut=u|user&nbsp;page>>|<<userSpace|space>></menurow>'); s.push('<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' + '<<newUserTalk|shortcut=+|leave comment>>'); if(!shorter) { s.push( 'if(ipuser){<<arin>>}else{' + email + '}' ); } else { s.push( 'if(ipuser){}else{' + email + '}' ); } s.push('<hr />' + contribs + '<<userlog|shortcut=L|user log>>'); s.push('if(wikimedia){<<count|shortcut=#|edit counter>>}'); s.push('if(admin){<menurow><<unblock|unblockShort>>|<<block|shortcut=b|block user>></menurow>}'); s.push('<<blocklog|shortcut=B|block log>>'); s.push('</menu>' + enddiv + '}'); // popups menu starts here if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */) { x.navpop.hasPopupMenu=true; s.push('*' + dropdiv + menuTitle('popupsMenu') + '<menu>'); s.push('<<togglePreviews|toggle previews>>'); s.push('<<purgePopups|reset>>'); s.push('<<disablePopups|disable>>'); s.push('</menu>'+enddiv); } return navlinkStringToHTML(s.join(''), x.article, x.params); }; function menuTitle(s) { return '<a href="#" noPopup=1>' + popupString(s) + '</a>'; } pg.structures.menus.popupRedirTitle=pg.structures.menus.popupTitle; pg.structures.menus.popupRedirTopLinks=pg.structures.menus.popupTopLinks; copyStructure('menus', 'shortmenus'); pg.structures.shortmenus.popupTopLinks=function(x) { return pg.structures.menus.popupTopLinks(x,true); }; pg.structures.shortmenus.popupRedirTopLinks=pg.structures.shortmenus.popupTopLinks; //</NOLITE> pg.structures.lite={}; pg.structures.lite.popupLayout=function () { return ['popupTitle', 'popupPreview' ]; }; pg.structures.lite.popupTitle=function (x) { log (x.article + ': structures.lite.popupTitle'); //return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params); return '<div><span class="popup_mainlink"><b>' + x.article.toString() + '</b></span></div>'; }; // ENDFILE: structures.js // STARTFILE: autoedit.js //<NOLITE> function substitute(data,cmdBody) { // alert('sub\nfrom: '+cmdBody.from+'\nto: '+cmdBody.to+'\nflags: '+cmdBody.flags); var fromRe=RegExp(cmdBody.from, cmdBody.flags); return data.replace(fromRe, cmdBody.to); } function execCmds(data, cmdList) { for (var i=0; i<cmdList.length; ++i) { data=cmdList[i].action(data, cmdList[i]); } return data; } function parseCmd(str) { // returns a list of commands if (!str.length) { return []; } var p=false; switch (str.charAt(0)) { case 's': p=parseSubstitute(str); break; default: return false; } if (p) { return [p].concat(parseCmd(p.remainder)); } return false; } function unEscape(str, sep) { return str.split('\\\\').join('\\').split('\\'+sep).join(sep).split('\\n').join('\n'); } function parseSubstitute(str) { // takes a string like s/a/b/flags;othercmds and parses it var from,to,flags,tmp; if (str.length<4) { return false; } var sep=str.charAt(1); str=str.substring(2); tmp=skipOver(str,sep); if (tmp) { from=tmp.segment; str=tmp.remainder; } else { return false; } tmp=skipOver(str,sep); if (tmp) { to=tmp.segment; str=tmp.remainder; } else { return false; } flags=''; if (str.length) { tmp=skipOver(str,';') || skipToEnd(str, ';'); if (tmp) {flags=tmp.segment; str=tmp.remainder; } } return {action: substitute, from: from, to: to, flags: flags, remainder: str}; } function skipOver(str,sep) { var endSegment=findNext(str,sep); if (endSegment<0) { return false; } var segment=unEscape(str.substring(0,endSegment), sep); return {segment: segment, remainder: str.substring(endSegment+1)}; } /*eslint-disable*/ function skipToEnd(str,sep) { return {segment: str, remainder: ''}; } /*eslint-enable */ function findNext(str, ch) { for (var i=0; i<str.length; ++i) { if (str.charAt(i)=='\\') { i+=2; } if (str.charAt(i)==ch) { return i; } } return -1; } function setCheckbox(param, box) { var val=mw.util.getParamValue(param); if (val) { switch (val) { case '1': case 'yes': case 'true': box.checked=true; break; case '0': case 'no': case 'false': box.checked=false; } } } function autoEdit() { setupPopups( function () { if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version') ) { return false; } if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken')===autoClickToken()) { pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action')); } if (!document.editform) { return false; } if (autoEdit.alreadyRan) { return false; } autoEdit.alreadyRan=true; var cmdString=mw.util.getParamValue('autoedit'); if (cmdString) { try { var editbox=document.editform.wpTextbox1; var cmdList=parseCmd(cmdString); var input=editbox.value; var output=execCmds(input, cmdList); editbox.value=output; } catch (dang) { return; } // wikEd user script compatibility if (typeof(wikEdUseWikEd) != 'undefined') { if (wikEdUseWikEd === true) { WikEdUpdateFrame(); } } } setCheckbox('autominor', document.editform.wpMinoredit); setCheckbox('autowatch', document.editform.wpWatchthis); var rvid = mw.util.getParamValue('autorv'); if (rvid) { var url=pg.wiki.apiwikibase + '?action=query&format=json&formatversion=2&prop=revisions&revids='+rvid; startDownload(url, null, autoEdit2); } else { autoEdit2(); } } ); } function autoEdit2(d) { var summary=mw.util.getParamValue('autosummary'); var summaryprompt=mw.util.getParamValue('autosummaryprompt'); var summarynotice=''; if (d && d.data && mw.util.getParamValue('autorv')) { var s = getRvSummary(summary, d.data); if (s === false) { summaryprompt=true; summarynotice=popupString('Failed to get revision information, please edit manually.\n\n'); summary = simplePrintf(summary, [mw.util.getParamValue('autorv'), '(unknown)', '(unknown)']); } else { summary = s; } } if (summaryprompt) { var txt= summarynotice + popupString('Enter a non-empty edit summary or press cancel to abort'); var response=prompt(txt, summary); if (response) { summary=response; } else { return; } } if (summary) { document.editform.wpSummary.value=summary; } // Attempt to avoid possible premature clicking of the save button // (maybe delays in updates to the DOM are to blame?? or a red herring) setTimeout(autoEdit3, 100); } function autoClickToken() { return mw.user.sessionId(); } function autoEdit3() { if( mw.util.getParamValue('actoken') != autoClickToken()) { return; } var btn=mw.util.getParamValue('autoclick'); if (btn) { if (document.editform && document.editform[btn]) { var button=document.editform[btn]; var msg=tprintf('The %s button has been automatically clicked. Please wait for the next page to load.', [ button.value ]); bannerMessage(msg); document.title='('+document.title+')'; button.click(); } else { alert(tprintf('Could not find button %s. Please check the settings in your javascript file.', [ btn ])); } } } function bannerMessage(s) { var headings=document.getElementsByTagName('h1'); if (headings) { var div=document.createElement('div'); div.innerHTML='<font size=+1><b>' + s + '</b></font>'; headings[0].parentNode.insertBefore(div, headings[0]); } } function getRvSummary(template, json) { try { var o=getJsObj(json); var edit = anyChild(o.query.pages).revisions[0]; var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, ''); return simplePrintf(template, [edit.revid, timestamp, edit.userhidden ? '(hidden)' : edit.user ]); } catch (badness) { return false; } } //</NOLITE> // ENDFILE: autoedit.js // STARTFILE: downloader.js /** @fileoverview {@link Downloader}, a xmlhttprequest wrapper, and helper functions. */ /** Creates a new Downloader @constructor @class The Downloader class. Create a new instance of this class to download stuff. @param {String} url The url to download. This can be omitted and supplied later. */ function Downloader(url) { if (typeof XMLHttpRequest!='undefined') { this.http = new XMLHttpRequest(); } /** The url to download @type String */ this.url = url; /** A universally unique ID number @type integer */ this.id=null; /** Modification date, to be culled from the incoming headers @type Date @private */ this.lastModified = null; /** What to do when the download completes successfully @type Function @private */ this.callbackFunction = null; /** What to do on failure @type Function @private */ this.onFailure = null; /** Flag set on <code>abort</code> @type boolean */ this.aborted = false; /** HTTP method. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for possibilities. @type String */ this.method='GET'; /** Async flag. @type boolean */ this.async=true; } new Downloader(); /** Submits the http request. */ Downloader.prototype.send = function (x) { if (!this.http) { return null; } return this.http.send(x); }; /** Aborts the download, setting the <code>aborted</code> field to true. */ Downloader.prototype.abort = function () { if (!this.http) { return null; } this.aborted=true; return this.http.abort(); }; /** Returns the downloaded data. */ Downloader.prototype.getData = function () {if (!this.http) { return null; } return this.http.responseText;}; /** Prepares the download. */ Downloader.prototype.setTarget = function () { if (!this.http) { return null; } this.http.open(this.method, this.url, this.async); this.http.setRequestHeader( 'Api-User-Agent', pg.misc.userAgent ); }; /** Gets the state of the download. */ Downloader.prototype.getReadyState=function () {if (!this.http) { return null; } return this.http.readyState;}; pg.misc.downloadsInProgress = { }; /** Starts the download. Note that setTarget {@link Downloader#setTarget} must be run first */ Downloader.prototype.start=function () { if (!this.http) { return; } pg.misc.downloadsInProgress[this.id] = this; this.http.send(null); }; /** Gets the 'Last-Modified' date from the download headers. Should be run after the download completes. Returns <code>null</code> on failure. @return {Date} */ Downloader.prototype.getLastModifiedDate=function () { if(!this.http) { return null; } var lastmod=null; try { lastmod=this.http.getResponseHeader('Last-Modified'); } catch (err) {} if (lastmod) { return new Date(lastmod); } return null; }; /** Sets the callback function. @param {Function} f callback function, called as <code>f(this)</code> on success */ Downloader.prototype.setCallback = function (f) { if(!this.http) { return; } this.http.onreadystatechange = f; }; Downloader.prototype.getStatus = function() { if (!this.http) { return null; } return this.http.status; }; ////////////////////////////////////////////////// // helper functions /** Creates a new {@link Downloader} and prepares it for action. @param {String} url The url to download @param {integer} id The ID of the {@link Downloader} object @param {Function} callback The callback function invoked on success @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser */ function newDownload(url, id, callback, onfailure) { var d=new Downloader(url); if (!d.http) { return 'ohdear'; } d.id=id; d.setTarget(); if (!onfailure) { onfailure=2; } var f = function () { if (d.getReadyState() == 4) { delete pg.misc.downloadsInProgress[this.id]; try { if ( d.getStatus() == 200 ) { d.data=d.getData(); d.lastModified=d.getLastModifiedDate(); callback(d); } else if (typeof onfailure == typeof 1) { if (onfailure > 0) { // retry newDownload(url, id, callback, onfailure - 1); } } else if (typeof onfailure === 'function') { onfailure(d,url,id,callback); } } catch (somerr) { /* ignore it */ } } }; d.setCallback(f); return d; } /** Simulates a download from cached data. The supplied data is put into a {@link Downloader} as if it had downloaded it. @param {String} url The url. @param {integer} id The ID. @param {Function} callback The callback, which is invoked immediately as <code>callback(d)</code>, where <code>d</code> is the new {@link Downloader}. @param {String} data The (cached) data. @param {Date} lastModified The (cached) last modified date. */ function fakeDownload(url, id, callback, data, lastModified, owner) { var d=newDownload(url,callback); d.owner=owner; d.id=id; d.data=data; d.lastModified=lastModified; return callback(d); } /** Starts a download. @param {String} url The url to download @param {integer} id The ID of the {@link Downloader} object @param {Function} callback The callback function invoked on success @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser */ function startDownload(url, id, callback) { var d=newDownload(url, id, callback); if (typeof d == typeof '' ) { return d; } d.start(); return d; } /** Aborts all downloads which have been started. */ function abortAllDownloads() { for ( var x in pg.misc.downloadsInProgress ) { try { pg.misc.downloadsInProgress[x].aborted=true; pg.misc.downloadsInProgress[x].abort(); delete pg.misc.downloadsInProgress[x]; } catch (e) {} } } // ENDFILE: downloader.js // STARTFILE: livepreview.js // TODO: location is often not correct (eg relative links in previews) // NOTE: removed md5 and image and math parsing. was broken, lots of bytes. /** * InstaView - a Mediawiki to HTML converter in JavaScript * Version 0.6.1 * Copyright (C) Pedro Fayolle 2005-2006 * https://en.wikipedia.org/wiki/User:Pilaf * Distributed under the BSD license * * Changelog: * * 0.6.1 * - Fixed problem caused by \r characters * - Improved inline formatting parser * * 0.6 * - Changed name to InstaView * - Some major code reorganizations and factored out some common functions * - Handled conversion of relative links (i.e. [[/foo]]) * - Fixed misrendering of adjacent definition list items * - Fixed bug in table headings handling * - Changed date format in signatures to reflect Mediawiki's * - Fixed handling of [[:Image:...]] * - Updated MD5 function (hopefully it will work with UTF-8) * - Fixed bug in handling of links inside images * * To do: * - Better support for math tags * - Full support for <nowiki> * - Parser-based (as opposed to RegExp-based) inline wikicode handling (make it one-pass and bullet-proof) * - Support for templates (through AJAX) * - Support for coloured links (AJAX) */ var Insta = {}; function setupLivePreview() { // options Insta.conf = { baseUrl: '', user: {}, wiki: { lang: pg.wiki.lang, interwiki: pg.wiki.interwiki, default_thumb_width: 180 }, paths: { articles: pg.wiki.articlePath + '/', // Only used for Insta previews with images. (not in popups) math: '/math/', images: '//upload.wikimedia.org/wikipedia/en/', // FIXME getImageUrlStart(pg.wiki.hostname), images_fallback: '//upload.wikimedia.org/wikipedia/commons/', }, locale: { user: mw.config.get('wgFormattedNamespaces')[pg.nsUserId], image: mw.config.get('wgFormattedNamespaces')[pg.nsImageId], category: mw.config.get('wgFormattedNamespaces')[pg.nsCategoryId], // shouldn't be used in popup previews, i think months: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'] } }; // options with default values or backreferences Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian'; Insta.conf.user.signature = '[['+Insta.conf.locale.user+':'+Insta.conf.user.name+'|'+Insta.conf.user.name+']]'; //Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/'; // define constants Insta.BLOCK_IMAGE = new RegExp('^\\[\\[(?:File|Image|'+Insta.conf.locale.image+ '):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)', 'i'); } Insta.dump = function(from, to) { if (typeof from == 'string') { from = document.getElementById(from); } if (typeof to == 'string') { to = document.getElementById(to); } to.innerHTML = this.convert(from.value); }; Insta.convert = function(wiki) { var ll = (typeof wiki == 'string')? wiki.replace(/\r/g,'').split(/\n/): wiki, // lines of wikicode o = '', // output p = 0, // para flag $r; // result of passing a regexp to $() // some shorthands function remain() { return ll.length; } function sh() { return ll.shift(); } // shift function ps(s) { o += s; } // push // similar to C's printf, uses ? as placeholders, ?? to escape question marks function f() { var i=1, a=arguments, f=a[0], o='', c, p; for (; i<a.length; i++) { if ((p=f.indexOf('?'))+1) { // allow character escaping i -= c = f.charAt(p+1)=='?' ? 1 : 0; o += f.substring(0,p) + (c ? '?' : a[i]); f = f.substr(p+1+c); } else { break; } } return o+f; } function html_entities(s) { return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); } // Wiki text parsing to html is a nightmare. // The below functions deliberately don't escape the ampersand since this would make it more difficult, // and we don't absolutely need to for how we need it. // This means that any unescaped ampersands in wikitext will remain unescaped and can cause invalid HTML. // Browsers should all be able to handle it though. // We also escape significant wikimarkup characters to prevent further matching on the processed text function htmlescape_text(s) { return s.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/:/g,"&#58;").replace(/\[/g,"&#91;").replace(/]/g,"&#93;"); } function htmlescape_attr(s) { return htmlescape_text(s).replace(/'/g,"&#39;").replace(/"/g,"&quot;"); } // return the first non matching character position between two strings function str_imatch(a, b) { for (var i=0, l=Math.min(a.length, b.length); i<l; i++) { if (a.charAt(i)!=b.charAt(i)) { break; } } return i; } // compare current line against a string or regexp // if passed a string it will compare only the first string.length characters // if passed a regexp the result is stored in $r function $(c) { return (typeof c == 'string') ? (ll[0].substr(0,c.length)==c) : ($r = ll[0].match(c)); } function $$(c) { return ll[0]==c; } // compare current line against a string function _(p) { return ll[0].charAt(p); } // return char at pos p function endl(s) { ps(s); sh(); } function parse_list() { var prev=''; while (remain() && $(/^([*#:;]+)(.*)$/)) { var l_match = $r; sh(); var ipos = str_imatch(prev, l_match[1]); // close uncontinued lists for (var prevPos=prev.length-1; prevPos >= ipos; prevPos--) { var pi = prev.charAt(prevPos); if (pi=='*') { ps('</ul>'); } else if (pi=='#') { ps('</ol>'); } // close a dl only if the new item is not a dl item (:, ; or empty) else if($.inArray(l_match[1].charAt(prevPos), ['','*','#'])) { ps('</dl>'); } } // open new lists for (var matchPos=ipos; matchPos<l_match[1].length; matchPos++) { var li = l_match[1].charAt(matchPos); if (li=='*') { ps('<ul>'); } else if (li=='#') { ps('<ol>'); } // open a new dl only if the prev item is not a dl item (:, ; or empty) else if ($.inArray(prev.charAt(matchPos), ['','*','#'])) { ps('<dl>'); } } switch (l_match[1].charAt(l_match[1].length-1)) { case '*': case '#': ps('<li>' + parse_inline_nowiki(l_match[2])); break; case ';': ps('<dt>'); var dt_match = l_match[2].match(/(.*?)(:.*?)$/); // handle ;dt :dd format if (dt_match) { ps(parse_inline_nowiki(dt_match[1])); ll.unshift(dt_match[2]); } else ps(parse_inline_nowiki(l_match[2])); break; case ':': ps('<dd>' + parse_inline_nowiki(l_match[2])); } prev=l_match[1]; } // close remaining lists for (var i=prev.length-1; i>=0; i--) { ps(f('</?>', (prev.charAt(i)=='*')? 'ul': ((prev.charAt(i)=='#')? 'ol': 'dl'))); } } function parse_table() { endl(f('<table>', $(/^\{\|( .*)$/)? $r[1]: '')); for (;remain();) if ($('|')) switch (_(1)) { case '}': endl('</table>'); return; case '-': endl(f('<tr>', $(/\|-*(.*)/)[1])); break; default: parse_table_data(); } else if ($('!')) { parse_table_data(); } else { sh(); } } function parse_table_data() { var td_line, match_i; // 1: "|+", '|' or '+' // 2: ?? // 3: attributes ?? // TODO: finish commenting this regexp var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/); if (td_match[1] == '|+') ps('<caption'); else ps('<t' + ((td_match[1]=='|')?'d':'h')); if (typeof td_match[3] != 'undefined') { //ps(' ' + td_match[3]) match_i = 4; } else match_i = 2; ps('>'); if (td_match[1] != '|+') { // use || or !! as a cell separator depending on context // NOTE: when split() is passed a regexp make sure to use non-capturing brackets td_line = td_match[match_i].split((td_match[1] == '|')? '||': /(?:\|\||!!)/); ps(parse_inline_nowiki(td_line.shift())); while (td_line.length) ll.unshift(td_match[1] + td_line.pop()); } else ps(td_match[match_i]); var tc = 0, td = []; while (remain()) { td.push(sh()); if ($('|')) { if (!tc) break; // we're at the outer-most level (no nested tables), skip to td parse else if (_(1)=='}') tc--; } else if (!tc && $('!')) break; else if ($('{|')) tc++; } if (td.length) ps(Insta.convert(td)); } function parse_pre() { ps('<pre>'); do { endl(parse_inline_nowiki(ll[0].substring(1)) + "\n"); } while (remain() && $(' ')); ps('</pre>'); } function parse_block_image() { ps(parse_image(sh())); } function parse_image(str) { //<NOLITE> // get what's in between "[[Image:" and "]]" var tag = str.substring(str.indexOf(':') + 1, str.length - 2); /* eslint-disable no-unused-vars */ var width; var attr = [], filename, caption = ''; var thumb=0, frame=0, center=0; var align=''; /* eslint-enable no-unused-vars */ if (tag.match(/\|/)) { // manage nested links var nesting = 0; var last_attr; for (var i = tag.length-1; i > 0; i--) { if (tag.charAt(i) == '|' && !nesting) { last_attr = tag.substr(i+1); tag = tag.substring(0, i); break; } else switch (tag.substr(i-1, 2)) { case ']]': nesting++; i--; break; case '[[': nesting--; i--; } } attr = tag.split(/\s*\|\s*/); attr.push(last_attr); filename = attr.shift(); var w_match; for (;attr.length; attr.shift()) { w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/); if (w_match) width = w_match[1]; else switch(attr[0]) { case 'thumb': case 'thumbnail': thumb=true; frame=true; break; case 'frame': frame=true; break; case 'none': case 'right': case 'left': center=false; align=attr[0]; break; case 'center': center=true; align='none'; break; default: if (attr.length == 1) caption = attr[0]; } } } else filename = tag; return ''; //</NOLITE> } function parse_inline_nowiki(str) { var start, lastend=0; var substart=0, nestlev=0, open, close, subloop; var html=''; while (-1 != (start = str.indexOf('<nowiki>', substart))) { html += parse_inline_wiki(str.substring(lastend, start)); start += 8; substart = start; subloop = true; do { open = str.indexOf('<nowiki>', substart); close = str.indexOf('</nowiki>', substart); if (close<=open || open==-1) { if (close==-1) { return html + html_entities(str.substr(start)); } substart = close+9; if (nestlev) { nestlev--; } else { lastend = substart; html += html_entities(str.substring(start, lastend-9)); subloop = false; } } else { substart = open+8; nestlev++; } } while (subloop); } return html + parse_inline_wiki(str.substr(lastend)); } function parse_inline_images(str) { //<NOLITE> var start, substart=0, nestlev=0; var loop, close, open, wiki, html; while (-1 != (start=str.indexOf('[[', substart))) { if(str.substr(start+2).match(RegExp('^(Image|File|' + Insta.conf.locale.image + '):','i'))) { loop=true; substart=start; do { substart+=2; close=str.indexOf(']]',substart); open=str.indexOf('[[',substart); if (close<=open||open==-1) { if (close==-1) return str; substart=close; if (nestlev) { nestlev--; } else { wiki=str.substring(start,close+2); html=parse_image(wiki); str=str.replace(wiki,html); substart=start+html.length; loop=false; } } else { substart=open; nestlev++; } } while (loop); } else break; } //</NOLITE> return str; } // the output of this function doesn't respect the FILO structure of HTML // but since most browsers can handle it I'll save myself the hassle function parse_inline_formatting(str) { var em,st,i,li,o=''; while ((i=str.indexOf("''",li))+1) { o += str.substring(li,i); li=i+2; if (str.charAt(i+2)=="'") { li++; st=!st; o+=st?'<strong>':'</strong>'; } else { em=!em; o+=em?'<em>':'</em>'; } } return o+str.substr(li); } function parse_inline_wiki(str) { str = parse_inline_images(str); str = parse_inline_formatting(str); // math str = str.replace(/<(?:)math>(.*?)<\/math>/ig, ''); // Build a Mediawiki-formatted date string var date = new Date(); var minutes = date.getUTCMinutes(); if (minutes < 10) minutes = '0' + minutes; date = f("?:?, ? ? ? (UTC)", date.getUTCHours(), minutes, date.getUTCDate(), Insta.conf.locale.months[date.getUTCMonth()], date.getUTCFullYear()); // text formatting return str. // signatures replace(/~{5}(?!~)/g, date). replace(/~{4}(?!~)/g, Insta.conf.user.name+' '+date). replace(/~{3}(?!~)/g, Insta.conf.user.name). // [[:Category:...]], [[:Image:...]], etc... replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):[^|]*?)\\]\\](\\w*)','gi'), function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2));}). // remove straight category and interwiki tags replace(RegExp('\\[\\[(?:'+Insta.conf.locale.category+'|'+Insta.conf.wiki.interwiki+'):.*?\\]\\]','gi'),''). // [[:Category:...|Links]], [[:Image:...|Links]], etc... replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):.*?)\\|([^\\]]+?)\\]\\](\\w*)','gi'), function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));}). // [[/Relative links]] replace(/\[\[(\/[^|]*?)\]\]/g, function($0,$1){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($1)); }). // [[/Replaced|Relative links]] replace(/\[\[(\/.*?)\|(.+?)\]\]/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($2)); }). // [[Common links]] replace(/\[\[([^[|]*?)\]\](\w*)/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2)); }). // [[Replaced|Links]] replace(/\[\[([^[]*?)\|([^\]]+?)\]\](\w*)/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3)); }). // [[Stripped:Namespace|Namespace]] replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1) + htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($2)); }). // External links replace(/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g, function($0,$1,$2,$3,$4){return f("<a class='external' href='?:?'>?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($4)); }). replace(/\[http:\/\/(.*?)\]/g, function($0,$1){return f("<a class='external' href='http://?'>[#]</a>", htmlescape_attr($1)); }). replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, function($0,$1,$2,$3){return f("<a class='external' href='?:?'>?:?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($1), htmlescape_text($2) + htmlescape_text($3)); }). replace(/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*[^.,!?;: $])/g, function($0,$1,$2,$3,$4){return f("?<a class='external' href='?:?'>?:?</a>", htmlescape_text($1), htmlescape_attr($2), htmlescape_attr($3) + htmlescape_attr($4), htmlescape_text($2), htmlescape_text($3) + htmlescape_text($4)); }). replace('__NOTOC__',''). replace('__NOEDITSECTION__',''); } // begin parsing for (;remain();) if ($(/^(={1,6})(.*)\1(.*)$/)) { p=0; endl(f('<h?>?</h?>?', $r[1].length, parse_inline_nowiki($r[2]), $r[1].length, $r[3])); } else if ($(/^[*#:;]/)) { p=0; parse_list(); } else if ($(' ')) { p=0; parse_pre(); } else if ($('{|')) { p=0; parse_table(); } else if ($(/^----+$/)) { p=0; endl('<hr />'); } else if ($(Insta.BLOCK_IMAGE)) { p=0; parse_block_image(); } else { // handle paragraphs if ($$('')) { p = (remain()>1 && ll[1]===('')); if (p) endl('<p><br>'); } else { if(!p) { ps('<p>'); p=1; } ps(parse_inline_nowiki(ll[0]) + ' '); } sh(); } return o; }; function wiki2html(txt,baseurl) { Insta.conf.baseUrl=baseurl; return Insta.convert(txt); } // ENDFILE: livepreview.js // STARTFILE: pageinfo.js //<NOLITE> function popupFilterPageSize(data) { return formatBytes(data.length); } function popupFilterCountLinks(data) { var num=countLinks(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('wikiLinks'):popupString('wikiLink')); } function popupFilterCountImages(data) { var num=countImages(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('images'):popupString('image')); } function popupFilterCountCategories(data) { var num=countCategories(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('categories'):popupString('category')); } function popupFilterLastModified(data,download) { var lastmod=download.lastModified; var now=new Date(); var age=now-lastmod; if (lastmod && getValueOf('popupLastModified')) { return (tprintf('%s old', [formatAge(age)])).replace(RegExp(' ','g'), '&nbsp;'); } return ''; } function formatAge(age) { // coerce into a number var a=0+age, aa=a; var seclen = 1000; var minlen = 60*seclen; var hourlen = 60*minlen; var daylen = 24*hourlen; var weeklen = 7*daylen; var numweeks = (a-a%weeklen)/weeklen; a = a-numweeks*weeklen; var sweeks = addunit(numweeks, 'week'); var numdays = (a-a%daylen)/daylen; a = a-numdays*daylen; var sdays = addunit(numdays, 'day'); var numhours = (a-a%hourlen)/hourlen; a = a-numhours*hourlen; var shours = addunit(numhours,'hour'); var nummins = (a-a%minlen)/minlen; a = a-nummins*minlen; var smins = addunit(nummins, 'minute'); var numsecs = (a-a%seclen)/seclen; a = a-numsecs*seclen; var ssecs = addunit(numsecs, 'second'); if (aa > 4*weeklen) { return sweeks; } if (aa > weeklen) { return sweeks + ' ' + sdays; } if (aa > daylen) { return sdays + ' ' + shours; } if (aa > 6*hourlen) { return shours; } if (aa > hourlen) { return shours + ' ' + smins; } if (aa > 10*minlen) { return smins; } if (aa > minlen) { return smins + ' ' + ssecs; } return ssecs; } function addunit(num,str) { return '' + num + ' ' + ((num!=1) ? popupString(str+'s') : popupString(str)) ;} function runPopupFilters(list, data, download) { var ret=[]; for (var i=0; i<list.length; ++i) { if (list[i] && typeof list[i] == 'function') { var s=list[i](data, download, download.owner.article); if (s) { ret.push(s); } } } return ret; } function getPageInfo(data, download) { if (!data || data.length === 0) { return popupString('Empty page'); } var popupFilters=getValueOf('popupFilters') || []; var extraPopupFilters = getValueOf('extraPopupFilters') || []; var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download); var pageInfo=pageInfoArray.join(', '); if (pageInfo !== '' ) { pageInfo = upcaseFirst(pageInfo); } return pageInfo; } // this could be improved! function countLinks(wikiText) { return wikiText.split('[[').length - 1; } // if N = # matches, n = # brackets, then // String.parenSplit(regex) intersperses the N+1 split elements // with Nn other elements. So total length is // L= N+1 + Nn = N(n+1)+1. So N=(L-1)/(n+1). function countImages(wikiText) { return (wikiText.parenSplit(pg.re.image).length - 1) / (pg.re.imageBracketCount + 1); } function countCategories(wikiText) { return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1); } function popupFilterStubDetect(data, download, article) { var counts=stubCount(data, article); if (counts.real) { return popupString('stub'); } if (counts.sect) { return popupString('section stub'); } return ''; } function popupFilterDisambigDetect(data, download, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return ''; } return (isDisambig(data, article)) ? popupString('disambig') : ''; } function formatBytes(num) { return (num > 949) ? (Math.round(num/100)/10+popupString('kB')) : (num +'&nbsp;' + popupString('bytes')) ; } //</NOLITE> // ENDFILE: pageinfo.js // STARTFILE: titles.js /** @fileoverview Defines the {@link Title} class, and associated crufty functions. <code>Title</code> deals with article titles and their various forms. {@link Stringwrapper} is the parent class of <code>Title</code>, which exists simply to make things a little neater. */ /** Creates a new Stringwrapper. @constructor @class the Stringwrapper class. This base class is not really useful on its own; it just wraps various common string operations. */ function Stringwrapper() { /** Wrapper for this.toString().indexOf() @param {String} x @type integer */ this.indexOf=function(x){return this.toString().indexOf(x);}; /** Returns this.value. @type String */ this.toString=function(){return this.value;}; /** Wrapper for {@link String#parenSplit} applied to this.toString() @param {RegExp} x @type Array */ this.parenSplit=function(x){return this.toString().parenSplit(x);}; /** Wrapper for this.toString().substring() @param {String} x @param {String} y (optional) @type String */ this.substring=function(x,y){ if (typeof y=='undefined') { return this.toString().substring(x); } return this.toString().substring(x,y); }; /** Wrapper for this.toString().split() @param {String} x @type Array */ this.split=function(x){return this.toString().split(x);}; /** Wrapper for this.toString().replace() @param {String} x @param {String} y @type String */ this.replace=function(x,y){ return this.toString().replace(x,y); }; } /** Creates a new <code>Title</code>. @constructor @class The Title class. Holds article titles and converts them into various forms. Also deals with anchors, by which we mean the bits of the article URL after a # character, representing locations within an article. @param {String} value The initial value to assign to the article. This must be the canonical title (see {@link Title#value}. Omit this in the constructor and use another function to set the title if this is unavailable. */ function Title(val) { /** The canonical article title. This must be in UTF-8 with no entities, escaping or nasties. Also, underscores should be replaced with spaces. @type String @private */ this.value=null; /** The canonical form of the anchor. This should be exactly as it appears in the URL, i.e. with the .C3.0A bits in. @type String */ this.anchor=''; this.setUtf(val); } Title.prototype=new Stringwrapper(); /** Returns the canonical representation of the article title, optionally without anchor. @param {boolean} omitAnchor @fixme Decide specs for anchor @return String The article title and the anchor. */ Title.prototype.toString=function(omitAnchor) { return this.value + ( (!omitAnchor && this.anchor) ? '#' + this.anchorString() : '' ); }; Title.prototype.anchorString=function() { if (!this.anchor) { return ''; } var split=this.anchor.parenSplit(/((?:[.][0-9A-F]{2})+)/); var len=split.length; for (var j=1; j<len; j+=2) { // FIXME s/decodeURI/decodeURIComponent/g ? split[j]=decodeURIComponent(split[j].split('.').join('%')).split('_').join(' '); } return split.join(''); }; Title.prototype.urlAnchor=function() { var split=this.anchor.parenSplit('/((?:[%][0-9A-F]{2})+)/'); var len=split.length; for (var j=1; j<len; j+=2) { split[j]=split[j].split('%').join('.'); } return split.join(''); }; Title.prototype.anchorFromUtf=function(str) { this.anchor=encodeURIComponent(str.split(' ').join('_')) .split('%3A').join(':').split("'").join('%27').split('%').join('.'); }; Title.fromURL=function(h) { return new Title().fromURL(h); }; Title.prototype.fromURL=function(h) { if (typeof h != 'string') { this.value=null; return this; } // NOTE : playing with decodeURI, encodeURI, escape, unescape, // we seem to be able to replicate the IE borked encoding // IE doesn't do this new-fangled utf-8 thing. // and it's worse than that. // IE seems to treat the query string differently to the rest of the url // the query is treated as bona-fide utf8, but the first bit of the url is pissed around with // we fix up & for all browsers, just in case. var splitted=h.split('?'); splitted[0]=splitted[0].split('&').join('%26'); h=splitted.join('?'); var contribs=pg.re.contribs.exec(h); if (contribs) { if (contribs[1]=='title=') { contribs[3]=contribs[3].split('+').join(' '); } var u=new Title(contribs[3]); this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + u.stripNamespace())); return this; } var email=pg.re.email.exec(h); if (email) { this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(email[3]).stripNamespace())); return this; } var backlinks=pg.re.backlinks.exec(h); if (backlinks) { this.setUtf(this.decodeNasties(new Title(backlinks[3]))); return this; } //A dummy title object for a Special:Diff link. var specialdiff=pg.re.specialdiff.exec(h); if (specialdiff) { this.setUtf(this.decodeNasties(new Title(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Diff'))); return this; } // no more special cases to check -- // hopefully it's not a disguised user-related or specially treated special page var m=pg.re.main.exec(h); if(m === null) { this.value=null; } else { var fromBotInterface = /[?](.+[&])?title=/.test(h); if (fromBotInterface) { m[2]=m[2].split('+').join('_'); } var extracted = m[2] + (m[3] ? '#' + m[3] : ''); if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) { // Fix Safari issue // Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3. this.setUtf(decodeURIComponent(unescape(extracted))); } else { this.setUtf(this.decodeNasties(extracted)); } } return this; }; Title.prototype.decodeNasties=function(txt) { var ret= this.decodeEscapes(decodeURI(txt)); ret = ret.replace(/[_ ]*$/, ''); return ret; }; Title.prototype.decodeEscapes=function(txt) { var split=txt.parenSplit(/((?:[%][0-9A-Fa-f]{2})+)/); var len=split.length; for (var i=1; i<len; i=i+2) { // FIXME is decodeURIComponent better? split[i]=unescape(split[i]); } return split.join(''); }; Title.fromAnchor=function(a) { return new Title().fromAnchor(a); }; Title.prototype.fromAnchor=function(a) { if (!a) { this.value=null; return this; } return this.fromURL(a.href); }; Title.fromWikiText=function(txt) { return new Title().fromWikiText(txt); }; Title.prototype.fromWikiText=function(txt) { // FIXME - testing needed txt=myDecodeURI(txt); this.setUtf(txt); return this; }; Title.prototype.hintValue=function(){ if(!this.value) { return ''; } return safeDecodeURI(this.value); }; //<NOLITE> Title.prototype.toUserName=function(withNs) { if (this.namespaceId() != pg.nsUserId && this.namespaceId() != pg.nsUsertalkId) { this.value=null; return; } this.value = (withNs ? mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' : '') + this.stripNamespace().split('/')[0]; }; Title.prototype.userName=function(withNs) { var t=(new Title(this.value)); t.toUserName(withNs); if (t.value) { return t; } return null; }; Title.prototype.toTalkPage=function() { // convert article to a talk page, or if we can't, return null // In other words: return null if this ALREADY IS a talk page // and return the corresponding talk page otherwise // // Per https://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces // * All discussion namespaces have odd-integer indices // * The discussion namespace index for a specific namespace with index n is n + 1 if (this.value === null) { return null; } var namespaceId = this.namespaceId(); if (namespaceId>=0 && namespaceId % 2 === 0) //non-special and subject namespace { var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId+1]; if (typeof localizedNamespace!=='undefined') { if (localizedNamespace === '') { this.value = this.stripNamespace(); } else { this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace(); } return this.value; } } this.value=null; return null; }; //</NOLITE> // Return canonical, localized namespace Title.prototype.namespace=function() { return mw.config.get('wgFormattedNamespaces')[this.namespaceId()]; }; Title.prototype.namespaceId=function() { var n=this.value.indexOf(':'); if (n<0) { return 0; } //mainspace var namespaceId = mw.config.get('wgNamespaceIds')[this.value.substring(0,n).split(' ').join('_').toLowerCase()]; if (typeof namespaceId=='undefined') return 0; //mainspace return namespaceId; }; //<NOLITE> Title.prototype.talkPage=function() { var t=new Title(this.value); t.toTalkPage(); if (t.value) { return t; } return null; }; Title.prototype.isTalkPage=function() { if (this.talkPage()===null) { return true; } return false; }; Title.prototype.toArticleFromTalkPage=function() { //largely copy/paste from toTalkPage above. if (this.value === null) { return null; } var namespaceId = this.namespaceId(); if (namespaceId >= 0 && namespaceId % 2 == 1) //non-special and talk namespace { var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId-1]; if (typeof localizedNamespace!=='undefined') { if (localizedNamespace === '') { this.value = this.stripNamespace(); } else { this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace(); } return this.value; } } this.value=null; return null; }; Title.prototype.articleFromTalkPage=function() { var t=new Title(this.value); t.toArticleFromTalkPage(); if (t.value) { return t; } return null; }; Title.prototype.articleFromTalkOrArticle=function() { var t=new Title(this.value); if ( t.toArticleFromTalkPage() ) { return t; } return this; }; Title.prototype.isIpUser=function() { return pg.re.ipUser.test(this.userName()); }; //</NOLITE> Title.prototype.stripNamespace=function(){ // returns a string, not a Title var n=this.value.indexOf(':'); if (n<0) { return this.value; } var namespaceId = this.namespaceId(); if (namespaceId === pg.nsMainspaceId) return this.value; return this.value.substring(n+1); }; Title.prototype.setUtf=function(value){ if (!value) { this.value=''; return; } var anch=value.indexOf('#'); if(anch < 0) { this.value=value.split('_').join(' '); this.anchor=''; return; } this.value=value.substring(0,anch).split('_').join(' '); this.anchor=value.substring(anch+1); this.ns=null; // wait until namespace() is called }; Title.prototype.setUrl=function(urlfrag) { var anch=urlfrag.indexOf('#'); this.value=safeDecodeURI(urlfrag.substring(0,anch)); this.anchor=this.value.substring(anch+1); }; Title.prototype.append=function(x){ this.setUtf(this.value + x); }; Title.prototype.urlString=function(x) { if(!x) { x={}; } var v=this.toString(true); if (!x.omitAnchor && this.anchor) { v+= '#' + this.urlAnchor(); } if (!x.keepSpaces) { v=v.split(' ').join('_'); } return encodeURI(v).split('&').join('%26').split('?').join('%3F').split('+').join('%2B'); }; Title.prototype.removeAnchor=function() { return new Title(this.toString(true)); }; Title.prototype.toUrl=function() { return pg.wiki.titlebase + this.urlString(); }; function parseParams(url) { var specialDiff = pg.re.specialdiff.exec(url); if (specialDiff) { var split= specialDiff[1].split('/'); if (split.length==1) return {oldid:split[0], diff: 'prev'}; else if (split.length==2) return {oldid: split[0], diff: split[1]}; } var ret={}; if (url.indexOf('?')==-1) { return ret; } url = url.split('#')[0]; var s=url.split('?').slice(1).join(); var t=s.split('&'); for (var i=0; i<t.length; ++i) { var z=t[i].split('='); z.push(null); ret[z[0]]=z[1]; } //Diff revision with no oldid is interpreted as a diff to the previous revision by MediaWiki if (ret.diff && typeof(ret.oldid)==='undefined') { ret.oldid = "prev"; } //Documentation seems to say something different, but oldid can also accept prev/next, and Echo is emitting such URLs. Simple fixup during parameter decoding: if (ret.oldid && (ret.oldid==='prev' || ret.oldid==='next' || ret.oldid==='cur')) { var helper = ret.diff; ret.diff = ret.oldid; ret.oldid = helper; } return ret; } // (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup) // (b) change spaces to underscores // (c) encodeURI (just the straight one, no pg.re.urlNoPopup) function myDecodeURI (str) { var ret; // FIXME decodeURIComponent?? try { ret=decodeURI(str.toString()); } catch (summat) { return str; } for (var i=0; i<pg.misc.decodeExtras.length; ++i) { var from=pg.misc.decodeExtras[i].from; var to=pg.misc.decodeExtras[i].to; ret=ret.split(from).join(to); } return ret; } function safeDecodeURI(str) { var ret=myDecodeURI(str); return ret || str; } /////////// // TESTS // /////////// //<NOLITE> function isDisambig(data, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; } return ! article.isTalkPage() && pg.re.disambig.test(data); } function stubCount(data, article) { if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; } var sectStub=0; var realStub=0; if (pg.re.stub.test(data)) { var s=data.parenSplit(pg.re.stub); for (var i=1; i<s.length; i=i+2) { if (s[i]) { ++sectStub; } else { ++realStub; } } } return { real: realStub, sect: sectStub }; } function isValidImageName(str){ // extend as needed... return ( str.indexOf('{') == -1 ); } function isInStrippableNamespace(article) { // Does the namespace allow subpages // Note, would be better if we had access to wgNamespacesWithSubpages return ( article.namespaceId() !== 0 ); } function isInMainNamespace(article) { return article.namespaceId() === 0; } function anchorContainsImage(a) { // iterate over children of anchor a // see if any are images if (a === null) { return false; } var kids=a.childNodes; for (var i=0; i<kids.length; ++i) { if (kids[i].nodeName=='IMG') { return true; } } return false; } //</NOLITE> function isPopupLink(a) { // NB for performance reasons, TOC links generally return true // they should be stripped out later if (!markNopopupSpanLinks.done) { markNopopupSpanLinks(); } if (a.inNopopupSpan) { return false; } // FIXME is this faster inline? if (a.onmousedown || a.getAttribute('nopopup')) { return false; } var h=a.href; if (h === document.location.href+'#') { return false; } if (!pg.re.basenames.test(h)) { return false; } if (!pg.re.urlNoPopup.test(h)) { return true; } return ( (pg.re.email.test(h) || pg.re.contribs.test(h) || pg.re.backlinks.test(h) || pg.re.specialdiff.test(h)) && h.indexOf('&limit=') == -1 ); } function markNopopupSpanLinks() { if( !getValueOf('popupOnlyArticleLinks')) fixVectorMenuPopups(); var s = $('.nopopups').toArray(); for (var i=0; i<s.length; ++i) { var as=s[i].getElementsByTagName('a'); for (var j=0; j<as.length; ++j) { as[j].inNopopupSpan=true; } } markNopopupSpanLinks.done=true; } function fixVectorMenuPopups() { $('div.vectorMenu h3:first a:first').prop('inNopopupSpan', true); } // ENDFILE: titles.js // STARTFILE: getpage.js ////////////////////////////////////////////////// // Wiki-specific downloading // // Schematic for a getWiki call // // getPageWithCaching // | // false | true // getPage<-[findPictureInCache]->-onComplete(a fake download) // \. // (async)->addPageToCache(download)->-onComplete(download) // check cache to see if page exists function getPageWithCaching(url, onComplete, owner) { log('getPageWithCaching, url='+url); var i=findInPageCache(url); var d; if (i > -1) { d=fakeDownload(url, owner.idNumber, onComplete, pg.cache.pages[i].data, pg.cache.pages[i].lastModified, owner); } else { d=getPage(url, onComplete, owner); if (d && owner && owner.addDownload) { owner.addDownload(d); d.owner=owner; } } } function getPage(url, onComplete, owner) { log('getPage'); var callback= function (d) { if (!d.aborted) {addPageToCache(d); onComplete(d);} }; return startDownload(url, owner.idNumber, callback); } function findInPageCache(url) { for (var i=0; i<pg.cache.pages.length; ++i) { if (url==pg.cache.pages[i].url) { return i; } } return -1; } function addPageToCache(download) { log('addPageToCache '+download.url); var page = {url: download.url, data: download.data, lastModified: download.lastModified}; return pg.cache.pages.push(page); } // ENDFILE: getpage.js // STARTFILE: parensplit.js ////////////////////////////////////////////////// // parenSplit // String.prototype.parenSplit should do what ECMAscript says String.prototype.split does, // interspersing paren matches (regex capturing groups) between the split elements. // i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c'] if (String('abc'.split(/(b)/))!='a,b,c') { // broken String.split, e.g. konq, IE < 10 String.prototype.parenSplit=function (re) { re=nonGlobalRegex(re); var s=this; var m=re.exec(s); var ret=[]; while (m && s) { // without the following loop, we have // 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/) for(var i=0; i<m.length; ++i) { if (typeof m[i]=='undefined') m[i]=''; } ret.push(s.substring(0,m.index)); ret = ret.concat(m.slice(1)); s=s.substring(m.index + m[0].length); m=re.exec(s); } ret.push(s); return ret; }; } else { String.prototype.parenSplit=function (re) { return this.split(re); }; String.prototype.parenSplit.isNative=true; } function nonGlobalRegex(re) { var s=re.toString(); var flags=''; for (var j=s.length; s.charAt(j) != '/'; --j) { if (s.charAt(j) != 'g') { flags += s.charAt(j); } } var t=s.substring(1,j); return RegExp(t,flags); } // ENDFILE: parensplit.js // STARTFILE: tools.js // IE madness with encoding // ======================== // // suppose throughout that the page is in utf8, like wikipedia // // if a is an anchor DOM element and a.href should consist of // // http://host.name.here/wiki/foo?bar=baz // // then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie)) // but IE gives bar=baz correctly as plain utf8 // // --------------------------------- // // IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here. // // --------------------------------- // // summat else // Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm //<NOLITE> function getJsObj(json) { try { var json_ret = JSON.parse(json); if( json_ret.warnings ) { for( var w=0; w < json_ret.warnings.length; w++ ) { if( json_ret.warnings[w]['*'] ) { log( json_ret.warnings[w]['*'] ); } else { log( json_ret.warnings[w]['warnings'] ); } } } else if ( json_ret.error ) { errlog( json_ret.error.code + ': ' + json_ret.error.info ); } return json_ret; } catch (someError) { errlog('Something went wrong with getJsObj, json='+json); return 1; } } function anyChild(obj) { for (var p in obj) { return obj[p]; } return null; } //</NOLITE> function upcaseFirst(str) { if (typeof str != typeof '' || str === '') return ''; return str.charAt(0).toUpperCase() + str.substring(1); } function findInArray(arr, foo) { if (!arr || !arr.length) { return -1; } var len=arr.length; for (var i=0; i<len; ++i) { if (arr[i]==foo) { return i; } } return -1; } /* eslint-disable no-unused-vars */ function nextOne (array, value) { // NB if the array has two consecutive entries equal // then this will loop on successive calls var i=findInArray(array, value); if (i<0) { return null; } return array[i+1]; } /* eslint-enable no-unused-vars */ function literalizeRegex(str){ return mw.util.escapeRegExp(str); } String.prototype.entify=function() { //var shy='&shy;'; return this.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;'/*+shy*/).split('"').join('&quot;'); }; // Array filter function function removeNulls(val) { return val !== null; } function joinPath(list) { return list.filter(removeNulls).join('/'); } function simplePrintf(str, subs) { if (!str || !subs) { return str; } var ret=[]; var s=str.parenSplit(/(%s|\$[0-9]+)/); var i=0; do { ret.push(s.shift()); if ( !s.length ) { break; } var cmd=s.shift(); if (cmd == '%s') { if ( i < subs.length ) { ret.push(subs[i]); } else { ret.push(cmd); } ++i; } else { var j=parseInt( cmd.replace('$', ''), 10 ) - 1; if ( j > -1 && j < subs.length ) { ret.push(subs[j]); } else { ret.push(cmd); } } } while (s.length > 0); return ret.join(''); } /* eslint-disable no-unused-vars */ function isString(x) { return (typeof x === 'string' || x instanceof String); } function isNumber(x) { return (typeof x === 'number' || x instanceof Number); } function isRegExp(x) { return x instanceof RegExp; } function isArray (x) { return x instanceof Array; } function isObject(x) { return x instanceof Object; } function isFunction(x) { return !isRegExp(x) && (typeof x === 'function' || x instanceof Function); } /* eslint-enable no-unused-vars */ function repeatString(s,mult) { var ret=''; for (var i=0; i<mult; ++i) { ret += s; } return ret; } function zeroFill(s, min) { min = min || 2; var t=s.toString(); return repeatString('0', min - t.length) + t; } function map(f, o) { if (isArray(o)) { return map_array(f,o); } return map_object(f,o); } function map_array(f,o) { var ret=[]; for (var i=0; i<o.length; ++i) { ret.push(f(o[i])); } return ret; } function map_object(f,o) { var ret={}; for (var i in o) { ret[o]=f(o[i]); } return ret; } pg.escapeQuotesHTML = function ( text ) { return text .replace(/&/g, "&amp;") .replace(/"/g, "&quot;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;"); }; // ENDFILE: tools.js // STARTFILE: dab.js //<NOLITE> ////////////////////////////////////////////////// // Dab-fixing code // function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) { log('retargetDab: newTarget='+newTarget + ' oldTarget=' + oldTarget); return changeLinkTargetLink( {newTarget: newTarget, text: newTarget.split(' ').join('&nbsp;'), hint: tprintf('disambigHint', [newTarget]), summary: simplePrintf( getValueOf('popupFixDabsSummary'), [friendlyCurrentArticleName, newTarget ]), clickButton: getValueOf('popupDabsAutoClick'), minor: true, oldTarget: oldTarget, watch: getValueOf('popupWatchDisambiggedPages'), title: titleToEdit}); } function listLinks(wikitext, oldTarget, titleToEdit) { // mediawiki strips trailing spaces, so we do the same // testcase: https://en.wikipedia.org/w/index.php?title=Radial&oldid=97365633 var reg=RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi'); var ret=[]; var splitted=wikitext.parenSplit(reg); // ^[a-z]+ should match interwiki links, hopefully (case-insensitive) // and ^[a-z]* should match those and [[:Category...]] style links too var omitRegex=RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory'); var friendlyCurrentArticleName= oldTarget.toString(); var wikPos = getValueOf('popupDabWiktionary'); for (var i=1; i<splitted.length; i=i+3) { if (typeof splitted[i] == typeof 'string' && splitted[i].length>0 && !omitRegex.test(splitted[i])) { ret.push( retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit) ); } /* if */ } /* for loop */ ret = rmDupesFromSortedList(ret.sort()); if (wikPos) { var wikTarget='wiktionary:' + friendlyCurrentArticleName.replace( RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1' ); var meth; if (wikPos.toLowerCase() == 'first') { meth = 'unshift'; } else { meth = 'push'; } ret[meth]( retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) ); } ret.push(changeLinkTargetLink( { newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'), hint: popupString("remove all links to this disambig page from this article"), clickButton: getValueOf('popupDabsAutoClick'), oldTarget: oldTarget, summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]), watch: getValueOf('popupWatchDisambiggedPages'), title: titleToEdit })); return ret; } function rmDupesFromSortedList(list) { var ret=[]; for (var i=0; i<list.length; ++i) { if (ret.length === 0 || list[i]!=ret[ret.length-1]) { ret.push(list[i]); } } return ret; } function makeFixDab(data, navpop) { // grab title from parent popup if there is one; default exists in changeLinkTargetLink var titleToEdit=(navpop.parentPopup && navpop.parentPopup.article.toString()); var list=listLinks(data, navpop.originalArticle, titleToEdit); if (list.length === 0) { log('listLinks returned empty list'); return null; } var html='<hr />' + popupString('Click to disambiguate this link to:') + '<br>'; html+=list.join(', '); return html; } function makeFixDabs(wikiText, navpop) { if (getValueOf('popupFixDabs') && isDisambig(wikiText, navpop.article) && Title.fromURL(location.href).namespaceId() != pg.nsSpecialId && navpop.article.talkPage() ) { setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber); } } function popupRedlinkHTML(article) { return changeLinkTargetLink( { newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'), hint: popupString("remove all links to this page from this article"), clickButton: getValueOf('popupRedlinkAutoClick'), oldTarget: article.toString(), summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()])}); } //</NOLITE> // ENDFILE: dab.js // STARTFILE: htmloutput.js // this has to use a timer loop as we don't know if the DOM element exists when we want to set the text function setPopupHTML (str, elementId, popupId, onSuccess, append) { if (typeof popupId === 'undefined') { //console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100)); popupId = pg.idNumber; } var popupElement=document.getElementById(elementId+popupId); if (popupElement) { if (!append) { popupElement.innerHTML=''; } if (isString(str)) { popupElement.innerHTML+=str; } else { popupElement.appendChild(str); } if (onSuccess) { onSuccess(); } setTimeout(checkPopupPosition, 100); return true; } else { // call this function again in a little while... setTimeout(function(){ setPopupHTML(str,elementId,popupId,onSuccess); }, 600); } return null; } //<NOLITE> function setPopupTrailer(str,id) {return setPopupHTML(str, 'popupData', id);} //</NOLITE> // args.navpopup is mandatory // optional: args.redir, args.redirTarget // FIXME: ye gods, this is ugly stuff function fillEmptySpans(args) { // if redir is present and true then redirTarget is mandatory var redir=true; var rcid; if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) { redir=false; } var a=args.navpopup.parentAnchor; var article, hint=null, oldid=null, params={}; if (redir && typeof args.redirTarget == typeof {}) { article=args.redirTarget; //hint=article.hintValue(); } else { article=(new Title()).fromAnchor(a); hint=a.originalTitle || article.hintValue(); params=parseParams(a.href); oldid=(getValueOf('popupHistoricalLinks')) ? params.oldid : null; rcid=params.rcid; } var x={ article:article, hint: hint, oldid: oldid, rcid: rcid, navpop:args.navpopup, params:params }; var structure=pg.structures[getValueOf('popupStructure')]; if (typeof structure != 'object') { setPopupHTML('popupError', 'Unknown structure (this should never happen): '+ pg.option.popupStructure, args.navpopup.idNumber); return; } var spans=flatten(pg.misc.layout); var numspans = spans.length; var redirs=pg.misc.redirSpans; for (var i=0; i<numspans; ++i) { var found = redirs && (redirs.indexOf( spans[i] ) !== -1); //log('redir='+redir+', found='+found+', spans[i]='+spans[i]); if ( (found && !redir) || (!found && redir) ) { //log('skipping this set of the loop'); continue; } var structurefn=structure[spans[i]]; var setfn = setPopupHTML; if (getValueOf('popupActiveNavlinks') && (spans[i].indexOf('popupTopLinks')===0 || spans[i].indexOf('popupRedirTopLinks')===0) ) { setfn = setPopupTipsAndHTML; } switch (typeof structurefn) { case 'function': log('running '+spans[i]+'({article:'+x.article+', hint:'+x.hint+', oldid: '+x.oldid+'})'); setfn(structurefn(x), spans[i], args.navpopup.idNumber); break; case 'string': setfn(structurefn, spans[i], args.navpopup.idNumber); break; default: errlog('unknown thing with label '+spans[i] + ' (span index was ' + i + ')'); break; } } } // flatten an array function flatten(list, start) { var ret=[]; if (typeof start == 'undefined') { start=0; } for (var i=start; i<list.length; ++i) { if (typeof list[i] == typeof []) { return ret.concat(flatten(list[i])).concat(flatten(list, i+1)); } else { ret.push(list[i]); } } return ret; } // Generate html for whole popup function popupHTML (a) { getValueOf('popupStructure'); var structure=pg.structures[pg.option.popupStructure]; if (typeof structure != 'object') { //return 'Unknown structure: '+pg.option.popupStructure; // override user choice pg.option.popupStructure=pg.optionDefault.popupStructure; return popupHTML(a); } if (typeof structure.popupLayout != 'function') { return 'Bad layout'; } pg.misc.layout=structure.popupLayout(); if (typeof structure.popupRedirSpans === 'function') { pg.misc.redirSpans=structure.popupRedirSpans(); } else { pg.misc.redirSpans=[]; } return makeEmptySpans(pg.misc.layout, a.navpopup); } function makeEmptySpans (list, navpop) { var ret=''; for (var i=0; i<list.length; ++i) { if (typeof list[i] == typeof '') { ret += emptySpanHTML(list[i], navpop.idNumber, 'div'); } else if (typeof list[i] == typeof [] && list[i].length > 0 ) { ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop)); } else if (typeof list[i] == typeof {} && list[i].nodeType ) { ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType); } } return ret; } function emptySpanHTML(name, id, tag, classname) { tag = tag || 'span'; if (!classname) { classname = emptySpanHTML.classAliases[name]; } classname = classname || name; if (name == getValueOf('popupDragHandle')) { classname += ' popupDragHandle'; } return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]); } emptySpanHTML.classAliases={ 'popupSecondPreview': 'popupPreview' }; // generate html for popup image // <a id="popupImageLinkn"><img id="popupImagen"> // where n=idNumber function imageHTML(article, idNumber) { return simplePrintf('<a id="popupImageLink$1">' + '<img align="right" valign="top" id="popupImg$1" style="display: none;"></img>' + '</a>', [ idNumber ]); } function popTipsSoonFn(id, when, popData) { if (!when) { when=250; } var popTips=function(){ setupTooltips(document.getElementById(id), false, true, popData); }; return function() { setTimeout( popTips, when, popData ); }; } function setPopupTipsAndHTML(html, divname, idnumber, popData) { setPopupHTML(html, divname, idnumber, getValueOf('popupSubpopups') ? popTipsSoonFn(divname + idnumber, null, popData) : null); } // ENDFILE: htmloutput.js // STARTFILE: mouseout.js ////////////////////////////////////////////////// // fuzzy checks function fuzzyCursorOffMenus(x,y, fuzz, parent) { if (!parent) { return null; } var uls=parent.getElementsByTagName('ul'); for (var i=0; i<uls.length; ++i) { if (uls[i].className=='popup_menu') { if (uls[i].offsetWidth > 0) return false; } // else {document.title+='.';} } return true; } function checkPopupPosition () { // stop the popup running off the right of the screen // FIXME avoid pg.current.link if (pg.current.link && pg.current.link.navpopup) pg.current.link.navpopup.limitHorizontalPosition(); } function mouseOutWikiLink () { //console ('mouseOutWikiLink'); var a=this; removeModifierKeyHandler(a); if (a.navpopup === null || typeof a.navpopup === 'undefined') return; if ( ! a.navpopup.isVisible() ) { a.navpopup.banish(); return; } restoreTitle(a); Navpopup.tracker.addHook(posCheckerHook(a.navpopup)); } function posCheckerHook(navpop) { return function() { if (!navpop.isVisible()) { return true; /* remove this hook */ } if (Navpopup.tracker.dirty) { return false; } var x=Navpopup.tracker.x, y=Navpopup.tracker.y; var mouseOverNavpop = navpop.isWithin(x,y,navpop.fuzz, navpop.mainDiv) || !fuzzyCursorOffMenus(x,y,navpop.fuzz, navpop.mainDiv); // FIXME it'd be prettier to do this internal to the Navpopup objects var t=getValueOf('popupHideDelay'); if (t) { t = t * 1000; } if (!t) { if(!mouseOverNavpop) { if(navpop.parentAnchor) { restoreTitle( navpop.parentAnchor ); } navpop.banish(); return true; /* remove this hook */ } return false; } // we have a hide delay set var d=+(new Date()); if ( !navpop.mouseLeavingTime ) { navpop.mouseLeavingTime = d; return false; } if ( mouseOverNavpop ) { navpop.mouseLeavingTime=null; return false; } if (d - navpop.mouseLeavingTime > t) { navpop.mouseLeavingTime=null; navpop.banish(); return true; /* remove this hook */ } return false; }; } function runStopPopupTimer(navpop) { // at this point, we should have left the link but remain within the popup // so we call this function again until we leave the popup. if (!navpop.stopPopupTimer) { navpop.stopPopupTimer=setInterval(posCheckerHook(navpop), 500); navpop.addHook(function(){clearInterval(navpop.stopPopupTimer);}, 'hide', 'before'); } } // ENDFILE: mouseout.js // STARTFILE: previewmaker.js /** @fileoverview Defines the {@link Previewmaker} object, which generates short previews from wiki markup. */ /** Creates a new Previewmaker @constructor @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext. @param {String} wikiText The Wikitext source of the page we wish to preview. @param {String} baseUrl The url we should prepend when creating relative urls. @param {Navpopup} owner The navpop associated to this preview generator */ function Previewmaker(wikiText, baseUrl, owner) { /** The wikitext which is manipulated to generate the preview. */ this.originalData=wikiText; this.baseUrl=baseUrl; this.owner=owner; this.maxCharacters=getValueOf('popupMaxPreviewCharacters'); this.maxSentences=getValueOf('popupMaxPreviewSentences'); this.setData(); } Previewmaker.prototype.setData=function() { var maxSize=Math.max(10000, 2*this.maxCharacters); this.data=this.originalData.substring(0,maxSize); }; /** Remove HTML comments @private */ Previewmaker.prototype.killComments = function () { // this also kills one trailing newline, eg [[diamyo]] this.data=this.data.replace(RegExp('^<!--[^$]*?-->\\n|\\n<!--[^$]*?-->(?=\\n)|<!--[^$]*?-->', 'g'), ''); }; /** @private */ Previewmaker.prototype.killDivs = function () { // say goodbye, divs (can be nested, so use * not *?) this.data=this.data.replace(RegExp('< *div[^>]* *>[\\s\\S]*?< */ *div *>', 'gi'), ''); }; /** @private */ Previewmaker.prototype.killGalleries = function () { this.data=this.data.replace(RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>', 'gi'), ''); }; /** @private */ Previewmaker.prototype.kill = function(opening, closing, subopening, subclosing, repl) { var oldk=this.data; var k=this.killStuff(this.data, opening, closing, subopening, subclosing, repl); while (k.length < oldk.length) { oldk=k; k=this.killStuff(k, opening, closing, subopening, subclosing, repl); } this.data=k; }; /** @private */ Previewmaker.prototype.killStuff = function (txt, opening, closing, subopening, subclosing, repl) { var op=this.makeRegexp(opening); var cl=this.makeRegexp(closing, '^'); var sb=subopening ? this.makeRegexp(subopening, '^') : null; var sc=subclosing ? this.makeRegexp(subclosing, '^') : cl; if (!op || !cl) { alert('Navigation Popups error: op or cl is null! something is wrong.'); return; } if (!op.test(txt)) { return txt; } var ret=''; var opResult = op.exec(txt); ret = txt.substring(0,opResult.index); txt=txt.substring(opResult.index+opResult[0].length); var depth = 1; while (txt.length > 0) { var removal=0; if (depth==1 && cl.test(txt)) { depth--; removal=cl.exec(txt)[0].length; } else if (depth > 1 && sc.test(txt)) { depth--; removal=sc.exec(txt)[0].length; }else if (sb && sb.test(txt)) { depth++; removal=sb.exec(txt)[0].length; } if ( !removal ) { removal = 1; } txt=txt.substring(removal); if (depth === 0) { break; } } return ret + (repl || '') + txt; }; /** @private */ Previewmaker.prototype.makeRegexp = function (x, prefix, suffix) { prefix = prefix || ''; suffix = suffix || ''; var reStr=''; var flags=''; if (isString(x)) { reStr=prefix + literalizeRegex(x) + suffix; } else if (isRegExp(x)) { var s=x.toString().substring(1); var sp=s.split('/'); flags=sp[sp.length-1]; sp[sp.length-1]=''; s=sp.join('/'); s=s.substring(0,s.length-1); reStr= prefix + s + suffix; } else { log ('makeRegexp failed'); } log ('makeRegexp: got reStr=' + reStr + ', flags=' + flags); return RegExp(reStr, flags); }; /** @private */ Previewmaker.prototype.killBoxTemplates = function () { // taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general // also, have float_begin, ... float_end this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /[}][}]\s*/, '{{'); // infoboxes etc // from User:Zyxw/popups.js: kill frames too this.kill(RegExp('[{][{][^{}\\s|]*?(infobox|elementbox|frame)[_ ]', 'i'), /[}][}]\s*/, '{{'); }; /** @private */ Previewmaker.prototype.killTemplates = function () { // the plus sign is to avoid transcluding [[Template:', ']] this.kill('{'+'{', '}'+'}', '{', '}', ' '); }; /** @private */ Previewmaker.prototype.killTables = function () { // tables are bad, too // this can be slow, but it's an inprovement over a browser hang // torture test: [[Comparison_of_Intel_Central_Processing_Units]] this.kill('{|', /[|]}\s*/, '{|'); this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i); // remove lines starting with a pipe for the hell of it (?) this.data=this.data.replace(RegExp('^[|].*$', 'mg'), ''); }; /** @private */ Previewmaker.prototype.killImages = function () { var forbiddenNamespaceAliases = []; jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) { if (_namespaceId!=pg.nsImageId && _namespaceId!=pg.nsCategoryId) return; forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments! }); // images and categories are a nono this.kill(RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'), /\]\]\s*/, '[', ']'); }; /** @private */ Previewmaker.prototype.killHTML = function () { // kill <ref ...>...</ref> this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i); // let's also delete entire lines starting with <. it's worth a try. this.data=this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n'); // and those pesky html tags, but not <nowiki> or <blockquote> var splitted=this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/); var len=splitted.length; for (var i=1; i<len; i=i+2) { switch (splitted[i]) { case '<nowiki>': case '</nowiki>': case '<blockquote>': case '</blockquote>': break; default: splitted[i]=''; } } this.data=splitted.join(''); }; /** @private */ Previewmaker.prototype.killChunks = function() { // heuristics alert // chunks of italic text? you crazy, man? var italicChunkRegex=new RegExp ("((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+", 'g'); // keep stuff separated, though, so stick in \n (fixes [[Union Jack]]? this.data=this.data.replace(italicChunkRegex, '\n'); }; /** @private */ Previewmaker.prototype.mopup = function () { // we simply *can't* be doing with horizontal rules right now this.data=this.data.replace(RegExp('^-{4,}','mg'),''); // no indented lines this.data=this.data.replace(RegExp('(^|\\n) *:[^\\n]*','g'), ''); // replace __TOC__, __NOTOC__ and whatever else there is // this'll probably do this.data=this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'),''); }; /** @private */ Previewmaker.prototype.firstBit = function () { // dont't be givin' me no subsequent paragraphs, you hear me? /// first we "normalize" section headings, removing whitespace after, adding before var d=this.data; if (getValueOf('popupPreviewCutHeadings')) { this.data=this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 '); /// then we want to get rid of paragraph breaks whose text ends badly this.data=this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n'); this.data=this.data.replace(RegExp('^[\\s\\n]*'), ''); var stuff=(RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data); if (stuff) { d = stuff[0]; } if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; } /// now put \n\n after sections so that bullets and numbered lists work d=d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n'); } // Split sentences. Superfluous sentences are RIGHT OUT. // note: exactly 1 set of parens here needed to make the slice work d = d.parenSplit(RegExp('([!?.]+["'+"'"+']*\\s)','g')); // leading space is bad, mmkay? d[0]=d[0].replace(RegExp('^\\s*'), ''); var notSentenceEnds=RegExp('([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$', 'i'); d = this.fixSentenceEnds(d, notSentenceEnds); this.fullLength=d.join('').length; var n=this.maxSentences; var dd=this.firstSentences(d,n); do { dd=this.firstSentences(d,n); --n; } while ( dd.length > this.maxCharacters && n !== 0 ); this.data = dd; }; /** @private */ Previewmaker.prototype.fixSentenceEnds = function(strs, reg) { // take an array of strings, strs // join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg for (var i=0; i<strs.length-2; ++i) { if (reg.test(strs[i])) { var a=[]; for (var j=0; j<strs.length; ++j) { if (j<i) a[j]=strs[j]; if (j==i) a[i]=strs[i]+strs[i+1]+strs[i+2]; if (j>i+2) a[j-2]=strs[j]; } return this.fixSentenceEnds(a,reg); } } return strs; }; /** @private */ Previewmaker.prototype.firstSentences = function(strs, howmany) { var t=strs.slice(0, 2*howmany); return t.join(''); }; /** @private */ Previewmaker.prototype.killBadWhitespace = function() { // also cleans up isolated '''', eg [[Suntory Sungoliath]] this.data=this.data.replace(RegExp('^ *\'+ *$', 'gm'), ''); }; /** Runs the various methods to generate the preview. The preview is stored in the <code>html</html> field. @private */ Previewmaker.prototype.makePreview = function() { if (this.owner.article.namespaceId()!=pg.nsTemplateId && this.owner.article.namespaceId()!=pg.nsImageId ) { this.killComments(); this.killDivs(); this.killGalleries(); this.killBoxTemplates(); if (getValueOf('popupPreviewKillTemplates')) { this.killTemplates(); } else { this.killMultilineTemplates(); } this.killTables(); this.killImages(); this.killHTML(); this.killChunks(); this.mopup(); this.firstBit(); this.killBadWhitespace(); } else { this.killHTML(); } this.html=wiki2html(this.data, this.baseUrl); // needs livepreview this.fixHTML(); this.stripLongTemplates(); }; /** @private */ Previewmaker.prototype.esWiki2HtmlPart = function(data) { var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink reLinks.lastIndex = 0; //reset regex var match; var result = ""; var postfixIndex = 0; while ((match = reLinks.exec(data))) //match all wikilinks { //FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases. result += pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) + '<a href="'+Insta.conf.paths.articles+pg.escapeQuotesHTML(match[1])+'">'+pg.escapeQuotesHTML((match[2]?match[2]:match[1])+match[3])+"</a>"; postfixIndex = reLinks.lastIndex; } //append the rest result += pg.escapeQuotesHTML(data.substring(postfixIndex)); return result; }; Previewmaker.prototype.editSummaryPreview=function() { var reAes = /\/\* *(.*?) *\*\//g; //match the first section marker reAes.lastIndex = 0; //reset regex var match; match = reAes.exec(this.data); if (match) { //we have a section link. Split it, process it, combine it. var prefix = this.data.substring(0,match.index-1); var section = match[1]; var postfix = this.data.substring(reAes.lastIndex); var start = "<span class='autocomment'>"; var end = "</span>"; if (prefix.length>0) start = this.esWiki2HtmlPart(prefix) + " " + start + "- "; if (postfix.length>0) end = ": " + end + this.esWiki2HtmlPart(postfix); var t=new Title().fromURL(this.baseUrl); t.anchorFromUtf(section); var sectionLink = Insta.conf.paths.articles + pg.escapeQuotesHTML(t.toString(true)) + '#' + pg.escapeQuotesHTML(t.anchor); return start + '<a href="'+sectionLink+'">&rarr;</a> '+pg.escapeQuotesHTML(section) + end; } //else there's no section link, htmlify the whole thing. return this.esWiki2HtmlPart(this.data); }; //<NOLITE> /** Test function for debugging preview problems one step at a time. */ /*eslint-disable */ function previewSteps(txt) { try { txt=txt || document.editform.wpTextbox1.value; } catch (err) { if (pg.cache.pages.length > 0) { txt=pg.cache.pages[pg.cache.pages.length-1].data; } else { alert('provide text or use an edit page'); } } txt=txt.substring(0,10000); var base=pg.wiki.articlebase + Title.fromURL(document.location.href).urlString(); var p=new Previewmaker(txt, base, pg.current.link.navpopup); if (this.owner.article.namespaceId() != pg.nsTemplateId) { p.killComments(); if (!confirm('done killComments(). Continue?\n---\n' + p.data)) { return; } p.killDivs(); if (!confirm('done killDivs(). Continue?\n---\n' + p.data)) { return; } p.killGalleries(); if (!confirm('done killGalleries(). Continue?\n---\n' + p.data)) { return; } p.killBoxTemplates(); if (!confirm('done killBoxTemplates(). Continue?\n---\n' + p.data)) { return; } if (getValueOf('popupPreviewKillTemplates')) { p.killTemplates(); if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) { return; } } else { p.killMultilineTemplates(); if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) { return; } } p.killTables(); if (!confirm('done killTables(). Continue?\n---\n' + p.data)) { return; } p.killImages(); if (!confirm('done killImages(). Continue?\n---\n' + p.data)) { return; } p.killHTML(); if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) { return; } p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; } p.mopup(); if (!confirm('done mopup(). Continue?\n---\n' + p.data)) { return; } p.firstBit(); if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) { return; } p.killBadWhitespace(); if (!confirm('done killBadWhitespace(). Continue?\n---\n' + p.data)) { return; } } p.html=wiki2html(p.data, base); // needs livepreview p.fixHTML(); if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) { return; } p.stripLongTemplates(); if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) { return; } alert('finished preview - end result follows.\n---\n' + p.html); } /*eslint-enable */ //</NOLITE> /** Works around livepreview bugs. @private */ Previewmaker.prototype.fixHTML = function() { if(!this.html) return; var ret = this.html; // fix question marks in wiki links // maybe this'll break some stuff :-( ret=ret.replace(RegExp('(<a href="' + pg.wiki.articlePath + '/[^"]*)[?](.*?")', 'g'), '$1%3F$2'); ret=ret.replace(RegExp('(<a href=\'' + pg.wiki.articlePath + '/[^\']*)[?](.*?\')', 'g'), '$1%3F$2'); // FIXME fix up % too this.html=ret; }; /** Generates the preview and displays it in the current popup. Does nothing if the generated preview is invalid or consists of whitespace only. Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true. */ Previewmaker.prototype.showPreview = function () { this.makePreview(); if (typeof this.html != typeof '') return; if (RegExp('^\\s*$').test(this.html)) return; setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber); setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, { owner: this.owner }); var more = (this.fullLength > this.data.length) ? this.moreLink() : ''; setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber); }; /** @private */ Previewmaker.prototype.moreLink=function() { var a=document.createElement('a'); a.className='popupMoreLink'; a.innerHTML=popupString('more...'); var savedThis=this; a.onclick=function() { savedThis.maxCharacters+=2000; savedThis.maxSentences+=20; savedThis.setData(); savedThis.showPreview(); }; return a; }; /** @private */ Previewmaker.prototype.stripLongTemplates = function() { // operates on the HTML! this.html=this.html.replace(RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'), ''); this.html=this.html.split('\n').join(' '); // workaround for <pre> templates this.html=this.html.replace(RegExp('[{][{][^}]*<pre>[^}]*[}][}]','gi'), ''); }; /** @private */ Previewmaker.prototype.killMultilineTemplates = function() { this.kill('{{{', '}}}'); this.kill(RegExp('\\s*[{][{][^{}]*\\n'), '}}', '{{'); }; // ENDFILE: previewmaker.js // STARTFILE: querypreview.js function loadAPIPreview(queryType, article, navpop) { var art=new Title(article).urlString(); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&'; var htmlGenerator=function(/*a, d*/){alert('invalid html generator');}; var usernameart = ''; switch (queryType) { case 'history': url += 'titles=' + art + '&prop=revisions&rvlimit=' + getValueOf('popupHistoryPreviewLimit'); htmlGenerator=APIhistoryPreviewHTML; break; case 'category': url += 'list=categorymembers&cmtitle=' + art; htmlGenerator=APIcategoryPreviewHTML; break; case 'userinfo': var username = new Title( article ).userName(); usernameart = encodeURIComponent( username ); if (pg.re.ipUser.test(username)) { url += 'list=blocks&bkprop=range|restrictions&bkip=' + usernameart; } else { url += 'list=users|usercontribs&usprop=blockinfo|groups|editcount|registration|gender&ususers=' + usernameart + "&meta=globaluserinfo&guiprop=groups|unattached&guiuser="+ usernameart + "&uclimit=1&ucprop=timestamp&ucuser=" + usernameart; } htmlGenerator=APIuserInfoPreviewHTML; break; case 'contribs': usernameart = encodeURIComponent( new Title( article ).userName() ); url += 'list=usercontribs&ucuser=' + usernameart + '&uclimit=' + getValueOf('popupContribsPreviewLimit'); htmlGenerator=APIcontribsPreviewHTML; break; case 'imagepagepreview': var trail=''; if (getValueOf('popupImageLinks')) { trail = '&list=imageusage&iutitle=' + art; } url += 'titles=' + art + '&prop=revisions|imageinfo&rvprop=content' + trail; htmlGenerator=APIimagepagePreviewHTML; break; case 'backlinks': url += 'list=backlinks&bltitle=' + art; htmlGenerator=APIbacklinksPreviewHTML; break; case 'revision': if (article.oldid) { url += 'revids=' + article.oldid; } else { url += 'titles=' + article.removeAnchor().urlString(); } url += '&prop=revisions|pageprops|info|images|categories&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max'; htmlGenerator=APIrevisionPreviewHTML; break; } pendingNavpopTask(navpop); var callback=function(d){ log( "callback of API functions was hit" ); showAPIPreview(queryType, htmlGenerator(article,d,navpop), navpop.idNumber, navpop, d); }; var go = function(){ getPageWithCaching(url, callback, navpop); return true; }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_'+queryType+'_QUERY_DATA'); } } function linkList(list) { list.sort(function(x,y) { return (x==y ? 0 : (x<y ? -1 : 1)); }); var buf=[]; for (var i=0; i<list.length; ++i) { buf.push(wikiLink({article: new Title(list[i]), text: list[i].split(' ').join('&nbsp;'), action: 'view'})); } return buf.join(', '); } function getTimeOffset() { var tz = mw.user.options.get('timecorrection'); if(tz) { if( tz.indexOf('|') > -1 ) { // New format return parseInt(tz.split('|')[1],10); } else if ( tz.indexOf(':') > -1 ) { // Old format return( parseInt(tz,10)*60 + parseInt(tz.split(':')[1],10) ); } } return 0; } /* * Creates a HTML table that's shown in the history and user-contribs popups. * @param {Object[]} h - a list of revisions, returned from the API * @param {boolean} reallyContribs - true only if we're displaying user contributions */ function editPreviewTable(article, h, reallyContribs, timeOffset) { var html=['<table>']; var day=null; var curart=article; var page=null; var makeFirstColumnLinks; if(reallyContribs) { // We're showing user contributions, so make (diff | hist) links makeFirstColumnLinks = function(currentRevision) { var result = '('; result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&diff=prev' + '&oldid=' + currentRevision.revid + '">' + popupString('diff') + '</a>'; result += '&nbsp;|&nbsp;'; result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&action=history">' + popupString('hist') + '</a>'; result += ')'; return result; }; } else { // It's a regular history page, so make (cur | last) links var firstRevid = h[0].revid; makeFirstColumnLinks = function(currentRevision) { var result = '('; result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=' + firstRevid + '&oldid=' + currentRevision.revid + '">' + popupString('cur') + '</a>'; result += '&nbsp;|&nbsp;'; result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=prev&oldid=' + currentRevision.revid + '">' + popupString('last') + '</a>'; result += ')'; return result; }; } for (var i=0; i<h.length; ++i) { if (reallyContribs) { page = h[i].title; curart = new Title(page); } var minor = h[i].minor ? '<b>m </b>' : ''; var editDate = adjustDate(getDateFromTimestamp(h[i].timestamp), timeOffset); var thisDay = dayFormat(editDate); var thisTime = timeFormat(editDate); if (thisDay == day) { thisDay = ''; } else { day = thisDay; } if (thisDay) { html.push( '<tr><td colspan=3><span class="popup_history_date">' + thisDay+'</span></td></tr>' ); } html.push('<tr class="popup_history_row_' + ( (i%2) ? 'odd' : 'even') + '">'); html.push('<td>' + makeFirstColumnLinks(h[i]) + '</td>'); html.push('<td>' + '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&oldid=' + h[i].revid + '">' + thisTime + '</a></td>'); var col3url='', col3txt=''; if (!reallyContribs) { var user=h[i].user; if( !h[i].userhidden ) { if( pg.re.ipUser.test(user) ) { col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Contributions&target=' + new Title(user).urlString(); } else { col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(user).urlString(); } col3txt=pg.escapeQuotesHTML(user); } else { col3url=getValueOf('popupRevDelUrl'); col3txt=pg.escapeQuotesHTML( popupString('revdel')); } } else { col3url=pg.wiki.titlebase + curart.urlString(); col3txt=pg.escapeQuotesHTML(page); } html.push('<td>' + (reallyContribs ? minor : '') + '<a href="' + col3url + '">' + col3txt + '</a></td>'); var comment=''; var c=h[i].comment || h[i].content; if (c) { comment=new Previewmaker(c, new Title(curart).toUrl()).editSummaryPreview(); } else if ( h[i].commenthidden ) { comment=popupString('revdel'); } html.push('<td>' + (!reallyContribs ? minor : '') + comment + '</td>'); html.push('</tr>'); html=[html.join('')]; } html.push('</table>'); return html.join(''); } function getDateFromTimestamp(t) { var s=t.split(/[^0-9]/); switch(s.length) { case 0: return null; case 1: return new Date(s[0]); case 2: return new Date(s[0], s[1]-1); case 3: return new Date(s[0], s[1]-1, s[2]); case 4: return new Date(s[0], s[1]-1, s[2], s[3]); case 5: return new Date(s[0], s[1]-1, s[2], s[3], s[4]); case 6: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5]); default: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5], s[6]); } } function adjustDate(d, offset) { // offset is in minutes var o=offset * 60 * 1000; return new Date( +d + o); } function dayFormat(editDate, utc) { if (utc) { return map(zeroFill, [editDate.getUTCFullYear(), editDate.getUTCMonth()+1, editDate.getUTCDate()]).join('-'); } return map(zeroFill, [editDate.getFullYear(), editDate.getMonth()+1, editDate.getDate()]).join('-'); } function timeFormat(editDate, utc) { if (utc) { return map(zeroFill, [editDate.getUTCHours(), editDate.getUTCMinutes(), editDate.getUTCSeconds()]).join(':'); } return map(zeroFill, [editDate.getHours(), editDate.getMinutes(), editDate.getSeconds()]).join(':'); } function showAPIPreview(queryType, html, id, navpop, download) { // DJ: done var target='popupPreview'; completedNavpopTask(navpop); switch (queryType) { case 'imagelinks': case 'category': target='popupPostPreview'; break; case 'userinfo': target='popupUserData'; break; case 'revision': insertPreview(download); return; } setPopupTipsAndHTML(html, target, id); } function APIrevisionPreviewHTML(article, download) { try{ var jsObj=getJsObj(download.data); var page=anyChild(jsObj.query.pages); if( page.missing ) { // TODO we need to fix this proper later on download.owner = null; return; } var content = ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; if( typeof content === 'string' ) { download.data = content; download.lastModified = new Date(page.revisions[0].timestamp); } } catch(someError) { return 'Revision preview failed :('; } } function APIbacklinksPreviewHTML(article, download/*, navpop*/ ) { try { var jsObj=getJsObj(download.data); var list=jsObj.query.backlinks; var html=[]; if (!list) { return popupString('No backlinks found'); } for ( var i=0; i < list.length; i++ ) { var t=new Title(list[i].title); html.push('<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t + '</a>'); } html=html.join(', '); if (jsObj['continue'] && jsObj['continue'].blcontinue) { html += popupString(' and more'); } return html; } catch (someError) { return 'backlinksPreviewHTML went wonky'; } } pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) { log( "APIsharedImagePagePreviewHTML" ); var popupid = obj.requestid; if( obj.query && obj.query.pages ) { var page=anyChild(obj.query.pages ); var content = ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; if( typeof content === 'string' ) { /* Not entirely safe, but the best we can do */ var p=new Previewmaker(content, pg.current.link.navpopup.article, pg.current.link.navpopup); p.makePreview(); setPopupHTML( p.html, "popupSecondPreview", popupid ); } } }; function APIimagepagePreviewHTML(article, download, navpop) { try { var jsObj=getJsObj(download.data); var page=anyChild(jsObj.query.pages); var content= ( page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ) ? page.revisions[0].content : null; var ret=''; var alt=''; try{alt=navpop.parentAnchor.childNodes[0].alt;} catch(e){} if (alt) { ret = ret + '<hr /><b>' + popupString('Alt text:') + '</b> ' + pg.escapeQuotesHTML(alt); } if (typeof content === 'string') { var p=prepPreviewmaker(content, article, navpop); p.makePreview(); if (p.html) { ret += '<hr />' + p.html; } if (getValueOf('popupSummaryData')) { var info=getPageInfo(content, download); log(info); setPopupTrailer(info, navpop.idNumber); } } if (page && page.imagerepository == "shared" ) { var art=new Title(article); var encart = encodeURIComponent( "File:" + art.stripNamespace() ); var shared_url = pg.wiki.apicommonsbase + '?format=json&formatversion=2' + '&callback=pg.fn.APIsharedImagePagePreviewHTML' + '&requestid=' + navpop.idNumber + '&action=query&prop=revisions&rvprop=content&titles=' + encart; ret = ret +'<hr />' + popupString( 'Image from Commons') + ': <a href="' + pg.wiki.commonsbase + '?title=' + encart + '">' + popupString( 'Description page') + '</a>'; mw.loader.load( shared_url ); } showAPIPreview('imagelinks', APIimagelinksPreviewHTML(article,download), navpop.idNumber, download); return ret; } catch (someError) { return 'API imagepage preview failed :('; } } function APIimagelinksPreviewHTML(article, download) { try { var jsobj=getJsObj(download.data); var list=jsobj.query.imageusage; if (list) { var ret=[]; for (var i=0; i < list.length; i++) { ret.push(list[i].title); } if (ret.length === 0) { return popupString('No image links found'); } return '<h2>' + popupString('File links') + '</h2>' + linkList(ret); } else { return popupString('No image links found'); } } catch(someError) { return 'Image links preview generation failed :('; } } function APIcategoryPreviewHTML(article, download) { try{ var jsobj=getJsObj(download.data); var list=jsobj.query.categorymembers; var ret=[]; for (var p=0; p < list.length; p++) { ret.push(list[p].title); } if (ret.length === 0) { return popupString('Empty category'); } ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' +linkList(ret); if (jsobj['continue'] && jsobj['continue'].cmcontinue) { ret += popupString(' and more'); } return ret; } catch(someError) { return 'Category preview failed :('; } } function APIuserInfoPreviewHTML(article, download) { var ret=[]; var queryobj = {}; try{ queryobj=getJsObj(download.data).query; } catch(someError) { return 'Userinfo preview failed :('; } var user=anyChild(queryobj.users); if (user) { var globaluserinfo=queryobj.globaluserinfo; if (user.invalid === '') { ret.push( popupString( 'Invalid user') ); } else if (user.missing === '') { ret.push( popupString( 'Not a registered username') ); } if( user.blockedby ) { if( user.blockpartial ) { ret.push('<b>' + popupString('Has blocks') + '</b>'); } else { ret.push('<b>' + popupString('CẤM') + '</b>'); } } if( globaluserinfo && ( 'locked' in globaluserinfo || 'hidden' in globaluserinfo ) ) { var lockedSulAccountIsAttachedToThis = true; for( var i=0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) { if ( globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname') ) { lockedSulAccountIsAttachedToThis=false; break; } } if (lockedSulAccountIsAttachedToThis) { if ( 'locked' in globaluserinfo ) ret.push('<b><i>' + popupString('LOCKED') + '</i></b>'); if ( 'hidden' in globaluserinfo ) ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>'); } } if( getValueOf('popupShowGender') && user.gender ) { switch( user.gender ) { case "male": ret.push( popupString( "\u2642" ) ); break; case "female": ret.push( popupString( "\u2640" ) ); break; } } if( user.groups ) { for( var j=0; j < user.groups.length; j++) { var currentGroup = user.groups[j]; if( ["*", "user", "autoconfirmed", "extendedconfirmed"].indexOf( currentGroup ) === -1 ) { ret.push( pg.escapeQuotesHTML(user.groups[j]) ); } } } if( globaluserinfo && globaluserinfo.groups ) { for( var k=0; k < globaluserinfo.groups.length; k++) { ret.push( '<i>'+pg.escapeQuotesHTML(globaluserinfo.groups[k])+'</i>' ); } } if( user.registration ) ret.push( pg.escapeQuotesHTML((user.editcount?user.editcount:'0') + popupString(' sửa đổi từ: ') + (user.registration?dayFormat(getDateFromTimestamp(user.registration)):'')) ); } if (queryobj.usercontribs && queryobj.usercontribs.length) { ret.push( popupString('sửa đổi lần cuối vào ') + dayFormat(getDateFromTimestamp(queryobj.usercontribs[0].timestamp)) ); } if (queryobj.blocks) { ret.push( popupString( 'IP user') ); //we only request list=blocks for IPs for (var l=0; l<queryobj.blocks.length; l++) { console.log(queryobj); var rbstr = queryobj.blocks[l].rangestart === queryobj.blocks[l].rangeend ? 'BLOCK' : 'RANGEBLOCK'; rbstr = (!Array.isArray(queryobj.blocks[l].restrictions) ? 'Has ' + rbstr.toLowerCase() + 's' : rbstr + 'ED'); ret.push('<b>' + popupString(rbstr) + '</b>' ); } } ret = '<hr />' + ret.join( ', ' ); return ret; } function APIcontribsPreviewHTML(article, download, navpop) { return APIhistoryPreviewHTML(article, download, navpop, true); } function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) { try { var jsobj=getJsObj(download.data); var edits = []; if( reallyContribs ) { edits=jsobj.query.usercontribs; } else { edits=anyChild(jsobj.query.pages).revisions; } var ret=editPreviewTable(article, edits, reallyContribs, getTimeOffset()); return ret; } catch (someError) { return 'History preview failed :-('; } } //</NOLITE> // ENDFILE: querypreview.js // STARTFILE: debug.js //////////////////////////////////////////////////////////////////// // Debugging functions //////////////////////////////////////////////////////////////////// function setupDebugging() { //<NOLITE> if (window.popupDebug) { // popupDebug is set from .version window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; }; window.console.log(x); }; window.errlog=function(x) { window.console.error(x); }; log('Initializing logger'); } else { //</NOLITE> window.log = function() {}; window.errlog = function() {}; //<NOLITE> } //</NOLITE> } // ENDFILE: debug.js // STARTFILE: images.js // load image of type Title. function loadImage(image, navpop) { if (typeof image.stripNamespace != 'function') { alert('loadImages bad'); } // API call to retrieve image info. if ( !getValueOf('popupImages') ) return; if ( !isValidImageName(image) ) return false; var art=image.urlString(); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query'; url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge'); url += '&titles=' + art; pendingNavpopTask(navpop); var callback=function(d){ popupsInsertImage(navpop.idNumber, navpop, d); }; var go = function(){ getPageWithCaching(url, callback, navpop); return true; }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA'); } } function popupsInsertImage(id, navpop, download) { log( "popupsInsertImage"); var imageinfo; try { var jsObj=getJsObj(download.data); var imagepage=anyChild(jsObj.query.pages); if (typeof imagepage.imageinfo === 'undefined') return; imageinfo = imagepage.imageinfo[0]; } catch (someError) { log( "popupsInsertImage failed :(" ); return; } var popupImage = document.getElementById("popupImg"+id); if (!popupImage) { log( "could not find insertion point for image"); return; } popupImage.width=getValueOf('popupImageSize'); popupImage.style.display='inline'; // Set the source for the image. if( imageinfo.thumburl ) popupImage.src=imageinfo.thumburl; else if( imageinfo.mime.indexOf("image") === 0 ){ popupImage.src=imageinfo.url; log( "a thumb could not be found, using original image" ); } else log( "fullsize imagethumb, but not sure if it's an image"); var a=document.getElementById("popupImageLink"+id); if (a === null) { return null; } // Determine the action of the surrouding imagelink. switch (getValueOf('popupThumbAction')) { case 'imagepage': if (pg.current.article.namespaceId()!=pg.nsImageId) { a.href=imageinfo.descriptionurl; // FIXME: unreliable pg.idNumber popTipsSoonFn('popupImage' + id)(); break; } /* falls through */ case 'sizetoggle': a.onclick=toggleSize; a.title=popupString('Toggle image size'); return; case 'linkfull': a.href = imageinfo.url; a.title=popupString('Open full-size image'); return; } } // Toggles the image between inline small and navpop fullwidth. // It's the same image, no actual sizechange occurs, only display width. function toggleSize() { var imgContainer=this; if (!imgContainer) { alert('imgContainer is null :/'); return; } var img=imgContainer.firstChild; if (!img) { alert('img is null :/'); return; } if (!img.style.width || img.style.width==='') { img.style.width='100%'; } else { img.style.width=''; } } // Returns one title of an image from wikiText. function getValidImageFromWikiText(wikiText) { // nb in pg.re.image we're interested in the second bracketed expression // this may change if the regex changes :-( //var match=pg.re.image.exec(wikiText); var matched=null; var match; // strip html comments, used by evil bots :-( var t = removeMatchesUnless(wikiText, RegExp('(<!--[\\s\\S]*?-->)'), 1, RegExp('^<!--[^[]*popup', 'i')); while ( ( match = pg.re.image.exec(t) ) ) { // now find a sane image name - exclude templates by seeking { var m = match[2] || match[6]; if ( isValidImageName(m) ) { matched=m; break; } } pg.re.image.lastIndex=0; if (!matched) { return null; } return mw.config.get('wgFormattedNamespaces')[pg.nsImageId]+':'+upcaseFirst(matched); } function removeMatchesUnless(str, re1, parencount, re2) { var split=str.parenSplit(re1); var c=parencount + 1; for (var i=0; i<split.length; ++i) { if ( i%c === 0 || re2.test(split[i]) ) { continue; } split[i]=''; } return split.join(''); } //</NOLITE> // ENDFILE: images.js // STARTFILE: namespaces.js // Set up namespaces and other non-strings.js localization // (currently that means redirs too) function setNamespaces() { pg.nsSpecialId = -1; pg.nsMainspaceId = 0; pg.nsImageId = 6; pg.nsUserId = 2; pg.nsUsertalkId = 3; pg.nsCategoryId = 14; pg.nsTemplateId = 10; } function setRedirs() { var r='redirect'; var R='REDIRECT'; var redirLists={ //<NOLITE> 'ar': [ R, 'تحويل' ], 'be': [ r, 'перанакіраваньне' ], 'bg': [ r, 'пренасочване', 'виж' ], 'bs': [ r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI' ], 'cs': [ R, 'PŘESMĚRUJ' ], 'cy': [ r, 'ail-cyfeirio' ], 'de': [ R, 'WEITERLEITUNG' ], 'el': [ R, 'ΑΝΑΚΑΤΕΥΘΥΝΣΗ'], 'eo': [ R, 'ALIDIREKTU', 'ALIDIREKTI' ], 'es': [ R, 'REDIRECCIÓN' ], 'et': [ r, 'suuna' ], 'ga': [ r, 'athsheoladh' ], 'gl': [ r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'], 'he': [ R, 'הפניה' ], 'hu': [ R, 'ÁTIRÁNYÍTÁS' ], 'is': [ r, 'tilvísun', 'TILVÍSUN' ], 'it': [ R, 'RINVIA', 'Rinvia'], 'ja': [ R, '転送' ], 'mk': [ r, 'пренасочување', 'види' ], 'nds': [ r, 'wiederleiden' ], 'nl': [ R, 'DOORVERWIJZING' ], 'nn': [ r, 'omdiriger' ], 'pl': [ R, 'PATRZ', 'PRZEKIERUJ', 'TAM' ], 'pt': [ R, 'redir' ], 'ru': [ R, 'ПЕРЕНАПРАВЛЕНИЕ', 'ПЕРЕНАПР' ], 'sk': [ r, 'presmeruj' ], 'sr': [ r, 'Преусмери', 'преусмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI' ], 'tt': [ R, 'yünältü', 'перенаправление', 'перенапр' ], 'uk': [ R, 'ПЕРЕНАПРАВЛЕННЯ', 'ПЕРЕНАПР' ], 'vi': [ r, 'đổi' ], 'zh': [ R, '重定向'] // no comma //</NOLITE> }; var redirList=redirLists[ pg.wiki.lang ] || [r, R]; // Mediawiki is very tolerant about what comes after the #redirect at the start pg.re.redirect=RegExp('^\\s*[#](' + redirList.join('|') + ').*?\\[{2}([^\\|\\]]*)(|[^\\]]*)?\\]{2}\\s*(.*)', 'i'); } function setInterwiki() { if (pg.wiki.wikimedia) { // From https://meta.wikimedia.org/wiki/List_of_Wikipedias pg.wiki.interwiki='aa|ab|ace|af|ak|als|am|an|ang|ar|arc|arz|as|ast|av|ay|az|ba|bar|bat-smg|bcl|be|be-x-old|bg|bh|bi|bjn|bm|bn|bo|bpy|br|bs|bug|bxr|ca|cbk-zam|cdo|ce|ceb|ch|cho|chr|chy|ckb|co|cr|crh|cs|csb|cu|cv|cy|da|de|diq|dsb|dv|dz|ee|el|eml|en|eo|es|et|eu|ext|fa|ff|fi|fiu-vro|fj|fo|fr|frp|frr|fur|fy|ga|gag|gan|gd|gl|glk|gn|got|gu|gv|ha|hak|haw|he|hi|hif|ho|hr|hsb|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|ilo|io|is|it|iu|ja|jbo|jv|ka|kaa|kab|kbd|kg|ki|kj|kk|kl|km|kn|ko|koi|kr|krc|ks|ksh|ku|kv|kw|ky|la|lad|lb|lbe|lg|li|lij|lmo|ln|lo|lt|ltg|lv|map-bms|mdf|mg|mh|mhr|mi|mk|ml|mn|mo|mr|mrj|ms|mt|mus|mwl|my|myv|mzn|na|nah|nap|nds|nds-nl|ne|new|ng|nl|nn|no|nov|nrm|nv|ny|oc|om|or|os|pa|pag|pam|pap|pcd|pdc|pfl|pi|pih|pl|pms|pnb|pnt|ps|pt|qu|rm|rmy|rn|ro|roa-rup|roa-tara|ru|rue|rw|sa|sah|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|sn|so|sq|sr|srn|ss|st|stq|su|sv|sw|szl|ta|te|tet|tg|th|ti|tk|tl|tn|to|tpi|tr|ts|tt|tum|tw|ty|udm|ug|uk|ur|uz|ve|vec|vi|vls|vo|wa|war|wo|wuu|xal|xh|yi|yo|za|zea|zh|zh-classical|zh-min-nan|zh-yue|zu'; pg.re.interwiki=RegExp('^'+pg.wiki.interwiki+':'); } else { pg.wiki.interwiki=null; pg.re.interwiki=RegExp('^$'); } } // return a regexp pattern matching all variants to write the given namespace function nsRe(namespaceId) { var imageNamespaceVariants = []; jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) { if (_namespaceId!=namespaceId) return; _localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc); imageNamespaceVariants.push(mw.util.escapeRegExp(_localizedNamespaceLc).split(' ').join('[ _]')); imageNamespaceVariants.push(mw.util.escapeRegExp(encodeURI(_localizedNamespaceLc))); }); return '(?:' + imageNamespaceVariants.join('|') + ')'; } function nsReImage() { return nsRe(pg.nsImageId); } // ENDFILE: namespaces.js // STARTFILE: selpop.js //<NOLITE> function getEditboxSelection() { // see http://www.webgurusforum.com/8/12/0 var editbox; try { editbox=document.editform.wpTextbox1; } catch (dang) { return; } // IE, Opera if (document.selection) { return document.selection.createRange().text; } // Mozilla var selStart = editbox.selectionStart; var selEnd = editbox.selectionEnd; return (editbox.value).substring(selStart, selEnd); } function doSelectionPopup() { // popup if the selection looks like [[foo|anything afterwards at all // or [[foo|bar]]text without ']]' // or [[foo|bar]] var sel=getEditboxSelection(); var open=sel.indexOf('[['); var pipe=sel.indexOf('|'); var close=sel.indexOf(']]'); if (open == -1 || ( pipe == -1 && close == -1) ) { return; } if (pipe != -1 && open > pipe || close != -1 && open > close) { return; } if (getValueOf('popupOnEditSelection')=='boxpreview') { return doSeparateSelectionPopup(sel); } var article=new Title(sel.substring(open+2, (pipe < 0) ? close : pipe)).urlString(); if (close > 0 && sel.substring(close+2).indexOf('[[') >= 0) { return; } var a=document.createElement('a'); a.href=pg.wiki.titlebase + article; mouseOverWikiLink2(a); if (a.navpopup) { a.navpopup.addHook(function(){runStopPopupTimer(a.navpopup);}, 'unhide', 'after'); } } function doSeparateSelectionPopup(str) { var div=document.getElementById('selectionPreview'); if (!div) { div = document.createElement('div'); div.id='selectionPreview'; try { var box=document.editform.wpTextbox1; box.parentNode.insertBefore(div, box); } catch (error) { return; } } div.innerHTML=wiki2html(str); div.ranSetupTooltipsAlready = false; popTipsSoonFn('selectionPreview')(); } //</NOLITE> // ENDFILE: selpop.js // STARTFILE: navpopup.js /** @fileoverview Defines two classes: {@link Navpopup} and {@link Mousetracker}. <code>Navpopup</code> describes popups: when they appear, where, what they look like and so on. <code>Mousetracker</code> "captures" the mouse using <code>document.onmousemove</code>. */ /** Creates a new Mousetracker. @constructor @class The Mousetracker class. This monitors mouse movements and manages associated hooks. */ function Mousetracker() { /** Interval to regularly run the hooks anyway, in milliseconds. @type Integer */ this.loopDelay=400; /** Timer for the loop. @type Timer */ this.timer=null; /** Flag - are we switched on? @type Boolean */ this.active=false; /** Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position? */ this.dirty=true; /** Array of hook functions. @private @type Array */ this.hooks=[]; } /** Adds a hook, to be called when we get events. @param {Function} f A function which is called as <code>f(x,y)</code>. It should return <code>true</code> when it wants to be removed, and <code>false</code> otherwise. */ Mousetracker.prototype.addHook = function (f) { this.hooks.push(f); }; /** Runs hooks, passing them the x and y coords of the mouse. Hook functions that return true are passed to {@link Mousetracker#removeHooks} for removal. @private */ Mousetracker.prototype.runHooks = function () { if (!this.hooks || !this.hooks.length) { return; } //log('Mousetracker.runHooks; we got some hooks to run'); var remove=false; var removeObj={}; // this method gets called a LOT - // pre-cache some variables var x=this.x, y=this.y, len = this.hooks.length; for (var i=0; i<len; ++i) { //~ run the hook function, and remove it if it returns true if (this.hooks[i](x, y)===true) { remove=true; removeObj[i]=true; } } if (remove) { this.removeHooks(removeObj); } }; /** Removes hooks. @private @param {Object} removeObj An object whose keys are the index numbers of functions for removal, with values that evaluate to true */ Mousetracker.prototype.removeHooks = function(removeObj) { var newHooks=[]; var len = this.hooks.length; for (var i=0; i<len; ++i) { if (! removeObj[i]) { newHooks.push(this.hooks[i]); } } this.hooks=newHooks; }; /** Event handler for mouse wiggles. We simply grab the event, set x and y and run the hooks. This makes the cpu all hot and bothered :-( @private @param {Event} e Mousemove event */ Mousetracker.prototype.track=function (e) { //~ Apparently this is needed in IE. e = e || window.event; var x, y; if (e) { if (e.pageX) { x=e.pageX; y=e.pageY; } else if (typeof e.clientX!='undefined') { var left, top, docElt = document.documentElement; if (docElt) { left=docElt.scrollLeft; } left = left || document.body.scrollLeft || document.scrollLeft || 0; if (docElt) { top=docElt.scrollTop; } top = top || document.body.scrollTop || document.scrollTop || 0; x=e.clientX + left; y=e.clientY + top; } else { return; } this.setPosition(x,y); } }; /** Sets the x and y coordinates stored and takes appropriate action, running hooks as appropriate. @param {Integer} x, y Screen coordinates to set */ Mousetracker.prototype.setPosition=function(x,y) { this.x = x; this.y = y; if (this.dirty || this.hooks.length === 0) { this.dirty=false; return; } if (typeof this.lastHook_x != 'number') { this.lastHook_x = -100; this.lastHook_y=-100; } var diff = (this.lastHook_x - x)*(this.lastHook_y - y); diff = (diff >= 0) ? diff : -diff; if ( diff > 1 ) { this.lastHook_x=x; this.lastHook_y=y; if (this.dirty) { this.dirty = false; } else { this.runHooks(); } } }; /** Sets things in motion, unless they are already that is, registering an event handler on <code>document.onmousemove</code>. A half-hearted attempt is made to preserve the old event handler if there is one. */ Mousetracker.prototype.enable = function () { if (this.active) { return; } this.active=true; //~ Save the current handler for mousemove events. This isn't too //~ robust, of course. this.savedHandler=document.onmousemove; //~ Gotta save @tt{this} again for the closure, and use apply for //~ the member function. var savedThis=this; document.onmousemove=function (e) {savedThis.track.apply(savedThis, [e]);}; if (this.loopDelay) { this.timer = setInterval(function() { //log('loop delay in mousetracker is working'); savedThis.runHooks();}, this.loopDelay); } }; /** Disables the tracker, removing the event handler. */ Mousetracker.prototype.disable = function () { if (!this.active) { return; } if (typeof this.savedHandler === 'function') { document.onmousemove=this.savedHandler; } else { delete document.onmousemove; } if (this.timer) { clearInterval(this.timer); } this.active=false; }; /** Creates a new Navpopup. Gets a UID for the popup and @param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable. @constructor @class The Navpopup class. This generates popup hints, and does some management of them. */ function Navpopup(/*init*/) { //alert('new Navpopup(init)'); /** UID for each Navpopup instance. Read-only. @type integer */ this.uid=Navpopup.uid++; /** Read-only flag for current visibility of the popup. @type boolean @private */ this.visible=false; /** Flag to be set when we want to cancel a previous request to show the popup in a little while. @private @type boolean */ this.noshow=false; /** Categorised list of hooks. @see #runHooks @see #addHook @private @type Object */ this.hooks={ 'create': [], 'unhide': [], 'hide': [] }; /** list of unique IDs of hook functions, to avoid duplicates @private */ this.hookIds={}; /** List of downloads associated with the popup. @private @type Array */ this.downloads=[]; /** Number of uncompleted downloads. @type integer */ this.pending=null; /** Tolerance in pixels when detecting whether the mouse has left the popup. @type integer */ this.fuzz=5; /** Flag to toggle running {@link #limitHorizontalPosition} to regulate the popup's position. @type boolean */ this.constrained=true; /** The popup width in pixels. @private @type integer */ this.width=0; /** The popup width in pixels. @private @type integer */ this.height=0; /** The main content DIV element. @type HTMLDivElement */ this.mainDiv=null; this.createMainDiv(); // if (!init || typeof init.popups_draggable=='undefined' || init.popups_draggable) { // this.makeDraggable(true); // } } /** A UID for each Navpopup. This constructor property is just a counter. @type integer @private */ Navpopup.uid=0; /** Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible. @type boolean */ Navpopup.prototype.isVisible=function() { return this.visible; }; /** Repositions popup using CSS style. @private @param {integer} x x-coordinate (px) @param {integer} y y-coordinate (px) @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition} */ Navpopup.prototype.reposition= function (x,y, noLimitHor) { log ('reposition('+x+','+y+','+noLimitHor+')'); if (typeof x != 'undefined' && x !== null) { this.left=x; } if (typeof y != 'undefined' && y !== null) { this.top=y; } if (typeof this.left != 'undefined' && typeof this.top != 'undefined') { this.mainDiv.style.left=this.left + 'px'; this.mainDiv.style.top=this.top + 'px'; } if (!noLimitHor) { this.limitHorizontalPosition(); } //console.log('navpop'+this.uid+' - (left,top)=(' + this.left + ',' + this.top + '), css=(' //+ this.mainDiv.style.left + ',' + this.mainDiv.style.top + ')'); }; /** Prevents popups from being in silly locations. Hopefully. Should not be run if {@link #constrained} is true. @private */ Navpopup.prototype.limitHorizontalPosition=function() { if (!this.constrained || this.tooWide) { return; } this.updateDimensions(); var x=this.left; var w=this.width; var cWidth=document.body.clientWidth; // log('limitHorizontalPosition: x='+x+ // ', this.left=' + this.left + // ', this.width=' + this.width + // ', cWidth=' + cWidth); if ( (x+w) >= cWidth || ( x > 0 && this.maxWidth && this.width < this.maxWidth && this.height > this.width && x > cWidth - this.maxWidth ) ) { // This is a very nasty hack. There has to be a better way! // We find the "natural" width of the div by positioning it at the far left // then reset it so that it should be flush right (well, nearly) this.mainDiv.style.left='-10000px'; this.mainDiv.style.width = this.maxWidth + 'px'; var naturalWidth=parseInt(this.mainDiv.offsetWidth, 10); var newLeft=cWidth - naturalWidth - 1; if (newLeft < 0) { newLeft = 0; this.tooWide=true; } // still unstable for really wide popups? log ('limitHorizontalPosition: moving to ('+newLeft + ','+ this.top+');' + ' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth); this.reposition(newLeft, null, true); } }; /** Counter indicating the z-order of the "highest" popup. We start the z-index at 1000 so that popups are above everything else on the screen. @private @type integer */ Navpopup.highest=1000; /** Brings popup to the top of the z-order. We increment the {@link #highest} property of the contructor here. @private */ Navpopup.prototype.raise = function () { this.mainDiv.style.zIndex=Navpopup.highest + 1; ++Navpopup.highest; }; /** Shows the popup provided {@link #noshow} is not true. Updates the position, brings the popup to the top of the z-order and unhides it. */ Navpopup.prototype.show = function () { //document.title+='s'; if (this.noshow) { return; } //document.title+='t'; this.reposition(); this.raise(); this.unhide(); }; /** Checks to see if the mouse pointer has stabilised (checking every <code>time</code>/2 milliseconds) and runs the {@link #show} method if it has. @param {integer} time The minimum time (ms) before the popup may be shown. */ Navpopup.prototype.showSoonIfStable = function (time) { log ('showSoonIfStable, time='+time); if (this.visible) { return; } this.noshow = false; //~ initialize these variables so that we never run @tt{show} after //~ just half the time this.stable_x = -10000; this.stable_y = -10000; var stableShow = function() { log('stableShow called'); var new_x = Navpopup.tracker.x, new_y = Navpopup.tracker.y; var dx = savedThis.stable_x - new_x, dy = savedThis.stable_y - new_y; var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz; //document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] '; if ( dx * dx <= fuzz2 && dy * dy <= fuzz2 ) { log ('mouse is stable'); clearInterval(savedThis.showSoonStableTimer); savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]); savedThis.show.apply(savedThis, []); savedThis.limitHorizontalPosition.apply(savedThis, []); return; } savedThis.stable_x = new_x; savedThis.stable_y = new_y; }; var savedThis = this; this.showSoonStableTimer = setInterval(stableShow, time/2); }; /** Sets the {@link #noshow} flag and hides the popup. This should be called when the mouse leaves the link before (or after) it's actually been displayed. */ Navpopup.prototype.banish = function () { log ('banish called'); // hide and prevent showing with showSoon in the future this.noshow=true; if (this.showSoonStableTimer) { log('clearing showSoonStableTimer'); clearInterval(this.showSoonStableTimer); } this.hide(); }; /** Runs hooks added with {@link #addHook}. @private @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide' @param {String} when Controls exactly when the hook is run: either 'before' or 'after' */ Navpopup.prototype.runHooks = function (key, when) { if (!this.hooks[key]) { return; } var keyHooks=this.hooks[key]; var len=keyHooks.length; for (var i=0; i< len; ++i) { if (keyHooks[i] && keyHooks[i].when == when) { if (keyHooks[i].hook.apply(this, [])) { // remove the hook if (keyHooks[i].hookId) { delete this.hookIds[keyHooks[i].hookId]; } keyHooks[i]=null; } } } }; /** Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the Navpopup instance, and no arguments. @param {Function} hook The hook function. Functions that return true are deleted. @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide' @param {String} when Controls exactly when the hook is run: either 'before' or 'after' @param {String} uid A truthy string identifying the hook function; if it matches another hook in this position, it won't be added again. */ Navpopup.prototype.addHook = function ( hook, key, when, uid ) { when = when || 'after'; if (!this.hooks[key]) { return; } // if uid is specified, don't add duplicates var hookId=null; if (uid) { hookId=[key,when,uid].join('|'); if (this.hookIds[hookId]) { return; } this.hookIds[hookId]=true; } this.hooks[key].push( {hook: hook, when: when, hookId: hookId} ); }; /** Creates the main DIV element, which contains all the actual popup content. Runs hooks with key 'create'. @private */ Navpopup.prototype.createMainDiv = function () { if (this.mainDiv) { return; } this.runHooks('create', 'before'); var mainDiv=document.createElement('div'); var savedThis=this; mainDiv.onclick=function(e) {savedThis.onclickHandler(e);}; mainDiv.className=(this.className) ? this.className : 'navpopup_maindiv'; mainDiv.id=mainDiv.className + this.uid; mainDiv.style.position='absolute'; mainDiv.style.minWidth = '350px'; mainDiv.style.display='none'; mainDiv.className='navpopup'; // easy access to javascript object through DOM functions mainDiv.navpopup=this; this.mainDiv=mainDiv; document.body.appendChild(mainDiv); this.runHooks('create', 'after'); }; /** Calls the {@link #raise} method. @private */ Navpopup.prototype.onclickHandler=function(/*e*/) { this.raise(); }; /** Makes the popup draggable, using a {@link Drag} object. @private */ Navpopup.prototype.makeDraggable=function(handleName) { if (!this.mainDiv) { this.createMainDiv(); } var drag=new Drag(); if (!handleName) { drag.startCondition=function(e) { try { if (!e.shiftKey) { return false; } } catch (err) { return false; } return true; }; } var dragHandle; if (handleName) dragHandle = document.getElementById(handleName); if (!dragHandle) dragHandle = this.mainDiv; var np=this; drag.endHook=function(x,y) { Navpopup.tracker.dirty=true; np.reposition(x,y); }; drag.init(dragHandle,this.mainDiv); }; /** Hides the popup using CSS. Runs hooks with key 'hide'. Sets {@link #visible} appropriately. {@link #banish} should be called externally instead of this method. @private */ Navpopup.prototype.hide = function () { this.runHooks('hide', 'before'); this.abortDownloads(); if (typeof this.visible != 'undefined' && this.visible) { this.mainDiv.style.display='none'; this.visible=false; } this.runHooks('hide', 'after'); }; /** Shows the popup using CSS. Runs hooks with key 'unhide'. Sets {@link #visible} appropriately. {@link #show} should be called externally instead of this method. @private */ Navpopup.prototype.unhide = function () { this.runHooks('unhide', 'before'); if (typeof this.visible != 'undefined' && !this.visible) { this.mainDiv.style.display='inline'; this.visible=true; } this.runHooks('unhide', 'after'); }; /** Sets the <code>innerHTML</code> attribute of the main div containing the popup content. @param {String} html The HTML to set. */ Navpopup.prototype.setInnerHTML = function (html) { this.mainDiv.innerHTML = html; }; /** Updates the {@link #width} and {@link #height} attributes with the CSS properties. @private */ Navpopup.prototype.updateDimensions = function () { this.width=parseInt(this.mainDiv.offsetWidth, 10); this.height=parseInt(this.mainDiv.offsetHeight, 10); }; /** Checks if the point (x,y) is within {@link #fuzz} of the {@link #mainDiv}. @param {integer} x x-coordinate (px) @param {integer} y y-coordinate (px) @type boolean */ Navpopup.prototype.isWithin = function(x,y) { //~ If we're not even visible, no point should be considered as //~ being within the popup. if (!this.visible) { return false; } this.updateDimensions(); var fuzz=this.fuzz || 0; //~ Use a simple box metric here. return (x+fuzz >= this.left && x-fuzz <= this.left + this.width && y+fuzz >= this.top && y-fuzz <= this.top + this.height); }; /** Adds a download to {@link #downloads}. @param {Downloader} download */ Navpopup.prototype.addDownload=function(download) { if (!download) { return; } this.downloads.push(download); }; /** Aborts the downloads listed in {@link #downloads}. @see Downloader#abort */ Navpopup.prototype.abortDownloads=function() { for(var i=0; i<this.downloads.length; ++i) { var d=this.downloads[i]; if (d && d.abort) { d.abort(); } } this.downloads=[]; }; /** A {@link Mousetracker} instance which is a property of the constructor (pseudo-global). */ Navpopup.tracker=new Mousetracker(); // ENDFILE: navpopup.js // STARTFILE: diff.js //<NOLITE> /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) and [[:en:User:Lupin]] * * More Info: * http://ejohn.org/projects/javascript-diff-algorithm/ */ function delFmt(x) { if (!x.length) { return ''; } return "<del class='popupDiff'>" + x.join('') +"</del>"; } function insFmt(x) { if (!x.length) { return ''; } return "<ins class='popupDiff'>" + x.join('') +"</ins>"; } function countCrossings(a, b, i, eject) { // count the crossings on the edge starting at b[i] if (!b[i].row && b[i].row !== 0) { return -1; } var count=0; for (var j=0; j<a.length; ++j) { if (!a[j].row && a[j].row !== 0) { continue; } if ( (j-b[i].row)*(i-a[j].row) > 0) { if(eject) { return true; } count++; } } return count; } function shortenDiffString(str, context) { var re=RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)'); var splitted=str.parenSplit(re); var ret=['']; for (var i=0; i<splitted.length; i+=2) { if (splitted[i].length < 2*context) { ret[ret.length-1] += splitted[i]; if (i+1<splitted.length) { ret[ret.length-1] += splitted[i+1]; } continue; } else { if (i > 0) { ret[ret.length-1] += splitted[i].substring(0,context); } if (i+1 < splitted.length) { ret.push(splitted[i].substring(splitted[i].length-context) + splitted[i+1]); } } } while (ret.length > 0 && !ret[0]) { ret = ret.slice(1); } return ret; } function diffString( o, n, simpleSplit ) { var splitRe=RegExp('([[]{2}|[\\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\\s|\\b)'); // We need to split the strings o and n first, and entify() the parts // individually, so that the HTML entities are never cut apart. (AxelBoldt) var out, i, oSplitted, nSplitted; if (simpleSplit) { oSplitted=o.split(/\b/); nSplitted=n.split(/\b/); } else { oSplitted=o.parenSplit(splitRe); nSplitted=n.parenSplit(splitRe); } for (i=0; i<oSplitted.length; ++i) {oSplitted[i]=oSplitted[i].entify();} for (i=0; i<nSplitted.length; ++i) {nSplitted[i]=nSplitted[i].entify();} out = diff (oSplitted, nSplitted); var str = ""; var acc=[]; // accumulator for prettier output // crossing pairings -- eg 'A B' vs 'B A' -- cause problems, so let's iron them out // this doesn't always do things optimally but it should be fast enough var maxOutputPair=0; for (i=0; i<out.n.length; ++i) { if ( out.n[i].paired ) { if( maxOutputPair > out.n[i].row ) { // tangle - delete pairing out.o[ out.n[i].row ]=out.o[ out.n[i].row ].text; out.n[i]=out.n[i].text; } if (maxOutputPair < out.n[i].row) { maxOutputPair = out.n[i].row; } } } // output the stuff preceding the first paired old line for (i=0; i<out.o.length && !out.o[i].paired; ++i) { acc.push( out.o[i] ); } str += delFmt(acc); acc=[]; // main loop for ( i = 0; i < out.n.length; ++i ) { // output unpaired new "lines" while ( i < out.n.length && !out.n[i].paired ) { acc.push( out.n[i++] ); } str += insFmt(acc); acc=[]; if ( i < out.n.length ) { // this new "line" is paired with the (out.n[i].row)th old "line" str += out.n[i].text; // output unpaired old rows starting after this new line's partner var m = out.n[i].row + 1; while ( m < out.o.length && !out.o[m].paired ) { acc.push ( out.o[m++] ); } str += delFmt(acc); acc=[]; } } return str; } // see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object // FIXME: use obj.hasOwnProperty instead of this kludge! var jsReservedProperties=RegExp('^(constructor|prototype|__((define|lookup)[GS]etter)__' + '|eval|hasOwnProperty|propertyIsEnumerable' + '|to(Source|String|LocaleString)|(un)?watch|valueOf)$'); function diffBugAlert(word) { if (!diffBugAlert.list[word]) { diffBugAlert.list[word]=1; alert('Bad word: '+word+'\n\nPlease report this bug.'); } } diffBugAlert.list={}; function makeDiffHashtable(src) { var ret={}; for ( var i = 0; i < src.length; i++ ) { if ( jsReservedProperties.test(src[i]) ) { src[i] += '<!-- -->'; } if ( !ret[ src[i] ] ) { ret[ src[i] ] = []; } try { ret[ src[i] ].push( i ); } catch (err) { diffBugAlert(src[i]); } } return ret; } function diff( o, n ) { // pass 1: make hashtable ns with new rows as keys var ns = makeDiffHashtable(n); // pass 2: make hashtable os with old rows as keys var os = makeDiffHashtable(o); // pass 3: pair unique new rows and matching unique old rows var i; for ( i in ns ) { if ( ns[i].length == 1 && os[i] && os[i].length == 1 ) { n[ ns[i][0] ] = { text: n[ ns[i][0] ], row: os[i][0], paired: true }; o[ os[i][0] ] = { text: o[ os[i][0] ], row: ns[i][0], paired: true }; } } // pass 4: pair matching rows immediately following paired rows (not necessarily unique) for ( i = 0; i < n.length - 1; i++ ) { if ( n[i].paired && ! n[i+1].paired && n[i].row + 1 < o.length && ! o[ n[i].row + 1 ].paired && n[i+1] == o[ n[i].row + 1 ] ) { n[i+1] = { text: n[i+1], row: n[i].row + 1, paired: true }; o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1, paired: true }; } } // pass 5: pair matching rows immediately preceding paired rows (not necessarily unique) for ( i = n.length - 1; i > 0; i-- ) { if ( n[i].paired && ! n[i-1].paired && n[i].row > 0 && ! o[ n[i].row - 1 ].paired && n[i-1] == o[ n[i].row - 1 ] ) { n[i-1] = { text: n[i-1], row: n[i].row - 1, paired: true }; o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1, paired: true }; } } return { o: o, n: n }; } //</NOLITE> // ENDFILE: diff.js // STARTFILE: init.js function setSiteInfo() { if (window.popupLocalDebug) { pg.wiki.hostname = 'en.wikipedia.org'; } else { pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?) } pg.wiki.wikimedia=RegExp('(wiki([pm]edia|source|books|news|quote|versity)|wiktionary|mediawiki)[.]org').test(pg.wiki.hostname); pg.wiki.wikia=RegExp('[.]wikia[.]com$', 'i').test(pg.wiki.hostname); pg.wiki.isLocal=RegExp('^localhost').test(pg.wiki.hostname); pg.wiki.commons=( pg.wiki.wikimedia && pg.wiki.hostname != 'commons.wikimedia.org') ? 'commons.wikimedia.org' : null; pg.wiki.lang = mw.config.get('wgContentLanguage'); var port = location.port ? ':' + location.port : ''; pg.wiki.sitebase = pg.wiki.hostname + port; } function setUserInfo() { var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'query', list: 'users', ususers: mw.config.get('wgUserName'), usprop: 'rights' }; pg.user.canReview = false; if (getValueOf('popupReview')) { api.get(params).done(function(data){ var rights = data.query.users[0].rights; pg.user.canReview = rights.indexOf('review') !== -1; // TODO: Should it be a getValueOf('ReviewRight') ? }); } } function setTitleBase() { var protocol = ( window.popupLocalDebug ? 'http:' : location.protocol ); pg.wiki.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, ""); // as in http://some.thing.com/wiki/Article pg.wiki.botInterfacePath = mw.config.get('wgScript'); pg.wiki.APIPath = mw.config.get('wgScriptPath') +"/api.php"; // default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo var titletail = pg.wiki.botInterfacePath + '?title='; //var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?title=']); // other sites may need to add code here to set titletail depending on how their urls work pg.wiki.titlebase = protocol + '//' + pg.wiki.sitebase + titletail; //pg.wiki.titlebase2 = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]); pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath; pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath; pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath; pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.botInterfacePath; pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.APIPath; pg.re.basenames = RegExp( '^(' + map( literalizeRegex, [ pg.wiki.titlebase, //pg.wiki.titlebase2, pg.wiki.articlebase ]).join('|') + ')' ); } ////////////////////////////////////////////////// // Global regexps function setMainRegex() { var reStart='[^:]*://'; var preTitles = literalizeRegex( mw.config.get('wgScriptPath') ) + '/(?:index[.]php|wiki[.]phtml)[?]title='; preTitles += '|' + literalizeRegex( pg.wiki.articlePath + '/' ); var reEnd='(' + preTitles + ')([^&?#]*)[^#]*(?:#(.+))?'; pg.re.main = RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd); } function setRegexps() { // TODO: We shoud use an api call to get the aliases for special pages, now it does not work for non-English wikipedias: // E.g., https://ru.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=specialpagealiases&formatversion=2 setMainRegex(); var sp=nsRe(pg.nsSpecialId); pg.re.urlNoPopup=RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)') ; pg.re.contribs =RegExp('(title=|/)' + sp + '(?:%3A|:)Contributions' + '(&target=|/|/' + nsRe(pg.nsUserId)+':)(.*)') ; pg.re.email =RegExp('(title=|/)' + sp + '(?:%3A|:)EmailUser' + '(&target=|/|/(?:' + nsRe(pg.nsUserId)+':)?)(.*)') ; pg.re.backlinks =RegExp('(title=|/)' + sp + '(?:%3A|:)WhatLinksHere' + '(&target=|/)([^&]*)'); pg.re.specialdiff=RegExp('/' + sp + '(?:%3A|:)Diff/([^?#]*)'); //<NOLITE> var im=nsReImage(); // note: tries to get images in infobox templates too, e.g. movie pages, album pages etc // (^|\[\[)image: *([^|\]]*[^|\] ]) * // (^|\[\[)image: *([^|\]]*[^|\] ])([^0-9\]]*([0-9]+) *px)? // $4 = 120 as in 120px pg.re.image = RegExp('(^|\\[\\[)' + im + ': *([^|\\]]*[^|\\] ])' + '([^0-9\\]]*([0-9]+) *px)?|(?:\\n *[|]?|[|]) *' + '(' + getValueOf('popupImageVarsRegexp') + ')' + ' *= *(?:\\[\\[ *)?(?:' + im + ':)?' + '([^|]*?)(?:\\]\\])? *[|]? *\\n', 'img') ; pg.re.imageBracketCount = 6; pg.re.category = RegExp('\\[\\[' +nsRe(pg.nsCategoryId) + ': *([^|\\]]*[^|\\] ]) *', 'i'); pg.re.categoryBracketCount = 1; pg.re.ipUser=RegExp('^' + // IPv6 '(?::(?::|(?::[0-9A-Fa-f]{1,4}){1,7})|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,6}::|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){7})' + // IPv4 '|(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' + '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$'); pg.re.stub= RegExp(getValueOf('popupStubRegexp'), 'im'); pg.re.disambig=RegExp(getValueOf('popupDabRegexp'), 'im'); //</NOLITE> // FIXME replace with general parameter parsing function, this is daft pg.re.oldid=RegExp('[?&]oldid=([^&]*)'); pg.re.diff=RegExp('[?&]diff=([^&]*)'); } ////////////////////////////////////////////////// // miscellany function setupCache() { // page caching pg.cache.pages = []; } function setMisc() { pg.current.link=null; pg.current.links=[]; pg.current.linksHash={}; setupCache(); pg.timer.checkPopupPosition=null; pg.counter.loop=0; // ids change with each popup: popupImage0, popupImage1 etc pg.idNumber=0; // for myDecodeURI pg.misc.decodeExtras = [ {from: '%2C', to: ',' }, {from: '_', to: ' ' }, {from: '%24', to: '$'}, {from: '%26', to: '&' } // no , ]; pg.misc.userAgent = 'Navigation popups/1.0 (' + mw.config.get( 'wgServerName' ) +')'; } // We need a callback since this might end up asynchronous because of // the mw.loader.using() call. function setupPopups( callback ) { if ( setupPopups.completed ) { if ( typeof callback === 'function' ) { callback(); } return; } // These dependencies are also enforced from the gadget, // but not everyone loads this as a gadget, so double check mw.loader.using( ['mediawiki.util', 'mediawiki.user', 'user.options'] ).then( function() { // NB translatable strings should be set up first (strings.js) // basics setupDebugging(); setSiteInfo(); setTitleBase(); setOptions(); // see options.js setUserInfo(); // namespaces etc setNamespaces(); setInterwiki(); // regexps setRegexps(); setRedirs(); // other stuff setMisc(); setupLivePreview(); // main deal here setupTooltips(); log('In setupPopups(), just called setupTooltips()'); Navpopup.tracker.enable(); setupPopups.completed = true; if ( typeof callback === 'function' ) { callback(); } }); } // ENDFILE: init.js // STARTFILE: navlinks.js //<NOLITE> ////////////////////////////////////////////////// // navlinks... let the fun begin // function defaultNavlinkSpec() { var str=''; str += '<b><<mainlink|shortcut= >></b>'; if (getValueOf('popupLastEditLink')) { str += '*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}'; } // user links // contribs - log - count - email - block // count only if applicable; block only if popupAdminLinks str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>'; str+='if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}'; // editing links // talkpage -> edit|new - history - un|watch - article|edit // other page -> edit - history - un|watch - talk|edit|new var editstr='<<edit|shortcut=e>>'; var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'; var historystr='<<history|shortcut=h>>|<<editors|shortcut=E|>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; str+='<br>if(talk){' + editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + // not a talk page editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}'; // misc links str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>'; // admin links str += 'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}'; return str; } function navLinksHTML (article, hint, params) { //oldid, rcid) { var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>'; // BAM return navlinkStringToHTML(str, article, params); } function expandConditionalNavlinkString(s,article,z,recursionCount) { var oldid=z.oldid, rcid=z.rcid, diff=z.diff; // nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out) if (typeof recursionCount!=typeof 0) { recursionCount=0; } var conditionalSplitRegex=RegExp( //(1 if \\( (2 2) \\) {(3 3)} (4 else {(5 5)} 4)1) '(;?\\s*if\\s*\\(\\s*([\\w]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))', 'i'); var splitted=s.parenSplit(conditionalSplitRegex); // $1: whole conditional // $2: test condition // $3: true expansion // $4: else clause (possibly empty) // $5: false expansion (possibly null) var numParens=5; var ret = splitted[0]; for (var i=1; i<splitted.length; i=i+numParens+1) { var testString=splitted[i+2-1]; var trueString=splitted[i+3-1]; var falseString=splitted[i+5-1]; if (typeof falseString=='undefined' || !falseString) { falseString=''; } var testResult=null; switch (testString) { case 'user': testResult=(article.userName())?true:false; break; case 'talk': testResult=(article.talkPage())?false:true; // talkPage converts _articles_ to talkPages break; case 'admin': testResult=getValueOf('popupAdminLinks')?true:false; break; case 'oldid': testResult=(typeof oldid != 'undefined' && oldid)?true:false; break; case 'rcid': testResult=(typeof rcid != 'undefined' && rcid)?true:false; break; case 'ipuser': testResult=(article.isIpUser())?true:false; break; case 'mainspace_en': testResult=isInMainNamespace(article) && pg.wiki.hostname=='en.wikipedia.org'; break; case 'wikimedia': testResult=(pg.wiki.wikimedia) ? true : false; break; case 'diff': testResult=(typeof diff != 'undefined' && diff)?true:false; break; } switch(testResult) { case null: ret+=splitted[i]; break; case true: ret+=trueString; break; case false: ret+=falseString; break; } // append non-conditional string ret += splitted[i+numParens]; } if (conditionalSplitRegex.test(ret) && recursionCount < 10) { return expandConditionalNavlinkString(ret,article,z,recursionCount+1); } return ret; } function navlinkStringToArray(s, article, params) { s=expandConditionalNavlinkString(s,article,params); var splitted=s.parenSplit(RegExp('<<(.*?)>>')); var ret=[]; for (var i=0; i<splitted.length; ++i) { if (i%2) { // i odd, so s is a tag var t=new navlinkTag(); var ss=splitted[i].split('|'); t.id=ss[0]; for (var j=1; j<ss.length; ++j) { var sss=ss[j].split('='); if (sss.length>1) { t[sss[0]]=sss[1]; } else { // no assignment (no "="), so treat this as a title (overwriting the last one) t.text=popupString(sss[0]); } } t.article=article; var oldid=params.oldid, rcid=params.rcid, diff=params.diff; if (typeof oldid !== 'undefined' && oldid !== null) { t.oldid=oldid; } if (typeof rcid !== 'undefined' && rcid !== null) { t.rcid=rcid; } if (typeof diff !== 'undefined' && diff !== null) { t.diff=diff; } if (!t.text && t.id !== 'mainlink') { t.text=popupString(t.id); } ret.push(t); } else { // plain HTML ret.push(splitted[i]); } } return ret; } function navlinkSubstituteHTML(s) { return s.split('*').join(getValueOf('popupNavLinkSeparator')) .split('<menurow>').join('<li class="popup_menu_row">') .split('</menurow>').join('</li>') .split('<menu>').join('<ul class="popup_menu">') .split('</menu>').join('</ul>'); } function navlinkDepth(magic,s) { return s.split('<' + magic + '>').length - s.split('</' + magic + '>').length; } // navlinkString: * becomes the separator // <<foo|bar=baz|fubar>> becomes a foo-link with attribute bar='baz' // and visible text 'fubar' // if(test){...} and if(test){...}else{...} work too (nested ok) function navlinkStringToHTML(s,article,params) { //limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article)); var p=navlinkStringToArray(s,article,params); var html=''; var menudepth = 0; // nested menus not currently allowed, but doesn't do any harm to code for it var menurowdepth = 0; for (var i=0; i<p.length; ++i) { if (typeof p[i] == typeof '') { html+=navlinkSubstituteHTML(p[i]); menudepth += navlinkDepth('menu', p[i]); menurowdepth += navlinkDepth('menurow', p[i]); // if (menudepth === 0) { // tagType='span'; // } else if (menurowdepth === 0) { // tagType='li'; // } else { // tagType = null; // } } else if (typeof p[i].type != 'undefined' && p[i].type=='navlinkTag') { if (menudepth > 0 && menurowdepth === 0) { html += '<li class="popup_menu_item">' + p[i].html() + '</li>'; } else { html+=p[i].html(); } } } return html; } function navlinkTag() { this.type='navlinkTag'; } navlinkTag.prototype.html=function () { this.getNewWin(); this.getPrintFunction(); var html=''; var opening, closing; var tagType='span'; if (!tagType) { opening = ''; closing = ''; } else { opening = '<' + tagType + ' class="popup_' + this.id + '">'; closing = '</' + tagType + '>'; } if (typeof this.print!='function') { errlog ('Oh dear - invalid print function for a navlinkTag, id='+this.id); } else { html=this.print(this); if (typeof html != typeof '') {html='';} else if (typeof this.shortcut!='undefined') html=addPopupShortcut(html, this.shortcut); } return opening + html + closing; }; navlinkTag.prototype.getNewWin=function() { getValueOf('popupLinksNewWindow'); if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') { this.newWin=null; } this.newWin=pg.option.popupLinksNewWindow[this.id]; }; navlinkTag.prototype.getPrintFunction=function() { //think about this some more // this.id and this.article should already be defined if (typeof this.id!=typeof '' || typeof this.article!=typeof {} ) { return; } this.noPopup=1; switch (this.id) { case 'contribs': case 'history': case 'whatLinksHere': case 'userPage': case 'monobook': case 'userTalk': case 'talk': case 'article': case 'lastEdit': this.noPopup=null; } switch (this.id) { case 'email': case 'contribs': case 'block': case 'unblock': case 'userlog': case 'userSpace': case 'deletedContribs': this.article=this.article.userName(); } switch (this.id) { case 'userTalk': case 'newUserTalk': case 'editUserTalk': case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog': this.article=this.article.userName(true); /* fall through */ case 'pagelog': case 'deletelog': case 'protectlog': delete this.oldid; } if (this.id=='editMonobook' || this.id=='monobook') { this.article.append('/monobook.js'); } if (this.id != 'mainlink') { // FIXME anchor handling should be done differently with Title object this.article=this.article.removeAnchor(); // if (typeof this.text=='undefined') this.text=popupString(this.id); } switch (this.id) { case 'undelete': this.print=specialLink; this.specialpage='Undelete'; this.sep='/'; break; case 'whatLinksHere': this.print=specialLink; this.specialpage='Whatlinkshere'; break; case 'relatedChanges': this.print=specialLink; this.specialpage='Recentchangeslinked'; break; case 'move': this.print=specialLink; this.specialpage='Movepage'; break; case 'contribs': this.print=specialLink; this.specialpage='Contributions'; break; case 'deletedContribs':this.print=specialLink; this.specialpage='Deletedcontributions'; break; case 'email': this.print=specialLink; this.specialpage='EmailUser'; this.sep='/'; break; case 'block': this.print=specialLink; this.specialpage='Blockip'; this.sep='&ip='; break; case 'unblock': this.print=specialLink; this.specialpage='Ipblocklist'; this.sep='&action=unblock&ip='; break; case 'userlog': this.print=specialLink; this.specialpage='Log'; this.sep='&user='; break; case 'blocklog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=block&page='; break; case 'pagelog': this.print=specialLink; this.specialpage='Log'; this.sep='&page='; break; case 'protectlog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=protect&page='; break; case 'deletelog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=delete&page='; break; case 'userSpace': this.print=specialLink; this.specialpage='PrefixIndex'; this.sep='&namespace=2&prefix='; break; case 'search': this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break; case 'thank': this.print=specialLink; this.specialpage='Thanks'; this.sep='/'; this.article.value = this.diff; break; case 'unwatch': case 'watch': this.print=magicWatchLink; this.action=this.id+'&autowatchlist=1&autoimpl=' + popupString('autoedit_version') + '&actoken='+autoClickToken(); break; case 'history': case 'historyfeed': case 'unprotect': case 'protect': this.print=wikiLink; this.action=this.id; break; case 'delete': this.print=wikiLink; this.action='delete'; if (this.article.namespaceId()==pg.nsImageId) { var img=this.article.stripNamespace(); this.action+='&image='+img; } break; case 'markpatrolled': case 'edit': // editOld should keep the oldid, but edit should not. delete this.oldid; /* fall through */ case 'view': case 'purge': case 'render': this.print=wikiLink; this.action=this.id; break; case 'raw': this.print=wikiLink; this.action='raw'; break; case 'new': this.print=wikiLink; this.action='edit&section=new'; break; case 'mainlink': if (typeof this.text=='undefined') { this.text=this.article.toString().entify(); } if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) { // only show the /subpage part of the title text var s=this.text.split('/'); this.text=s[s.length-1]; if (this.text==='' && s.length > 1) { this.text=s[s.length-2]; } } this.print=titledWikiLink; if (typeof this.title==='undefined' && pg.current.link && typeof pg.current.link.href !== 'undefined') { this.title=safeDecodeURI((pg.current.link.originalTitle)?pg.current.link.originalTitle:this.article); if (typeof this.oldid !== 'undefined' && this.oldid) { this.title=tprintf('Revision %s of %s', [this.oldid, this.title]); } } this.action='view'; break; case 'userPage': case 'article': case 'monobook': case 'editMonobook': case 'editArticle': delete this.oldid; //alert(this.id+'\n'+this.article + '\n'+ typeof this.article); this.article=this.article.articleFromTalkOrArticle(); //alert(this.id+'\n'+this.article + '\n'+ typeof this.article); this.print=wikiLink; if (this.id.indexOf('edit')===0) { this.action='edit'; } else { this.action='view';} break; case 'userTalk': case 'talk': this.article=this.article.talkPage(); delete this.oldid; this.print=wikiLink; this.action='view'; break; case 'arin': this.print=arinLink; break; case 'count': this.print=editCounterLink; break; case 'google': this.print=googleLink; break; case 'editors': this.print=editorListLink; break; case 'globalsearch': this.print=globalSearchLink; break; case 'lastEdit': this.print=titledDiffLink; this.title=popupString('Show the last edit'); this.from='prev'; this.to='cur'; break; case 'oldEdit': this.print=titledDiffLink; this.title=popupString('Show the edit made to get revision') + ' ' + this.oldid; this.from='prev'; this.to=this.oldid; break; case 'editOld': this.print=wikiLink; this.action='edit'; break; case 'undo': this.print=wikiLink; this.action='edit&undo='; break; case 'revert': this.print=wikiLink; this.action='revert'; break; case 'nullEdit': this.print=wikiLink; this.action='nullEdit'; break; case 'diffCur': this.print=titledDiffLink; this.title=tprintf('Show changes since revision %s', [this.oldid]); this.from=this.oldid; this.to='cur'; break; case 'editUserTalk': case 'editTalk': delete this.oldid; this.article=this.article.talkPage(); this.action='edit'; this.print=wikiLink; break; case 'newUserTalk': case 'newTalk': this.article=this.article.talkPage(); this.action='edit&section=new'; this.print=wikiLink; break; case 'lastContrib': case 'sinceMe': this.print=magicHistoryLink; break; case 'togglePreviews': this.text=popupString(pg.option.simplePopups ? 'enable previews' : 'disable previews'); /* fall through */ case 'disablePopups': case 'purgePopups': this.print=popupMenuLink; break; default: this.print=function () {return 'Unknown navlink type: '+this.id+'';}; } }; // // end navlinks ////////////////////////////////////////////////// //</NOLITE> // ENDFILE: navlinks.js // STARTFILE: shortcutkeys.js //<NOLITE> function popupHandleKeypress(evt) { var keyCode = window.event ? window.event.keyCode : ( evt.keyCode ? evt.keyCode : evt.which); if (!keyCode || !pg.current.link || !pg.current.link.navpopup) { return; } if (keyCode==27) { // escape killPopup(); return false; // swallow keypress } var letter=String.fromCharCode(keyCode); var links=pg.current.link.navpopup.mainDiv.getElementsByTagName('A'); var startLink=0; var i,j; if (popupHandleKeypress.lastPopupLinkSelected) { for (i=0; i<links.length; ++i) { if (links[i]==popupHandleKeypress.lastPopupLinkSelected) { startLink=i; } } } for (j=0; j<links.length; ++j) { i=(startLink + j + 1) % links.length; if (links[i].getAttribute('popupkey')==letter) { if (evt && evt.preventDefault) evt.preventDefault(); links[i].focus(); popupHandleKeypress.lastPopupLinkSelected=links[i]; return false; // swallow keypress } } // pass keypress on if (document.oldPopupOnkeypress) { return document.oldPopupOnkeypress(evt); } return true; } function addPopupShortcuts() { if (document.onkeypress!=popupHandleKeypress) { document.oldPopupOnkeypress=document.onkeypress; } document.onkeypress=popupHandleKeypress; } function rmPopupShortcuts() { popupHandleKeypress.lastPopupLinkSelected=null; try { if (document.oldPopupOnkeypress && document.oldPopupOnkeypress==popupHandleKeypress) { // panic document.onkeypress=null; //function () {}; return; } document.onkeypress=document.oldPopupOnkeypress; } catch (nasties) { /* IE goes here */ } } function addLinkProperty(html, property) { // take "<a href=...>...</a> and add a property // not sophisticated at all, easily broken var i=html.indexOf('>'); if (i<0) { return html; } return html.substring(0,i) + ' ' + property + html.substring(i); } function addPopupShortcut(html, key) { if (!getValueOf('popupShortcutKeys')) { return html; } var ret= addLinkProperty(html, 'popupkey="'+key+'"'); if (key==' ') { key=popupString('spacebar'); } return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'),'$1$2$3 ['+key+']$4'); } //</NOLITE> // ENDFILE: shortcutkeys.js // STARTFILE: diffpreview.js //<NOLITE> //lets jump through hoops to find the rev ids we need to retrieve function loadDiff(article, oldid, diff, navpop) { navpop.diffData={ oldRev: {}, newRev: {} }; mw.loader.using( 'mediawiki.api' ).then( function() { var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'compare', prop: 'ids|title' }; if(article.title){ params.fromtitle = article.title; } switch (diff) { case 'cur': switch ( oldid ) { case null: case '': case 'prev': // this can only work if we have the title // cur -> prev params.torelative = 'prev'; break; default: params.fromrev = oldid; params.torelative = 'cur'; break; } break; case 'prev': if( oldid ) { params.fromrev = oldid; } else { params.fromtitle; } params.torelative = 'prev'; break; case 'next': params.fromrev = oldid || 0; params.torelative = 'next'; break; default: params.fromrev = oldid || 0; params.torev = diff || 0; break; } api.get( params ).then( function( data ) { navpop.diffData.oldRev.revid = data.compare.fromrevid; navpop.diffData.newRev.revid = data.compare.torevid; addReviewLink(navpop, 'popupMiscTools'); var go = function() { pendingNavpopTask(navpop); var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&'; url += 'revids=' + navpop.diffData.oldRev.revid + '|' + navpop.diffData.newRev.revid; url += '&prop=revisions&rvprop=ids|timestamp|content'; getPageWithCaching(url, doneDiff, navpop); return true; // remove hook once run }; if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); } else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS'); } } ); } ); } // Put a "mark patrolled" link to an element target // TODO: Allow patrol a revision, as well as a diff function addReviewLink (navpop, target) { if (! pg.user.canReview) return; // If 'newRev' is older than 'oldRev' than it could be confusing, so we do not show the review link. if (navpop.diffData.newRev.revid <= navpop.diffData.oldRev.revid) return; var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); var params = { action: 'query', prop: 'info|flagged', revids: navpop.diffData.oldRev.revid, formatversion : 2 }; api.get (params).then(function(data){ var stable_revid = data.query.pages[0].flagged && data.query.pages[0].flagged.stable_revid || 0; // The diff can be reviewed if the old version is the last reviewed version // TODO: Other possible conditions that we may want to implement instead of this one: // * old version is patrolled and the new version is not patrolled // * old version is patrolled and the new version is more recent than the last reviewed version if (stable_revid == navpop.diffData.oldRev.revid) { var a = document.createElement('a'); a.innerHTML = popupString('mark patrolled'); a.title=popupString('markpatrolledHint'); a.onclick = function() { var params = { action: 'review', revid: navpop.diffData.newRev.revid, comment: tprintf('defaultpopupReviewedSummary', [navpop.diffData.oldRev.revid, navpop.diffData.newRev.revid]) }; api.postWithToken('csrf',params).done(function(){ a.style.display = "none"; // TODO: Update current page and other already constructed popups } ).fail(function(){ alert(popupString('Could not marked this edit as patrolled')); }); }; setPopupHTML(a, target, navpop.idNumber,null,true); } }); } function doneDiff(download) { if (!download.owner || !download.owner.diffData) { return; } var navpop=download.owner; completedNavpopTask(navpop); var pages, revisions=[]; try{ // Process the downloads pages = getJsObj(download.data).query.pages; for(var i=0; i < pages.length; i++ ) { revisions = revisions.concat(pages[i].revisions); } for(i=0; i< revisions.length; i++){ if(revisions[i].revid == navpop.diffData.oldRev.revid) { navpop.diffData.oldRev.revision = revisions[i]; } else if (revisions[i].revid == navpop.diffData.newRev.revid) { navpop.diffData.newRev.revision = revisions[i]; } } } catch(someError) { errlog( 'Could not get diff' ); } insertDiff(navpop); } function rmBoringLines(a,b,context) { if (typeof context == 'undefined') { context=2; } // this is fairly slow... i think it's quicker than doing a word-based diff from the off, though var aa=[], aaa=[]; var bb=[], bbb=[]; var i, j; // first, gather all disconnected nodes in a and all crossing nodes in a and b for (i=0; i<a.length; ++i ) { if(!a[i].paired) { aa[i]=1; } else if (countCrossings(b,a,i, true)) { aa[i]=1; bb[ a[i].row ] = 1; } } // pick up remaining disconnected nodes in b for (i=0; i<b.length; ++i ) { if (bb[i]==1) { continue; } if(!b[i].paired) { bb[i]=1; } } // another pass to gather context: we want the neighbours of included nodes which are not yet included // we have to add in partners of these nodes, but we don't want to add context for *those* nodes in the next pass for (i=0; i<b.length; ++i) { if ( bb[i] == 1 ) { for (j=Math.max(0,i-context); j < Math.min(b.length, i+context); ++j) { if ( !bb[j] ) { bb[j] = 1; aa[ b[j].row ] = 0.5; } } } } for (i=0; i<a.length; ++i) { if ( aa[i] == 1 ) { for (j=Math.max(0,i-context); j < Math.min(a.length, i+context); ++j) { if ( !aa[j] ) { aa[j] = 1; bb[ a[j].row ] = 0.5; } } } } for (i=0; i<bb.length; ++i) { if (bb[i] > 0) { // it's a row we need if (b[i].paired) { bbb.push(b[i].text); } // joined; partner should be in aa else { bbb.push(b[i]); } } } for (i=0; i<aa.length; ++i) { if (aa[i] > 0) { // it's a row we need if (a[i].paired) { aaa.push(a[i].text); } // joined; partner should be in aa else { aaa.push(a[i]); } } } return { a: aaa, b: bbb}; } function stripOuterCommonLines(a,b,context) { var i=0; while (i<a.length && i < b.length && a[i]==b[i]) { ++i; } var j=a.length-1; var k=b.length-1; while ( j>=0 && k>=0 && a[j]==b[k] ) { --j; --k; } return { a: a.slice(Math.max(0,i - 1 - context), Math.min(a.length+1, j + context+1)), b: b.slice(Math.max(0,i - 1 - context), Math.min(b.length+1, k + context+1)) }; } function insertDiff(navpop) { // for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff // FIXME: sometimes this gives misleading diffs as distant chunks are squashed together var oldlines = navpop.diffData.oldRev.revision.content.split('\n'); var newlines = navpop.diffData.newRev.revision.content.split('\n'); var inner=stripOuterCommonLines(oldlines,newlines,getValueOf('popupDiffContextLines')); oldlines=inner.a; newlines=inner.b; var truncated=false; getValueOf('popupDiffMaxLines'); if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) { // truncate truncated=true; inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines), newlines.slice(0,pg.option.popupDiffMaxLines), pg.option.popupDiffContextLines); oldlines=inner.a; newlines=inner.b; } var lineDiff=diff(oldlines, newlines); var lines2=rmBoringLines(lineDiff.o, lineDiff.n); var oldlines2=lines2.a; var newlines2=lines2.b; var simpleSplit = !String.prototype.parenSplit.isNative; var html='<hr />'; if (getValueOf('popupDiffDates')) { html += diffDatesTable(navpop); html += '<hr />'; } html += shortenDiffString( diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit), getValueOf('popupDiffContextCharacters') ).join('<hr />'); setPopupTipsAndHTML(html.split('\n').join('<br>') + (truncated ? '<hr /><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') , 'popupPreview', navpop.idNumber); } function diffDatesTable( navpop ) { var html='<table class="popup_diff_dates">'; html += diffDatesTableRow( navpop.diffData.newRev.revision, tprintf('New revision')); html += diffDatesTableRow( navpop.diffData.oldRev.revision, tprintf('Old revision')); html += '</table>'; return html; } function diffDatesTableRow( revision, label ) { var txt=''; var lastModifiedDate = new Date(revision.timestamp); var datePrint=getValueOf('popupDiffDatePrinter'); if (typeof lastModifiedDate[datePrint] == 'function') { var d2 = adjustDate(lastModifiedDate, getTimeOffset()); txt = dayFormat(d2, true) + ' ' + timeFormat(d2, true); } else { txt = tprintf('Invalid %s %s', ['popupDiffDatePrinter', datePrint]); } var revlink = generalLink({url: mw.config.get('wgScript') + '?oldid='+revision.revid, text: label, title: label}); return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [ revlink, txt ]); } //</NOLITE> // ENDFILE: diffpreview.js // STARTFILE: links.js //<NOLITE> ///////////////////// // LINK GENERATION // ///////////////////// // titledDiffLink --> titledWikiLink --> generalLink // wikiLink --> titledWikiLink --> generalLink // editCounterLink --> generalLink // TODO Make these functions return Element objects, not just raw HTML strings. function titledDiffLink(l) { // article, text, title, from, to) { return titledWikiLink({article: l.article, action: l.to + '&oldid=' + l.from, newWin: l.newWin, noPopup: l.noPopup, text: l.text, title: l.title, /* hack: no oldid here */ actionName: 'diff'}); } function wikiLink(l) { //{article:article, action:action, text:text, oldid, newid}) { if (! (typeof l.article == typeof {} && typeof l.action == typeof '' && typeof l.text==typeof '')) return null; if (typeof l.oldid == 'undefined') { l.oldid=null; } var savedOldid = l.oldid; if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) { l.oldid=null; } var hint=popupString(l.action + 'Hint'); // revertHint etc etc etc var oldidData=[l.oldid, safeDecodeURI(l.article)]; var revisionString = tprintf('revision %s of %s', oldidData); log('revisionString='+revisionString); switch (l.action) { case 'edit&section=new': hint = popupString('newSectionHint'); break; case 'edit&undo=': if (l.diff && l.diff != 'prev' && savedOldid ) { l.action += l.diff + '&undoafter=' + savedOldid; } else if (savedOldid) { l.action += savedOldid; } hint = popupString('undoHint'); break; case 'raw&ctype=text/css': hint=popupString('rawHint'); break; case 'revert': var p=parseParams(pg.current.link.href); l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=' + revertSummary(l.oldid, p.diff); if (p.diff=='prev') { l.action += '&direction=prev'; revisionString = tprintf('the revision prior to revision %s of %s', oldidData); } if (getValueOf('popupRevertSummaryPrompt')) { l.action += '&autosummaryprompt=true'; } if (getValueOf('popupMinorReverts')) { l.action += '&autominor=true'; } log('revisionString is now '+revisionString); break; case 'nullEdit': l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=null'; break; case 'historyfeed': l.action='history&feed=rss'; break; case 'markpatrolled': l.action='markpatrolled&rcid='+l.rcid; } if (hint) { if (l.oldid) { hint = simplePrintf(hint, [revisionString]); } else { hint = simplePrintf(hint, [safeDecodeURI(l.article)]); } } else { hint = safeDecodeURI(l.article + '&action=' + l.action) + (l.oldid) ? '&oldid='+l.oldid : ''; } return titledWikiLink({article: l.article, action: l.action, text: l.text, newWin:l.newWin, title: hint, oldid: l.oldid, noPopup: l.noPopup, onclick: l.onclick}); } function revertSummary(oldid, diff) { var ret=''; if (diff == 'prev') { ret=getValueOf('popupQueriedRevertToPreviousSummary'); } else { ret = getValueOf('popupQueriedRevertSummary'); } return ret + '&autorv=' + oldid; } function titledWikiLink(l) { // possible properties of argument: // article, action, text, title, oldid, actionName, className, noPopup // oldid = null is fine here // article and action are mandatory args if (typeof l.article == 'undefined' || typeof l.action=='undefined') { errlog('got undefined article or action in titledWikiLink'); return null; } var base = pg.wiki.titlebase + l.article.urlString(); var url=base; if (typeof l.actionName=='undefined' || !l.actionName) { l.actionName='action'; } // no need to add &action=view, and this confuses anchors if (l.action != 'view') { url = base + '&' + l.actionName + '=' + l.action; } if (typeof l.oldid!='undefined' && l.oldid) { url+='&oldid='+l.oldid; } var cssClass=pg.misc.defaultNavlinkClassname; if (typeof l.className!='undefined' && l.className) { cssClass=l.className; } return generalNavLink({url: url, newWin: l.newWin, title: (typeof l.title != 'undefined') ? l.title : null, text: (typeof l.text!='undefined')?l.text:null, className: cssClass, noPopup:l.noPopup, onclick:l.onclick}); } pg.fn.getLastContrib = function getLastContrib(wikipage, newWin) { getHistoryInfo(wikipage, function(x) { processLastContribInfo(x, {page: wikipage, newWin: newWin}); }); }; function processLastContribInfo(info, stuff) { if(!info.edits || !info.edits.length) { alert('Popups: an odd thing happened. Please retry.'); return; } if(!info.firstNewEditor) { alert(tprintf('Only found one editor: %s made %s edits', [info.edits[0].editor,info.edits.length])); return; } var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+info.firstNewEditor.oldid; displayUrl(newUrl, stuff.newWin); } pg.fn.getDiffSinceMyEdit = function getDiffSinceMyEdit(wikipage, newWin) { getHistoryInfo(wikipage, function(x){ processDiffSinceMyEdit(x, {page: wikipage, newWin: newWin}); }); }; function processDiffSinceMyEdit(info, stuff) { if(!info.edits || !info.edits.length) { alert('Popups: something fishy happened. Please try again.'); return; } var friendlyName=stuff.page.split('_').join(' '); if(!info.myLastEdit) { alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s', [info.userName, getValueOf('popupHistoryLimit'), friendlyName])); return; } if(info.myLastEdit.index === 0) { alert(tprintf("%s seems to be the last editor to the page %s", [info.userName, friendlyName])); return; } var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+ info.myLastEdit.oldid; displayUrl(newUrl, stuff.newWin); } function displayUrl(url, newWin){ if(newWin) { window.open(url); } else { document.location=url; } } pg.fn.purgePopups = function purgePopups() { processAllPopups(true); setupCache(); // deletes all cached items (not browser cached, though...) pg.option={}; abortAllDownloads(); }; function processAllPopups(nullify, banish) { for (var i=0; pg.current.links && i<pg.current.links.length; ++i) { if (!pg.current.links[i].navpopup) { continue; } if (nullify || banish) pg.current.links[i].navpopup.banish(); pg.current.links[i].simpleNoMore=false; if (nullify) pg.current.links[i].navpopup=null; } } pg.fn.disablePopups = function disablePopups(){ processAllPopups(false, true); setupTooltips(null, true); }; pg.fn.togglePreviews = function togglePreviews() { processAllPopups(true, true); pg.option.simplePopups=!pg.option.simplePopups; abortAllDownloads(); }; function magicWatchLink(l) { //Yuck!! Would require a thorough redesign to add this as a click event though ... l.onclick = simplePrintf( 'pg.fn.modifyWatchlist(\'%s\',\'%s\');return false;', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), this.id] ); return wikiLink(l); } pg.fn.modifyWatchlist = function modifyWatchlist(title, action) { var reqData = { 'action': 'watch', 'formatversion': 2, 'titles': title, 'uselang': mw.config.get('wgUserLanguage') }; if ( action === 'unwatch' ) reqData.unwatch = true; var api = new mw.Api( { ajax: { headers: { 'Api-User-Agent': pg.misc.userAgent } } } ); // Load the Addedwatchtext or Removedwatchtext message and show it var mwTitle = mw.Title.newFromText( title ); var messageName; if ( mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1 ) { messageName = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk'; } else { messageName = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext'; } $.when( api.postWithToken( 'watch', reqData ), mw.loader.using( [ 'mediawiki.api', 'mediawiki.jqueryMsg' ] ).then( function () { return api.loadMessagesIfMissing( [ messageName ] ); } ) ).done( function () { mw.notify( mw.message( messageName, title ).parseDom() ); } ); }; function magicHistoryLink(l) { // FIXME use onclick change href trick to sort this out instead of window.open var jsUrl='', title='', onClick=''; switch(l.id) { case 'lastContrib': onClick=simplePrintf('pg.fn.getLastContrib(\'%s\',%s)', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]); title=popupString('lastContribHint'); break; case 'sinceMe': onClick=simplePrintf('pg.fn.getDiffSinceMyEdit(\'%s\',%s)', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]); title=popupString('sinceMeHint'); break; } jsUrl = 'javascript:' + onClick; // jshint ignore:line onClick += ';return false;'; return generalNavLink({url: jsUrl, newWin: false, // can't have new windows with JS links, I think title: title, text: l.text, noPopup: l.noPopup, onclick: onClick }); } function popupMenuLink(l) { var jsUrl=simplePrintf('javascript:pg.fn.%s()', [l.id]); // jshint ignore:line var title=popupString(simplePrintf('%sHint', [l.id])); var onClick=simplePrintf('pg.fn.%s();return false;', [l.id]); return generalNavLink({url: jsUrl, newWin:false, title:title, text:l.text, noPopup:l.noPopup, onclick: onClick}); } function specialLink(l) { // properties: article, specialpage, text, sep if (typeof l.specialpage=='undefined'||!l.specialpage) return null; var base = pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId]+':'+l.specialpage; if (typeof l.sep == 'undefined' || l.sep === null) l.sep='&target='; var article=l.article.urlString({keepSpaces: l.specialpage=='Search'}); var hint=popupString(l.specialpage+'Hint'); switch (l.specialpage) { case 'Log': switch (l.sep) { case '&user=': hint=popupString('userLogHint'); break; case '&type=block&page=': hint=popupString('blockLogHint'); break; case '&page=': hint=popupString('pageLogHint'); break; case '&type=protect&page=': hint=popupString('protectLogHint'); break; case '&type=delete&page=': hint=popupString('deleteLogHint'); break; default: log('Unknown log type, sep=' + l.sep); hint='Missing hint (FIXME)'; } break; case 'PrefixIndex': article += '/'; break; } if (hint) hint = simplePrintf(hint, [safeDecodeURI(l.article)]); else hint = safeDecodeURI(l.specialpage+':'+l.article) ; var url = base + l.sep + article; return generalNavLink({url: url, title: hint, text: l.text, newWin:l.newWin, noPopup:l.noPopup}); } function generalLink(l) { // l.url, l.text, l.title, l.newWin, l.className, l.noPopup, l.onclick if (typeof l.url=='undefined') return null; // only quotation marks in the url can screw us up now... I think var url=l.url.split('"').join('%22'); var ret='<a href="' + url + '"'; if (typeof l.title!='undefined' && l.title) { ret += ' title="' + pg.escapeQuotesHTML(l.title) + '"'; } if (typeof l.onclick!='undefined' && l.onclick) { ret += ' onclick="' + pg.escapeQuotesHTML(l.onclick) + '"'; } if (l.noPopup) { ret += ' noPopup=1'; } var newWin; if (typeof l.newWin=='undefined' || l.newWin === null) { newWin=getValueOf('popupNewWindows'); } else { newWin=l.newWin; } if (newWin) { ret += ' target="_blank"'; } if (typeof l.className!='undefined'&&l.className) { ret+=' class="'+l.className+'"'; } ret += '>'; if (typeof l.text==typeof '') { ret+= l.text; } ret +='</a>'; return ret; } function appendParamsToLink(linkstr, params) { var sp=linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i')); if (sp.length<2) return null; var ret=sp.shift() + sp.shift(); ret += '&' + params + '"'; ret += sp.join(''); return ret; } function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor, title (optional), alsoChangeLabel { if (x.newTarget) { log ('changeLinkTargetLink: newTarget=' + x.newTarget); } if (x.oldTarget !== decodeURIComponent( x.oldTarget ) ) { log ('This might be an input problem: ' + x.oldTarget ); } // FIXME: first character of page title as well as namespace should be case insensitive // eg [[category:X1]] and [[Category:X1]] are equivalent // this'll break if charAt(0) is nasty var cA = mw.util.escapeRegExp(x.oldTarget); var chs = cA.charAt(0).toUpperCase(); chs='['+chs + chs.toLowerCase()+']'; var currentArticleRegexBit=chs+cA.substring(1); currentArticleRegexBit=currentArticleRegexBit .split(RegExp('(?:[_ ]+|%20)', 'g')).join('(?:[_ ]+|%20)') .split('\\(').join('(?:%28|\\()') .split('\\)').join('(?:%29|\\))'); // why does this need to match encoded strings ? links in the document ? // leading and trailing space should be ignored, and anchor bits optional: currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + '(?:#[^\\[\\|]*)?)\\s*'; // e.g. Computer (archaic) -> \s*([Cc]omputer[_ ](?:%2528|\()archaic(?:%2528|\)))\s* // autoedit=s~\[\[([Cc]ad)\]\]~[[Computer-aided%20design|$1]]~g;s~\[\[([Cc]AD)[|]~[[Computer-aided%20design|~g var title=x.title || mw.config.get('wgPageName').split('_').join(' '); var lk=titledWikiLink({article: new Title(title), newWin:x.newWin, action: 'edit', text: x.text, title: x.hint, className: 'popup_change_title_link' }); var cmd=''; if (x.newTarget) { // escape '&' and other nasties var t = x.newTarget; var s = mw.util.escapeRegExp(x.newTarget); if (x.alsoChangeLabel) { cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + ']]~g;'; cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;'; cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g'; } else { cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + '|$1]]~g;'; cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;'; cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g'; } } else { cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~$1~g;'; cmd += 's~\\[\\['+currentArticleRegexBit+'[|](.*?)\\]\\]~$2~g'; } // Build query cmd = 'autoedit=' + encodeURIComponent ( cmd ); cmd += '&autoclick='+ encodeURIComponent( x.clickButton ) + '&actoken=' + encodeURIComponent( autoClickToken() ); cmd += ( x.minor === null ) ? '' : '&autominor='+ encodeURIComponent( x.minor ); cmd += ( x.watch === null ) ? '' : '&autowatch='+ encodeURIComponent( x.watch ); cmd += '&autosummary='+encodeURIComponent(x.summary); cmd += '&autoimpl='+encodeURIComponent( popupString('autoedit_version') ); return appendParamsToLink(lk, cmd); } function redirLink(redirMatch, article) { // NB redirMatch is in wikiText var ret=''; if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) { ret += '<hr />'; if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) { ret += popupString('Redirects to: (Fix '); log('redirLink: newTarget=' + redirMatch); ret += addPopupShortcut(changeLinkTargetLink({ newTarget: redirMatch, text: popupString('target'), hint: popupString('Fix this redirect, changing just the link target'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]), oldTarget: article.toString(), clickButton: getValueOf('popupRedirAutoClick'), minor: true, watch: getValueOf('popupWatchRedirredPages') }), 'R'); ret += popupString(' or '); ret += addPopupShortcut(changeLinkTargetLink({ newTarget: redirMatch, text: popupString('target & label'), hint: popupString('Fix this redirect, changing the link target and label'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]), oldTarget: article.toString(), clickButton: getValueOf('popupRedirAutoClick'), minor: true, watch: getValueOf('popupWatchRedirredPages'), alsoChangeLabel: true }), 'R'); ret += popupString(')'); } else ret += popupString('Redirects') + popupString(' to '); return ret; } else return '<br> ' + popupString('Redirects') + popupString(' to ') + titledWikiLink({article: new Title().fromWikiText(redirMatch), action: 'view', /* FIXME: newWin */ text: safeDecodeURI(redirMatch), title: popupString('Bypass redirect')}); } function arinLink(l) { if (!saneLinkCheck(l)) { return null; } if ( ! l.article.isIpUser() || ! pg.wiki.wikimedia) return null; var uN=l.article.userName(); return generalNavLink({url:'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + encodeURIComponent(uN), newWin:l.newWin, title: tprintf('Look up %s in ARIN whois database', [uN]), text: l.text, noPopup:1}); } function toolDbName(cookieStyle) { var ret = mw.config.get('wgDBname'); if (!cookieStyle) { ret+= '_p'; } return ret; } function saneLinkCheck(l) { if (typeof l.article != typeof {} || typeof l.text != typeof '') { return false; } return true; } function editCounterLink(l) { if(!saneLinkCheck(l)) return null; if (! pg.wiki.wikimedia) return null; var uN=l.article.userName(); var tool=getValueOf('popupEditCounterTool'); var url; var defaultToolUrl='//tools.wmflabs.org/supercount/index.php?user=$1&project=$2.$3'; switch(tool) { case 'custom': url=simplePrintf(getValueOf('popupEditCounterUrl'), [ encodeURIComponent(uN), toolDbName() ]); break; case 'soxred': // no longer available case 'kate': // no longer available case 'interiot':// no longer available /* fall through */ case 'supercount': default: var theWiki=pg.wiki.hostname.split('.'); url=simplePrintf(defaultToolUrl, [ encodeURIComponent(uN), theWiki[0], theWiki[1] ]); } return generalNavLink({url:url, title: tprintf('editCounterLinkHint', [uN]), newWin:l.newWin, text: l.text, noPopup:1}); } function globalSearchLink(l) { if(!saneLinkCheck(l)) return null; var base='http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search='; var article=l.article.urlString({keepSpaces:true}); return generalNavLink({url:base + article, newWin:l.newWin, title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]), text: l.text, noPopup:1}); } function googleLink(l) { if(!saneLinkCheck(l)) return null; var base='https://www.google.com/search?q='; var article=l.article.urlString({keepSpaces:true}); return generalNavLink({url:base + '%22' + article + '%22', newWin:l.newWin, title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]), text: l.text, noPopup:1}); } function editorListLink(l) { if(!saneLinkCheck(l)) return null; var article= l.article.articleFromTalkPage() || l.article; var url='https://xtools.wmflabs.org/articleinfo/' + encodeURI( pg.wiki.hostname ) + '/' + article.urlString() + '?uselang=' + mw.config.get('wgUserLanguage'); return generalNavLink({url:url, title: tprintf('editorListHint', [article]), newWin:l.newWin, text: l.text, noPopup:1}); } function generalNavLink(l) { l.className = (l.className === null) ? 'popupNavLink' : l.className; return generalLink(l); } ////////////////////////////////////////////////// // magic history links // function getHistoryInfo(wikipage, whatNext) { log('getHistoryInfo'); getHistory(wikipage, whatNext ? function(d){whatNext(processHistory(d));} : processHistory); } // FIXME eliminate pg.idNumber ... how? :-( function getHistory(wikipage, onComplete) { log('getHistory'); var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&prop=revisions&titles=' + new Title(wikipage).urlString() + '&rvlimit=' + getValueOf('popupHistoryLimit'); log('getHistory: url='+url); return startDownload(url, pg.idNumber+'history', onComplete); } function processHistory(download) { var jsobj = getJsObj(download.data); try { var revisions = anyChild(jsobj.query.pages).revisions; var edits=[]; for (var i=0; i<revisions.length; ++i) { edits.push({ oldid: revisions[i].revid, editor: revisions[i].user }); } log('processed ' + edits.length + ' edits'); return finishProcessHistory( edits, mw.config.get('wgUserName') ); } catch (someError) { log('Something went wrong with JSON business'); return finishProcessHistory([]); } } function finishProcessHistory(edits, userName) { var histInfo={}; histInfo.edits=edits; histInfo.userName=userName; for (var i=0; i<edits.length; ++i) { if (typeof histInfo.myLastEdit === 'undefined' && userName && edits[i].editor==userName) { histInfo.myLastEdit={index: i, oldid: edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)}; } if (typeof histInfo.firstNewEditor === 'undefined' && edits[i].editor != edits[0].editor) { histInfo.firstNewEditor={index:i, oldid:edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)}; } } //pg.misc.historyInfo=histInfo; return histInfo; } //</NOLITE> // ENDFILE: links.js // STARTFILE: options.js ////////////////////////////////////////////////// // options // check for existing value, else use default function defaultize(x) { if (pg.option[x]===null || typeof pg.option[x]=='undefined') { if (typeof window[x] != 'undefined' ) pg.option[x]=window[x]; else pg.option[x]=pg.optionDefault[x]; } } function newOption(x, def) { pg.optionDefault[x]=def; } function setDefault(x, def) { return newOption(x, def); } function getValueOf(varName) { defaultize(varName); return pg.option[varName]; } /*eslint-disable */ function useDefaultOptions() { // for testing for (var p in pg.optionDefault) { pg.option[p]=pg.optionDefault[p]; if (typeof window[p]!='undefined') { delete window[p]; } } } /*eslint-enable */ function setOptions() { // user-settable parameters and defaults var userIsSysop = false; if ( mw.config.get('wgUserGroups') ) { for ( var g = 0; g < mw.config.get('wgUserGroups').length; ++g ) { if ( mw.config.get('wgUserGroups')[g] == "sysop" ) userIsSysop = true; } } // Basic options newOption('popupDelay', 0.5); newOption('popupHideDelay', 0.5); newOption('simplePopups', false); newOption('popupStructure', 'shortmenus'); // see later - default for popupStructure is 'original' if simplePopups is true newOption('popupActionsMenu', true); newOption('popupSetupMenu', true); newOption('popupAdminLinks', userIsSysop); newOption('popupShortcutKeys', false); newOption('popupHistoricalLinks', true); newOption('popupOnlyArticleLinks', true); newOption('removeTitles', true); newOption('popupMaxWidth', 350); newOption('popupSimplifyMainLink', true); newOption('popupAppendRedirNavLinks', true); newOption('popupTocLinks', false); newOption('popupSubpopups', true); newOption('popupDragHandle', false /* 'popupTopLinks'*/); newOption('popupLazyPreviews', true); newOption('popupLazyDownloads', true); newOption('popupAllDabsStubs', false); newOption('popupDebugging', false); newOption('popupActiveNavlinks', true); newOption('popupModifier', false); // ctrl, shift, alt or meta newOption('popupModifierAction', 'enable'); // or 'disable' newOption('popupDraggable', true); newOption('popupReview', false); //<NOLITE> // images newOption('popupImages', true); newOption('imagePopupsForImages', true); newOption('popupNeverGetThumbs', false); //newOption('popupImagesToggleSize', true); newOption('popupThumbAction', 'imagepage'); //'sizetoggle'); newOption('popupImageSize', 60); newOption('popupImageSizeLarge', 200); // redirs, dabs, reversion newOption('popupFixRedirs', false); newOption('popupRedirAutoClick', 'wpDiff'); newOption('popupFixDabs', false); newOption('popupDabsAutoClick', 'wpDiff'); newOption('popupRevertSummaryPrompt', false); newOption('popupMinorReverts', false); newOption('popupRedlinkRemoval', false); newOption('popupRedlinkAutoClick', 'wpDiff'); newOption('popupWatchDisambiggedPages', null); newOption('popupWatchRedirredPages', null); newOption('popupDabWiktionary', 'last'); // navlinks newOption('popupNavLinks', true); newOption('popupNavLinkSeparator', ' &sdot; '); newOption('popupLastEditLink', true); newOption('popupEditCounterTool', 'supercount'); newOption('popupEditCounterUrl', ''); //</NOLITE> // previews etc newOption('popupPreviews', true); newOption('popupSummaryData', true); newOption('popupMaxPreviewSentences', 9); newOption('popupMaxPreviewCharacters', 900); newOption('popupLastModified', true); newOption('popupPreviewKillTemplates', true); newOption('popupPreviewRawTemplates', true); newOption('popupPreviewFirstParOnly', false); newOption('popupPreviewCutHeadings', false); newOption('popupPreviewButton', false); newOption('popupPreviewButtonEvent', 'click'); //<NOLITE> // diffs newOption('popupPreviewDiffs', true); newOption('popupDiffMaxLines', 100); newOption('popupDiffContextLines', 2); newOption('popupDiffContextCharacters', 40); newOption('popupDiffDates', true); newOption('popupDiffDatePrinter', 'toLocaleString'); // edit summaries. God, these are ugly. newOption('popupReviewedSummary', popupString('defaultpopupReviewedSummary') ); newOption('popupFixDabsSummary', popupString('defaultpopupFixDabsSummary') ); newOption('popupExtendedRevertSummary', popupString('defaultpopupExtendedRevertSummary') ); newOption('popupRevertSummary', popupString('defaultpopupRevertSummary') ); newOption('popupRevertToPreviousSummary', popupString('defaultpopupRevertToPreviousSummary') ); newOption('popupQueriedRevertSummary', popupString('defaultpopupQueriedRevertSummary') ); newOption('popupQueriedRevertToPreviousSummary', popupString('defaultpopupQueriedRevertToPreviousSummary') ); newOption('popupFixRedirsSummary', popupString('defaultpopupFixRedirsSummary') ); newOption('popupRedlinkSummary', popupString('defaultpopupRedlinkSummary') ); newOption('popupRmDabLinkSummary', popupString('defaultpopupRmDabLinkSummary') ); //</NOLITE> // misc newOption('popupHistoryLimit', 50); //<NOLITE> newOption('popupFilters', [popupFilterStubDetect, popupFilterDisambigDetect, popupFilterPageSize, popupFilterCountLinks, popupFilterCountImages, popupFilterCountCategories, popupFilterLastModified]); newOption('extraPopupFilters', []); newOption('popupOnEditSelection', 'cursor'); newOption('popupPreviewHistory', true); newOption('popupImageLinks', true); newOption('popupCategoryMembers', true); newOption('popupUserInfo', true); newOption('popupHistoryPreviewLimit', 25); newOption('popupContribsPreviewLimit',25); newOption('popupRevDelUrl', '//en.wikipedia.org/wiki/Wikipedia:Revision_deletion'); newOption('popupShowGender', true); //</NOLITE> // new windows newOption('popupNewWindows', false); newOption('popupLinksNewWindow', {'lastContrib': true, 'sinceMe': true}); // regexps newOption('popupDabRegexp', '(\\{\\{\\s*disambig(?!uation needed)|disambig(uation|)\\s*\\}\\}|disamb\\s*\\}\\}|dab\\s*\\}\\})|\\{\\{\\s*(((geo|hn|road?|school|number)dis)|[234][lc][acw]|(road|ship)index)(\\s*[|][^}]*)?\\s*[}][}]|is a .*disambiguation.*page'); newOption('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub'); newOption('popupImageVarsRegexp', 'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo'); } // ENDFILE: options.js // STARTFILE: strings.js //<NOLITE> ////////////////////////////////////////////////// // Translatable strings ////////////////////////////////////////////////// // // See instructions at // https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation pg.string = { ///////////////////////////////////// // summary data, searching etc. ///////////////////////////////////// 'article': 'bài viết', 'category': 'thể loại', 'categories': 'thể loại', 'image': 'image', 'images': 'tập tin', 'stub': 'stub', 'section stub': 'section stub', 'Empty page': 'Trang trống', 'kB': 'kB', 'bytes': 'byte', 'day': 'ngày', 'days': 'ngày', 'hour': 'giờ', 'hours': 'giờ', 'minute': 'minute', 'minutes': 'minutes', 'second': 'second', 'seconds': 'seconds', 'week': 'tuần', 'weeks': 'weeks', 'search': 'search', 'SearchHint': 'Find English Wikipedia articles containing %s', 'web': 'web', 'global': 'global', 'globalSearchHint': 'Search across Wikipedias in different languages for %s', 'googleSearchHint': 'Google for %s', ///////////////////////////////////// // article-related actions and info // (some actions also apply to user pages) ///////////////////////////////////// 'tác vụ': 'tác vụ', ///// view articles and view talk 'popupsMenu': 'popups', 'togglePreviewsHint': 'Toggle preview generation in popups on this page', 'enable previews': 'enable previews', 'disable previews': 'disable previews', 'toggle previews': 'toggle previews', 'show preview': 'show preview', 'reset': 'tái tạo', 'more...': 'more...', 'disable': 'tắt popup', 'disablePopupsHint': 'Disable popups on this page. Reload page to re-enable.', 'historyfeedHint': 'RSS feed of recent changes to this page', 'purgePopupsHint': 'Reset popups, clearing all cached popup data.', 'PopupsHint': 'Reset popups, clearing all cached popup data.', 'spacebar': 'space', 'view': 'view', 'view article': 'xem bài viết', 'viewHint': 'Go to %s', 'talk': 'talk', 'talk page': 'trang thảo luận', 'this&nbsp;revision': 'this&nbsp;revision', 'revision %s of %s': 'revision %s of %s', 'Revision %s of %s': 'Revision %s of %s', 'the revision prior to revision %s of %s': 'the revision prior to revision %s of %s', 'Toggle image size': 'Click to toggle image size', 'del': 'del', ///// delete, protect, move 'delete': 'xóa', 'deleteHint': 'xóa %s', 'undeleteShort': 'un', 'UndeleteHint': 'Show the deletion history for %s', 'protect': 'protect', 'protectHint': 'Restrict editing rights to %s', 'unprotectShort': 'un', 'unprotectHint': 'Allow %s to be edited by anyone again', 'send thanks': 'send thanks', 'ThanksHint': 'Send a thank you notification to this user', 'move': 'di chuyển', 'move page': 'di chuyển trang', 'MovepageHint': 'Change the title of %s', 'sửa': 'sửa', ///// edit articles and talk 'edit article': 'sửa đổi', 'editHint': 'Change the content of %s', 'edit talk': 'edit talk', 'new': 'mới', 'new topic': 'chủ đề mới', 'newSectionHint': 'Start a new section on %s', 'null edit': 'null edit', 'nullEditHint': 'Submit an edit to %s, making no changes ', 'hist': 'hist', ///// history, diffs, editors, related 'history': 'lịch sử', 'historyHint': 'List the changes made to %s', 'last': 'prev', // For labelling the previous revision in history pages; the key is "last" for backwards compatibility 'lastEdit': 'lastEdit', 'mark patrolled': 'đánh dấu tuần tra', 'markpatrolledHint': 'Mark this edit as patrolled', 'Could not marked this edit as patrolled': 'Could not marked this edit as patrolled', 'show last edit': 'lần sửa đổi gần nhất', 'Show the last edit': 'Show the effects of the most recent change', 'lastContrib': 'lastContrib', 'last set of edits': 'latest edits', 'lastContribHint': 'Show the net effect of changes made by the last editor', 'cur': 'cur', 'diffCur': 'diffCur', 'Show changes since revision %s': 'Show changes since revision %s', '%s old': '%s old', // as in 4 weeks old 'oldEdit': 'oldEdit', 'purge': 'purge', 'purgeHint': 'Demand a fresh copy of %s', 'raw': 'source', 'rawHint': 'Download the source of %s', 'render': 'simple', 'renderHint': 'Show a plain HTML version of %s', 'Show the edit made to get revision': 'Show the edit made to get revision', 'sinceMe': 'sinceMe', 'changes since mine': 'diff my edit', 'sinceMeHint': 'Show changes since my last edit', 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s': 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s', 'eds': 'eds', 'editors': 'editors', 'editorListHint': 'List the users who have edited %s', 'related': 'related', 'relatedChanges': 'relatedChanges', 'related changes': 'related changes', 'RecentchangeslinkedHint': 'Show changes in articles related to %s', 'editOld': 'editOld', ///// edit old version, or revert 'rv': 'rv', 'revert': 'revert', 'revertHint': 'Revert to %s', 'defaultpopupReviewedSummary': 'Accepted by reviewing the [[Special:diff/%s/%s|difference]] between this version and previously accepted version using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRedlinkSummary': 'Removing link to empty page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupFixDabsSummary': 'Disambiguate [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupFixRedirsSummary': 'Redirect bypass from [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupExtendedRevertSummary': 'Revert to revision dated %s by %s, oldid %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRevertToPreviousSummary': 'Revert to the revision prior to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRevertSummary': 'Revert to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupQueriedRevertToPreviousSummary': 'Revert to the revision prior to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupQueriedRevertSummary': 'Revert to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'defaultpopupRmDabLinkSummary': 'Remove link to dab page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]', 'Redirects': 'Redirects', // as in Redirects to ... ' to ': ' to ', // as in Redirects to ... 'Bypass redirect': 'Bypass redirect', 'Fix this redirect': 'Fix this redirect', 'disambig': 'disambig', ///// add or remove dab etc. 'disambigHint': 'Disambiguate this link to [[%s]]', 'Click to disambiguate this link to:': 'Click to disambiguate this link to:', 'remove this link': 'remove this link', 'remove all links to this page from this article': 'remove all links to this page from this article', 'remove all links to this disambig page from this article': 'remove all links to this disambig page from this article', 'mainlink': 'mainlink', ///// links, watch, unwatch 'wikiLink': 'wikiLink', 'wikiLinks': 'liên kết wiki', 'links here': 'links here', 'whatLinksHere': 'whatLinksHere', 'what links here': 'what links here', 'WhatlinkshereHint': 'List the pages that are hyperlinked to %s', 'unwatchShort': 'un', 'watchThingy': 'xem', // called watchThingy because {}.watch is a function 'watchHint': 'Thêm %s vào danh sách theo dõi', 'unwatchHint': 'Remove %s from my watchlist', 'Only found one editor: %s made %s edits': 'Only found one editor: %s made %s edits', '%s seems to be the last editor to the page %s': '%s seems to be the last editor to the page %s', 'rss': 'rss', ///////////////////////////////////// // diff previews ///////////////////////////////////// 'Diff truncated for performance reasons': 'Diff truncated for performance reasons', 'Old revision': 'Old revision', 'New revision': 'New revision', 'Something went wrong :-(': 'Something went wrong :-(', 'Empty revision, maybe non-existent': 'Empty revision, maybe non-existent', 'Unknown date': 'Unknown date', ///////////////////////////////////// // other special previews ///////////////////////////////////// 'Empty category': 'Empty category', 'Category members (%s shown)': 'Category members (%s shown)', 'No image links found': 'No image links found', 'File links': 'File links', 'No image found': 'No image found', 'Image from Commons': 'Image from Commons', 'Description page': 'Description page', 'Alt text:': 'Alt text:', 'revdel':'Hidden revision', ///////////////////////////////////// // user-related actions and info ///////////////////////////////////// 'user': 'user', ///// user page, talk, email, space 'user&nbsp;page': 'user&nbsp;page', 'user talk': 'user talk', 'edit user talk': 'edit user talk', 'leave comment': 'leave comment', 'email': 'email', 'email user': 'email cho thành viên', 'EmailuserHint': 'Send an email to %s', 'space': 'space', // short form for userSpace link 'PrefixIndexHint': 'Show pages in the userspace of %s', 'count': 'count', ///// contributions, log 'edit counter': 'đếm sửa đổi', 'editCounterLinkHint': 'Count the contributions made by %s', 'contribs': 'đóng góp', 'contributions': 'đóng góp', 'deletedContribs': 'đóng góp bị xóa', 'DeletedcontributionsHint': 'List deleted edits made by %s', 'ContributionsHint': 'List the contributions made by %s', 'log': 'nhật trình', 'user log': 'user log', 'userLogHint': 'Show %s\'s user log', 'arin': 'ARIN lookup', ///// ARIN lookup, block user or IP 'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database', 'unblockShort': 'un', 'block': 'cấm', 'block user': 'cấm thành viên', 'IpblocklistHint': 'Unblock %s', 'BlockipHint': 'Prevent %s from editing', 'block log': 'nhật trình cấm', 'blockLogHint': 'Show the block log for %s', 'protectLogHint': 'Show the protection log for %s', 'pageLogHint': 'Show the page log for %s', 'deleteLogHint': 'Show the deletion log for %s', 'Invalid %s %s': 'The option %s is invalid: %s', 'No backlinks found': 'No backlinks found', ' and more': ' and more', 'undo': 'undo', 'undoHint': 'undo this edit', 'Download preview data': 'Download preview data', 'Invalid or IP user': 'Invalid or IP user', 'Not a registered username': 'Not a registered username', 'CẤM': 'CẤM', 'Has blocks': 'Cấm một phần', ' sửa đổi từ: ': ' sửa đổi từ: ', 'sửa đổi lần cuối vào ': 'sửa đổi lần cuối vào ', ///////////////////////////////////// // Autoediting ///////////////////////////////////// 'Enter a non-empty edit summary or press cancel to abort': 'Enter a non-empty edit summary or press cancel to abort', 'Failed to get revision information, please edit manually.\n\n': 'Failed to get revision information, please edit manually.\n\n', 'The %s button has been automatically clicked. Please wait for the next page to load.': 'The %s button has been automatically clicked. Please wait for the next page to load.', 'Could not find button %s. Please check the settings in your javascript file.': 'Could not find button %s. Please check the settings in your javascript file.', ///////////////////////////////////// // Popups setup ///////////////////////////////////// 'Open full-size image': 'Open full-size image', 'zxy': 'zxy', 'autoedit_version': 'np20140416' }; function popupString(str) { if (typeof popupStrings != 'undefined' && popupStrings && popupStrings[str]) { return popupStrings[str]; } if (pg.string[str]) { return pg.string[str]; } return str; } function tprintf(str,subs) { if (typeof subs != typeof []) { subs = [subs]; } return simplePrintf(popupString(str), subs); } //</NOLITE> // ENDFILE: strings.js // STARTFILE: run.js //////////////////////////////////////////////////////////////////// // Run things //////////////////////////////////////////////////////////////////// // For some reason popups requires a fully loaded page jQuery.ready(...) causes problems for some. // The old addOnloadHook did something similar to the below if (document.readyState=="complete") autoEdit(); //will setup popups else $( window ).on( 'load', autoEdit ); // Support for MediaWiki's live preview, VisualEditor's saves and Echo's flyout. ( function () { var once = true; function dynamicContentHandler( $content ) { // Try to detect the hook fired on initial page load and disregard // it, we already hook to onload (possibly to different parts of // page - it's configurable) and running twice might be bad. Ugly… if ( $content.attr( 'id' ) == 'mw-content-text' ) { if ( once ) { once = false; return; } } function registerHooksForVisibleNavpops () { for (var i=0; pg.current.links && i<pg.current.links.length; ++i) { var navpop = pg.current.links[i].navpopup; if (!navpop || !navpop.isVisible()) { continue; } Navpopup.tracker.addHook(posCheckerHook(navpop)); } } function doIt () { registerHooksForVisibleNavpops(); $content.each( function () { this.ranSetupTooltipsAlready = false; setupTooltips( this ); } ); } setupPopups( doIt ); } // This hook is also fired after page load. mw.hook( 'wikipage.content' ).add( dynamicContentHandler ); mw.hook( 'ext.echo.overlay.beforeShowingOverlay' ).add( function($overlay){ dynamicContentHandler( $overlay.find(".mw-echo-state") ); }); } )(); // Forked from User:Connel MacKenzie/mess-with-popups.js Previewmaker.prototype.firstBit = function() { try{ if (mw.config.get("wgNamespaceNumber") !== 0){ // dont't be givin' me no subsequent paragraphs, you hear me? /// first we "normalize" section headings, removing whitespace after, adding before var d = this.data; if (getValueOf('popupPreviewCutHeadings')) { this.data = this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 '); /// then we want to get rid of paragraph breaks whose text ends badly this.data = this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n'); this.data = this.data.replace(RegExp('^[\\s\\n]*'), ''); stuff = (RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data); if (stuff) { d = stuff[0]; } if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; } /// now put \n\n after sections so that bullets and numbered lists work d = d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n'); } // superfluous sentences are RIGHT OUT. // note: exactly 1 set of parens here needed to make the slice work d = d.parenSplit(RegExp('([!?.]+["' + "'" + ']*\\s)', 'g')); // leading space is bad, mmkay? d[0] = d[0].replace(RegExp('^\\s*'), ''); var notSentenceEnds = RegExp('([^.][a-z][.][a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|\\[[^\\]]*|\\s[A-Zvclm])$', 'i'); d = this.fixSentenceEnds(d, notSentenceEnds); var maxChars=getValueOf('popupMaxPreviewCharacters'); var n = getValueOf('popupMaxPreviewSentences'); var dd; do { dd = this.firstSentences(d, n); --n; } while (dd.length > maxChars && n > 0); this.data = dd; } else { //TODO: Limit this override to urls that start with http://*.wiktionary.org/ //TODO: extend the above section to also preview Wikipedia links var charsWikt = 0; var linesWikt = this.data.split('\n'); for (jWikt = 0; jWikt < linesWikt.length; jWikt++) { linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{[in]transitive}}/g, ''); linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{[un]countable}}/g, ''); linesWikt[jWikt] = linesWikt[jWikt].replace(/\{\{cattag\|(.*?)}}/g, ""); linesWikt[jWikt] = linesWikt[jWikt].replace(/[\{\}]/g, "'"); if (linesWikt[jWikt].search(/^[#]/) == -1) { if (linesWikt[jWikt].search(/^==[A-Z]/) == -1) { linesWikt[jWikt] = ''; } } charsWikt += linesWikt[jWikt].length; } if (charsWikt < 2) { linesWikt[0] = "No properly formatted content"; } var maxChars = getValueOf('popupMaxPreviewCharacters'); var n = getValueOf('popupMaxPreviewSentences'); var dd = ''; for (jWikt = 0; jWikt < linesWikt.length; jWikt++) { if (linesWikt[jWikt] !== '') { dd += linesWikt[jWikt] + '\n'; } if (dd.length < maxChars) { this.data = dd; } } } } catch (e) { console.log("error from mess with popups: ", e); } }; }); tgw02rj17vat4hppgvmjmwl3h8srxpi ᠡᠷᠬᠡᠦ 0 412106 2513937 2026-08-14T04:33:59Z WhoAlone 40420 Trang mới: “{{also|ᡝᡵᡴᡝᠣ}} =={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{lb|cmg|~ {{m|cmg|ᠭᠣᠣᠯ|tr=-}}}} {{place|cmg|sông|rep/Buryatia|và|obl/Irkutsk|c/Nga}} # {{lb|cmg|~ {{m|cmg|ᠬᠣᠲᠠ|tr=-}}}} {{place|cmg|thành phố|obl/Irkutsk|c/Nga|t=Irkutsk}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ riêng}} ===={{ĐM|alter form}}==== * {{alt|cmg|ᠡᠷᠬᠦᠦ}} ===={{ĐM|desc}}==== * {{desc|mn|Эрхүү}} * {{desc|mnc|ᡝᡵ…” 2513937 wikitext text/x-wiki {{also|ᡝᡵᡴᡝᠣ}} =={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{lb|cmg|~ {{m|cmg|ᠭᠣᠣᠯ|tr=-}}}} {{place|cmg|sông|rep/Buryatia|và|obl/Irkutsk|c/Nga}} # {{lb|cmg|~ {{m|cmg|ᠬᠣᠲᠠ|tr=-}}}} {{place|cmg|thành phố|obl/Irkutsk|c/Nga|t=Irkutsk}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ riêng}} ===={{ĐM|alter form}}==== * {{alt|cmg|ᠡᠷᠬᠦᠦ}} ===={{ĐM|desc}}==== * {{desc|mn|Эрхүү}} * {{desc|mnc|ᡝᡵᡴᡝᠣ|bor=1}} ** {{desc|cmn|厄爾口|bor=1}} p5id838ur8f8lobrjmxi7eatneyaee9 ᠡᠷᠬᠦᠦ 0 412107 2513939 2026-08-14T04:36:39Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{alt form of|cmg|ᠡᠷᠬᠡᠦ}}” 2513939 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{alt form of|cmg|ᠡᠷᠬᠡᠦ}} ek17wdog86jc3bqaz698zrw917dmb23 ᠢᠯᠡ 0 412108 2513940 2026-08-14T04:39:48Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== So sánh {{ncog|och|伊列|tr=*qlil red}}, {{ncog|ltc|伊麗|tr='jij lje}}, {{ncog|xqa|الا}}, {{ncog|chg|ایله|tr=ilä}}, {{ncog|kk|ىلە}} / {{mention|kk|Іле}}, v.v. {{rfe|cmg}} ==={{ĐM|pron}}=== * {{IPA4|cmg|/i.le/}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{place|cmg|sông|autonomous region/Tân Cương|c/Trung Quốc|và|c/Kazakhstan|t=Ili}} #* {{quote-book|cmg|year=19th century|author=J̌imbadorǰi|…” 2513940 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== So sánh {{ncog|och|伊列|tr=*qlil red}}, {{ncog|ltc|伊麗|tr='jij lje}}, {{ncog|xqa|الا}}, {{ncog|chg|ایله|tr=ilä}}, {{ncog|kk|ىلە}} / {{mention|kk|Іле}}, v.v. {{rfe|cmg}} ==={{ĐM|pron}}=== * {{IPA4|cmg|/i.le/}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{place|cmg|sông|autonomous region/Tân Cương|c/Trung Quốc|và|c/Kazakhstan|t=Ili}} #* {{quote-book|cmg|year=19th century|author=J̌imbadorǰi|title=[[ᠪᠣᠯᠤᠷ ᠲᠣᠯᠢ]]|trans-title=the Crystal Mirror||text=[[ᠣᠶᠢᠷᠣᠳ]] [[ᠵᠡᠭᠦᠨ ᠭᠠᠷᠤᠨ]] [[ᠨᠤᠲᠤᠭ ᠤᠨ]] [[ᠵᠡᠭᠦᠨ]] [[ᠥᠵᠢᠭᠦᠷ ᠦᠨ]] [[ᠬᠢᠲᠠᠳ ᠤᠨ]] [[ᠵᠠᠶᠠᠭᡡᠨ]] [[ᠬᠡᠮᠡᠬᠦ]] [[ᠬᠡᠷᠡᠮ ᠡᠴᠡ]] [[ᠪᠠᠷᠠᠭᠤᠨ]] [[ᠪᠢᠶ᠎ᠡ ᠦ]] '''ᠢᠯᠡ'''᠂ [[ᠡᠷᠴᠢᠰ]] [[ᠬᠣᠶᠠᠷ ᠤᠨ]] [[ᠤᠰᠤᠨ ᠤ]] [[ᠤᠷᠤᠰᠬ᠌ᠠᠯ]] [[ᠳᠠᠭᠠᠵᠤ]] [[ᠪᠠᠶᠢᠳᠠᠭ]] [[ᠮᠣᠩᠭᠣᠯ]] [[ᠲᠡᠭᠦᠨ ᠢ]] ︽[[ᠵᠡᠭᠦᠨ ᠭᠠᠷ]]︾ [[ᠬᠡᠮᠡᠮᠦᠢ]]᠃}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ riêng}} ==={{ĐM|alter form}}=== * {{alter|cmg|ᠢᠯᠢ}} 5kwx7tj06rpex3ysf0qbk5twawp6rll ᠢᠯᠢ 0 412109 2513941 2026-08-14T04:41:11Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{alt form of|cmg|ᠢᠯᠡ}}” 2513941 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{alt form of|cmg|ᠢᠯᠡ}} rfuj6kpqitoidvkt9vn6ffu49fdy0is Thành viên:Hiyuune/Gadget-PagePreviews.js 2 412110 2513943 2026-08-14T04:41:35Z Hiyuune 50834 Hiyuune đã đổi [[Thành viên:Hiyuune/Gadget-PagePreviews.js]] thành [[Thành viên:Hiyuune/Gadget-popups.js]] 2513943 javascript text/javascript /* #REDIRECT */mw.loader.load("//vi.wiktionary.org/w/index.php?title=Th%C3%A0nh_vi%C3%AAn:Hiyuune/Gadget-popups.js&action=raw&ctype=text/javascript"); kfky0qxwt7r06x5ykcmqmmxq1rwdogf ᠣᠪᠯᠠᠰᠲ᠋ᠢ 0 412111 2513944 2026-08-14T04:42:43Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|ru|о́бласть}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{lb|cmg|historical}} [[oblast]] {{gloss|tỉnh có đồn trú}} #: {{uxi|bua|[[ᠪᠠᠶ᠋ᠢᠭᠠᠯ]] [[ᠤ᠋ᠨ]] [[ᠡᠮᠦᠨᠡᠬᠢ]] '''ᠣᠪᠯᠠᠰᠲ᠋ᠢ'''|Zabaikalskaya '''Oblast''' (của Đế quốc Nga)}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ}} ===={{ĐM|syn}}==== * {{l|cmg|ᠮᠤᠵ…” 2513944 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|ru|о́бласть}}. ==={{ĐM|pron}}=== {{rfp|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{lb|cmg|historical}} [[oblast]] {{gloss|tỉnh có đồn trú}} #: {{uxi|bua|[[ᠪᠠᠶ᠋ᠢᠭᠠᠯ]] [[ᠤ᠋ᠨ]] [[ᠡᠮᠦᠨᠡᠬᠢ]] '''ᠣᠪᠯᠠᠰᠲ᠋ᠢ'''|Zabaikalskaya '''Oblast''' (của Đế quốc Nga)}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ}} ===={{ĐM|syn}}==== * {{l|cmg|ᠮᠤᠵᠢ}} 4ksylhzrud5ukl0oe0qsp61vojsyymw ᠣᠲ᠋ᠣᠭ 0 412112 2513947 2026-08-14T04:45:47Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alternative spelling of|cmg|ᠣᠲᠣᠭ}} ==={{ĐM|ref}}=== * {{cite-web|year=2016–now|work={{lang|ru|Электронная база данных архивных документов на монгольской письменности из фондов Государственного архива Республики Бурятия}}|location=Ulan-Ude|url=https://gbu-garb.ru/nashi-proektyi/elektr…” 2513947 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alternative spelling of|cmg|ᠣᠲᠣᠭ}} ==={{ĐM|ref}}=== * {{cite-web|year=2016–now|work={{lang|ru|Электронная база данных архивных документов на монгольской письменности из фондов Государственного архива Республики Бурятия}}|location=Ulan-Ude|url=https://gbu-garb.ru/nashi-proektyi/elektronnaya-baza-dannyix-arxivnyix-dokumentov-na-mongolskoj-pismennosti.html}} izet5gsblajwn0gtej80aufmh0worcg ᠨ᠋ᠣᠶ᠋ᠠᠨ 0 412113 2513950 2026-08-14T04:47:22Z WhoAlone 40420 Trang mới: “{{also|ᠨᠣᠶ᠋ᠠᠨ|ᠨᠣᠶᠠᠨ}} =={{langname|cmg}}== ==={{ĐM|etym}}=== {{inh+|cmg|xng|ᠨ᠋ᠣᠶ᠋ᠠᠨ|t=lãnh chúa}}, có lẽ từ {{der|cmg|cmn-ear|老爺|t=chủ nhân}}. ==={{ĐM|pron}}=== * {{IPA4|cmg|/nɔ.jän/}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alternative spelling of|cmg|ᠨᠣᠶ᠋ᠠᠨ}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ}} ===={{ĐM|desc}}==== * {{desc|mn|ᠨᠣᠶᠠᠨ}} *: {{desc|mn|ноён|sclang=1}}” 2513950 wikitext text/x-wiki {{also|ᠨᠣᠶ᠋ᠠᠨ|ᠨᠣᠶᠠᠨ}} =={{langname|cmg}}== ==={{ĐM|etym}}=== {{inh+|cmg|xng|ᠨ᠋ᠣᠶ᠋ᠠᠨ|t=lãnh chúa}}, có lẽ từ {{der|cmg|cmn-ear|老爺|t=chủ nhân}}. ==={{ĐM|pron}}=== * {{IPA4|cmg|/nɔ.jän/}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alternative spelling of|cmg|ᠨᠣᠶ᠋ᠠᠨ}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ}} ===={{ĐM|desc}}==== * {{desc|mn|ᠨᠣᠶᠠᠨ}} *: {{desc|mn|ноён|sclang=1}} srtzns3lqrquxmad676rb4swqa999ge ᠨᠠᠷᠠᠨ 0 412114 2513953 2026-08-14T04:50:56Z WhoAlone 40420 Trang mới: “=={{langname|mn}}== {{mn-variant|ᠨᠠᠷᠠᠨ|наран}} ==={{ĐM|pron}}=== {{rfp|mn}} ==={{ĐM|noun}}=== {{mn-noun-form}} # {{infl of|mn|ᠨᠠᠷᠠ|t=mặt trời||obl}}” 2513953 wikitext text/x-wiki =={{langname|mn}}== {{mn-variant|ᠨᠠᠷᠠᠨ|наран}} ==={{ĐM|pron}}=== {{rfp|mn}} ==={{ĐM|noun}}=== {{mn-noun-form}} # {{infl of|mn|ᠨᠠᠷᠠ|t=mặt trời||obl}} 1wtc4urdaktt9vn21hsogy6qfyu5i97 2513954 2513953 2026-08-14T04:54:40Z WhoAlone 40420 2513954 wikitext text/x-wiki =={{langname|mn}}== {{mn-variant|ᠨᠠᠷᠠᠨ|наран}} ==={{ĐM|pron}}=== {{rfp|mn}} ==={{ĐM|noun}}=== {{mn-noun-form}} # {{infl of|mn|ᠨᠠᠷᠠ|t=mặt trời||obl}} =={{langname|xng}}== {{xng-alt|ᠨᠠᠷᠠᠨ|[[ꡋ ꡘꡋ|ꡋ ꡘꡋ]]|納<small>舌</small>闌}} ==={{ĐM|alter form}}=== * {{alt|xng|ᠨ᠋ᠠᠷᠠᠨ}} * {{alt|xng|ناران||Muqaddimat al-Adab}} ==={{ĐM|etym}}=== {{inh+|xng|xgn-pro|*naran}}. ==={{ĐM|noun}}=== {{head|xng|Danh từ}} {{attn|xng|FVS/VS needed for final n}} # [[Mặt Trời]]. ===={{ĐM|decl}}==== {{rfinfl|xng|danh từ}} ===={{ĐM|desc}}==== * {{desc|xgn-cen}} ** {{desc|cmg|ᠨᠠᠷᠠᠨ}} *** {{desc|ykh|нара<tr:nara>|нару<tr:naru>}} *** {{desc|mn|-}} ***: {{desc|mn|ᠨᠠᠷᠠ|sclang=1}} ***: {{desc|mn|нар|sclang=1}} ** Oirat *** {{desc|bua|наран}} *** {{desc|xwo|ᠨᠠᠷᠠᠨ}} **** {{desc|xal|нарн}} 293bq8nl6k8ppqif5g2so1a7pf8w8z8 2513955 2513954 2026-08-14T04:57:26Z WhoAlone 40420 2513955 wikitext text/x-wiki =={{langname|mn}}== {{mn-variant|ᠨᠠᠷᠠᠨ|наран}} ==={{ĐM|pron}}=== {{rfp|mn}} ==={{ĐM|noun}}=== {{mn-noun-form}} # {{infl of|mn|ᠨᠠᠷᠠ|t=mặt trời||obl}} =={{langname|cmg}}== ==={{ĐM|etym}}=== {{inh+|cmg|xng|ᠨᠠᠷᠠᠨ}}, từ {{inh|cmg|xgn-pro|*naran}}. ==={{ĐM|pron}}=== * {{IPA4|cmg|/nä.rän/}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # [[Mặt Trời]]. ===={{ĐM|decl}}==== {{rfinfl|cmg|noun}} ===={{ĐM|desc}}==== * {{desc|ykh|нара<tr:nara>|наран<tr:naran>|нару<tr:naru>}} * {{desc|mn|ᠨᠠᠷᠠ}} *: {{desc|mn|нар|sclang=1}} =={{langname|xng}}== {{xng-alt|ᠨᠠᠷᠠᠨ|[[ꡋ ꡘꡋ|ꡋ ꡘꡋ]]|納<small>舌</small>闌}} ==={{ĐM|alter form}}=== * {{alt|xng|ᠨ᠋ᠠᠷᠠᠨ}} * {{alt|xng|ناران||Muqaddimat al-Adab}} ==={{ĐM|etym}}=== {{inh+|xng|xgn-pro|*naran}}. ==={{ĐM|noun}}=== {{head|xng|Danh từ}} {{attn|xng|FVS/VS needed for final n}} # [[Mặt Trời]]. ===={{ĐM|decl}}==== {{rfinfl|xng|danh từ}} ===={{ĐM|desc}}==== * {{desc|xgn-cen}} ** {{desc|cmg|ᠨᠠᠷᠠᠨ}} *** {{desc|ykh|нара<tr:nara>|нару<tr:naru>}} *** {{desc|mn|-}} ***: {{desc|mn|ᠨᠠᠷᠠ|sclang=1}} ***: {{desc|mn|нар|sclang=1}} ** Oirat *** {{desc|bua|наран}} *** {{desc|xwo|ᠨᠠᠷᠠᠨ}} **** {{desc|xal|нарн}} 8hrpb7uho94z812zjdu3hjbg55a0jgd Thể loại:tuw-kkl:Lớp Lưỡng cư 14 412115 2513957 2026-08-14T05:03:22Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513957 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Phân ngành Có xương sống 14 412116 2513958 2026-08-14T05:03:46Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513958 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Danh sách chủ đề thuộc nhóm tập hợp 14 412117 2513959 2026-08-14T05:03:48Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513959 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Tất cả chủ đề 14 412118 2513960 2026-08-14T05:03:57Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513960 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Ngành Dây sống 14 412119 2513961 2026-08-14T05:03:59Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513961 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Động vật 14 412120 2513962 2026-08-14T05:04:06Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513962 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Sinh vật 14 412121 2513963 2026-08-14T05:04:15Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513963 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Sự sống 14 412122 2513964 2026-08-14T05:04:22Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513964 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Danh sách chủ đề thuộc nhóm liên quan 14 412123 2513965 2026-08-14T05:04:31Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513965 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Tự nhiên 14 412124 2513966 2026-08-14T05:04:32Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513966 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Hôn nhân 14 412125 2513968 2026-08-14T05:07:37Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513968 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Mọi người 14 412126 2513969 2026-08-14T05:07:39Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513969 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Con người 14 412127 2513970 2026-08-14T05:08:26Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513970 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Văn hóa 14 412128 2513971 2026-08-14T05:08:28Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513971 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Gia đình 14 412129 2513972 2026-08-14T05:08:29Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513972 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Xã hội 14 412130 2513973 2026-08-14T05:08:38Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513973 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Họ Chó 14 412131 2513976 2026-08-14T05:09:57Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513976 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Bộ Ăn thịt 14 412132 2513977 2026-08-14T05:10:03Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513977 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Thể loại:tuw-kkl:Lớp Thú 14 412133 2513978 2026-08-14T05:10:11Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2513978 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx 父子有親 0 412134 2513985 2026-08-14T08:25:44Z Hiyuune 50834 Trang mới: “=={{langname|ko}}== {{ko-hanjatab}} ==={{section|n}}=== {{ko-noun|hangeul=부자유친}} # {{hanja form of|부자유친}}” 2513985 wikitext text/x-wiki =={{langname|ko}}== {{ko-hanjatab}} ==={{section|n}}=== {{ko-noun|hangeul=부자유친}} # {{hanja form of|부자유친}} c8zhugsg9tlv1d4wi67k1kbn8xnkmks ᠨᠢᠲ᠋ᠤᠭ 0 412135 2513988 2026-08-14T08:31:00Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alternative spelling of|cmg|ᠨᠢᠲᠤᠭ}} ==={{ĐM|ref}}=== * {{cite-web|year=2016–now|work={{lang|ru|Электронная база данных архивных документов на монгольской письменности из фондов Государственного архива Республики Бурятия}}|location=Ulan-Ude|url=https://gbu-garb.ru/nashi-proektyi/ele…” 2513988 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alternative spelling of|cmg|ᠨᠢᠲᠤᠭ}} ==={{ĐM|ref}}=== * {{cite-web|year=2016–now|work={{lang|ru|Электронная база данных архивных документов на монгольской письменности из фондов Государственного архива Республики Бурятия}}|location=Ulan-Ude|url=https://gbu-garb.ru/nashi-proektyi/elektronnaya-baza-dannyix-arxivnyix-dokumentov-na-mongolskoj-pismennosti.html}} akcz2ank07kdke5w1vn2wfop4b6iwgc ᠪᠯᠠᠮ ᠠ 0 412136 2513989 2026-08-14T08:39:05Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|xct|བླ་མ}}. ==={{ĐM|noun}}=== {{head|cmg|Danh từ|head=ᠪᠯᠠᠮ᠎ᠠ}} # {{lb|cmg|religion}} {{alt form of|cmg|ᠯᠠᠮ᠎ᠠ|t=lạt ma}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ}}” 2513989 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|xct|བླ་མ}}. ==={{ĐM|noun}}=== {{head|cmg|Danh từ|head=ᠪᠯᠠᠮ᠎ᠠ}} # {{lb|cmg|religion}} {{alt form of|cmg|ᠯᠠᠮ᠎ᠠ|t=lạt ma}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ}} 82oggguugs9hclq90v4tz9t1x8n574r ᠯᠠᠮ ᠠ 0 412137 2513990 2026-08-14T08:41:19Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # [[lạt ma]].” 2513990 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # [[lạt ma]]. 20yojdkd39fps2r7kyjitv8wpgq3hmf ᠬ᠋ᠠᠲ᠋ᠠᠭᠢᠨ 0 412138 2513991 2026-08-14T08:43:54Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alternative spelling of|cmg|ᠬᠠᠲᠠᠭᠢᠨ}} ==={{ĐM|ref}}=== * {{cite-web|year=2016–now|work={{lang|ru|Электронная база данных архивных документов на монгольской письменности из фондов Государственного архива Республики Бурятия}}|location=Ulan-Ude|url=https://gbu-garb.ru/nashi-proekt…” 2513991 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alternative spelling of|cmg|ᠬᠠᠲᠠᠭᠢᠨ}} ==={{ĐM|ref}}=== * {{cite-web|year=2016–now|work={{lang|ru|Электронная база данных архивных документов на монгольской письменности из фондов Государственного архива Республики Бурятия}}|location=Ulan-Ude|url=https://gbu-garb.ru/nashi-proektyi/elektronnaya-baza-dannyix-arxivnyix-dokumentov-na-mongolskoj-pismennosti.html}} 25enpwjze790inq471iew9w4z6ng18e ᠬᠠᠭ᠋ᠠᠨ 0 412139 2513992 2026-08-14T08:47:27Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alt form of|cmg|ᠬᠠᠭᠠᠨ|t=hãn|nocat=1}} #: {{ux|cmg|[[ᠬᠠᠯᠬ᠎ᠠ ᠶᠢᠨ]] [[ᠸᠠᠴᠢᠷᠠᠢ]] [[ᠪᠠᠲᠤ]] [[ᠲᠦᠰᠢᠶ᠎ᠡ ᠲ᠋ᠦ]] '''ᠬᠠᠭᠠᠨ''' ᠤ [[ᠲᠠᠮᠠᠭ᠋᠎ᠠ]]᠉|tr=qalq-a-yin wačirai batu tüsiy-ä-tü '''qaɣan'''-u tamaɣ-a.}}” 2513992 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alt form of|cmg|ᠬᠠᠭᠠᠨ|t=hãn|nocat=1}} #: {{ux|cmg|[[ᠬᠠᠯᠬ᠎ᠠ ᠶᠢᠨ]] [[ᠸᠠᠴᠢᠷᠠᠢ]] [[ᠪᠠᠲᠤ]] [[ᠲᠦᠰᠢᠶ᠎ᠡ ᠲ᠋ᠦ]] '''ᠬᠠᠭᠠᠨ''' ᠤ [[ᠲᠠᠮᠠᠭ᠋᠎ᠠ]]᠉|tr=qalq-a-yin wačirai batu tüsiy-ä-tü '''qaɣan'''-u tamaɣ-a.}} a22b5xbzh3eeoop63me68d0hqt2ck2o ᠬᠠᠭ᠍ᠠᠨ 0 412140 2513993 2026-08-14T08:51:38Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alt form of|cmg|ᠬᠠᠭᠠᠨ|t=hãn|nocat=1}}” 2513993 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alt form of|cmg|ᠬᠠᠭᠠᠨ|t=hãn|nocat=1}} c9jdkk2yehfze34to4fb1a8cudqv7md ᠬᠠᠯᠪᠢᠨ 0 412141 2513994 2026-08-14T08:53:59Z WhoAlone 40420 Trang mới: “{{also|ᡥᠠᠯᠪᡳᠨ}} =={{langname|cmg}}== ==={{ĐM|etym}}=== So sánh {{ncog|bua|хальбан}}. {{rfe|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # Khalban {{gloss|một gia tộc thuộc nhóm Khori của người Buryat}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ}}” 2513994 wikitext text/x-wiki {{also|ᡥᠠᠯᠪᡳᠨ}} =={{langname|cmg}}== ==={{ĐM|etym}}=== So sánh {{ncog|bua|хальбан}}. {{rfe|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # Khalban {{gloss|một gia tộc thuộc nhóm Khori của người Buryat}} ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ}} 51bzj16ud8se65n9nd4pd2miiebq7ch ᠬᠢᠲᠠᠳ 0 412142 2513995 2026-08-14T09:00:01Z WhoAlone 40420 Trang mới: “=={{langname|mn}}== ==={{ĐM|etym}}=== {{inh+|mn|cmg|ᠬᠢᠲᠠᠳ|t=Trung Quốc}}, từ {{inh|mn|xng|乞塔惕|乞塔<sup>惕</sup>|t=[[Khiết Đan]]}}. So sánh {{w|đại tự Khiết Đan}} {{mention|zkt|}}, {{w|tiểu tự Khiết Đan}} {{mention|zkt|^𘱿𘲫}}; {{ncog|otk|𐰶𐰃𐱃𐰪|tr=Qïtań}}, và {{ncog|oui|𐽲𐽶𐾀𐽰𐽶|ts=qïtay}}, {{mention|oui|𐽲𐾀𐽰𐽶|ts=qïtay}}, {{m|oui|𐽲𐽶𐾀𐽶|ts=qïtay}}, cùng với {{ncog|ru|Ки…” 2513995 wikitext text/x-wiki =={{langname|mn}}== ==={{ĐM|etym}}=== {{inh+|mn|cmg|ᠬᠢᠲᠠᠳ|t=Trung Quốc}}, từ {{inh|mn|xng|乞塔惕|乞塔<sup>惕</sup>|t=[[Khiết Đan]]}}. So sánh {{w|đại tự Khiết Đan}} {{mention|zkt|}}, {{w|tiểu tự Khiết Đan}} {{mention|zkt|^𘱿𘲫}}; {{ncog|otk|𐰶𐰃𐱃𐰪|tr=Qïtań}}, và {{ncog|oui|𐽲𐽶𐾀𐽰𐽶|ts=qïtay}}, {{mention|oui|𐽲𐾀𐽰𐽶|ts=qïtay}}, {{m|oui|𐽲𐽶𐾀𐽶|ts=qïtay}}, cùng với {{ncog|ru|Кита́й|t=Trung Quốc}} và {{ncog|en|Cathay}}. ==={{ĐM|pron}}=== {{mn-IPA-see|Хятад}} ==={{ĐM|pr-noun}}=== {{mn-variant|ᠬᠢᠲᠠᠳ|Хятад}} {{mn-proper noun|Хятад}} # {{lb|mn|Mongolia}} {{place|mn|khu vực/và/quốc gia|r/Đông Á|t1=Trung Quốc}}, [[Cathay]] #: {{syn|mn|ᠳᠤᠮᠳᠠᠳᠤ ᠤᠯᠤᠰ|q1=Trung Quốc}} # {{lb|mn|historical}} {{given name|mn|nam}} ==={{ĐM|noun}}=== {{mn-variant|ᠬᠢᠲᠠᠳ|хятад}} {{mn-noun|хятад}} # {{w|Người Hán}}. #: {{uxi|mn|'''ᠬᠢᠲᠠᠳ''' [[ᠬᠡᠯᠡ]]|t=tiếng Trung Quốc}} #: {{uxi|mn|'''ᠬᠢᠲᠠᠳ''' [[ᠬᠦᠮᠦᠨ]]|t=người Trung Quốc}} #: {{uxi|mn|'''ᠬᠢᠲᠠᠳ''' [[ᠴᠠᠭᠠᠰᠤ]]|t=giấy Trung Hoa}} #: {{uxi|mn|'''ᠬᠢᠲᠠᠳ''' [[ᠬᠠᠨᠵᠢ]]|t=chữ Hán}} ===={{ĐM|decl}}==== {{Mong-decl|ᠬᠢᠲᠠᠳ|a|d|pl=[[ᠬᠢᠲᠠᠳ]] [[ᠤ᠋ᠳ]]|d}} kv4wcetx13bw8d0e3oeezwuvvb4gsxk 2513996 2513995 2026-08-14T09:05:32Z WhoAlone 40420 2513996 wikitext text/x-wiki =={{langname|mn}}== ==={{ĐM|etym}}=== {{inh+|mn|cmg|ᠬᠢᠲᠠᠳ|t=Trung Quốc}}, từ {{inh|mn|xng|乞塔惕|乞塔<sup>惕</sup>|t=[[Khiết Đan]]}}. So sánh {{w|đại tự Khiết Đan}} {{mention|zkt|}}, {{w|tiểu tự Khiết Đan}} {{mention|zkt|^𘱿𘲫}}; {{ncog|otk|𐰶𐰃𐱃𐰪|tr=Qïtań}}, và {{ncog|oui|𐽲𐽶𐾀𐽰𐽶|ts=qïtay}}, {{mention|oui|𐽲𐾀𐽰𐽶|ts=qïtay}}, {{m|oui|𐽲𐽶𐾀𐽶|ts=qïtay}}, cùng với {{ncog|ru|Кита́й|t=Trung Quốc}} và {{ncog|en|Cathay}}. ==={{ĐM|pron}}=== {{mn-IPA|Хятад}} ==={{ĐM|pr-noun}}=== {{mn-variant|ᠬᠢᠲᠠᠳ|Хятад}} {{mn-proper noun|Хятад}} # {{lb|mn|Mông Cổ}} {{place|mn|khu vực/và/quốc gia|r/Đông Á|t1=Trung Quốc}}, [[Cathay]] #: {{syn|mn|ᠳᠤᠮᠳᠠᠳᠤ ᠤᠯᠤᠰ|q1=Trung Quốc}} # {{lb|mn|historical}} {{given name|mn|nam}} ==={{ĐM|noun}}=== {{mn-variant|ᠬᠢᠲᠠᠳ|хятад}} {{mn-noun|хятад}} # {{w|Người Hán}}. #: {{uxi|mn|'''ᠬᠢᠲᠠᠳ''' [[ᠬᠡᠯᠡ]]|t=tiếng Trung Quốc}} #: {{uxi|mn|'''ᠬᠢᠲᠠᠳ''' [[ᠬᠦᠮᠦᠨ]]|t=người Trung Quốc}} #: {{uxi|mn|'''ᠬᠢᠲᠠᠳ''' [[ᠴᠠᠭᠠᠰᠤ]]|t=giấy Trung Hoa}} #: {{uxi|mn|'''ᠬᠢᠲᠠᠳ''' [[ᠬᠠᠨᠵᠢ]]|t=chữ Hán}} ===={{ĐM|decl}}==== {{Mong-decl|ᠬᠢᠲᠠᠳ|a|d|pl=[[ᠬᠢᠲᠠᠳ]] [[ᠤ᠋ᠳ]]|d}} =={{langname|cmg}}== ==={{ĐM|alter form}}=== * {{alt|cmg|ᠬᠢᠲ᠋ᠠᠳ}} ==={{ĐM|etym}}=== {{inh+|cmg|xng||[[乞]][[塔]]<sup>[[惕]]</sup>|tr=kitad|t=[[Khiết Đan]]}}. ==={{ĐM|pron}}=== * {{IPA4|cmg|/kʰi.tʰät/}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{w|Người Hán}}. ===={{ĐM|decl}}==== {{rfinfl|cmg|danh từ}} ===={{ĐM|desc}}==== * {{desc|mn|ᠬᠢᠲᠠᠳ}} *: {{desc|mn|хятад|sclang=1}} * {{desc|bua|хитад|bor=1}} * {{desc|sah|кытат|bor=1}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # [[Trung Quốc]], {{w|nhà Minh}} # {{given name|cmg|nam}} ===={{ĐM|decl}}==== {{rfinfl|cmg|proper noun}} ===={{ĐM|desc}}==== * {{desc|mn|ᠬᠢᠲᠠᠳ}} *: {{desc|mn|Хятад|sclang=1}} * {{desc|bua|Хитад|bor=1}} 8s9rfxuub0ifs5hzdx7eddwj3868g3b ᠬᠢᠲ᠋ᠠᠳ 0 412143 2513997 2026-08-14T09:06:29Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alt form of|cmg|ᠬᠢᠲᠠᠳ}}” 2513997 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{alt form of|cmg|ᠬᠢᠲᠠᠳ}} kpm3iq3owtw10vzyuw1imysqc0kvfmm ᠬᠤᠸᠠᠩᠳᠠᠢ 0 412144 2513998 2026-08-14T09:08:24Z WhoAlone 40420 Trang mới: “=={{langname|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{lb|cmg|Buryat}} {{altform|cmg|ᠬᠤᠸᠠᠩᠳᠢ|t=hoàng đế}}” 2513998 wikitext text/x-wiki =={{langname|cmg}} ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{lb|cmg|Buryat}} {{altform|cmg|ᠬᠤᠸᠠᠩᠳᠢ|t=hoàng đế}} 6ok60wuoa157md6eimhlofsy37rmi5l 2513999 2513998 2026-08-14T09:11:34Z WhoAlone 40420 2513999 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{lb|cmg|Buryat}} {{altform|cmg|ᠬᠤᠸᠠᠩᠳᠢ|t=hoàng đế}} mxshbqqwoz8u3cybqfkpuxe9fm2cv3f ᠮᠢᠨ᠌ᠤ 0 412145 2514000 2026-08-14T09:16:38Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|determiner}}=== {{head|cmg|Từ hạn định}} # {{alternative spelling of|cmg|ᠮᠢᠨᠤ|t=của tôi}}” 2514000 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|determiner}}=== {{head|cmg|Từ hạn định}} # {{alternative spelling of|cmg|ᠮᠢᠨᠤ|t=của tôi}} 23lambe3ribw6zx17dcr4a6kin8726c Thể loại:Từ hạn định tiếng Mông Cổ cổ điển 14 412146 2514001 2026-08-14T09:17:38Z WhoAlone 40420 Trang mới: “{{auto cat}}” 2514001 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx ᠮᠤᠬᠤᠷ ᠰᠢᠪᠡᠷ 0 412147 2514002 2026-08-14T09:19:08Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|alter form}}=== * {{alt|cmg|ᠮᠤᠬᠤᠷᠰᠢᠪᠡᠷ}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{place|cmg|sông|rep/Buryatia|c/Nga|t=Mukhorshibirka}} # {{place|cmg|làng|rep/Buryatia|c/Nga|t=Mukhorshibir}} ===={{ĐM|drv}}==== {{col|cmg|ᠮᠤᠬᠤᠷ ᠰᠢᠪᠡᠷ ᠦᠨ ᠠᠢᠮᠠᠭ}} ===={{ĐM|desc}}==== * {{desc|ru|Мухоршиби́рь|bor=1}} ** {{desc|ru|Мухоршиби́рка|der=1}}” 2514002 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|alter form}}=== * {{alt|cmg|ᠮᠤᠬᠤᠷᠰᠢᠪᠡᠷ}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{place|cmg|sông|rep/Buryatia|c/Nga|t=Mukhorshibirka}} # {{place|cmg|làng|rep/Buryatia|c/Nga|t=Mukhorshibir}} ===={{ĐM|drv}}==== {{col|cmg|ᠮᠤᠬᠤᠷ ᠰᠢᠪᠡᠷ ᠦᠨ ᠠᠢᠮᠠᠭ}} ===={{ĐM|desc}}==== * {{desc|ru|Мухоршиби́рь|bor=1}} ** {{desc|ru|Мухоршиби́рка|der=1}} 9x2wdr7yo53jf5hap0yy7ooyqd65bd0 2514003 2514002 2026-08-14T09:20:38Z WhoAlone 40420 2514003 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|alter form}}=== * {{alt|cmg|ᠮᠤᠬᠤᠷᠰᠢᠪᠡᠷ}} ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{place|cmg|sông|rep/Buryatia|c/Nga|t=Mukhorshibirka}} # {{place|cmg|làng|rep/Buryatia|c/Nga|t=Mukhorshibir}} ===={{ĐM|drv}}==== {{col|cmg|ᠮᠤᠬᠤᠷ ᠰᠢᠪᠡᠷ ᠦᠨ ᠠᠢᠮᠠᠭ}} ===={{ĐM|desc}}==== * {{desc|bua|Мухар Шэбэр}} * {{desc|ru|Мухоршиби́рь|bor=1}} ** {{desc|ru|Мухоршиби́рка|der=1}} 9yr2qbib8xxozuhrix038vuhg2ksiyp ᠮᠤᠬᠤᠷᠰᠢᠪᠡᠷ 0 412148 2514004 2026-08-14T09:21:10Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{altform|cmg|ᠮᠤᠬᠤᠷ ᠰᠢᠪᠡᠷ}}” 2514004 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{altform|cmg|ᠮᠤᠬᠤᠷ ᠰᠢᠪᠡᠷ}} st650uxnh2ctgf8bd8bzglhho5jd1p1 ᡥᠣᠪᠣ 0 412149 2514013 2026-08-14T09:47:23Z Kelly zhrm 58416 Trang mới: “=={{langname|mnc}}== ==={{ĐM|pron}}=== {{mnc-IPA}} ==={{ĐM|n}}=== {{head|mnc|Danh từ}} # [[quan tài|Quan tài]], [[hòm]].” 2514013 wikitext text/x-wiki =={{langname|mnc}}== ==={{ĐM|pron}}=== {{mnc-IPA}} ==={{ĐM|n}}=== {{head|mnc|Danh từ}} # [[quan tài|Quan tài]], [[hòm]]. esjxjy8uypa8mcs7z8cjtvpxj3m9yqf ᠮᠻᠠᠨᠪᠤᠸᠠ 0 412150 2514014 2026-08-14T10:50:24Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|xct|མཁན་པོ}}. ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{lb|cmg|Tibetan Buddhism}} {{alt form of|cmg|ᠻᠠᠮᠪᠤ|t=khenpo}}” 2514014 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== {{bor+|cmg|xct|མཁན་པོ}}. ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{lb|cmg|Tibetan Buddhism}} {{alt form of|cmg|ᠻᠠᠮᠪᠤ|t=khenpo}} ju20ufvabzi1pbiywgwnzxvq59k1xwm ᠻᠠᠮᠪᠤ 0 412151 2514015 2026-08-14T10:59:25Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # [[khenpo]].” 2514015 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # [[khenpo]]. 87jz1sifvov58k9lsq37evfnw6clqb9 похвалата 0 412152 2514016 2026-08-14T11:02:24Z Hiyuune 50834 /* */ + bg 2514016 wikitext text/x-wiki =={{langname|bg}}== ==={{section|pron}}=== {{bg-pr|похва́лата}} ==={{section|n}}=== {{head|bg|Biến thể hình thái danh từ|head=похва́лата}} # {{infl of|bg|похва́ла||def|s}} s3ttib6to9jxk8f13o64sfpd2zyyde5 ᠯᠣᠪᠽᠠᠩ 0 412153 2514017 2026-08-14T11:03:59Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{alt form of|cmg|ᠯᠣᠪᠰᠠᠩ}}” 2514017 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{alt form of|cmg|ᠯᠣᠪᠰᠠᠩ}} oxvhz9zl49493y6amaz53roxv6wq2k0 ᠯᠠᠰᠠ 0 412154 2514018 2026-08-14T11:16:39Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{altform|cmg|ᡀᠠᠰᠠ}}” 2514018 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|noun}}=== {{head|cmg|Danh từ}} # {{altform|cmg|ᡀᠠᠰᠠ}} 3i2jupzg09yvkbgv167djw73s7vodbb Mabana 0 412155 2514019 2026-08-14T11:28:12Z Hiyuune 50834 /* */ 2514019 wikitext text/x-wiki =={{langname|en}}== {{wp|en:}} ==={{section|pr-noun}}=== {{en-prop}} # {{place|en|thị trấn|p/Annobón|c/Guinea Xích Đạo}}. psb95d9kytg8pzqb37vo5vn8c1krkxv ᠰᠡᠯᠡᠩᠭᠡ 0 412156 2514020 2026-08-14T11:48:01Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== {{inh+|cmg|xng|薛鿌格|ts=säläŋgä}}. ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{tcl|cmg|Selenga}} #* {{quote-book|cmg|year=1675|title=Thư của Tüsiyetü Khan Čaqundorji gửi Sa hoàng Nga|text=[[ᠲᠡᠳᠡ ᠡᠴᠡ|ᠲᠡᠳᠡᠴᠡ]] [[ᠡᠷᠬᠡᠦ ᠶᠢᠨ ᠪᠠᠢᠰᠢᠩ|ᠡᠷᠬᠡᠦ ᠭᠢ᠊ ᠶᠢᠨ ᠪᠠᠢᠰᠢᠭ᠌]] '''ᠰᠡᠯᠡᠩᠭᠡ ᠶᠢᠨ ᠪᠠᠢᠰᠢ…” 2514020 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== {{inh+|cmg|xng|薛鿌格|ts=säläŋgä}}. ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{tcl|cmg|Selenga}} #* {{quote-book|cmg|year=1675|title=Thư của Tüsiyetü Khan Čaqundorji gửi Sa hoàng Nga|text=[[ᠲᠡᠳᠡ ᠡᠴᠡ|ᠲᠡᠳᠡᠴᠡ]] [[ᠡᠷᠬᠡᠦ ᠶᠢᠨ ᠪᠠᠢᠰᠢᠩ|ᠡᠷᠬᠡᠦ ᠭᠢ᠊ ᠶᠢᠨ ᠪᠠᠢᠰᠢᠭ᠌]] '''[[ᠰᠡᠯᠡᠩᠭᠡ ᠶᠢᠨ ᠪᠠᠢᠰᠢᠩ ᠳ᠋ᠤ]]''' [[ᠨᠢᠭᠡᠨ|ᠨ᠋ᠢᠭᠡᠨ]] [[ᠰᠠᠢᠨ]] [[ᠬᠦᠮᠦᠨ ᠢ]] [[ᠢᠯᠡᠭᠡ]]᠃|t=Từ đó, hãy cử một người giỏi đến '''Irkutsk''' và '''Selenginsk'''.}} #* {{quote-book|cmg|year=1675|title=Thư của Tüsiyetü Khan Čaqundorji gửi Sa hoàng Nga|text=[[ᠬᠠᠷᠢᠶ᠋ᠠᠳ ᠢ]] [[ᠮᠠᠨ ᠢ]] [[ᠡᠰᠡ]] [[ᠥᠭᠭᠦᠭᠡᠳ]]᠃ [[ᠡᠨᠡ|ᠡᠨ᠌ᠡ]] [[ᠱᠣᠭᠯᠠᠭᠰᠠᠨ ᠢ]] [[ᠵᠥᠪ ᠪᠤᠷᠤᠭᠤ ᠶᠢ|ᠵᠥᠪ᠋ ᠪᠤᠷᠤᠭ᠍ᠤ ᠭᠢ᠊]] [[ᠡᠰᠡ]] [[ᠣᠯᠪᠠᠰᠤ]]᠂ '''[[ᠰᠡᠯᠡᠩᠭᠡ ᠶᠢᠨ ᠪᠠᠢᠰᠢᠩ|ᠰᠡᠯᠡᠩᠭᠡ]]''' [[ᠡᠷᠬᠡᠦ ᠶᠢᠨ ᠪᠠᠢᠰᠢᠩ|ᠡᠷᠬᠡᠦ]] [[ᠡᠷᠭᠡᠩ ᠦᠨ ᠪᠠᠢᠰᠢᠩ|ᠡᠷᠭᠡ᠋ᠩ ᠶᠢᠨ ᠪᠠᠢᠰᠢᠩ]] [[ᠰᠠᠭᠤᠵᠤ|ᠰᠠᠭ᠍ᠤᠵᠢ]] [[ᠪᠣᠯᠬᠤ]] [[ᠦᠭᠡᠢ]]᠃}} ===={{ĐM|alter form}}==== * {{alter|cmg|ᠰᠡᠯᠢᠩᠭᠡ|ᠴᠡᠯᠢᠩᠭᠡ||Buryat}} ===={{ĐM|desc}}==== * {{desc|mn|-|inh=1}} *: {{desc|mn|ᠰᠡᠯᠡᠩᠭᠡ|sclang=1}} *: {{desc|mn|Сэлэнгэ|sclang=1}} * {{desc|mnc|ᠰᡝᠯᡝᠩᡤᡝ|bor=1}} ** {{desc|zh|色楞額|色楞格|qq2=via Mandarin}} * {{desc|zle-mru|Селенга|bor=1}} ** {{desc|ru|Селенга́|g=f-in|inh=1}} *** {{desc|en|Selenga|translit=1}} 5xx1q21gwda5nkhebnxyhg0hg8tk0ey ᠴᠡᠯᠢᠩᠭᠡ 0 412157 2514021 2026-08-14T11:50:18Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{label|cmg|Buryat}} {{alt form|cmg|ᠰᠡᠯᠡᠩᠭᠡ|t=Selenga}}” 2514021 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{label|cmg|Buryat}} {{alt form|cmg|ᠰᠡᠯᠡᠩᠭᠡ|t=Selenga}} adn5g4bxbl7y7lkto14i0hvtkgus33x ᠰᠡᠯᠢᠩᠭᠡ 0 412158 2514022 2026-08-14T11:50:59Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{alt form|cmg|ᠰᠡᠯᠡᠩᠭᠡ|t=Selenga}}” 2514022 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{alt form|cmg|ᠰᠡᠯᠡᠩᠭᠡ|t=Selenga}} s8as146j2eubdakeeezrniotx9qujdb ᠰᠢᠢᠯᠬᠡ 0 412159 2514023 2026-08-14T11:54:09Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|etym}}=== Dạng viết của {{ncog|bua|Шиилхэ}}. ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{place|cmg|sông|t=Shilka}} #* {{quote-book|cmg|year=1875|author=Vandan Yumsunov|title=ᠬᠣᠷᠢ ᠶᠢᠨ ᠠᠷᠪᠠᠨ ᠨᠢᠭᠡᠨ ᠡᠴᠢᠭᠡ ᠵᠣᠨ ᠤ ᠤᠭ ᠢᠵᠠᠭᠤᠷ ᠤᠨ ᠲᠤᠭᠤᠵᠢ|text='''ᠰᠢᠢᠯᠬᠡ''' [[ᠠᠷᠭᠤᠨᠢ]] [[ᠬᠣᠶ᠋ᠠᠷ]] [[ᠭ᠋ᠣᠣᠯ]] …” 2514023 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|etym}}=== Dạng viết của {{ncog|bua|Шиилхэ}}. ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{place|cmg|sông|t=Shilka}} #* {{quote-book|cmg|year=1875|author=Vandan Yumsunov|title=ᠬᠣᠷᠢ ᠶᠢᠨ ᠠᠷᠪᠠᠨ ᠨᠢᠭᠡᠨ ᠡᠴᠢᠭᠡ ᠵᠣᠨ ᠤ ᠤᠭ ᠢᠵᠠᠭᠤᠷ ᠤᠨ ᠲᠤᠭᠤᠵᠢ|text='''ᠰᠢᠢᠯᠬᠡ''' [[ᠠᠷᠭᠤᠨᠢ]] [[ᠬᠣᠶ᠋ᠠᠷ]] [[ᠭ᠋ᠣᠣᠯ]] [[ᠤ᠋ᠨ|ᠤᠨ]] [[ᠨᠡᠶ᠋ᠢᠯᠡᠬᠦ|ᠨᠢᠢᠯᠡᠬᠦ]] [[ᠪᠡᠯᠴᠢᠷ|ᠪᠢᠯᠴᠢᠷ]]᠃|t=…hợp lưu của sông '''Shilka''' và sông Argun…}} at70o2z4kzlv3lmyp2p9s4u9t5gq3qx ᠰᠢᠲ᠋ᠢ 0 412160 2514024 2026-08-14T11:55:48Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{alt form|cmg|ᠴᠢᠲᠡ|t=Chita}} ==={{ĐM|ref}}=== * {{cite-book|cmg|author={{xlit|ru|Тугултур Тобоев}}|year=1863|title=ᠬᠣᠷᠢ ᠬᠢᠭᠡᠳ ᠠᠭ᠋ᠤᠶ᠋ᠢᠨ ᠪᠤᠷᠢᠶ᠋ᠠᠳ ᠨᠠᠷ ᠤᠨ ᠤᠷᠢᠲᠠ ᠳ᠋ᠠᠭ᠋ᠠᠨ ᠪᠣᠯᠤᠭᠰᠠᠨ ᠠᠨᠤ|url=https://altaica.ru/LIBRARY/POPPE/Poppe_Hori.pdf}}” 2514024 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # {{alt form|cmg|ᠴᠢᠲᠡ|t=Chita}} ==={{ĐM|ref}}=== * {{cite-book|cmg|author={{xlit|ru|Тугултур Тобоев}}|year=1863|title=ᠬᠣᠷᠢ ᠬᠢᠭᠡᠳ ᠠᠭ᠋ᠤᠶ᠋ᠢᠨ ᠪᠤᠷᠢᠶ᠋ᠠᠳ ᠨᠠᠷ ᠤᠨ ᠤᠷᠢᠲᠠ ᠳ᠋ᠠᠭ᠋ᠠᠨ ᠪᠣᠯᠤᠭᠰᠠᠨ ᠠᠨᠤ|url=https://altaica.ru/LIBRARY/POPPE/Poppe_Hori.pdf}} ricaxxkr1m8tpiqoyfudnr1143kabp9 ᠴᠢᠲᠡ 0 412161 2514025 2026-08-14T11:58:37Z WhoAlone 40420 Trang mới: “=={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # [[Chita]].” 2514025 wikitext text/x-wiki =={{langname|cmg}}== ==={{ĐM|pr-noun}}=== {{head|cmg|Danh từ riêng}} # [[Chita]]. ehl7sqihzergfby6e7r3qabnpaycg7p