ETTHelper-TagEditer

Help to edit the gallery's tags.

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        ETTHelper-TagEditer
// @name:zh-CN  E绅士标签翻译辅助工具-标签编辑
// @namespace   EhTagTranslation
// @description Help to edit the gallery's tags.
// @description:zh-CN 辅助编辑E绅士画廊的标签
// @include     *://exhentai.org/g/*
// @include     *://e-hentai.org/g/*
// @version     1.4.6
// @author      Mapaler <[email protected]>
// @copyright   2019+, Mapaler <[email protected]>
// @homepage    https://github.com/EhTagTranslation/UserScripts
// @supportURL  https://github.com/EhTagTranslation/UserScripts/issues
// @grant       GM_registerMenuCommand
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_deleteValue
// ==/UserScript==

const scriptVersion = (defaultVersion=>typeof GM_info != "undefined" ? GM_info.script.version.replace(/(^\s*)|(\s*$)/g, "") : defaultVersion)("unknown"); //本程序的版本
const scriptName = (defaultName=>{
	if (typeof(GM_info)!="undefined")
	{
		if (GM_info.script.name_i18n)
		{
			const i18n = navigator.language.replace("-","_"); //获取浏览器语言
			return GM_info.script.name_i18n[i18n]; //支持Tampermonkey
		}
		else
		{
			return GM_info.script.localizedName || //支持Greasemonkey 油猴子 3.x
						GM_info.script.name; //支持Violentmonkey 暴力猴
		}
	}else
	{
		return defaultName;
	}
})("ETTWikiHelper-TagEditer"); //本程序的名称

//限定数值最大最小值
function limitMaxAndMin(num,max,min)
{
	return Math.max(Math.min(num, max), min);
}

//默认CSS内容
const ewh_tag_styleText_Default = `
/* fallback */
@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/s/materialicons/v90/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2');
}

.material-icons {
  font-family: 'Material Icons';
  font-weight: normal;
  font-style: normal;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  display: inline-block;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  -moz-font-feature-settings: 'liga';
  -moz-osx-font-smoothing: grayscale;
}

#gd4.ewh-float { /*浮动窗体*/
	position: fixed;
	top: 10%;
	left: 10%;
	background-color : inherit;
	margin: 0 !important;
	padding: 0 !important;
	border-style: ridge;
	border-width: 3px;
	border-color: #eee black black #eee;
	opacity: 0.8;
}
.ewh-bar-floatcaption { /*标题栏整体*/
	height: 22px;
	position: relative;
}
.ewh-cpttext-box { /*标题栏文字框*/
	width: 100%;
	height: 100%;
	float: left;
	color: white;
	line-height: 22px;
	font-size: 14px;
	background-image: linear-gradient(to right,#808080,#B7B5BB);
}
.ewh-float .ewh-cpttext-box  { /*浮动时的标题颜色*/
	background-image: linear-gradient(to right,#000280,#0F80CD);
}
.ewh-cpttext-box::before{ /*标题图标*/
	content: "🏷️";
}
.ewh-cpttext-box span { /*标题文字*/
	pointer-events:none;
	user-select: none;
}
.ewh-cptbtn-box { /*标题按钮框*/
	height: 100%;
	position: absolute;
	top: 0;
	right: 8px;
	line-height: 22px;
}
.ewh-cpt-btn { /*平时的按钮*/
	vertical-align: middle;
	padding: 0;
	font-size: 14px;
	margin-top:-2px;
	height: 18px;
	width: 20px;
	background-color: #c0c0c0;
	border-style: outset;
	border-width: 2px;
	border-color: white black black white;
}
.ewh-cpt-rag { /*平时的范围拖动条*/
	vertical-align: middle;
	padding: 0;
	font-size: 14px;
	margin-top:0;
	height: 18px;
	width: 100px;
}
.ewh-cpt-btn:active { /*按钮按下时的凹陷*/
	background-color: #d8d8d8;
	padding-left: 1px !important;
	padding-top: 1px !important;
	border-style: inset;
	border-color:  black white white black;
}
.ewh-cpt-btn:focus { /*激活后的虚线*/
	outline: dotted 1px black;
}
.ewh-btn-closefloat,.ewh-rag-opacity { /*平时隐藏关闭浮动的按钮*/
	display: none;
}
.ewh-float .ewh-btn-closefloat,.ewh-float .ewh-rag-opacity { /*浮动时显示关闭浮动的按钮*/
	display: unset;
}
.ewh-float .ewh-btn-openfloat{ /*浮动时隐藏开启浮动的按钮*/
	display: none;
}
.ewh-bar-tagsearch{
	position: relative;
}
.ewh-ipt-tagsearch{
	width: 200px;
	box-sizing: border-box;
}
.ewh-tagsearchtext,.ewh-tagsearchlink{
	font-size: 10pt;
}
.ewh-bar-tagsearch a::before{
	font-size: 10pt;
	font-weight: bold;
}
.ewh-bar-tagsearch a::after{
	font-size: 10pt;
	background: #c0c0c0;
	color:black;
	border-style: ridge;
	border-width: 3px;
	border-color: #eee black black #eee;
	position:absolute;
	z-index:999;
	padding:8px;
	min-width:150px;
	max-width:500px;
	white-space:pre-wrap;
	opacity: 0;
	transition: opacity 0.1s;
	top:28px;
	left:45%;
	pointer-events:none;
	font-weight: 400;
	line-height: 20px;
}
.ewh-bar-tagsearch a:hover::after{
	opacity: 1;
}
`;
//获取Tag编辑区
var ewhWindow = document.querySelector("#gd4");

