context_menu.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. (function(){
  2. function module_init() {
  3. console.log("[lora-prompt-tool] load context menu module");
  4. lorahelper.lorahelper_context_menu = null;
  5. lorahelper.lorahelper_context_menu_list = null;
  6. lorahelper.lorahelper_context_menu_edit_btn = null;
  7. lorahelper.lorahelper_context_menu_search_box = null;
  8. lorahelper.lorahelper_sub_context_menu = null;
  9. lorahelper.lorahelper_context_menu_opt = null;
  10. lorahelper.context_menu_list = [];
  11. lorahelper.context_menu_search_box_item = null;
  12. lorahelper.lorahelper_context_menu_search_text = "";
  13. lorahelper.lorahelper_context_menu_search_lock = false;
  14. lorahelper.lorahelper_mouse_position = {x: -1, y: -1};
  15. function updateLoraHelperSearchingBox(event){
  16. if(lorahelper.lorahelper_context_menu_search_lock){
  17. return;
  18. }
  19. if(lorahelper.lorahelper_context_menu_search_text == lorahelper.lorahelper_context_menu_search_box.value){
  20. return;
  21. }
  22. lorahelper.lorahelper_context_menu_search_lock = true;
  23. lorahelper.lorahelper_context_menu_search_text = lorahelper.lorahelper_context_menu_search_box.value;
  24. let filter = lorahelper.lorahelper_context_menu_search_text.trim();
  25. const menu_list = lorahelper.lorahelper_context_menu_list.querySelectorAll('.item');
  26. if(filter === ""){
  27. for(const menu_item of menu_list){
  28. menu_item.style.display = "block";
  29. }
  30. lorahelper.lorahelper_context_menu_search_lock = false;
  31. return;
  32. }
  33. let is_regex = false;
  34. if(filter.charAt(filter.length-1) == filter.charAt(0) && filter.charAt(0) == "/" && filter.length >= 2){
  35. try {
  36. const reg_filter = filter.substring(1, filter.length-1);
  37. if(reg_filter !== ""){
  38. filter = new RegExp(reg_filter);
  39. is_regex = true;
  40. }
  41. } catch (error) {
  42. is_regex = false;
  43. }
  44. }
  45. if(!is_regex){
  46. filter = filter.toLowerCase();
  47. if(filter.indexOf("\\") > -1){
  48. try {
  49. filter = lorahelper.unescape_string(filter);
  50. } catch (error) {
  51. }
  52. }
  53. }
  54. for(const menu_item of menu_list){
  55. if(menu_item.classList.contains('edit-btn')) continue;
  56. let flag = false;
  57. const find_areas = [
  58. menu_item.innerHTML || "",
  59. menu_item.getAttribute("prompt") || "",
  60. menu_item.getAttribute("categorys") || ""
  61. ];
  62. for(const find_area of find_areas){
  63. if(is_regex){
  64. flag = find_area.toLowerCase().search(filter) > -1
  65. } else {
  66. flag = find_area.toLowerCase().indexOf(filter) > -1
  67. }
  68. if (flag) break;
  69. }
  70. menu_item.style.display = flag ? "block" : "none";
  71. }
  72. lorahelper.lorahelper_context_menu_search_lock = false;
  73. }
  74. function normalizePosition(the_context_menu, mouseX, mouseY){
  75. const {
  76. left: scopeOffsetX,
  77. top: scopeOffsetY,
  78. } = lorahelper.lorahelper_scope_div.getBoundingClientRect();
  79. const scopeX = mouseX - scopeOffsetX;
  80. const scopeY = mouseY - scopeOffsetY;
  81. //check if the element will go out of bounds
  82. const outOfBoundsOnX =
  83. scopeX + the_context_menu.clientWidth > lorahelper.lorahelper_scope_div.clientWidth;
  84. const outOfBoundsOnY =
  85. scopeY + the_context_menu.clientHeight > lorahelper.lorahelper_scope_div.clientHeight;
  86. let normalizeX = mouseX;
  87. let normalizeY = mouseY;
  88. //normalize on X
  89. if(outOfBoundsOnX){
  90. normalizeX =
  91. scopeOffsetX + lorahelper.lorahelper_scope_div.clientWidth - the_context_menu.clientWidth;
  92. }
  93. //normalize on Y
  94. if(outOfBoundsOnY){
  95. normalizeY =
  96. scopeOffsetY + lorahelper.lorahelper_scope_div.clientHeight - the_context_menu.clientHeight;
  97. }
  98. return {normalizeX, normalizeY};
  99. }
  100. function show_lora_context_menu(mouseX, mouseY){
  101. lorahelper.lorahelper_context_menu_search_box.value = "";
  102. lorahelper.lorahelper_context_menu_search_text = "";
  103. lorahelper.lorahelper_context_menu_list.style.height = "unset";
  104. lorahelper.lorahelper_context_menu_search_box.style.width = "unset";
  105. lorahelper.lorahelper_context_menu_list.style.overflow = "unset";
  106. if(lorahelper.lorahelper_context_menu_list.clientHeight > lorahelper.lorahelper_scope_div.clientHeight - 150){
  107. lorahelper.lorahelper_context_menu_list.style.height = `${lorahelper.lorahelper_scope_div.clientHeight - 150}px`;
  108. lorahelper.lorahelper_context_menu_list.style.overflow = "scroll";
  109. }
  110. lorahelper.lorahelper_context_menu_search_box.style.width = `${lorahelper.lorahelper_context_menu_list.clientWidth - 10}px`;
  111. lorahelper.lorahelper_context_menu_edit_btn.innerHTML = lorahelper.get_UI_display("edit prompt words...");
  112. lorahelper.lorahelper_context_menu_search_box.placeholder = lorahelper.my_getTranslation("Search...");
  113. const {normalizeX, normalizeY} = normalizePosition(lorahelper.lorahelper_context_menu, mouseX, mouseY);
  114. lorahelper.lorahelper_context_menu.style.top = `${normalizeY}px`;
  115. lorahelper.lorahelper_context_menu.style.left = `${normalizeX}px`;
  116. open_lora_context_menu(lorahelper.lorahelper_context_menu);
  117. }
  118. function show_lora_sub_context_menu(sub_context_menu, parent_menu){
  119. const parent_rect = parent_menu.getBoundingClientRect();
  120. sub_context_menu.style.height = "unset";
  121. sub_context_menu.style.overflow = "unset";
  122. if(sub_context_menu.clientHeight > lorahelper.lorahelper_scope_div.clientHeight - 100){
  123. sub_context_menu.style.height = `${lorahelper.lorahelper_scope_div.clientHeight - 100}px`;
  124. sub_context_menu.style.overflow = "scroll";
  125. }
  126. let pos_x = parent_rect.right
  127. let pos_y = parent_rect.top
  128. if(parent_rect.right + sub_context_menu.clientWidth > lorahelper.lorahelper_scope_div.clientWidth){
  129. pos_x = parent_rect.left - sub_context_menu.clientWidth;
  130. }
  131. const {normalizeX, normalizeY} = normalizePosition(sub_context_menu, pos_x, pos_y);
  132. sub_context_menu.style.top = `${normalizeY}px`;
  133. sub_context_menu.style.left = `${normalizeX}px`;
  134. open_lora_context_menu(sub_context_menu);
  135. }
  136. function close_lora_context_menu(selected_context_menu){
  137. if (!lorahelper.is_nullptr(selected_context_menu)){
  138. selected_context_menu.classList.remove("visible");
  139. if (typeof(selected_context_menu.on_close) === typeof(lorahelper.noop_func)){
  140. selected_context_menu.on_close(selected_context_menu);
  141. }
  142. }else{
  143. lorahelper.resetElementLayer();
  144. let available_context_menu = get_lora_context_menu_list();
  145. for (let the_context_menu of available_context_menu){
  146. the_context_menu.classList.remove("visible");
  147. if (typeof(the_context_menu.on_close) === typeof(lorahelper.noop_func)){
  148. the_context_menu.on_close(the_context_menu);
  149. }
  150. }
  151. }
  152. }
  153. function open_lora_context_menu(selected_context_menu){
  154. selected_context_menu.classList.remove("visible");
  155. setTimeout(()=>{
  156. selected_context_menu.classList.add("visible");
  157. });
  158. }
  159. function add_lora_context_menu(the_context_menu){
  160. lorahelper.lorahelper_scope.appendChild(the_context_menu);
  161. lorahelper.context_menu_list.push(the_context_menu);
  162. }
  163. function delete_lora_context_menu(the_context_menu){
  164. try {
  165. the_context_menu.remove();
  166. } catch (error) { }
  167. const index = lorahelper.context_menu_list.indexOf(the_context_menu);
  168. if (index > -1) { // only splice array when item is found
  169. lorahelper.context_menu_list.splice(index, 1); // 2nd parameter means remove one item only
  170. }
  171. }
  172. function get_lora_context_menu_list(){
  173. let copy_list = [];
  174. for (let item of lorahelper.context_menu_list) copy_list.push(item);
  175. return copy_list;
  176. }
  177. function create_context_menu(id){
  178. let the_context_menu = document.createElement("div");
  179. if(typeof(id) === typeof("string")){
  180. if(id.trim() != "") the_context_menu.setAttribute("id", id);
  181. }
  182. the_context_menu.classList.add("lora-context-menu");
  183. the_context_menu.innerHTML = "";
  184. return the_context_menu;
  185. }
  186. function create_context_menu_group(){
  187. let context_menu_group = document.createElement("div");
  188. context_menu_group.innerHTML = "";
  189. return context_menu_group;
  190. }
  191. function create_context_menu_hr_item(){
  192. let context_menu_hr_item = document.createElement("div");
  193. context_menu_hr_item.classList.add('hritem');
  194. context_menu_hr_item.appendChild(document.createElement("hr"));
  195. return context_menu_hr_item;
  196. }
  197. function create_context_menu_button(text){
  198. let context_menu_button_item = document.createElement("div");
  199. context_menu_button_item.classList.add('item');
  200. context_menu_button_item.innerHTML = text;
  201. return context_menu_button_item;
  202. }
  203. function create_context_subset(text, subset){
  204. let context_menu_button_item = document.createElement("div");
  205. let context_menu_subset_icon = document.createElement("span");
  206. context_menu_subset_icon.innerHTML = "\u27a4";
  207. context_menu_subset_icon.style.float = "right";
  208. context_menu_button_item.classList.add('item');
  209. context_menu_button_item.innerHTML = text;
  210. context_menu_button_item.appendChild(context_menu_subset_icon);
  211. context_menu_button_item.menu_subset = [];
  212. for(const set_item of subset) {
  213. context_menu_button_item.menu_subset.push(set_item);
  214. }
  215. const display_submenu = (event) => {
  216. let old_sub_menu = context_menu_button_item.sub_menu_object;
  217. if (!lorahelper.is_nullptr(old_sub_menu)){
  218. } else {
  219. let the_sub_menu = create_context_menu();
  220. for(const set_item of context_menu_button_item.menu_subset){
  221. the_sub_menu.appendChild(set_item);
  222. }
  223. the_sub_menu.on_close = (self)=>{
  224. context_menu_button_item.sub_menu_object = undefined;
  225. delete_lora_context_menu(self);
  226. };
  227. context_menu_button_item.sub_menu_object = the_sub_menu;
  228. add_lora_context_menu(the_sub_menu);
  229. show_lora_sub_context_menu(the_sub_menu, context_menu_button_item);
  230. }
  231. }
  232. context_menu_button_item.addEventListener('mouseover', function(event) {
  233. display_submenu(event);
  234. }, false);
  235. context_menu_button_item.addEventListener('touchstart', function(event) {
  236. display_submenu(event);
  237. let current_sub_menu = context_menu_button_item.sub_menu_object;
  238. if (!lorahelper.is_nullptr(current_sub_menu)){
  239. lorahelper.sendontop(current_sub_menu);
  240. }
  241. }, false);
  242. context_menu_button_item.addEventListener('mouseleave', function(eve) {
  243. window.setTimeout(((context_menu_button_item) => {
  244. return () => {
  245. let the_sub_menu = context_menu_button_item.sub_menu_object;
  246. if (!lorahelper.is_nullptr(the_sub_menu)){
  247. const sub_menu_rect = the_sub_menu.getBoundingClientRect();
  248. if(!lorahelper.pointInRect(sub_menu_rect, lorahelper.lorahelper_mouse_position)){
  249. close_lora_context_menu(the_sub_menu);
  250. }
  251. }
  252. }
  253. })(context_menu_button_item), 50);
  254. }, false);
  255. return context_menu_button_item;
  256. }
  257. function create_context_menu_textbox(text, onchange){
  258. let context_menu_textbox_item = document.createElement("div");
  259. let the_textbox = document.createElement("input");
  260. the_textbox.placeholder = text;
  261. if (typeof(onchange) === typeof(lorahelper.noop_func)){
  262. the_textbox.addEventListener("change", onchange);
  263. the_textbox.addEventListener("keypress", onchange);
  264. the_textbox.addEventListener("paste", onchange);
  265. the_textbox.addEventListener("input", onchange);
  266. }
  267. context_menu_textbox_item.classList.add('item');
  268. context_menu_textbox_item.appendChild(the_textbox);
  269. return context_menu_textbox_item;
  270. }
  271. function create_context_menu_iframe(href){
  272. let context_menu_iframe_item = document.createElement("div");
  273. let the_iframe = document.createElement("iframe");
  274. the_iframe.setAttribute("src", href);
  275. the_iframe.style.width = "600px";
  276. the_iframe.style.height = "60vh";
  277. the_iframe.style.overflow = "scroll";
  278. context_menu_iframe_item.classList.add('item');
  279. context_menu_iframe_item.appendChild(the_iframe);
  280. return context_menu_iframe_item;
  281. }
  282. lorahelper.context_menu_edit_hr_item = null;
  283. function hide_edit_btn(){
  284. lorahelper.context_menu_edit_hr_item.style.display = "none";
  285. lorahelper.lorahelper_context_menu_edit_btn.style.display = "none";
  286. }
  287. function show_edit_btn(){
  288. lorahelper.context_menu_edit_hr_item.style.display = "block";
  289. lorahelper.lorahelper_context_menu_edit_btn.style.display = "block";
  290. }
  291. function build_mahiro_menu(event){
  292. const {clientX: mouseX, clientY: mouseY} = event;
  293. lorahelper.lorahelper_context_menu_list.innerHTML = "";
  294. lorahelper.lorahelper_context_menu_opt.innerHTML = "";
  295. const onimai_list = ["Mahiro", "Mihari", "Momiji", "Kaede", "Asahi", "Miyo"];
  296. for(const onimai of onimai_list){
  297. let mahiro_off_website = create_context_menu_button(`${onimai}!`);
  298. mahiro_off_website.setAttribute("onclick",`lorahelper.build_mahiro_iframe(event, '${onimai.toLowerCase()}')`);
  299. lorahelper.lorahelper_context_menu_list.appendChild(mahiro_off_website);
  300. }
  301. lorahelper.context_menu_search_box_item.style.display = "block";
  302. hide_edit_btn();
  303. show_lora_context_menu(mouseX, mouseY);
  304. event.stopPropagation();
  305. event.preventDefault();
  306. }
  307. function build_mahiro_iframe(event, mahiro){
  308. const {clientX: mouseX, clientY: mouseY} = event;
  309. window.setTimeout(
  310. ((mouseX, mouseY)=>{
  311. return ()=>{
  312. lorahelper.lorahelper_context_menu_list.innerHTML = "";
  313. let mahiro_off_website = create_context_menu_iframe(`https://onimai.jp/character/${mahiro}.html`);
  314. if(lorahelper.lorahelper_scope_div.clientWidth <= 600){
  315. mahiro_off_website.style.width = "100vw";
  316. } else {
  317. mahiro_off_website.style.width = "600px";
  318. }
  319. mahiro_off_website.style.overflow = "scroll";
  320. lorahelper.lorahelper_context_menu_list.appendChild(mahiro_off_website);
  321. lorahelper.context_menu_search_box_item.style.display = "none";
  322. hide_edit_btn();
  323. show_lora_context_menu(mouseX, mouseY);
  324. };
  325. })(mouseX, mouseY)
  326. , 50);
  327. close_lora_context_menu();
  328. event.stopPropagation();
  329. event.preventDefault();
  330. }
  331. function setup_context_menu(){
  332. lorahelper.lorahelper_context_menu = create_context_menu("lora-context-menu");
  333. lorahelper.lorahelper_sub_context_menu = create_context_menu("lora-sub-context-menu");
  334. //searching box
  335. lorahelper.context_menu_search_box_item = create_context_menu_textbox(lorahelper.my_getTranslation("Search..."), updateLoraHelperSearchingBox);
  336. lorahelper.lorahelper_context_menu_search_box = lorahelper.context_menu_search_box_item.querySelector("input");
  337. lorahelper.lorahelper_context_menu_search_box.setAttribute("id", "lora-context-menu-search-box");
  338. lorahelper.lorahelper_context_menu.appendChild(lorahelper.context_menu_search_box_item);
  339. //prompt list
  340. lorahelper.lorahelper_context_menu_list = create_context_menu_group();
  341. lorahelper.lorahelper_context_menu.appendChild(lorahelper.lorahelper_context_menu_list);
  342. //other option
  343. lorahelper.lorahelper_context_menu_opt = create_context_menu_group();
  344. lorahelper.lorahelper_context_menu.appendChild(lorahelper.lorahelper_context_menu_opt);
  345. //分隔線
  346. lorahelper.context_menu_edit_hr_item = create_context_menu_hr_item();
  347. lorahelper.lorahelper_context_menu.appendChild(lorahelper.context_menu_edit_hr_item);
  348. //編輯按鈕
  349. lorahelper.lorahelper_context_menu_edit_btn = create_context_menu_button(lorahelper.get_UI_display("edit prompt words..."))
  350. lorahelper.lorahelper_context_menu_edit_btn.classList.add('edit-btn');
  351. lorahelper.lorahelper_context_menu.appendChild(lorahelper.lorahelper_context_menu_edit_btn);
  352. //test
  353. /*let my_sub = create_context_subset("a", [
  354. create_context_subset("b", [
  355. create_context_menu_button("c"),
  356. create_context_menu_button("d")
  357. ]),
  358. create_context_menu_button("e"),
  359. create_context_menu_button("f")
  360. ]);
  361. lorahelper.lorahelper_context_menu.appendChild(my_sub);*/
  362. lorahelper.lorahelper_scope = document.querySelector("body");
  363. lorahelper.lorahelper_scope.addEventListener("click", (e) => {
  364. let flag = true;
  365. let available_context_menu = get_lora_context_menu_list();
  366. for (let the_context_menu of available_context_menu){
  367. flag = flag && (e.target.offsetParent != the_context_menu);
  368. }
  369. if(flag){
  370. close_lora_context_menu();
  371. }
  372. });
  373. lorahelper.lorahelper_scope.addEventListener("mousemove", (e) => {
  374. lorahelper.lorahelper_mouse_position.x = e.clientX;
  375. lorahelper.lorahelper_mouse_position.y = e.clientY;
  376. });
  377. lorahelper.lorahelper_scope.addEventListener("touchstart", (e) => {
  378. if(!lorahelper.is_nullptr(e.changedTouches)){
  379. const touches = e.changedTouches;
  380. lorahelper.lorahelper_mouse_position.x = touches[0].clientX;
  381. lorahelper.lorahelper_mouse_position.y = touches[0].clientY;
  382. }
  383. });
  384. add_lora_context_menu(lorahelper.lorahelper_context_menu);
  385. add_lora_context_menu(lorahelper.lorahelper_sub_context_menu);
  386. //只覆蓋目前螢幕可見範圍的div,用於計算右鍵選單位置
  387. lorahelper.lorahelper_scope_div = document.createElement("div");
  388. lorahelper.lorahelper_scope_div.style.position = "fixed";
  389. lorahelper.lorahelper_scope_div.style.top = "0px";
  390. lorahelper.lorahelper_scope_div.style.left = "0px";
  391. lorahelper.lorahelper_scope_div.style.width = "100vw";
  392. lorahelper.lorahelper_scope_div.style.height = "100vh";
  393. lorahelper.lorahelper_scope_div.style.zIndex = -1;
  394. lorahelper.lorahelper_scope_div.style.backgroundColor = "transparent";
  395. lorahelper.lorahelper_scope_div.style.opacity = 0;
  396. lorahelper.lorahelper_scope_div.style.pointerEvents = "none";
  397. lorahelper.lorahelper_scope.appendChild(lorahelper.lorahelper_scope_div);
  398. }
  399. lorahelper.show_lora_context_menu = show_lora_context_menu;
  400. lorahelper.close_lora_context_menu = close_lora_context_menu;
  401. lorahelper.create_context_menu_hr_item = create_context_menu_hr_item;
  402. lorahelper.create_context_menu_button = create_context_menu_button;
  403. lorahelper.create_context_subset = create_context_subset;
  404. lorahelper.show_edit_btn = show_edit_btn;
  405. lorahelper.build_mahiro_menu = build_mahiro_menu;
  406. lorahelper.build_mahiro_iframe = build_mahiro_iframe;
  407. lorahelper.setup_context_menu = setup_context_menu;
  408. }
  409. let module_loadded = false;
  410. document.addEventListener("DOMContentLoaded", () => {
  411. if (module_loadded) return;
  412. module_loadded = true;
  413. module_init();
  414. });
  415. document.addEventListener("load", () => {
  416. if (module_loadded) return;
  417. module_loadded = true;
  418. module_init();
  419. });
  420. })();