ETTHelper-TagEditer

Help to edit the gallery's tags.

// ==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 <mapaler@163.com>
// @copyright   2019+, Mapaler <mapaler@163.com>
// @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 = "";
			}
		}
	};
}