//增加浮动窗标题栏
var divCaptionBar = ewhWindow.insertBefore(document.createElement("div"),gd4.firstChild);
divCaptionBar.className = "ewh-bar-floatcaption";

//生成辅助器CSS
var ewh_tag_style = divCaptionBar.appendChild(document.createElement("style"));
ewh_tag_style.type = "text/css";
ewh_tag_style.appendChild(document.createTextNode(ewh_tag_styleText_Default));

//生成标题栏文字
var divCaption = divCaptionBar.appendChild(document.createElement("div"));
divCaption.className = "ewh-cpttext-box";
divCaption.appendChild(document.createElement("span")).appendChild(document.createTextNode(scriptName));

//添加窗体鼠标拖拽移动
var windowPosition = ewhWindow.position = [0, 0]; //[X,Y] 用以储存窗体开始拖动时的鼠标相对窗口坐标差值。
divCaption.addEventListener("mousedown", function(e) { //按下鼠标则添加移动事件
	if (!ewhWindow.classList.contains("ewh-float")) return; //如果不是浮动窗体直接结束
	var eX = limitMaxAndMin(e.clientX,document.documentElement.clientWidth,0), eY = limitMaxAndMin(e.clientY,document.documentElement.clientHeight,0); //不允许鼠标超出网页。
	windowPosition[0] = eX - ewhWindow.offsetLeft;
	windowPosition[1] = eY - ewhWindow.offsetTop;
	var handler_mousemove = function(e) { //移动鼠标则修改窗体坐标
		var eX = limitMaxAndMin(e.clientX,document.documentElement.clientWidth,0), eY = limitMaxAndMin(e.clientY,document.documentElement.clientHeight,0); //不允许鼠标超出网页。
		ewhWindow.style.left = (eX - windowPosition[0]) + 'px';
		ewhWindow.style.top = (eY - windowPosition[1]) + 'px';
	};
	var handler_mouseup = function(e) { //抬起鼠标则取消移动事件
		document.removeEventListener("mousemove", handler_mousemove);
		if (ewhWindow.style.left) GM_setValue("floatwindow-left",ewhWindow.style.left); //储存窗体位置
		if (ewhWindow.style.top) GM_setValue("floatwindow-top",ewhWindow.style.top); //储存窗体位置

	};
	document.addEventListener("mousemove", handler_mousemove);
	document.addEventListener("mouseup", handler_mouseup, { once: true });
});

//生成标题栏按钮
var divButtonBox = divCaptionBar.appendChild(document.createElement("div"));
divButtonBox.className = "ewh-cptbtn-box";

//生成修改设置的按钮
var ragOpacity = divButtonBox.appendChild(document.createElement("input"));
ragOpacity.className = "ewh-cpt-rag ewh-rag-opacity";
ragOpacity.type = "range";
ragOpacity.max = 1;
ragOpacity.min = 0.5;
ragOpacity.step = 0.01;
ragOpacity.title = "窗体不透明度";
ragOpacity.onchange = ragOpacity.oninput = function(){
	ewhWindow.style.opacity = this.value;
};
ragOpacity.onchange = function(){
	ragOpacity.oninput();
	if (ewhWindow.style.opacity) GM_setValue("floatwindow-opacity",ewhWindow.style.opacity); //储存窗体透明度
};

