作品內的單一角色介紹文字超過二十行,則建議轉為獨立角色條目。

MediaWiki:Gadget-vote.js

出自 Komica wiki
跳至導覽 跳至搜尋

注意:在您儲存之後您必須清除瀏覽器快取才可看到最新的變更。

  • Firefox / Safari:按住 Shift 時點選 重新整理,或按 Ctrl-F5Ctrl-R (Mac 則為 ⌘-R)
  • Google Chrome:Ctrl-Shift-R (Mac 則為 ⌘-Shift-R)
  • Internet Explorer:按住 Ctrl 時點選 重新整理,或按 Ctrl-F5
  • Opera:前往 選單 → 設定 (在 Mac 為 Opera → 偏好設定) 然後再到 隱私 & 安全性 → 清除瀏覽資料 → 已快取的圖片與檔案
function encode(input) {
	return $('<span>').text(input).html();
}
function getAjaxErrorText(jqxhr, exception){
	if (jqxhr.status === 0) {
		return '網路未連線';
	} else if (jqxhr.status === 404) {
		return '找不到頁面 [404]';
	} else if (jqxhr.status === 500) {
		return '伺服器內部錯誤 [500]';
	} else if (exception === 'parsererror') {
		return '解析失敗';
	} else if (exception === 'timeout') {
		return '已逾時';
	} else if (exception === 'abort') {
		return '程序被中止';
	} else {
		return '' + jqxhr.responseText;
	}
}
(function (){
	var voteBlockClass = 'vote-block';
	var apiURL = '/api.php';
	var isPreview = false;
	var pageTitle = mw.config.get('wgPageName');

	if(!document.querySelector("." + voteBlockClass)){
		return;
	}
	if(mw.config.get('wgAction') == "edit" || mw.config.get('wgAction') == "submit"){
		isPreview = true;
		console.log("編輯中狀態,將取消投票按鈕功能");
	}

	$("." + voteBlockClass + " .vote-page").each(function(){
		var votePageTitle = this.dataset.votePage,
		    section = this.dataset.section,
		    _this = this;

		$.ajax({
			url: apiURL,
			data: {
				"action": "query",
				"format": "json",
				"meta": "userinfo",
				"uiprop": "groups"
			},
			dataType: "json",
			beforeSend: function(){
				$(_this ).html("<p>使用者身份確認中……</p>");
			},
			error: function(jqxhr, exception) {
					var msg = getAjaxErrorText(jqxhr, exception);
					console.error("(gadget-vote)使用者資訊取得失敗:"+msg);
			},
			success: function(data){
				var userInfoJson = data.query.userinfo;
				var isAutoconfirmed = false;

				if( userInfoJson != null && userInfoJson.groups.indexOf("autoconfirmed") >= 0){
					isAutoconfirmed = true;
					console.log("(gadget-vote)"+userInfoJson.name+" is in \"autoconfirmed\" group.");
				}
				else{
					isAutoconfirmed = false;
					console.log("(gadget-vote)"+userInfoJson.name+" is not in \"autoconfirmed\" group.");
				}

				// 如果是已自動確認的使用者,就顯示投票頁的連結
				if(isAutoconfirmed){
					$(_this).siblings(".vote-link").show();
				}

				$.ajax({
					url: apiURL,
					method: "post",
					data: {
						"titles": votePageTitle,
						"rvsection": section,
						"action": "query",
						"format": "json",
						"prop": "revisions",
						"rvprop": "content",
						"rvlimit": "1"
					},
					dataType: "json",
					beforeSend: function(){
						$(_this ).html("<p>投票選項讀取中……</p>")
					},
					success: function(data){
						var pageJSONs = data.query.pages;
						// 建立投票 table
						
						for(i in pageJSONs){
							var text = pageJSONs[i].revisions[0]["*"]; //取得選項的wikitext
							var textAfterJson = "";
							var textInHtml;	//存放轉換成html後的選項文字用
							var sectionTitle = "";
							var sectionTitleInHtml = "";
							var searchResult;
							console.log("fetched = \r\n"+text);
							
							if(section > 0){	// 如果 section == 0,那就表示沒有標題,不用特別處理
								// 取得章節標題
								sectionTitle = text.substring(0,text.indexOf("\n")); 
								// 取得內文
								// 章節標題會影響JSON轉換,所以先將標題移除
								// 必須分兩行執行,否則後半的index會計算到前半切除前的
								text = text.substr(text.indexOf("\n") + 1)
								text = text.substring(text.search(/[\{\[\"]/));
								// 將章節標題去除頭尾包夾語法,並另外存放
								sectionTitleInHtml = sectionTitle.replace(/^=+ *| *=+[\s]*$/g,"");
							}
							// 如果該章節有<references/>語法會影響判斷,所以先將之移除
							//text = text.replace(/[\s]*<references *\/>[\s]*/g,"");
							// 如果JSON語法最後有文字會影響判斷,所以先將之另外存放
							regexJsonPart = /^[\s\S]*\}/g;
							searchResult = regexJsonPart.exec(text);
							if(searchResult != null && searchResult[0] != null){
								// 抽出後續部份
								textAfterJson = text.substring(searchResult[0].length);
								// 原字串移除後續部份
								text = text.substring(0,searchResult[0].length);
							}
							// 清除換行字元,以加速轉換處理
							text = text.replace(/[\r\n]/g,"");
							console.log("text = \r\n"+text);
							// 預先將「\」取代成「\\」、「"」取代成「\"」,以確保後續的JSON轉換不會出錯
							textInHtml = text.replace(/(\\)|(\")/g,
								function(match, p1, p2, offset, string){
									if(p1 != null){
										return "\\\\";
									}
									else if(p2 != null){
										return "\\\"";
									}
									else{
										return match;
									}
								}
							);

							console.log("escaped text = \r\n"+textInHtml);
							
							// 將找到的wikitext字串傳給api,轉換成html格式
							$.ajax({
								url: apiURL,
								method: "post",	// 為應付過長的字串而附加的method,缺少它可能會導致轉換失敗
								data: {
									"action": "parse",
									"title": pageTitle,
									"text": sectionTitleInHtml,
									"contentmodel": "wikitext",
									"prop": "text",
									"utf8": "1",
									"format": "json"
								},
								success: function(data){

									if(sectionTitleInHtml.length <= 0){
										// 如果標題不存在,則捨棄轉換結果,直接進行後續動作
										console.log("標題不存在,使用空字串");
										// sectionTitleInHtml = "";
									}
									else if(data["parse"]["text"]["*"] != null){
										// 如果轉換成功,則將結果抓出並存放
										console.log("api return = \r\n"+data["parse"]["text"]["*"]);
										// 將字串存入sectionTitleInHtml,並去除自動生成的<p></p>成對標籤與其外的部份
										// 使用API轉換後,都會自動生成一組<div><p></p></div>將整串內容包圍
										sectionTitleInHtml = data["parse"]["text"]["*"];
										sectionTitleInHtml = sectionTitleInHtml.substring(sectionTitleInHtml.indexOf("<p>")+3,sectionTitleInHtml.lastIndexOf("</p>"));

										// 清除無法執行的ref
										sectionTitleInHtml = sectionTitleInHtml.replace(/<sup[^>\r\n]+class=\"reference\"[^>\r\n]*>.*?<\/sup>/g,"");
										/// sectionTitleInHtml = sectionTitleInHtml.replace(/<div[^>\r\n]+class=\"mw-references-wrap\"[\s\S]*?<\/div>/g,"");
										console.log("heading cleanup = \r\n"+sectionTitleInHtml);
									}
									else
										// 如果轉換失敗,顯示錯誤訊息,但不終止後續動作
										console.error("無法抽取轉換後資料");

								},
								error: function(jqxhr, exception) {
										var msg = getAjaxErrorText(jqxhr, exception);
										console.error("標題轉換失敗:"+msg);
								}

							}).always(function(){
								// 不論wikitext轉換html成功與否,皆繼續執行

								// 將找到的wikitext字串傳給api,轉換成html格式
								$.ajax({
									url: apiURL,
									method: "post",	// 為應付過長的字串而附加的method,缺少它可能會導致轉換失敗
									data: {
										"action": "parse",
										"title": pageTitle,
										"text": textInHtml,
										"contentmodel": "wikitext",
										"prop": "text",
										"utf8": "1",
										"format": "json"
									},
									success: function(data){

										if(data["parse"]["text"]["*"] != null){
											// 如果轉換成功,則將結果抓出並存放
											console.log("api return = \r\n"+data["parse"]["text"]["*"]);
											// 將字串存入textInHtml並剪裁至只剩JSON括號({})與包含在內的部份
											textInHtml = data["parse"]["text"]["*"];
											textInHtml = textInHtml.substring(textInHtml.indexOf("{"),textInHtml.lastIndexOf("}")+1);
											// 清除無法執行的ref
											textInHtml = textInHtml.replace(/<sup[^>\r\n]+class=\"reference\"[^>\r\n]*>.*?<\/sup>/g,"");
											/// textInHtml = textInHtml.replace(/([^\}]\})(?:<\/p>)?<div[^>\r\n]+class=\"mw-references-wrap\"[\s\S]*/g,"$1");
											console.log("cleanup = \r\n"+textInHtml);

										}
										else
											// 如果轉換失敗,顯示錯誤訊息,但不終止後續動作
											console.error("無法抽取轉換後資料");

									},
									error: function(jqxhr, exception) {
											var msg = getAjaxErrorText(jqxhr, exception);
											console.error("投票選項轉換失敗:"+msg);
									}

								}).always(function(){
									// 不論wikitext轉換html成功與否,皆執行顯示投票表

									// 將「\\」復原為「\」,並將「"」和「\"」對調、「'」取代成「\'」,以確保後續的JSON轉換不會出錯
									// 在生成HTML的過程中會在必要處附加「"」,不做處理會導致JSON轉換時無法正確判別變數和值的區隔
									// 本取代動作必須將所有符號平行處理,才能確保不會被重複取代
									textInHtml = textInHtml.replace(/(\\\\)|(\\\")|(\")|(\')/g,
										function(match, p1, p2, p3, p4, offset, string){
											if(p1 != null){
												return "\\";
											}
											else if(p2 != null){
												return "\"";
											}
											else if(p3 != null){
												return "\\\"";
											}
											else if(p4 != null){
												return "\\\'";
											}	
											else{
												return match;
											}
										}
									);
									console.log("result = \r\n"+textInHtml);
									// 將字串轉為JSON物件
									var voteJSON;
									var choiceJSON;
									var choiceArray = [];
									console.log(sectionTitle, text);
									
									// 將未轉成HTML的文字轉換為JSON物件,供票數記錄用
									try{
										voteJSON = JSON.parse(text);
									}catch(e){
										// JSON.parse 因為其嚴謹度,強制要求 obj 的 index 一定要用引號,無法接受 {a:0, b:0} 這種格式
										// 所以特地多寫一條保險用的判斷
										try{
											console.warn("票數用JSON讀取出現異常");
											voteJSON = eval("(" + text +  ")");
										}catch(e2){
											console.error("投票資料轉換失敗,請檢查該區塊的JSON語法是否有錯誤");
											console.error(e2);
											$(_this).html("<p>投票資料轉換失敗,請檢查該區塊的JSON語法是否有錯誤</p>");
											return;
										}
										
									}
									// 將已轉成HTML的文字轉換為JSON物件,供顯示用
									try{
										choiceJSON = JSON.parse(textInHtml);
									}catch(e){
										// JSON.parse 因為其嚴謹度,強制要求 obj 的 index 一定要用引號,無法接受 {a:0, b:0} 這種格式
										// 所以特地多寫一條保險用的判斷
										try{
											console.warn("選項用JSON讀取出現異常");
											choiceJSON = eval("(" + textInHtml +  ")");
										}catch(e2){
											console.error("無法讀取選項名稱");
											console.error(e2);
											$(_this).html("<p>無法讀取選項名稱</p>");
											// 以未轉換文字填滿,不做return
											for(var str in voteJSON){
												choiceArray.push("name = "+str);
											}
										}
									}
									// 如果陣列是空的,則表示JSON轉換正常,將JSON中的鍵值(選項名稱)轉換為陣列
									if(choiceArray.length <= 0){
										for(var str in choiceJSON){
											choiceArray.push(str);
											console.log("name = "+str);
										}
										console.log("array = \r\n"+choiceArray.join(" , "));
									}

									$(_this).data("vote", voteJSON)
									// 生成表格,並加入表格頭的第一列
									var table = $("<table class=\"wikitable vote-table\" style=\"\">")
										.append(
											$("<caption style=\"font-size:1.2em;\">"+sectionTitleInHtml+"</caption>")
										)
										.append(
											$("<thead><tr><th>選項</th><th>票數</th></tr></thead>")
										)
										.append(
											$("<tbody>")
										);
									$(_this).html(table);
									
									// 將選項放進表格中
									var a = 0;
									for(var v in voteJSON){
										$(table).find("tbody")
											.append(
												$("<tr>")
													.append(
														// v為選項識別名稱(wikitext),choicaArray[a]為顯示文字(HTML)
														// v當中的「&」必須取代為「&amp;」,以防止後面選項名稱被進行HTML編碼後,票數沒有正確加到選項中的狀況
														//// 例:
														//// 選項原始碼為「A&lt;B」,在HTML屬性data-title中存放「A&lt;B」後,取出時會被視為「A<B」
														//// 要正確地將票數存進「A&lt;B」,必須轉換名稱為「A&amp;lt;B」才行
														$("<td class=\"vote-title\" data-title=\"" + v.replace("&","&amp;") + "\">")
															.append(choiceArray[a])
													)
													.append(
														$("<td>")
															.append(
																$("<div class=\"vote-num-detail\">")
																	.append(
																		"<span class=\"vote-num\">" + voteJSON[v] + "</span>"
																	)
																	.append(
																		"<button class=\"vote-btn\">投票</button>"
																	)
															)
													)
											);
										a++;
									}
									
									$(table).find("tbody tr").each(function(){
										var voteTitle = $(this).find(".vote-title").attr("data-title");
										var voteTitleText = $(this).find(".vote-title").text().trim().replace(/\n/g, "");
										var numBlock = $(this).find(".vote-num");
										var btn = $(this).find(".vote-btn");
										
										// 如果使用者不屬於「自動確認(autoconfirmed)」群組,就停用所有投票鈕
										if(!isAutoconfirmed){
											$(btn).prop('disabled', true);
											$(btn).attr("title", "所在之使用者群組未開放投票");
											$(btn).hide();
										}										

										// 如果是預覽畫面,就停用所有投票鈕
										if(isPreview){
											$(btn).prop('disabled', true);
											$(btn).attr("title", "預覽中不允許投票");
										}

										// 給予各投票按鈕功能
										$(btn).click(function(){
											var num = Number($(numBlock).text());
											if(isNaN(num)){
												// 保險用
												num = 0;
											}
											voteJSON[voteTitle] = ++num;
											
											$.ajax({
												url: apiURL,
												method: "post",
												data: {
													"action": "edit",
													"format": "json",
													"title": votePageTitle,
													"text": sectionTitle + "\n" + JSON.stringify(voteJSON, null,5) + textAfterJson,
													"minor": 1,
													//"tags": "投票",
													section: section,
													"summary": "投票給「" + voteTitleText + "」(" + num + " 票)",
													"token": mw.user.tokens.get("editToken")
												},
												beforeSend: function(){
													$(table).find(".vote-btn").prop("disabled", true);
												},
												complete: function(){
													$(table).find(".vote-btn").prop("disabled", false);
												},
												success: function(data, textStatus, jqxhr){
													// 即使送出成功(回傳200),仍然有可能為編輯失敗
													// 編輯失敗時,XHR檔頭裡會出現mediawiki-api-error欄位
													var msg = jqxhr.getResponseHeader("mediawiki-api-error");
													if(msg == null){
														// 缺少mediawiki-api-error欄位,才算是送出成功
														console.log("投票送出成功:"+voteTitle+" = "+num);
													}
													else{
														// 如果mediawiki-api-error欄位存在,即為送出失敗
														console.error("投票送出失敗:"+msg);
														num--;
													}
													$(numBlock).text(num);

												}, 
												error: function(jqxhr, exception){
													var msg = getAjaxErrorText(jqxhr, exception);
													console.error("投票送出失敗:"+msg);
													$(numBlock).text(--num);
												}
											})
										})
									})
								});
							});
							break;
						}
					},
					error: function(jqxhr, exception){
						var msg = getAjaxErrorText(jqxhr, exception);
						console.error("投票資料讀取失敗:"+msg);
						$(_this).html("<p>無法讀取投票資料</p>");

					}
				})
			}
		});
	})
})()