//生成打开浮动状态的按钮
var btnOpenFloat = divButtonBox.appendChild(document.createElement("button"));
btnOpenFloat.className = "ewh-cpt-btn material-icons ewh-btn-openfloat";
btnOpenFloat.title = "浮动标签编辑框";
btnOpenFloat.appendChild(document.createElement("span").appendChild(document.createTextNode("open_in_new")));
btnOpenFloat.onclick = function(){
	//ewhWindow.setAttribute("style",ewhWindow.getAttribute("style_bak"));
	//ewhWindow.removeAttribute("style_bak");
	ewhWindow.classList.add("ewh-float");
	ewhWindow.style.left = GM_getValue("floatwindow-left");
	ewhWindow.style.top = GM_getValue("floatwindow-top");
	ewhWindow.style.opacity = ragOpacity.value = GM_getValue("floatwindow-opacity") || 0.8;
};
GM_registerMenuCommand("打开浮动标签编辑框", btnOpenFloat.onclick);

//生成关闭浮动状态的按钮
var btnCloseFloat = divButtonBox.appendChild(document.createElement("button"));
btnCloseFloat.className = "ewh-cpt-btn material-icons ewh-btn-closefloat";
btnCloseFloat.title = "关闭浮动窗体";
btnCloseFloat.appendChild(document.createElement("span").appendChild(document.createTextNode("close")));
btnCloseFloat.onclick = function(){
	//ewhWindow.setAttribute("style_bak",ewhWindow.getAttribute("style"));
	if (ewhWindow.style.left) GM_setValue("floatwindow-left",ewhWindow.style.left); //储存窗体位置
	if (ewhWindow.style.top) GM_setValue("floatwindow-top",ewhWindow.style.top); //储存窗体位置
	if (ewhWindow.style.opacity) GM_setValue("floatwindow-opacity",ewhWindow.style.opacity); //储存窗体透明度
	ewhWindow.removeAttribute("style");
	ewhWindow.classList.remove("ewh-float");
};
GM_registerMenuCommand("重置浮动窗位置与透明度", function(){
	btnCloseFloat.onclick(); //先关掉窗体,然后删除设置
	GM_deleteValue("floatwindow-left");
	GM_deleteValue("floatwindow-top");
	GM_deleteValue("floatwindow-opacity");
});

var nameSpaceC = {
	artist:"艺术家",
	female:"女性",
	male:"男性",
	parody:"原作",
	character:"角色",
	group:"团队",
	language:"语言",
	reclass:"重新分类",
	misc:"杂项"
};
//获取标签数据列表
var tagdatalist = document.querySelector("#tbs-tags");
var tagData;
//获取真实标签输入框
var newTagText = document.querySelector("#newtagfield");
if (!tagdatalist) //没有ETS,但有ETS扩展版的处理方式
{
	var tagDataStr = localStorage.getItem("EhSyringe.tag-list"); //ETS扩展版1.2.1的数据
	if (typeof(tagDataStr) == "string")
	{
		tagData = JSON.parse(tagDataStr);
		tagdatalist = document.createElement("datalist");
		tagdatalist.id = "tbs-tags";
		newTagText.setAttribute("list","tbs-tags");
		tagData.forEach(function(tag){
			tagdatalist.appendChild(new Option(nameSpaceC[tag.namespace]+":"+tag.name,tag.search));
		})
		newTagText.insertAdjacentElement('afterend',tagdatalist);
	}
}
if (tagdatalist) //如果存在则生成标签搜索框
{
	var taglist = tagdatalist.options;
	//增加标签搜索框箱子
	var divSearchBar = ewhWindow.insertBefore(document.createElement("div"),document.querySelector("#tagmenu_act"));
	divSearchBar.className = "ewh-bar-tagsearch";

	//增加标签搜索框
	var iptTagSearch = divSearchBar.appendChild(document.createElement("input"));
	iptTagSearch.type = "text";
	iptTagSearch.placeholder = "🔍标签搜索:回车附加到下方▼";
	iptTagSearch.setAttribute("list","tbs-tags");
	iptTagSearch.className="ewh-ipt-tagsearch";
	//增加标签搜索提醒文字
	var spnTagSearchInfo = divSearchBar.appendChild(document.createElement("span"));
	spnTagSearchInfo.className="ewh-tagsearchtext";
	//增加标签搜索提醒标签
	var aTagSearchInfo = divSearchBar.appendChild(document.createElement("a"));
	aTagSearchInfo.className="ewh-tagsearchlink";

	iptTagSearch.onkeypress = function(e){
		if(e.key == "Enter"){ //回车,将内容附加到真实Tag框,并清空搜索框
			var _this = this;
			if (_this.value.length == 0)
			{ //如果什么都没输入,相当于提交
				spnTagSearchInfo.innerHTML = "";
				aTagSearchInfo.removeAttribute("id");
				aTagSearchInfo.innerHTML = "";
				if (newTagText.value.length > 0)tag_from_field(); //如果输入框有内容点击Tag提交
				return;
			};

			var clabel = false, useGuess = false, guess = false;
			var tagC;

			if (tagData) //如果有JSON数据,直接使用
			{ //扩展版的JSON数据
				var searchRes = tagData.filter(function(tag){ //搜索绝对等于的
					return tag.search == _this.value;
				});
				if (searchRes.length<1)
				{ //猜测式搜索
					searchRes = tagData.filter(function(tag){
						return tag.name.indexOf(_this.value)>=0 || tag.key.indexOf(_this.value)>=0; //有非tag字符时才搜索中文,其他时候搜索key
					});
					if (searchRes.length>0)
					{
						guess = true; //标记为猜的
						_this.value = searchRes[0].search; //目前的输入修改为猜的tag
					}
				}
				if (searchRes.length>0)
				{
					tagC = searchRes[0];
					clabel = tagC.name;
				}
			}else
			{ //脚本版的数据
				if (_this.value.replace(/[\w\:\"\s\-\.\'\$]/,"").length>0) useGuess = true; //如果存在非tag字符,则尝试搜索中文。
				for (var ti=0;ti<taglist.length;ti++)
				{ //循环搜索列表中是否已存在这个Tag
					if (taglist[ti].value == _this.value)
					{
						clabel = taglist[ti].label;
						break;
					}else if(useGuess && taglist[ti].label.indexOf(_this.value)>0)
					{
						clabel = taglist[ti].label;
						guess = true; //标记为猜的
						_this.value = taglist[ti].value; //目前的输入修改为猜的tag
						break;
					}
				}
			}
			if (clabel)
			{
				var shortTag;
				if (tagData)
				{ //扩展版的JSON数据
					shortTag = (tagC.namespace=="misc"?"":(tagC.namespace.substr(0,1) + ":")) + tagC.key; //缩减Tag长度,以便一次能多提交一些Tag
				}else
				{ //脚本版的数据
					var regArr = /^(\w+):"?([\w+\s\-\'\.]+)\$?"?$/ig.exec(_this.value);
					shortTag = (regArr[1]=="misc"?"":(regArr[1].substr(0,1) + ":")) + regArr[2]; //缩减Tag长度,以便一次能多提交一些Tag
				}
				if ((newTagText.value+","+shortTag).length>200)
				{
					spnTagSearchInfo.innerHTML = "⛔超长(原始标签输入框限定200字符)";
					if (!tagData) aTagSearchInfo.removeAttribute("id");
					aTagSearchInfo.innerHTML = "";
				}else
				{
					newTagText.value = (newTagText.value.length>0)?(newTagText.value+","+shortTag):shortTag;
					spnTagSearchInfo.innerHTML = (guess?"程序猜测你想添加":"你添加了")+" " + (tagData?nameSpaceC[tagC.namespace]:clabel.split(":")[0]) + ":";
					if (!tagData) aTagSearchInfo.id = "ta_" + (regArr[1]=="misc"?"":regArr[1]+":") + regArr[2].replace(/\s/igm,"_");
					aTagSearchInfo.innerHTML = clabel;
					_this.value = "";
				}
			}else
			{
				spnTagSearchInfo.innerHTML = "☹️数据库里没有这个标签";
				if (!tagData) aTagSearchInfo.removeAttribute("id");
				aTagSearchInfo.innerHTML = "";
			}
		}
	};
}