twitter-art-tags

Userscript for organizing art on twitter.

Stars
0
Committers
2

Bot releases are hidden (Show)

twitter-art-tags - v1.1.1

Published by poohcom1 4 months ago

// ==UserScript==
// @name        Twitter Art Tags
// @description Tag artwork on twitter and view it in a gallery. https://github.com/poohcom1/twitter-art-tags
// @version     1.1.1
// @author      poohcom1
// @match       https://x.com/*
// @grant       GM.setValue
// @grant       GM.getValue
// @grant       GM.deleteValue
// @grant       GM.listValues
// @grant       GM.registerMenuCommand
// @require     https://github.com/poohcom1/vanilla-context-menu/releases/download/v1.8.1/vanilla-context-menu.js
// ==/UserScript==

(()=>{"use strict";var e={376:(e,t,n)=>{n.d(t,{A:()=>s});var o=n(601),r=n.n(o),a=n(314),i=n.n(a)()(r());i.push([e.id,"/* Drop down */\n#tagInput {\n    width: 100%;\n    height: 25px;\n    font-family: TwitterChirp;\n    margin-top: 4px;\n    border-radius: 5px;\n}\n\n.tag-dropdown {\n    padding: 16px;\n    display: none;\n    position: absolute;\n    padding: 10px 20px;\n    color: inherit;\n    white-space: nowrap;\n    z-index: 1000; /* Ensure it is above other content */\n    font-family: TwitterChirp;\n    font-weight: 700;\n    max-width: 300px;\n}\n\n.tag {\n    height: 30px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n    font-family: TwitterChirp;\n    font-weight: 700;\n    border: 2px solid currentColor;\n    border-radius: 5px;\n}\n\n.tag svg {\n    stroke: currentcolor;\n    width: 20px;\n    height: 20px;\n    margin-right: 5px;\n}\n\n.tag__inactive {\n    opacity: 0.75;\n    border: 2px solid transparent;\n}\n\n#tagsContainer {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 8px;\n}\n",""]);const s=i},314:e=>{e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",o=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),o&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),o&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,o,r,a){"string"==typeof e&&(e=[[null,e,void 0]]);var i={};if(o)for(var s=0;s<this.length;s++){var l=this[s][0];null!=l&&(i[l]=!0)}for(var c=0;c<e.length;c++){var d=[].concat(e[c]);o&&i[d[0]]||(void 0!==a&&(void 0===d[5]||(d[1]="@layer".concat(d[5].length>0?" ".concat(d[5]):""," {").concat(d[1],"}")),d[5]=a),n&&(d[2]?(d[1]="@media ".concat(d[2]," {").concat(d[1],"}"),d[2]=n):d[2]=n),r&&(d[4]?(d[1]="@supports (".concat(d[4],") {").concat(d[1],"}"),d[4]=r):d[4]="".concat(r)),t.push(d))}},t}},601:e=>{e.exports=function(e){return e[1]}}},t={};function n(o){var r=t[o];if(void 0!==r)return r.exports;var a=t[o]={id:o,exports:{}};return e[o](a,a.exports,n),a.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var o in t)n.o(t,o)&&!n.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{const e={allowedChars:/^[a-zA-Z0-9 ]+$/,maxLength:20};function t(t){return e.allowedChars.test(t.key)||"Enter"===t.key||"Backspace"===t.key||"Delete"===t.key}function o(e){return e.trim().toLowerCase()}function r(e){return e.split(" ").map((e=>e.charAt(0).toUpperCase()+e.slice(1))).join(" ")}async function a(e,t=document){return new Promise((n=>{{const o=t.querySelector(e);o&&n(o)}const o=setInterval((()=>{const r=t.querySelector(e);r&&(clearInterval(o),n(r))}),10);setTimeout((()=>{clearInterval(o),n(null)}),5e3)}))}const i=new DOMParser;function s(e){return i.parseFromString(e,"text/html").body.firstChild}var l,c,d,u;function p(e,t,n,o,r){const a=r&&"input"in r?r.input:n.value,i=r?.expected??e.expects,s=r?.received??function(e){let t=typeof e;return"object"===t&&(t=(e&&Object.getPrototypeOf(e)?.constructor?.name)??"null"),"string"===t?`"${e}"`:"number"===t||"bigint"===t||"boolean"===t?`${e}`:t}(a),l={kind:e.kind,type:e.type,input:a,expected:i,received:s,message:`Invalid ${t}: ${i?`Expected ${i} but r`:"R"}eceived ${s}`,requirement:e.requirement,path:r?.path,issues:r?.issues,lang:o.lang,abortEarly:o.abortEarly,abortPipeEarly:o.abortPipeEarly,skipPipe:o.skipPipe},p="schema"===e.kind,g=e.message??(w=e.reference,y=l.lang,u?.get(w)?.get(y))??(p?function(e){return d?.get(e)}(l.lang):null)??o.message??function(e){return c?.get(e)}(l.lang);var w,y;g&&(l.message="function"==typeof g?g(l):g),p&&(n.typed=!1),n.issues?n.issues.push(l):n.issues=[l]}function g(e){return"__proto__"!==e&&"prototype"!==e&&"constructor"!==e}function w(e,t){return{kind:"schema",type:"array",reference:w,expects:"Array",async:!1,item:e,message:t,_run(e,t){const n=e.value;if(Array.isArray(n)){e.typed=!0,e.value=[];for(let o=0;o<n.length;o++){const r=n[o],a=this.item._run({typed:!1,value:r},t);if(a.issues){const i={type:"array",origin:"value",input:n,key:o,value:r};for(const t of a.issues)t.path?t.path.unshift(i):t.path=[i],e.issues?.push(t);if(e.issues||(e.issues=a.issues),t.abortEarly){e.typed=!1;break}}a.typed||(e.typed=!1),e.value.push(a.value)}}else p(this,"type",e,t);return e}}}function y(e,t){return{kind:"schema",type:"object",reference:y,expects:"Object",async:!1,entries:e,message:t,_run(e,t){const n=e.value;if(n&&"object"==typeof n){e.typed=!0,e.value={};for(const o in this.entries){const r=n[o],a=this.entries[o]._run({typed:!1,value:r},t);if(a.issues){const i={type:"object",origin:"value",input:n,key:o,value:r};for(const t of a.issues)t.path?t.path.unshift(i):t.path=[i],e.issues?.push(t);if(e.issues||(e.issues=a.issues),t.abortEarly){e.typed=!1;break}}a.typed||(e.typed=!1),(void 0!==a.value||o in n)&&(e.value[o]=a.value)}}else p(this,"type",e,t);return e}}}function v(e,t,n){return{kind:"schema",type:"record",reference:v,expects:"Object",async:!1,key:e,value:t,message:n,_run(e,t){const n=e.value;if(n&&"object"==typeof n){e.typed=!0,e.value={};for(const o in n)if(g(o)){const r=n[o],a=this.key._run({typed:!1,value:o},t);if(a.issues){const i={type:"record",origin:"key",input:n,key:o,value:r};for(const t of a.issues)t.path=[i],e.issues?.push(t);if(e.issues||(e.issues=a.issues),t.abortEarly){e.typed=!1;break}}const i=this.value._run({typed:!1,value:r},t);if(i.issues){const a={type:"record",origin:"value",input:n,key:o,value:r};for(const t of i.issues)t.path?t.path.unshift(a):t.path=[a],e.issues?.push(t);if(e.issues||(e.issues=i.issues),t.abortEarly){e.typed=!1;break}}a.typed&&i.typed||(e.typed=!1),a.typed&&(e.value[a.value]=i.value)}}else p(this,"type",e,t);return e}}}function h(e){return{kind:"schema",type:"string",reference:h,expects:"string",async:!1,message:e,_run(e,t){return"string"==typeof e.value?e.typed=!0:p(this,"type",e,t),e}}}function f(e,t,n){const o=e._run({typed:!1,value:t},function(e){return{lang:e?.lang??l?.lang,message:e?.message,abortEarly:e?.abortEarly??l?.abortEarly,abortPipeEarly:e?.abortPipeEarly??l?.abortPipeEarly,skipPipe:e?.skipPipe}}(n));return{typed:o.typed,success:!o.issues,output:o.value,issues:o.issues}}Error;const m=y({images:w(h())}),C=v(h(),m),x=y({tweets:w(h()),lastUpdated:function e(t){return{kind:"schema",type:"number",reference:e,expects:"number",async:!1,message:t,_run(e,t){return"number"!=typeof e.value||isNaN(e.value)?p(this,"type",e,t):e.typed=!0,e}}}()}),k=y({tweets:C,tags:v(h(),x)}),b="tags",M="tweets";async function L(e,t,n){if(null===e)return void console.error("No tweet selected");if(""===(t=o(t)))return void console.error("Invalid tag name");const r=await GM.getValue(b,{});let a={tweets:[],lastUpdated:Date.now()};t in r?a=r[t]:r[t]=a,a.lastUpdated=Date.now(),a.tweets.includes(e)||a.tweets.push(e),await GM.setValue(b,r);const i=await GM.getValue(M,{});n.length>0&&(i[e]={images:n}),await GM.setValue(M,i)}async function S(e,t){if(null===e)return void console.error("No tweet selected");if(""===(t=o(t)))return void console.error("Invalid tag name");const n=await GM.getValue(b,{});t in n&&(n[t].tweets=n[t].tweets.filter((t=>t!==e)),await GM.setValue(b,n))}async function V(){return GM.getValue(b,{})}async function T(){return GM.getValue(M,{})}const E='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M8 8H8.01M2 5.2L2 9.67451C2 10.1637 2 10.4083 2.05526 10.6385C2.10425 10.8425 2.18506 11.0376 2.29472 11.2166C2.4184 11.4184 2.59135 11.5914 2.93726 11.9373L10.6059 19.6059C11.7939 20.7939 12.388 21.388 13.0729 21.6105C13.6755 21.8063 14.3245 21.8063 14.927 21.6105C15.612 21.388 16.2061 20.7939 17.3941 19.6059L19.6059 17.3941C20.7939 16.2061 21.388 15.612 21.6105 14.927C21.8063 14.3245 21.8063 13.6755 21.6105 13.0729C21.388 12.388 20.7939 11.7939 19.6059 10.6059L11.9373 2.93726C11.5914 2.59135 11.4184 2.4184 11.2166 2.29472C11.0376 2.18506 10.8425 2.10425 10.6385 2.05526C10.4083 2 10.1637 2 9.67452 2L5.2 2C4.0799 2 3.51984 2 3.09202 2.21799C2.7157 2.40973 2.40973 2.71569 2.21799 3.09202C2 3.51984 2 4.07989 2 5.2ZM8.5 8C8.5 8.27614 8.27614 8.5 8 8.5C7.72386 8.5 7.5 8.27614 7.5 8C7.5 7.72386 7.72386 7.5 8 7.5C8.27614 7.5 8.5 7.72386 8.5 8Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',G='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">\r\n    <path\r\n        d="M7.8 21H16.2C17.8802 21 18.7202 21 19.362 20.673C19.9265 20.3854 20.3854 19.9265 20.673 19.362C21 18.7202 21 17.8802 21 16.2V7.8C21 6.11984 21 5.27976 20.673 4.63803C20.3854 4.07354 19.9265 3.6146 19.362 3.32698C18.7202 3 17.8802 3 16.2 3H7.8C6.11984 3 5.27976 3 4.63803 3.32698C4.07354 3.6146 3.6146 4.07354 3.32698 4.63803C3 5.27976 3 6.11984 3 7.8V16.2C3 17.8802 3 18.7202 3.32698 19.362C3.6146 19.9265 4.07354 20.3854 4.63803 20.673C5.27976 21 6.11984 21 7.8 21Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none" />\r\n</svg>',H='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M9 11L12 14L22 4M16 3H7.8C6.11984 3 5.27976 3 4.63803 3.32698C4.07354 3.6146 3.6146 4.07354 3.32698 4.63803C3 5.27976 3 6.11984 3 7.8V16.2C3 17.8802 3 18.7202 3.32698 19.362C3.6146 19.9265 4.07354 20.3854 4.63803 20.673C5.27976 21 6.11984 21 7.8 21H16.2C17.8802 21 18.7202 21 19.362 20.673C19.9265 20.3854 20.3854 19.9265 20.673 19.362C21 18.7202 21 17.8802 21 16.2V12"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',_="/twitter-art-tags/gallery",j="Tags / X";function q(e){return Array.from(document.querySelectorAll("a")).filter((t=>t.href.includes(e))).flatMap((e=>Array.from(e.querySelectorAll("img")))).map((e=>e.src))}const I='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M2.42012 12.7132C2.28394 12.4975 2.21584 12.3897 2.17772 12.2234C2.14909 12.0985 2.14909 11.9015 2.17772 11.7766C2.21584 11.6103 2.28394 11.5025 2.42012 11.2868C3.54553 9.50484 6.8954 5 12.0004 5C17.1054 5 20.4553 9.50484 21.5807 11.2868C21.7169 11.5025 21.785 11.6103 21.8231 11.7766C21.8517 11.9015 21.8517 12.0985 21.8231 12.2234C21.785 12.3897 21.7169 12.4975 21.5807 12.7132C20.4553 14.4952 17.1054 19 12.0004 19C6.8954 19 3.54553 14.4952 2.42012 12.7132Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n    <path\r\n        d="M12.0004 15C13.6573 15 15.0004 13.6569 15.0004 12C15.0004 10.3431 13.6573 9 12.0004 9C10.3435 9 9.0004 10.3431 9.0004 12C9.0004 13.6569 10.3435 15 12.0004 15Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',$='<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M20 10.5V6.8C20 5.11984 20 4.27976 19.673 3.63803C19.3854 3.07354 18.9265 2.6146 18.362 2.32698C17.7202 2 16.8802 2 15.2 2H8.8C7.11984 2 6.27976 2 5.63803 2.32698C5.07354 2.6146 4.6146 3.07354 4.32698 3.63803C4 4.27976 4 5.11984 4 6.8V17.2C4 18.8802 4 19.7202 4.32698 20.362C4.6146 20.9265 5.07354 21.3854 5.63803 21.673C6.27976 22 7.11984 22 8.8 22H12M18 21V15M15 18H21"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',R='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M20 11.9412V6.8C20 5.11984 20 4.27976 19.673 3.63803C19.3854 3.07354 18.9265 2.6146 18.362 2.32698C17.7202 2 16.8802 2 15.2 2H8.8C7.11984 2 6.27976 2 5.63803 2.32698C5.07354 2.6146 4.6146 3.07354 4.32698 3.63803C4 4.27976 4 5.11984 4 6.8V17.2C4 18.8802 4 19.7202 4.32698 20.362C4.6146 20.9265 5.07354 21.3854 5.63803 21.673C6.27976 22 7.11984 22 8.8 22H14M15 17H21"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',O='<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M21 9.00001L21 3.00001M21 3.00001H15M21 3.00001L12 12M10 3H7.8C6.11984 3 5.27976 3 4.63803 3.32698C4.07354 3.6146 3.6146 4.07354 3.32698 4.63803C3 5.27976 3 6.11984 3 7.8V16.2C3 17.8802 3 18.7202 3.32698 19.362C3.6146 19.9265 4.07354 20.3854 4.63803 20.673C5.27976 21 6.11984 21 7.8 21H16.2C17.8802 21 18.7202 21 19.362 20.673C19.9265 20.3854 20.3854 19.9265 20.673 19.362C21 18.7202 21 17.8802 21 16.2V14"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',A='<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M12 20.0002H21M3 20.0002H4.67454C5.16372 20.0002 5.40832 20.0002 5.63849 19.945C5.84256 19.896 6.03765 19.8152 6.2166 19.7055C6.41843 19.5818 6.59138 19.4089 6.93729 19.063L19.5 6.50023C20.3285 5.6718 20.3285 4.32865 19.5 3.50023C18.6716 2.6718 17.3285 2.6718 16.5 3.50023L3.93726 16.063C3.59136 16.4089 3.4184 16.5818 3.29472 16.7837C3.18506 16.9626 3.10425 17.1577 3.05526 17.3618C3 17.5919 3 17.8365 3 18.3257V20.0002Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',N='<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M16 6V5.2C16 4.0799 16 3.51984 15.782 3.09202C15.5903 2.71569 15.2843 2.40973 14.908 2.21799C14.4802 2 13.9201 2 12.8 2H11.2C10.0799 2 9.51984 2 9.09202 2.21799C8.71569 2.40973 8.40973 2.71569 8.21799 3.09202C8 3.51984 8 4.0799 8 5.2V6M10 11.5V16.5M14 11.5V16.5M3 6H21M19 6V17.2C19 18.8802 19 19.7202 18.673 20.362C18.3854 20.9265 17.9265 21.3854 17.362 21.673C16.7202 22 15.8802 22 14.2 22H9.8C8.11984 22 7.27976 22 6.63803 21.673C6.07354 21.3854 5.6146 20.9265 5.32698 20.362C5 19.7202 5 18.8802 5 17.2V6"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',B="tagImage",P="tags",U="imageGallery",D="context-menu-icon",Z="tag__inactive",z="image-container",F="image-container__hover",J="image-container__loaded",Y="image-container__skeleton",W=[];let X=[],K=!1;async function Q(e=!1){const t=document.querySelector("#"+U);e&&(t.innerHTML=`<div class="${z} ${Y}"></div>`.repeat(15));const[n,o]=await Promise.all([V(),T()]),a=Object.keys(n).filter((e=>X.includes(e))).reduce(((e,t)=>e.filter((e=>n[t].tweets.includes(e)))),Object.keys(o)).reverse().filter((e=>e in o)).flatMap((e=>o[e].images.map(((t,n)=>({tweetId:e,image:t,index:n,element:s(`\n                    <a id="${B}__${e}__${n}" class="${z} ${J}" href="/poohcom1/status/${e}" target="_blank">\n                        <img src="${t}" />\n                    </a>`)})))));t.innerHTML="",t.append(...a.map((e=>e.element))),0===Object.keys(n).length?t.innerHTML='<h3>No tags yet!<br>Add one by clicking on the "..." menu of a tweet with images and selected "Tag Tweet"</h3>':0===a.length&&(t.innerHTML="<h3>Nothing to see here!</h3>"),a.forEach((e=>{const t=a.filter((t=>t.tweetId===e.tweetId)),o=()=>{K||(document.querySelectorAll("."+F).forEach((e=>e.classList.remove(F))),t.forEach((e=>e.element.classList.add(F))))},i=()=>{K||t.forEach((e=>e.element.classList.remove(F)))},s=()=>{K=!1,i()};e.element.addEventListener("mouseover",o),e.element.addEventListener("mouseout",i),e.element.addEventListener("contextmenu",(()=>{K=!1,o(),K=!0})),document.addEventListener("click",s),W.push((()=>{document.removeEventListener("click",s)}));const l=[{label:"Open image",iconHTML:oe(I),callback:t=>{console.log(t.target),window.open(e.image,"_blank")}},{label:"Open tweet",iconHTML:oe(O),callback:()=>{window.open(`/poohcom1/status/${e.tweetId}`,"_blank")}}],c=[],d=Object.keys(n).filter((t=>!n[t].tweets.includes(e.tweetId))),u=Object.keys(n).filter((t=>n[t].tweets.includes(e.tweetId)));d.length>0&&c.push({label:"Add to",iconHTML:oe($),preventCloseOnClick:!0,nestedMenu:d.map((t=>({label:r(t),iconHTML:oe($),callback:async()=>{await L(e.tweetId,t,[]),te()}})))}),u.length>0&&c.push({label:"Remove from",iconHTML:oe(R),preventCloseOnClick:!0,nestedMenu:u.map((t=>({label:r(t),iconHTML:oe(R),callback:async()=>{await S(e.tweetId,t),te()}})))}),c.push({label:"Remove tweet",iconHTML:oe(N),callback:async()=>{confirm("Are you sure you want to remove this tweet from all tags?")&&(await async function(e){const t=await GM.getValue(b,{});for(const n of Object.values(t))n.tweets=n.tweets.filter((t=>t!==e));await GM.setValue(b,t);const n=await GM.getValue(M,{});delete n[e],await GM.setValue(M,n)}(e.tweetId),te())}});const p=Object.keys(n).filter((t=>n[t].tweets.includes(e.tweetId))).map((e=>({label:r(e),iconHTML:oe(E),callback:()=>{X=[e],te()}}))),g=[...l,"hr",...c];p.length>0&&(g.push("hr"),g.push(...p)),new VanillaContextMenu({scope:e.element,transitionDuration:0,menuItems:g,normalizePosition:!0,customNormalizeScope:document.querySelector("main").firstElementChild,openSubMenuOnHover:!0})}))}async function ee(){const[t,n]=await Promise.all([V(),T()]),a=Object.keys(t),i=document.querySelector("#"+P);a.sort(((e,t)=>e.localeCompare(t)));const l=a.map((a=>{const i=X.includes(a),l=t[a].tweets.map((e=>n[e].images.length)).reduce(((e,t)=>e+t),0),c=s(`<button class="tag ${!i&&Z}">\n                ${i?H:G}\n                <div class="text">${r(a)} (${l})</div>\n            </button>`);return c.addEventListener("click",(()=>{i?X=X.filter((e=>e!==a)):X.push(a),te(!0)})),new VanillaContextMenu({scope:c,normalizePosition:!1,transitionDuration:0,menuItems:[{label:"Rename",iconHTML:oe(A),callback:async()=>{let t;for(;;){if(t=prompt("Enter new tag name:",r(a)),!t)return;if((n=t)&&e.allowedChars.test(n))break;alert("Invalid tag name! Tag names can only contain letters, numbers, and spaces.")}var n;await async function(e,t){if(e=o(e),t=o(t),""===e||""===t)return void console.error("Invalid tag name");if(e===t)return;const n=await GM.getValue(b,{});e in n&&(t in n?alert("Tag already exists"):(n[t]=n[e],delete n[e],await GM.setValue(b,n)))}(a,t),te()}},{label:"Delete",iconHTML:oe(N),callback:async()=>{confirm("Are you sure you want to delete this tag?")&&(await async function(e){const t=await GM.getValue(b,{});e in t&&(delete t[e],await GM.setValue(b,t))}(a),te())}}]}),c}));i.innerHTML="",i.append(...l)}function te(e=!1){ee(),Q(e),W.forEach((e=>e())),W.length=0}async function ne(){const n=(await a('div[data-testid="error-detail"]')).parentElement;n.style.maxWidth="100%",n.innerHTML='<div> <style>.root{padding:40px 0;font-family:TwitterChirp;stroke:currentcolor}.title{display:flex;align-items:center}.title div{margin-left:auto;width:50px;height:50px;display:flex;align-items:center;justify-content:center}.title div svg{width:20px;height:20px;opacity:.5}#imageGallery{display:flex;flex-wrap:wrap;gap:10px}@keyframes fadeIn{from{opacity:.2}to{opacity:1}}.image-container{width:200px;height:200px;overflow:hidden;padding:0;margin:0;border-radius:5px}.image-container__loaded{background-color:#aaa;animation:.1s fadeIn ease-in-out}.image-container img{object-fit:cover;width:100%;height:100%}.image-container__hover{outline:4px solid #8c8cff}@keyframes loading{to{background-position-x:-20%}}.image-container__skeleton{background-color:#aaa;background:linear-gradient(100deg,rgba(255,255,255,0) 0,rgba(255,255,255,.7) 50%,rgba(255,255,255,0) 70%) #aaa;background-size:200% 100%;background-position-x:180%;animation:.5s loading linear infinite;opacity:.2}#tagSelect{width:200px}#tags{display:flex;flex-wrap:wrap;gap:10px;margin:10px 0}#addTag{width:200px}button{display:flex;align-items:center}svg.button-icon{display:inline-block;stroke:currentcolor;fill:none;width:20px;height:20px;margin-right:5px}.context-menu-icon{width:14px;height:14px;stroke:currentcolor;opacity:.8;margin-right:2px}</style> <div class="root"> <div class="title"> <h1>Tag Gallery</h1> <div title="Right click to view more options for tags and images"> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M9.09 9C9.3251 8.33167 9.78915 7.76811 10.4 7.40913C11.0108 7.05016 11.7289 6.91894 12.4272 7.03871C13.1255 7.15849 13.7588 7.52152 14.2151 8.06353C14.6713 8.60553 14.9211 9.29152 14.92 10C14.92 12 11.92 13 11.92 13M12 17H12.01M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </div> </div> <div style="display:flex;gap:10px"> <svg style="width:20px;stroke:currentcolor;fill:none" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M8 8H8.01M2 5.2L2 9.67451C2 10.1637 2 10.4083 2.05526 10.6385C2.10425 10.8425 2.18506 11.0376 2.29472 11.2166C2.4184 11.4184 2.59135 11.5914 2.93726 11.9373L10.6059 19.6059C11.7939 20.7939 12.388 21.388 13.0729 21.6105C13.6755 21.8063 14.3245 21.8063 14.927 21.6105C15.612 21.388 16.2061 20.7939 17.3941 19.6059L19.6059 17.3941C20.7939 16.2061 21.388 15.612 21.6105 14.927C21.8063 14.3245 21.8063 13.6755 21.6105 13.0729C21.388 12.388 20.7939 11.7939 19.6059 10.6059L11.9373 2.93726C11.5914 2.59135 11.4184 2.4184 11.2166 2.29472C11.0376 2.18506 10.8425 2.10425 10.6385 2.05526C10.4083 2 10.1637 2 9.67452 2L5.2 2C4.0799 2 3.51984 2 3.09202 2.21799C2.7157 2.40973 2.40973 2.71569 2.21799 3.09202C2 3.51984 2 4.07989 2 5.2ZM8.5 8C8.5 8.27614 8.27614 8.5 8 8.5C7.72386 8.5 7.5 8.27614 7.5 8C7.5 7.72386 7.72386 7.5 8 7.5C8.27614 7.5 8.5 7.72386 8.5 8Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> <input id="addTag" type="text" placeholder="Press enter to add a tag..."/> <button id="tagExport" style="margin-left:auto"> <svg class="button-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M15 8H8.6C8.03995 8 7.75992 8 7.54601 7.89101C7.35785 7.79513 7.20487 7.64215 7.10899 7.45399C7 7.24008 7 6.96005 7 6.4V3M17 21V14.6C17 14.0399 17 13.7599 16.891 13.546C16.7951 13.3578 16.6422 13.2049 16.454 13.109C16.2401 13 15.9601 13 15.4 13H8.6C8.03995 13 7.75992 13 7.54601 13.109C7.35785 13.2049 7.20487 13.3578 7.10899 13.546C7 13.7599 7 14.0399 7 14.6V21M21 9.32548V16.2C21 17.8802 21 18.7202 20.673 19.362C20.3854 19.9265 19.9265 20.3854 19.362 20.673C18.7202 21 17.8802 21 16.2 21H7.8C6.11984 21 5.27976 21 4.63803 20.673C4.07354 20.3854 3.6146 19.9265 3.32698 19.362C3 18.7202 3 17.8802 3 16.2V7.8C3 6.11984 3 5.27976 3.32698 4.63803C3.6146 4.07354 4.07354 3.6146 4.63803 3.32698C5.27976 3 6.11984 3 7.8 3H14.6745C15.1637 3 15.4083 3 15.6385 3.05526C15.8425 3.10425 16.0376 3.18506 16.2166 3.29472C16.4184 3.4184 16.5914 3.59135 16.9373 3.93726L20.0627 7.06274C20.4086 7.40865 20.5816 7.5816 20.7053 7.78343C20.8149 7.96237 20.8957 8.15746 20.9447 8.36154C21 8.59171 21 8.8363 21 9.32548Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Export Tags </button> <button id="tagImport"> <svg class="button-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M21 12V16.2C21 17.8802 21 18.7202 20.673 19.362C20.3854 19.9265 19.9265 20.3854 19.362 20.673C18.7202 21 17.8802 21 16.2 21H7.8C6.11984 21 5.27976 21 4.63803 20.673C4.07354 20.3854 3.6146 19.9265 3.32698 19.362C3 18.7202 3 17.8802 3 16.2V12M16 7L12 3M12 3L8 7M12 3V15" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Import Tags </button> </div> <hr/> <div id="tags"></div> <div id="imageGallery"></div> </div> </div> ',document.title=j;const r=new MutationObserver((e=>{e.forEach((e=>{"childList"===e.type&&(document.title!==j&&(document.title=j,r.disconnect()),window.location.href.includes(j)||r.disconnect())}))}));r.observe(document.querySelector("title"),{childList:!0});const i=document.querySelector("#addTag");i.maxLength=e.maxLength,i.addEventListener("keydown",(async e=>{const n=e.target;t(e)?"Enter"===e.key&&(console.log(n.value),await async function(e){if(""===(e=o(e)))return void console.error("Invalid tag name");const t=await GM.getValue(b,{});e in t?alert("Tag already exists"):(t[e]={tweets:[],lastUpdated:Date.now()},await GM.setValue(b,t))}(n.value),n.value="",ee()):e.preventDefault()})),document.querySelector("#tagExport").addEventListener("click",(async()=>{const e=await async function(){const e={tags:await V(),tweets:await T()};return JSON.stringify(e,null,2)}(),t=new Blob([e],{type:"application/json"}),n=URL.createObjectURL(t),o=document.createElement("a");o.href=n,o.download=function(){const e=new Date;return`twitter-art-tag_data_${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}_${String(e.getHours()).padStart(2,"0")}.json`}(),o.click(),URL.revokeObjectURL(n)})),document.querySelector("#tagImport").addEventListener("click",(async()=>{const e=document.createElement("input");e.type="file",e.accept="application/json",e.style.display="none",e.addEventListener("change",(async()=>{const t=e.files[0],n=new FileReader;n.onload=async()=>{confirm("Are you sure you want to overwrite all tags?")&&(await async function(e){const t=JSON.parse(e),n=f(k,t);n.success?(await GM.setValue(b,n.output.tags),await GM.setValue(M,n.output.tweets)):(console.error(n.issues),alert("Failed to import data due to potentially corrupted file. Check the console for more information."))}(n.result),te())},n.readAsText(t)})),e.click()})),await ee(),Q(!0)}function oe(e){const t=s(e);return t.classList.add(D),t.outerHTML}var re=n(376);const ae="tagsPageButton",ie={"rgb(0, 0, 0)":"rgb(22, 24, 28)","rgb(255, 255, 255)":"rgb(247, 249, 249)","rgb(21, 32, 43)":"rgb(30, 39, 50)"};GM.registerMenuCommand("Twitter Art Tags - View tags",(()=>window.location.href=window.location.origin+_)),GM.registerMenuCommand("Twitter Art Tags - Clear all tags",(async function(){confirm("Are you sure you want to delete all tags?")&&await GM.deleteValue(b)})),function(e){const t=document.getElementsByTagName("head")[0];if(t){const n=document.createElement("style");n.setAttribute("type","text/css"),n.textContent=e,t.appendChild(n)}}(re.A),async function(){await a("nav")&&new MutationObserver((e=>{e.forEach((async e=>{if(e.addedNodes.length>0){const t=e.addedNodes[0],n=await a('div[data-testid="Dropdown"]',t);if(!n)return;if(n.querySelector(`#${ae}`))return;const o=t.querySelector('div[role="menu"]'),r=ie[window.getComputedStyle(o).backgroundColor],i=n.querySelector('a[href="/settings"]'),s=null==i?void 0:i.parentElement;if(!i||!s)return;const l=s.cloneNode(!0);l.id=ae,l.querySelector("a").href=_,l.querySelector("a").style.backgroundColor="transparent",l.querySelector("a").onmouseenter=()=>l.querySelector("a").style.backgroundColor=null!=r?r:"",l.querySelector("a").onmouseleave=()=>l.querySelector("a").style.backgroundColor="transparent",l.querySelector("svg").innerHTML=E,l.querySelector("svg").style.stroke="currentcolor",l.querySelector("span").innerText="Tags",s.parentElement.prepend(l)}}))})).observe(document.getElementById("layers"),{childList:!0})}(),async function(){let n=null,o=null,i=null,l=null;const c=document.createElement("div");c.innerHTML='<input id="tagInput" type="text" placeholder="Add a tag..." /><hr style="width: 100%" /><div id="tagsContainer"/>',c.classList.add("tag-dropdown"),document.body.appendChild(c);const d=c.querySelector("#tagInput");d.maxLength=e.maxLength,d.addEventListener("keydown",(async e=>{const r=e.target;if(t(e))if("Enter"===e.key){if(null===n)return void console.error("No tweet selected");await L(n,r.value,q(n)),r.value="",null==o||o()}else null==o||o();else e.preventDefault()}));const u=c.querySelector("#tagsContainer");function p(){u.innerHTML=""}await a("#layers"),new MutationObserver((e=>{e.forEach((async e=>{if(e.addedNodes.length>0){a('div[role="menu"]',e.addedNodes[0]).then((e=>{if(!e)return;const t=window.getComputedStyle(e);c.style.border=t.border,c.style.borderRadius=t.borderRadius,c.style.backgroundColor=t.backgroundColor,c.style.color=t.color,c.style.boxShadow=t.boxShadow}));const t=await a('div[data-testid="Dropdown"]',e.addedNodes[0]);if(!t)return;let g="";const w=t.querySelectorAll("a");for(const e of w)if(e.href.includes("/status/")){g=e.href.split("/")[5];break}if(!g)return;n=g;const y=g+"_tagButton";if(document.getElementById(y))return;const v=q(g);if(0===v.length)return;!function(e,t,a){if(null===i||null===l){i=e.childNodes[0].cloneNode(!0),l=i.cloneNode(!0);const t=i.querySelector("svg").classList;i.querySelector("span").innerText="Tag Tweet",i.querySelector("svg").outerHTML=E,i.querySelector("svg").classList.add(...t),i.querySelector("svg").style.stroke="currentColor",i.querySelector("svg").style.fill="transparent",l.querySelector("span").innerText="View Tags",l.querySelector("svg").outerHTML='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M21 11L13.4059 3.40589C12.887 2.88703 12.6276 2.6276 12.3249 2.44208C12.0564 2.27759 11.7638 2.15638 11.4577 2.08289C11.1124 2 10.7455 2 10.0118 2L6 2M3 8.7L3 10.6745C3 11.1637 3 11.4083 3.05526 11.6385C3.10425 11.8425 3.18506 12.0376 3.29472 12.2166C3.4184 12.4184 3.59136 12.5914 3.93726 12.9373L11.7373 20.7373C12.5293 21.5293 12.9253 21.9253 13.382 22.0737C13.7837 22.2042 14.2163 22.2042 14.618 22.0737C15.0747 21.9253 15.4707 21.5293 16.2627 20.7373L18.7373 18.2627C19.5293 17.4707 19.9253 17.0747 20.0737 16.618C20.2042 16.2163 20.2042 15.7837 20.0737 15.382C19.9253 14.9253 19.5293 14.5293 18.7373 13.7373L11.4373 6.43726C11.0914 6.09136 10.9184 5.9184 10.7166 5.79472C10.5376 5.68506 10.3425 5.60425 10.1385 5.55526C9.90829 5.5 9.6637 5.5 9.17452 5.5H6.2C5.0799 5.5 4.51984 5.5 4.09202 5.71799C3.7157 5.90973 3.40973 6.21569 3.21799 6.59202C3 7.01984 3 7.57989 3 8.7Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',l.querySelector("svg").classList.add(...t),l.querySelector("svg").style.stroke="currentColor",l.querySelector("svg").style.fill="transparent",l.querySelector("svg").style.height="20px",l.querySelector("svg").style.width="20px",l.addEventListener("click",(async()=>{window.location.href=window.location.origin+_}))}i.id=t,i.addEventListener("click",(async()=>{const e=i.getBoundingClientRect();async function t(){if(null===n)return;const e=await V(),o=await async function(e){if(null===e)return console.error("No tweet selected"),[];const t=await V(),n=Object.keys(t);return n.sort(((e,t)=>e.localeCompare(t))),n}(n),i=o.filter((e=>e.toLowerCase().includes(d.value.toLowerCase())));if(p(),0===o.length)return void(u.innerHTML="No tags yet!");if(0===i.length)return void(u.innerHTML=`<div style="overflow: hidden; text-overflow: ellipsis; max-width: 200px">Create a new tag: ${d.value}</div>`);const l=i.map((t=>function(e,t){return s(`<button id="${e}" class="tag ${!t&&"tag__inactive"}">\n            ${t?H:G}\n            <div class="text">${r(e)}</div>\n        </button>`)}(t,e[t].tweets.includes(null!=n?n:""))));u.append(...l);for(const o of c.querySelectorAll(".tag"))o.addEventListener("click",(async()=>{if(null===n)return void console.error("No tweet selected");const r=o.id;r in e&&e[r].tweets.includes(n)?await S(n,r):await L(n,r,a),t()}))}c.style.top=`${e.top+window.scrollY}px`,c.style.left=`${e.right+10}px`,c.style.display="block",t(),o=t,d.focus()})),e.prepend(l),e.prepend(i)}(t,y,v)}else e.removedNodes.length>0&&(c.style.display="none",p(),o=null)}))})).observe(document.getElementById("layers"),{childList:!0})}(),window.location.href.includes(_)&&ne(),window.addEventListener("popstate",(e=>{window.location.href.includes(_)&&ne()}))})()})();

Changes:

  • fix: title on tag page nav
twitter-art-tags - v1.1.0

Published by poohcom1 4 months ago

// ==UserScript==
// @name        Twitter Art Tags
// @description Tag artwork on twitter and view it in a gallery
// @version     1.1.0
// @author      poohcom1
// @match       https://x.com/*
// @grant       GM.setValue
// @grant       GM.getValue
// @grant       GM.deleteValue
// @grant       GM.listValues
// @grant       GM.registerMenuCommand
// @require     https://github.com/poohcom1/vanilla-context-menu/releases/download/v1.8.1/vanilla-context-menu.js
// ==/UserScript==

(()=>{"use strict";var e={376:(e,t,n)=>{n.d(t,{A:()=>i});var r=n(601),o=n.n(r),a=n(314),s=n.n(a)()(o());s.push([e.id,"/* Drop down */\n#tagInput {\n    width: 100%;\n    height: 25px;\n    font-family: TwitterChirp;\n    margin-top: 4px;\n    border-radius: 5px;\n}\n\n.tag-dropdown {\n    padding: 16px;\n    display: none;\n    position: absolute;\n    padding: 10px 20px;\n    color: inherit;\n    white-space: nowrap;\n    z-index: 1000; /* Ensure it is above other content */\n    font-family: TwitterChirp;\n    font-weight: 700;\n    max-width: 300px;\n}\n\n.tag {\n    height: 30px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n    font-family: TwitterChirp;\n    font-weight: 700;\n    border: 2px solid currentColor;\n    border-radius: 5px;\n}\n\n.tag svg {\n    stroke: currentcolor;\n    width: 20px;\n    height: 20px;\n    margin-right: 5px;\n}\n\n.tag__inactive {\n    opacity: 0.75;\n    border: 2px solid transparent;\n}\n\n#tagsContainer {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 8px;\n}\n",""]);const i=s},314:e=>{e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",r=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),r&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),r&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,r,o,a){"string"==typeof e&&(e=[[null,e,void 0]]);var s={};if(r)for(var i=0;i<this.length;i++){var l=this[i][0];null!=l&&(s[l]=!0)}for(var c=0;c<e.length;c++){var d=[].concat(e[c]);r&&s[d[0]]||(void 0!==a&&(void 0===d[5]||(d[1]="@layer".concat(d[5].length>0?" ".concat(d[5]):""," {").concat(d[1],"}")),d[5]=a),n&&(d[2]?(d[1]="@media ".concat(d[2]," {").concat(d[1],"}"),d[2]=n):d[2]=n),o&&(d[4]?(d[1]="@supports (".concat(d[4],") {").concat(d[1],"}"),d[4]=o):d[4]="".concat(o)),t.push(d))}},t}},601:e=>{e.exports=function(e){return e[1]}}},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var a=t[r]={id:r,exports:{}};return e[r](a,a.exports,n),a.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{const e={allowedChars:/^[a-zA-Z0-9 ]+$/,maxLength:20};function t(t){return e.allowedChars.test(t.key)||"Enter"===t.key||"Backspace"===t.key||"Delete"===t.key}function r(e){return e.trim().toLowerCase()}function o(e){return e.split(" ").map((e=>e.charAt(0).toUpperCase()+e.slice(1))).join(" ")}async function a(e,t=document){return new Promise((n=>{{const r=t.querySelector(e);r&&n(r)}const r=setInterval((()=>{const o=t.querySelector(e);o&&(clearInterval(r),n(o))}),10);setTimeout((()=>{clearInterval(r),n(null)}),5e3)}))}const s=new DOMParser;function i(e){return s.parseFromString(e,"text/html").body.firstChild}var l,c,d,u;function p(e,t,n,r,o){const a=o&&"input"in o?o.input:n.value,s=o?.expected??e.expects,i=o?.received??function(e){let t=typeof e;return"object"===t&&(t=(e&&Object.getPrototypeOf(e)?.constructor?.name)??"null"),"string"===t?`"${e}"`:"number"===t||"bigint"===t||"boolean"===t?`${e}`:t}(a),l={kind:e.kind,type:e.type,input:a,expected:s,received:i,message:`Invalid ${t}: ${s?`Expected ${s} but r`:"R"}eceived ${i}`,requirement:e.requirement,path:o?.path,issues:o?.issues,lang:r.lang,abortEarly:r.abortEarly,abortPipeEarly:r.abortPipeEarly,skipPipe:r.skipPipe},p="schema"===e.kind,g=e.message??(w=e.reference,y=l.lang,u?.get(w)?.get(y))??(p?function(e){return d?.get(e)}(l.lang):null)??r.message??function(e){return c?.get(e)}(l.lang);var w,y;g&&(l.message="function"==typeof g?g(l):g),p&&(n.typed=!1),n.issues?n.issues.push(l):n.issues=[l]}function g(e){return"__proto__"!==e&&"prototype"!==e&&"constructor"!==e}function w(e,t){return{kind:"schema",type:"array",reference:w,expects:"Array",async:!1,item:e,message:t,_run(e,t){const n=e.value;if(Array.isArray(n)){e.typed=!0,e.value=[];for(let r=0;r<n.length;r++){const o=n[r],a=this.item._run({typed:!1,value:o},t);if(a.issues){const s={type:"array",origin:"value",input:n,key:r,value:o};for(const t of a.issues)t.path?t.path.unshift(s):t.path=[s],e.issues?.push(t);if(e.issues||(e.issues=a.issues),t.abortEarly){e.typed=!1;break}}a.typed||(e.typed=!1),e.value.push(a.value)}}else p(this,"type",e,t);return e}}}function y(e,t){return{kind:"schema",type:"object",reference:y,expects:"Object",async:!1,entries:e,message:t,_run(e,t){const n=e.value;if(n&&"object"==typeof n){e.typed=!0,e.value={};for(const r in this.entries){const o=n[r],a=this.entries[r]._run({typed:!1,value:o},t);if(a.issues){const s={type:"object",origin:"value",input:n,key:r,value:o};for(const t of a.issues)t.path?t.path.unshift(s):t.path=[s],e.issues?.push(t);if(e.issues||(e.issues=a.issues),t.abortEarly){e.typed=!1;break}}a.typed||(e.typed=!1),(void 0!==a.value||r in n)&&(e.value[r]=a.value)}}else p(this,"type",e,t);return e}}}function v(e,t,n){return{kind:"schema",type:"record",reference:v,expects:"Object",async:!1,key:e,value:t,message:n,_run(e,t){const n=e.value;if(n&&"object"==typeof n){e.typed=!0,e.value={};for(const r in n)if(g(r)){const o=n[r],a=this.key._run({typed:!1,value:r},t);if(a.issues){const s={type:"record",origin:"key",input:n,key:r,value:o};for(const t of a.issues)t.path=[s],e.issues?.push(t);if(e.issues||(e.issues=a.issues),t.abortEarly){e.typed=!1;break}}const s=this.value._run({typed:!1,value:o},t);if(s.issues){const a={type:"record",origin:"value",input:n,key:r,value:o};for(const t of s.issues)t.path?t.path.unshift(a):t.path=[a],e.issues?.push(t);if(e.issues||(e.issues=s.issues),t.abortEarly){e.typed=!1;break}}a.typed&&s.typed||(e.typed=!1),a.typed&&(e.value[a.value]=s.value)}}else p(this,"type",e,t);return e}}}function h(e){return{kind:"schema",type:"string",reference:h,expects:"string",async:!1,message:e,_run(e,t){return"string"==typeof e.value?e.typed=!0:p(this,"type",e,t),e}}}function f(e,t,n){const r=e._run({typed:!1,value:t},function(e){return{lang:e?.lang??l?.lang,message:e?.message,abortEarly:e?.abortEarly??l?.abortEarly,abortPipeEarly:e?.abortPipeEarly??l?.abortPipeEarly,skipPipe:e?.skipPipe}}(n));return{typed:r.typed,success:!r.issues,output:r.value,issues:r.issues}}Error;const m=y({images:w(h())}),C=v(h(),m),x=y({tweets:w(h()),lastUpdated:function e(t){return{kind:"schema",type:"number",reference:e,expects:"number",async:!1,message:t,_run(e,t){return"number"!=typeof e.value||isNaN(e.value)?p(this,"type",e,t):e.typed=!0,e}}}()}),k=y({tweets:C,tags:v(h(),x)}),b="tags",M="tweets";async function L(e,t,n){if(null===e)return void console.error("No tweet selected");if(""===(t=r(t)))return void console.error("Invalid tag name");const o=await GM.getValue(b,{});let a={tweets:[],lastUpdated:Date.now()};t in o?a=o[t]:o[t]=a,a.lastUpdated=Date.now(),a.tweets.includes(e)||a.tweets.push(e),await GM.setValue(b,o);const s=await GM.getValue(M,{});n.length>0&&(s[e]={images:n}),await GM.setValue(M,s)}async function S(e,t){if(null===e)return void console.error("No tweet selected");if(""===(t=r(t)))return void console.error("Invalid tag name");const n=await GM.getValue(b,{});t in n&&(n[t].tweets=n[t].tweets.filter((t=>t!==e)),await GM.setValue(b,n))}async function V(){return GM.getValue(b,{})}async function T(){return GM.getValue(M,{})}const E='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M8 8H8.01M2 5.2L2 9.67451C2 10.1637 2 10.4083 2.05526 10.6385C2.10425 10.8425 2.18506 11.0376 2.29472 11.2166C2.4184 11.4184 2.59135 11.5914 2.93726 11.9373L10.6059 19.6059C11.7939 20.7939 12.388 21.388 13.0729 21.6105C13.6755 21.8063 14.3245 21.8063 14.927 21.6105C15.612 21.388 16.2061 20.7939 17.3941 19.6059L19.6059 17.3941C20.7939 16.2061 21.388 15.612 21.6105 14.927C21.8063 14.3245 21.8063 13.6755 21.6105 13.0729C21.388 12.388 20.7939 11.7939 19.6059 10.6059L11.9373 2.93726C11.5914 2.59135 11.4184 2.4184 11.2166 2.29472C11.0376 2.18506 10.8425 2.10425 10.6385 2.05526C10.4083 2 10.1637 2 9.67452 2L5.2 2C4.0799 2 3.51984 2 3.09202 2.21799C2.7157 2.40973 2.40973 2.71569 2.21799 3.09202C2 3.51984 2 4.07989 2 5.2ZM8.5 8C8.5 8.27614 8.27614 8.5 8 8.5C7.72386 8.5 7.5 8.27614 7.5 8C7.5 7.72386 7.72386 7.5 8 7.5C8.27614 7.5 8.5 7.72386 8.5 8Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',G='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">\r\n    <path\r\n        d="M7.8 21H16.2C17.8802 21 18.7202 21 19.362 20.673C19.9265 20.3854 20.3854 19.9265 20.673 19.362C21 18.7202 21 17.8802 21 16.2V7.8C21 6.11984 21 5.27976 20.673 4.63803C20.3854 4.07354 19.9265 3.6146 19.362 3.32698C18.7202 3 17.8802 3 16.2 3H7.8C6.11984 3 5.27976 3 4.63803 3.32698C4.07354 3.6146 3.6146 4.07354 3.32698 4.63803C3 5.27976 3 6.11984 3 7.8V16.2C3 17.8802 3 18.7202 3.32698 19.362C3.6146 19.9265 4.07354 20.3854 4.63803 20.673C5.27976 21 6.11984 21 7.8 21Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none" />\r\n</svg>',H='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M9 11L12 14L22 4M16 3H7.8C6.11984 3 5.27976 3 4.63803 3.32698C4.07354 3.6146 3.6146 4.07354 3.32698 4.63803C3 5.27976 3 6.11984 3 7.8V16.2C3 17.8802 3 18.7202 3.32698 19.362C3.6146 19.9265 4.07354 20.3854 4.63803 20.673C5.27976 21 6.11984 21 7.8 21H16.2C17.8802 21 18.7202 21 19.362 20.673C19.9265 20.3854 20.3854 19.9265 20.673 19.362C21 18.7202 21 17.8802 21 16.2V12"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',_="/twitter-art-tags/gallery",j="Tags / X";function q(e){return Array.from(document.querySelectorAll("a")).filter((t=>t.href.includes(e))).flatMap((e=>Array.from(e.querySelectorAll("img")))).map((e=>e.src))}const I='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M2.42012 12.7132C2.28394 12.4975 2.21584 12.3897 2.17772 12.2234C2.14909 12.0985 2.14909 11.9015 2.17772 11.7766C2.21584 11.6103 2.28394 11.5025 2.42012 11.2868C3.54553 9.50484 6.8954 5 12.0004 5C17.1054 5 20.4553 9.50484 21.5807 11.2868C21.7169 11.5025 21.785 11.6103 21.8231 11.7766C21.8517 11.9015 21.8517 12.0985 21.8231 12.2234C21.785 12.3897 21.7169 12.4975 21.5807 12.7132C20.4553 14.4952 17.1054 19 12.0004 19C6.8954 19 3.54553 14.4952 2.42012 12.7132Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n    <path\r\n        d="M12.0004 15C13.6573 15 15.0004 13.6569 15.0004 12C15.0004 10.3431 13.6573 9 12.0004 9C10.3435 9 9.0004 10.3431 9.0004 12C9.0004 13.6569 10.3435 15 12.0004 15Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',$='<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M20 10.5V6.8C20 5.11984 20 4.27976 19.673 3.63803C19.3854 3.07354 18.9265 2.6146 18.362 2.32698C17.7202 2 16.8802 2 15.2 2H8.8C7.11984 2 6.27976 2 5.63803 2.32698C5.07354 2.6146 4.6146 3.07354 4.32698 3.63803C4 4.27976 4 5.11984 4 6.8V17.2C4 18.8802 4 19.7202 4.32698 20.362C4.6146 20.9265 5.07354 21.3854 5.63803 21.673C6.27976 22 7.11984 22 8.8 22H12M18 21V15M15 18H21"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',R='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M20 11.9412V6.8C20 5.11984 20 4.27976 19.673 3.63803C19.3854 3.07354 18.9265 2.6146 18.362 2.32698C17.7202 2 16.8802 2 15.2 2H8.8C7.11984 2 6.27976 2 5.63803 2.32698C5.07354 2.6146 4.6146 3.07354 4.32698 3.63803C4 4.27976 4 5.11984 4 6.8V17.2C4 18.8802 4 19.7202 4.32698 20.362C4.6146 20.9265 5.07354 21.3854 5.63803 21.673C6.27976 22 7.11984 22 8.8 22H14M15 17H21"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',O='<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M21 9.00001L21 3.00001M21 3.00001H15M21 3.00001L12 12M10 3H7.8C6.11984 3 5.27976 3 4.63803 3.32698C4.07354 3.6146 3.6146 4.07354 3.32698 4.63803C3 5.27976 3 6.11984 3 7.8V16.2C3 17.8802 3 18.7202 3.32698 19.362C3.6146 19.9265 4.07354 20.3854 4.63803 20.673C5.27976 21 6.11984 21 7.8 21H16.2C17.8802 21 18.7202 21 19.362 20.673C19.9265 20.3854 20.3854 19.9265 20.673 19.362C21 18.7202 21 17.8802 21 16.2V14"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',A='<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M12 20.0002H21M3 20.0002H4.67454C5.16372 20.0002 5.40832 20.0002 5.63849 19.945C5.84256 19.896 6.03765 19.8152 6.2166 19.7055C6.41843 19.5818 6.59138 19.4089 6.93729 19.063L19.5 6.50023C20.3285 5.6718 20.3285 4.32865 19.5 3.50023C18.6716 2.6718 17.3285 2.6718 16.5 3.50023L3.93726 16.063C3.59136 16.4089 3.4184 16.5818 3.29472 16.7837C3.18506 16.9626 3.10425 17.1577 3.05526 17.3618C3 17.5919 3 17.8365 3 18.3257V20.0002Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',N='<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M16 6V5.2C16 4.0799 16 3.51984 15.782 3.09202C15.5903 2.71569 15.2843 2.40973 14.908 2.21799C14.4802 2 13.9201 2 12.8 2H11.2C10.0799 2 9.51984 2 9.09202 2.21799C8.71569 2.40973 8.40973 2.71569 8.21799 3.09202C8 3.51984 8 4.0799 8 5.2V6M10 11.5V16.5M14 11.5V16.5M3 6H21M19 6V17.2C19 18.8802 19 19.7202 18.673 20.362C18.3854 20.9265 17.9265 21.3854 17.362 21.673C16.7202 22 15.8802 22 14.2 22H9.8C8.11984 22 7.27976 22 6.63803 21.673C6.07354 21.3854 5.6146 20.9265 5.32698 20.362C5 19.7202 5 18.8802 5 17.2V6"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',B="tagImage",P="tags",U="imageGallery",D="context-menu-icon",Z="tag__inactive",z="image-container",F="image-container__hover",J="image-container__loaded",Y="image-container__skeleton",W=[];let X=[],K=!1;async function Q(e=!1){const t=document.querySelector("#"+U);e&&(t.innerHTML=`<div class="${z} ${Y}"></div>`.repeat(15));const[n,r]=await Promise.all([V(),T()]),a=Object.keys(n).filter((e=>X.includes(e))).reduce(((e,t)=>e.filter((e=>n[t].tweets.includes(e)))),Object.keys(r)).reverse().filter((e=>e in r)).flatMap((e=>r[e].images.map(((t,n)=>({tweetId:e,image:t,index:n,element:i(`\n                    <a id="${B}__${e}__${n}" class="${z} ${J}" href="/poohcom1/status/${e}" target="_blank">\n                        <img src="${t}" />\n                    </a>`)})))));t.innerHTML="",t.append(...a.map((e=>e.element))),0===Object.keys(n).length?t.innerHTML='<h3>No tags yet!<br>Add one by clicking on the "..." menu of a tweet with images and selected "Tag Tweet"</h3>':0===a.length&&(t.innerHTML="<h3>Nothing to see here!</h3>"),a.forEach((e=>{const t=a.filter((t=>t.tweetId===e.tweetId)),r=()=>{K||(document.querySelectorAll("."+F).forEach((e=>e.classList.remove(F))),t.forEach((e=>e.element.classList.add(F))))},s=()=>{K||t.forEach((e=>e.element.classList.remove(F)))},i=()=>{K=!1,s()};e.element.addEventListener("mouseover",r),e.element.addEventListener("mouseout",s),e.element.addEventListener("contextmenu",(()=>{K=!1,r(),K=!0})),document.addEventListener("click",i),W.push((()=>{document.removeEventListener("click",i)}));const l=[{label:"Open image",iconHTML:re(I),callback:t=>{console.log(t.target),window.open(e.image,"_blank")}},{label:"Open tweet",iconHTML:re(O),callback:()=>{window.open(`/poohcom1/status/${e.tweetId}`,"_blank")}}],c=[],d=Object.keys(n).filter((t=>!n[t].tweets.includes(e.tweetId))),u=Object.keys(n).filter((t=>n[t].tweets.includes(e.tweetId)));d.length>0&&c.push({label:"Add to",iconHTML:re($),preventCloseOnClick:!0,nestedMenu:d.map((t=>({label:o(t),iconHTML:re($),callback:async()=>{await L(e.tweetId,t,[]),te()}})))}),u.length>0&&c.push({label:"Remove from",iconHTML:re(R),preventCloseOnClick:!0,nestedMenu:u.map((t=>({label:o(t),iconHTML:re(R),callback:async()=>{await S(e.tweetId,t),te()}})))}),c.push({label:"Remove tweet",iconHTML:re(N),callback:async()=>{confirm("Are you sure you want to remove this tweet from all tags?")&&(await async function(e){const t=await GM.getValue(b,{});for(const n of Object.values(t))n.tweets=n.tweets.filter((t=>t!==e));await GM.setValue(b,t);const n=await GM.getValue(M,{});delete n[e],await GM.setValue(M,n)}(e.tweetId),te())}});const p=Object.keys(n).filter((t=>n[t].tweets.includes(e.tweetId))).map((e=>({label:o(e),iconHTML:re(E),callback:()=>{X=[e],te()}}))),g=[...l,"hr",...c];p.length>0&&(g.push("hr"),g.push(...p)),new VanillaContextMenu({scope:e.element,transitionDuration:0,menuItems:g,normalizePosition:!0,customNormalizeScope:document.querySelector("main").firstElementChild,openSubMenuOnHover:!0})}))}async function ee(){const[t,n]=await Promise.all([V(),T()]),a=Object.keys(t),s=document.querySelector("#"+P);a.sort(((e,t)=>e.localeCompare(t)));const l=a.map((a=>{const s=X.includes(a),l=t[a].tweets.map((e=>n[e].images.length)).reduce(((e,t)=>e+t),0),c=i(`<button class="tag ${!s&&Z}">\n                ${s?H:G}\n                <div class="text">${o(a)} (${l})</div>\n            </button>`);return c.addEventListener("click",(()=>{s?X=X.filter((e=>e!==a)):X.push(a),te(!0)})),new VanillaContextMenu({scope:c,normalizePosition:!1,transitionDuration:0,menuItems:[{label:"Rename",iconHTML:re(A),callback:async()=>{let t;for(;;){if(t=prompt("Enter new tag name:",o(a)),!t)return;if((n=t)&&e.allowedChars.test(n))break;alert("Invalid tag name! Tag names can only contain letters, numbers, and spaces.")}var n;await async function(e,t){if(e=r(e),t=r(t),""===e||""===t)return void console.error("Invalid tag name");if(e===t)return;const n=await GM.getValue(b,{});e in n&&(t in n?alert("Tag already exists"):(n[t]=n[e],delete n[e],await GM.setValue(b,n)))}(a,t),te()}},{label:"Delete",iconHTML:re(N),callback:async()=>{confirm("Are you sure you want to delete this tag?")&&(await async function(e){const t=await GM.getValue(b,{});e in t&&(delete t[e],await GM.setValue(b,t))}(a),te())}}]}),c}));s.innerHTML="",s.append(...l)}function te(e=!1){ee(),Q(e),W.forEach((e=>e())),W.length=0}async function ne(){const n=(await a('div[data-testid="error-detail"]')).parentElement;n.style.maxWidth="100%",n.innerHTML='<div> <style>.root{padding:40px 0;font-family:TwitterChirp;stroke:currentcolor}.title{display:flex;align-items:center}.title div{margin-left:auto;width:50px;height:50px;display:flex;align-items:center;justify-content:center}.title div svg{width:20px;height:20px;opacity:.5}#imageGallery{display:flex;flex-wrap:wrap;gap:10px}@keyframes fadeIn{from{opacity:.2}to{opacity:1}}.image-container{width:200px;height:200px;overflow:hidden;padding:0;margin:0;border-radius:5px}.image-container__loaded{background-color:#aaa;animation:.1s fadeIn ease-in-out}.image-container img{object-fit:cover;width:100%;height:100%}.image-container__hover{outline:4px solid #8c8cff}@keyframes loading{to{background-position-x:-20%}}.image-container__skeleton{background-color:#aaa;background:linear-gradient(100deg,rgba(255,255,255,0) 0,rgba(255,255,255,.7) 50%,rgba(255,255,255,0) 70%) #aaa;background-size:200% 100%;background-position-x:180%;animation:.5s loading linear infinite;opacity:.2}#tagSelect{width:200px}#tags{display:flex;flex-wrap:wrap;gap:10px;margin:10px 0}#addTag{width:200px}button{display:flex;align-items:center}svg.button-icon{display:inline-block;stroke:currentcolor;fill:none;width:20px;height:20px;margin-right:5px}.context-menu-icon{width:14px;height:14px;stroke:currentcolor;opacity:.8;margin-right:2px}</style> <div class="root"> <div class="title"> <h1>Tag Gallery</h1> <div title="Right click to view more options for tags and images"> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M9.09 9C9.3251 8.33167 9.78915 7.76811 10.4 7.40913C11.0108 7.05016 11.7289 6.91894 12.4272 7.03871C13.1255 7.15849 13.7588 7.52152 14.2151 8.06353C14.6713 8.60553 14.9211 9.29152 14.92 10C14.92 12 11.92 13 11.92 13M12 17H12.01M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </div> </div> <div style="display:flex;gap:10px"> <svg style="width:20px;stroke:currentcolor;fill:none" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M8 8H8.01M2 5.2L2 9.67451C2 10.1637 2 10.4083 2.05526 10.6385C2.10425 10.8425 2.18506 11.0376 2.29472 11.2166C2.4184 11.4184 2.59135 11.5914 2.93726 11.9373L10.6059 19.6059C11.7939 20.7939 12.388 21.388 13.0729 21.6105C13.6755 21.8063 14.3245 21.8063 14.927 21.6105C15.612 21.388 16.2061 20.7939 17.3941 19.6059L19.6059 17.3941C20.7939 16.2061 21.388 15.612 21.6105 14.927C21.8063 14.3245 21.8063 13.6755 21.6105 13.0729C21.388 12.388 20.7939 11.7939 19.6059 10.6059L11.9373 2.93726C11.5914 2.59135 11.4184 2.4184 11.2166 2.29472C11.0376 2.18506 10.8425 2.10425 10.6385 2.05526C10.4083 2 10.1637 2 9.67452 2L5.2 2C4.0799 2 3.51984 2 3.09202 2.21799C2.7157 2.40973 2.40973 2.71569 2.21799 3.09202C2 3.51984 2 4.07989 2 5.2ZM8.5 8C8.5 8.27614 8.27614 8.5 8 8.5C7.72386 8.5 7.5 8.27614 7.5 8C7.5 7.72386 7.72386 7.5 8 7.5C8.27614 7.5 8.5 7.72386 8.5 8Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> <input id="addTag" type="text" placeholder="Press enter to add a tag..."/> <button id="tagExport" style="margin-left:auto"> <svg class="button-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M15 8H8.6C8.03995 8 7.75992 8 7.54601 7.89101C7.35785 7.79513 7.20487 7.64215 7.10899 7.45399C7 7.24008 7 6.96005 7 6.4V3M17 21V14.6C17 14.0399 17 13.7599 16.891 13.546C16.7951 13.3578 16.6422 13.2049 16.454 13.109C16.2401 13 15.9601 13 15.4 13H8.6C8.03995 13 7.75992 13 7.54601 13.109C7.35785 13.2049 7.20487 13.3578 7.10899 13.546C7 13.7599 7 14.0399 7 14.6V21M21 9.32548V16.2C21 17.8802 21 18.7202 20.673 19.362C20.3854 19.9265 19.9265 20.3854 19.362 20.673C18.7202 21 17.8802 21 16.2 21H7.8C6.11984 21 5.27976 21 4.63803 20.673C4.07354 20.3854 3.6146 19.9265 3.32698 19.362C3 18.7202 3 17.8802 3 16.2V7.8C3 6.11984 3 5.27976 3.32698 4.63803C3.6146 4.07354 4.07354 3.6146 4.63803 3.32698C5.27976 3 6.11984 3 7.8 3H14.6745C15.1637 3 15.4083 3 15.6385 3.05526C15.8425 3.10425 16.0376 3.18506 16.2166 3.29472C16.4184 3.4184 16.5914 3.59135 16.9373 3.93726L20.0627 7.06274C20.4086 7.40865 20.5816 7.5816 20.7053 7.78343C20.8149 7.96237 20.8957 8.15746 20.9447 8.36154C21 8.59171 21 8.8363 21 9.32548Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Export Tags </button> <button id="tagImport"> <svg class="button-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M21 12V16.2C21 17.8802 21 18.7202 20.673 19.362C20.3854 19.9265 19.9265 20.3854 19.362 20.673C18.7202 21 17.8802 21 16.2 21H7.8C6.11984 21 5.27976 21 4.63803 20.673C4.07354 20.3854 3.6146 19.9265 3.32698 19.362C3 18.7202 3 17.8802 3 16.2V12M16 7L12 3M12 3L8 7M12 3V15" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Import Tags </button> </div> <hr/> <div id="tags"></div> <div id="imageGallery"></div> </div> </div> ',document.title=j,new MutationObserver((e=>{e.forEach((e=>{"childList"===e.type&&document.title!==j&&(document.title=j)}))})).observe(document.querySelector("title"),{childList:!0});const o=document.querySelector("#addTag");o.maxLength=e.maxLength,o.addEventListener("keydown",(async e=>{const n=e.target;t(e)?"Enter"===e.key&&(console.log(n.value),await async function(e){if(""===(e=r(e)))return void console.error("Invalid tag name");const t=await GM.getValue(b,{});e in t?alert("Tag already exists"):(t[e]={tweets:[],lastUpdated:Date.now()},await GM.setValue(b,t))}(n.value),n.value="",ee()):e.preventDefault()})),document.querySelector("#tagExport").addEventListener("click",(async()=>{const e=await async function(){const e={tags:await V(),tweets:await T()};return JSON.stringify(e,null,2)}(),t=new Blob([e],{type:"application/json"}),n=URL.createObjectURL(t),r=document.createElement("a");r.href=n,r.download=function(){const e=new Date;return`twitter-art-tag_data_${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}_${String(e.getHours()).padStart(2,"0")}.json`}(),r.click(),URL.revokeObjectURL(n)})),document.querySelector("#tagImport").addEventListener("click",(async()=>{const e=document.createElement("input");e.type="file",e.accept="application/json",e.style.display="none",e.addEventListener("change",(async()=>{const t=e.files[0],n=new FileReader;n.onload=async()=>{confirm("Are you sure you want to overwrite all tags?")&&(await async function(e){const t=JSON.parse(e),n=f(k,t);n.success?(await GM.setValue(b,n.output.tags),await GM.setValue(M,n.output.tweets)):(console.error(n.issues),alert("Failed to import data due to potentially corrupted file. Check the console for more information."))}(n.result),te())},n.readAsText(t)})),e.click()})),await ee(),Q(!0)}function re(e){const t=i(e);return t.classList.add(D),t.outerHTML}var oe=n(376);const ae="tagsPageButton",se={"rgb(0, 0, 0)":"rgb(22, 24, 28)","rgb(255, 255, 255)":"rgb(247, 249, 249)","rgb(21, 32, 43)":"rgb(30, 39, 50)"};GM.registerMenuCommand("Twitter Art Tags - View tags",(()=>window.location.href=window.location.origin+_)),GM.registerMenuCommand("Twitter Art Tags - Clear all tags",(async function(){confirm("Are you sure you want to delete all tags?")&&await GM.deleteValue(b)})),function(e){const t=document.getElementsByTagName("head")[0];if(t){const n=document.createElement("style");n.setAttribute("type","text/css"),n.textContent=e,t.appendChild(n)}}(oe.A),async function(){await a("nav")&&new MutationObserver((e=>{e.forEach((async e=>{if(e.addedNodes.length>0){const t=e.addedNodes[0],n=await a('div[data-testid="Dropdown"]',t);if(!n)return;if(n.querySelector(`#${ae}`))return;const r=t.querySelector('div[role="menu"]'),o=se[window.getComputedStyle(r).backgroundColor],s=n.querySelector('a[href="/settings"]'),i=null==s?void 0:s.parentElement;if(!s||!i)return;const l=i.cloneNode(!0);l.id=ae,l.querySelector("a").href=_,l.querySelector("a").style.backgroundColor="transparent",l.querySelector("a").onmouseenter=()=>l.querySelector("a").style.backgroundColor=null!=o?o:"",l.querySelector("a").onmouseleave=()=>l.querySelector("a").style.backgroundColor="transparent",l.querySelector("svg").innerHTML=E,l.querySelector("svg").style.stroke="currentcolor",l.querySelector("span").innerText="Tags",i.parentElement.prepend(l)}}))})).observe(document.getElementById("layers"),{childList:!0})}(),async function(){let n=null,r=null,s=null,l=null;const c=document.createElement("div");c.innerHTML='<input id="tagInput" type="text" placeholder="Add a tag..." /><hr style="width: 100%" /><div id="tagsContainer"/>',c.classList.add("tag-dropdown"),document.body.appendChild(c);const d=c.querySelector("#tagInput");d.maxLength=e.maxLength,d.addEventListener("keydown",(async e=>{const o=e.target;if(t(e))if("Enter"===e.key){if(null===n)return void console.error("No tweet selected");await L(n,o.value,q(n)),o.value="",null==r||r()}else null==r||r();else e.preventDefault()}));const u=c.querySelector("#tagsContainer");function p(){u.innerHTML=""}await a("#layers"),new MutationObserver((e=>{e.forEach((async e=>{if(e.addedNodes.length>0){a('div[role="menu"]',e.addedNodes[0]).then((e=>{if(!e)return;const t=window.getComputedStyle(e);c.style.border=t.border,c.style.borderRadius=t.borderRadius,c.style.backgroundColor=t.backgroundColor,c.style.color=t.color,c.style.boxShadow=t.boxShadow}));const t=await a('div[data-testid="Dropdown"]',e.addedNodes[0]);if(!t)return;let g="";const w=t.querySelectorAll("a");for(const e of w)if(e.href.includes("/status/")){g=e.href.split("/")[5];break}if(!g)return;n=g;const y=g+"_tagButton";if(document.getElementById(y))return;const v=q(g);if(0===v.length)return;!function(e,t,a){if(null===s||null===l){s=e.childNodes[0].cloneNode(!0),l=s.cloneNode(!0);const t=s.querySelector("svg").classList;s.querySelector("span").innerText="Tag Tweet",s.querySelector("svg").outerHTML=E,s.querySelector("svg").classList.add(...t),s.querySelector("svg").style.stroke="currentColor",s.querySelector("svg").style.fill="transparent",l.querySelector("span").innerText="View Tags",l.querySelector("svg").outerHTML='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M21 11L13.4059 3.40589C12.887 2.88703 12.6276 2.6276 12.3249 2.44208C12.0564 2.27759 11.7638 2.15638 11.4577 2.08289C11.1124 2 10.7455 2 10.0118 2L6 2M3 8.7L3 10.6745C3 11.1637 3 11.4083 3.05526 11.6385C3.10425 11.8425 3.18506 12.0376 3.29472 12.2166C3.4184 12.4184 3.59136 12.5914 3.93726 12.9373L11.7373 20.7373C12.5293 21.5293 12.9253 21.9253 13.382 22.0737C13.7837 22.2042 14.2163 22.2042 14.618 22.0737C15.0747 21.9253 15.4707 21.5293 16.2627 20.7373L18.7373 18.2627C19.5293 17.4707 19.9253 17.0747 20.0737 16.618C20.2042 16.2163 20.2042 15.7837 20.0737 15.382C19.9253 14.9253 19.5293 14.5293 18.7373 13.7373L11.4373 6.43726C11.0914 6.09136 10.9184 5.9184 10.7166 5.79472C10.5376 5.68506 10.3425 5.60425 10.1385 5.55526C9.90829 5.5 9.6637 5.5 9.17452 5.5H6.2C5.0799 5.5 4.51984 5.5 4.09202 5.71799C3.7157 5.90973 3.40973 6.21569 3.21799 6.59202C3 7.01984 3 7.57989 3 8.7Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',l.querySelector("svg").classList.add(...t),l.querySelector("svg").style.stroke="currentColor",l.querySelector("svg").style.fill="transparent",l.querySelector("svg").style.height="20px",l.querySelector("svg").style.width="20px",l.addEventListener("click",(async()=>{window.location.href=window.location.origin+_}))}s.id=t,s.addEventListener("click",(async()=>{const e=s.getBoundingClientRect();async function t(){if(null===n)return;const e=await V(),r=await async function(e){if(null===e)return console.error("No tweet selected"),[];const t=await V(),n=Object.keys(t);return n.sort(((e,t)=>e.localeCompare(t))),n}(n),s=r.filter((e=>e.toLowerCase().includes(d.value.toLowerCase())));if(p(),0===r.length)return void(u.innerHTML="No tags yet!");if(0===s.length)return void(u.innerHTML=`<div style="overflow: hidden; text-overflow: ellipsis; max-width: 200px">Create a new tag: ${d.value}</div>`);const l=s.map((t=>function(e,t){return i(`<button id="${e}" class="tag ${!t&&"tag__inactive"}">\n            ${t?H:G}\n            <div class="text">${o(e)}</div>\n        </button>`)}(t,e[t].tweets.includes(null!=n?n:""))));u.append(...l);for(const r of c.querySelectorAll(".tag"))r.addEventListener("click",(async()=>{if(null===n)return void console.error("No tweet selected");const o=r.id;o in e&&e[o].tweets.includes(n)?await S(n,o):await L(n,o,a),t()}))}c.style.top=`${e.top+window.scrollY}px`,c.style.left=`${e.right+10}px`,c.style.display="block",t(),r=t,d.focus()})),e.prepend(l),e.prepend(s)}(t,y,v)}else e.removedNodes.length>0&&(c.style.display="none",p(),r=null)}))})).observe(document.getElementById("layers"),{childList:!0})}(),window.location.href.includes(_)&&ne(),window.addEventListener("popstate",(e=>{window.location.href.includes(_)&&ne()}))})()})();

Changes:

  • feat: nav button and improve speeds
twitter-art-tags - v1.0.0

Published by poohcom1 4 months ago

// ==UserScript==
// @name        Twitter Art Tags
// @description Tag artwork on twitter and view it in a gallery
// @version     1.0.0
// @author      poohcom1
// @match       https://x.com/*
// @grant       GM.setValue
// @grant       GM.getValue
// @grant       GM.deleteValue
// @grant       GM.listValues
// @grant       GM.registerMenuCommand
// @require     https://github.com/poohcom1/vanilla-context-menu/releases/download/v1.8.1/vanilla-context-menu.js
// ==/UserScript==

(()=>{"use strict";var e={376:(e,t,n)=>{n.d(t,{A:()=>s});var o=n(601),r=n.n(o),a=n(314),i=n.n(a)()(r());i.push([e.id,"/* Drop down */\n#tagInput {\n    width: 100%;\n    height: 25px;\n    font-family: TwitterChirp;\n    margin-top: 4px;\n    border-radius: 5px;\n}\n\n.tag-dropdown {\n    padding: 16px;\n    display: none;\n    position: absolute;\n    padding: 10px 20px;\n    color: inherit;\n    white-space: nowrap;\n    z-index: 1000; /* Ensure it is above other content */\n    font-family: TwitterChirp;\n    font-weight: 700;\n    max-width: 300px;\n}\n\n.tag {\n    height: 30px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n    font-family: TwitterChirp;\n    font-weight: 700;\n    border: 2px solid currentColor;\n    border-radius: 5px;\n}\n\n.tag svg {\n    stroke: currentcolor;\n    width: 20px;\n    height: 20px;\n    margin-right: 5px;\n}\n\n.tag__inactive {\n    opacity: 0.75;\n    border: 2px solid transparent;\n}\n\n#tagsContainer {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 8px;\n}\n",""]);const s=i},314:e=>{e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",o=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),o&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),o&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,o,r,a){"string"==typeof e&&(e=[[null,e,void 0]]);var i={};if(o)for(var s=0;s<this.length;s++){var l=this[s][0];null!=l&&(i[l]=!0)}for(var c=0;c<e.length;c++){var d=[].concat(e[c]);o&&i[d[0]]||(void 0!==a&&(void 0===d[5]||(d[1]="@layer".concat(d[5].length>0?" ".concat(d[5]):""," {").concat(d[1],"}")),d[5]=a),n&&(d[2]?(d[1]="@media ".concat(d[2]," {").concat(d[1],"}"),d[2]=n):d[2]=n),r&&(d[4]?(d[1]="@supports (".concat(d[4],") {").concat(d[1],"}"),d[4]=r):d[4]="".concat(r)),t.push(d))}},t}},601:e=>{e.exports=function(e){return e[1]}}},t={};function n(o){var r=t[o];if(void 0!==r)return r.exports;var a=t[o]={id:o,exports:{}};return e[o](a,a.exports,n),a.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var o in t)n.o(t,o)&&!n.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{const e={allowedChars:/^[a-zA-Z0-9 ]+$/,maxLength:20};function t(t){return e.allowedChars.test(t.key)||"Enter"===t.key||"Backspace"===t.key||"Delete"===t.key}function o(e){return e.trim().toLowerCase()}function r(e){return e.split(" ").map((e=>e.charAt(0).toUpperCase()+e.slice(1))).join(" ")}async function a(e,t=document){return new Promise((n=>{{const o=t.querySelector(e);o&&n(o)}const o=setInterval((()=>{const r=t.querySelector(e);r&&(clearInterval(o),n(r))}),100)}))}const i=new DOMParser;function s(e){return i.parseFromString(e,"text/html").body.firstChild}var l,c,d,u;function p(e,t,n,o,r){const a=r&&"input"in r?r.input:n.value,i=r?.expected??e.expects,s=r?.received??function(e){let t=typeof e;return"object"===t&&(t=(e&&Object.getPrototypeOf(e)?.constructor?.name)??"null"),"string"===t?`"${e}"`:"number"===t||"bigint"===t||"boolean"===t?`${e}`:t}(a),l={kind:e.kind,type:e.type,input:a,expected:i,received:s,message:`Invalid ${t}: ${i?`Expected ${i} but r`:"R"}eceived ${s}`,requirement:e.requirement,path:r?.path,issues:r?.issues,lang:o.lang,abortEarly:o.abortEarly,abortPipeEarly:o.abortPipeEarly,skipPipe:o.skipPipe},p="schema"===e.kind,g=e.message??(w=e.reference,y=l.lang,u?.get(w)?.get(y))??(p?function(e){return d?.get(e)}(l.lang):null)??o.message??function(e){return c?.get(e)}(l.lang);var w,y;g&&(l.message="function"==typeof g?g(l):g),p&&(n.typed=!1),n.issues?n.issues.push(l):n.issues=[l]}function g(e){return"__proto__"!==e&&"prototype"!==e&&"constructor"!==e}function w(e,t){return{kind:"schema",type:"array",reference:w,expects:"Array",async:!1,item:e,message:t,_run(e,t){const n=e.value;if(Array.isArray(n)){e.typed=!0,e.value=[];for(let o=0;o<n.length;o++){const r=n[o],a=this.item._run({typed:!1,value:r},t);if(a.issues){const i={type:"array",origin:"value",input:n,key:o,value:r};for(const t of a.issues)t.path?t.path.unshift(i):t.path=[i],e.issues?.push(t);if(e.issues||(e.issues=a.issues),t.abortEarly){e.typed=!1;break}}a.typed||(e.typed=!1),e.value.push(a.value)}}else p(this,"type",e,t);return e}}}function y(e,t){return{kind:"schema",type:"object",reference:y,expects:"Object",async:!1,entries:e,message:t,_run(e,t){const n=e.value;if(n&&"object"==typeof n){e.typed=!0,e.value={};for(const o in this.entries){const r=n[o],a=this.entries[o]._run({typed:!1,value:r},t);if(a.issues){const i={type:"object",origin:"value",input:n,key:o,value:r};for(const t of a.issues)t.path?t.path.unshift(i):t.path=[i],e.issues?.push(t);if(e.issues||(e.issues=a.issues),t.abortEarly){e.typed=!1;break}}a.typed||(e.typed=!1),(void 0!==a.value||o in n)&&(e.value[o]=a.value)}}else p(this,"type",e,t);return e}}}function h(e,t,n){return{kind:"schema",type:"record",reference:h,expects:"Object",async:!1,key:e,value:t,message:n,_run(e,t){const n=e.value;if(n&&"object"==typeof n){e.typed=!0,e.value={};for(const o in n)if(g(o)){const r=n[o],a=this.key._run({typed:!1,value:o},t);if(a.issues){const i={type:"record",origin:"key",input:n,key:o,value:r};for(const t of a.issues)t.path=[i],e.issues?.push(t);if(e.issues||(e.issues=a.issues),t.abortEarly){e.typed=!1;break}}const i=this.value._run({typed:!1,value:r},t);if(i.issues){const a={type:"record",origin:"value",input:n,key:o,value:r};for(const t of i.issues)t.path?t.path.unshift(a):t.path=[a],e.issues?.push(t);if(e.issues||(e.issues=i.issues),t.abortEarly){e.typed=!1;break}}a.typed&&i.typed||(e.typed=!1),a.typed&&(e.value[a.value]=i.value)}}else p(this,"type",e,t);return e}}}function v(e){return{kind:"schema",type:"string",reference:v,expects:"string",async:!1,message:e,_run(e,t){return"string"==typeof e.value?e.typed=!0:p(this,"type",e,t),e}}}function f(e,t,n){const o=e._run({typed:!1,value:t},function(e){return{lang:e?.lang??l?.lang,message:e?.message,abortEarly:e?.abortEarly??l?.abortEarly,abortPipeEarly:e?.abortPipeEarly??l?.abortPipeEarly,skipPipe:e?.skipPipe}}(n));return{typed:o.typed,success:!o.issues,output:o.value,issues:o.issues}}Error;const m=y({images:w(v())}),C=h(v(),m),x=y({tweets:w(v()),lastUpdated:function e(t){return{kind:"schema",type:"number",reference:e,expects:"number",async:!1,message:t,_run(e,t){return"number"!=typeof e.value||isNaN(e.value)?p(this,"type",e,t):e.typed=!0,e}}}()}),k=y({tweets:C,tags:h(v(),x)}),b="tags",M="tweets";async function L(e,t,n){if(null===e)return void console.error("No tweet selected");if(""===(t=o(t)))return void console.error("Invalid tag name");const r=await GM.getValue(b,{});let a={tweets:[],lastUpdated:Date.now()};t in r?a=r[t]:r[t]=a,a.lastUpdated=Date.now(),a.tweets.includes(e)||a.tweets.push(e),await GM.setValue(b,r);const i=await GM.getValue(M,{});n.length>0&&(i[e]={images:n}),await GM.setValue(M,i)}async function V(e,t){if(null===e)return void console.error("No tweet selected");if(""===(t=o(t)))return void console.error("Invalid tag name");const n=await GM.getValue(b,{});t in n&&(n[t].tweets=n[t].tweets.filter((t=>t!==e)),await GM.setValue(b,n))}async function S(){return GM.getValue(b,{})}async function T(){return GM.getValue(M,{})}const E='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M8 8H8.01M2 5.2L2 9.67451C2 10.1637 2 10.4083 2.05526 10.6385C2.10425 10.8425 2.18506 11.0376 2.29472 11.2166C2.4184 11.4184 2.59135 11.5914 2.93726 11.9373L10.6059 19.6059C11.7939 20.7939 12.388 21.388 13.0729 21.6105C13.6755 21.8063 14.3245 21.8063 14.927 21.6105C15.612 21.388 16.2061 20.7939 17.3941 19.6059L19.6059 17.3941C20.7939 16.2061 21.388 15.612 21.6105 14.927C21.8063 14.3245 21.8063 13.6755 21.6105 13.0729C21.388 12.388 20.7939 11.7939 19.6059 10.6059L11.9373 2.93726C11.5914 2.59135 11.4184 2.4184 11.2166 2.29472C11.0376 2.18506 10.8425 2.10425 10.6385 2.05526C10.4083 2 10.1637 2 9.67452 2L5.2 2C4.0799 2 3.51984 2 3.09202 2.21799C2.7157 2.40973 2.40973 2.71569 2.21799 3.09202C2 3.51984 2 4.07989 2 5.2ZM8.5 8C8.5 8.27614 8.27614 8.5 8 8.5C7.72386 8.5 7.5 8.27614 7.5 8C7.5 7.72386 7.72386 7.5 8 7.5C8.27614 7.5 8.5 7.72386 8.5 8Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',G='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">\r\n    <path\r\n        d="M7.8 21H16.2C17.8802 21 18.7202 21 19.362 20.673C19.9265 20.3854 20.3854 19.9265 20.673 19.362C21 18.7202 21 17.8802 21 16.2V7.8C21 6.11984 21 5.27976 20.673 4.63803C20.3854 4.07354 19.9265 3.6146 19.362 3.32698C18.7202 3 17.8802 3 16.2 3H7.8C6.11984 3 5.27976 3 4.63803 3.32698C4.07354 3.6146 3.6146 4.07354 3.32698 4.63803C3 5.27976 3 6.11984 3 7.8V16.2C3 17.8802 3 18.7202 3.32698 19.362C3.6146 19.9265 4.07354 20.3854 4.63803 20.673C5.27976 21 6.11984 21 7.8 21Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none" />\r\n</svg>',H='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M9 11L12 14L22 4M16 3H7.8C6.11984 3 5.27976 3 4.63803 3.32698C4.07354 3.6146 3.6146 4.07354 3.32698 4.63803C3 5.27976 3 6.11984 3 7.8V16.2C3 17.8802 3 18.7202 3.32698 19.362C3.6146 19.9265 4.07354 20.3854 4.63803 20.673C5.27976 21 6.11984 21 7.8 21H16.2C17.8802 21 18.7202 21 19.362 20.673C19.9265 20.3854 20.3854 19.9265 20.673 19.362C21 18.7202 21 17.8802 21 16.2V12"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',_="/twitter-art-tags/gallery",j="Tags / X";function $(e){return Array.from(document.querySelectorAll("a")).filter((t=>t.href.includes(e))).flatMap((e=>Array.from(e.querySelectorAll("img")))).map((e=>e.src))}const q='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M2.42012 12.7132C2.28394 12.4975 2.21584 12.3897 2.17772 12.2234C2.14909 12.0985 2.14909 11.9015 2.17772 11.7766C2.21584 11.6103 2.28394 11.5025 2.42012 11.2868C3.54553 9.50484 6.8954 5 12.0004 5C17.1054 5 20.4553 9.50484 21.5807 11.2868C21.7169 11.5025 21.785 11.6103 21.8231 11.7766C21.8517 11.9015 21.8517 12.0985 21.8231 12.2234C21.785 12.3897 21.7169 12.4975 21.5807 12.7132C20.4553 14.4952 17.1054 19 12.0004 19C6.8954 19 3.54553 14.4952 2.42012 12.7132Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n    <path\r\n        d="M12.0004 15C13.6573 15 15.0004 13.6569 15.0004 12C15.0004 10.3431 13.6573 9 12.0004 9C10.3435 9 9.0004 10.3431 9.0004 12C9.0004 13.6569 10.3435 15 12.0004 15Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',I='<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M20 10.5V6.8C20 5.11984 20 4.27976 19.673 3.63803C19.3854 3.07354 18.9265 2.6146 18.362 2.32698C17.7202 2 16.8802 2 15.2 2H8.8C7.11984 2 6.27976 2 5.63803 2.32698C5.07354 2.6146 4.6146 3.07354 4.32698 3.63803C4 4.27976 4 5.11984 4 6.8V17.2C4 18.8802 4 19.7202 4.32698 20.362C4.6146 20.9265 5.07354 21.3854 5.63803 21.673C6.27976 22 7.11984 22 8.8 22H12M18 21V15M15 18H21"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',R='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M20 11.9412V6.8C20 5.11984 20 4.27976 19.673 3.63803C19.3854 3.07354 18.9265 2.6146 18.362 2.32698C17.7202 2 16.8802 2 15.2 2H8.8C7.11984 2 6.27976 2 5.63803 2.32698C5.07354 2.6146 4.6146 3.07354 4.32698 3.63803C4 4.27976 4 5.11984 4 6.8V17.2C4 18.8802 4 19.7202 4.32698 20.362C4.6146 20.9265 5.07354 21.3854 5.63803 21.673C6.27976 22 7.11984 22 8.8 22H14M15 17H21"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',O='<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M21 9.00001L21 3.00001M21 3.00001H15M21 3.00001L12 12M10 3H7.8C6.11984 3 5.27976 3 4.63803 3.32698C4.07354 3.6146 3.6146 4.07354 3.32698 4.63803C3 5.27976 3 6.11984 3 7.8V16.2C3 17.8802 3 18.7202 3.32698 19.362C3.6146 19.9265 4.07354 20.3854 4.63803 20.673C5.27976 21 6.11984 21 7.8 21H16.2C17.8802 21 18.7202 21 19.362 20.673C19.9265 20.3854 20.3854 19.9265 20.673 19.362C21 18.7202 21 17.8802 21 16.2V14"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',A='<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M12 20.0002H21M3 20.0002H4.67454C5.16372 20.0002 5.40832 20.0002 5.63849 19.945C5.84256 19.896 6.03765 19.8152 6.2166 19.7055C6.41843 19.5818 6.59138 19.4089 6.93729 19.063L19.5 6.50023C20.3285 5.6718 20.3285 4.32865 19.5 3.50023C18.6716 2.6718 17.3285 2.6718 16.5 3.50023L3.93726 16.063C3.59136 16.4089 3.4184 16.5818 3.29472 16.7837C3.18506 16.9626 3.10425 17.1577 3.05526 17.3618C3 17.5919 3 17.8365 3 18.3257V20.0002Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',B='<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M16 6V5.2C16 4.0799 16 3.51984 15.782 3.09202C15.5903 2.71569 15.2843 2.40973 14.908 2.21799C14.4802 2 13.9201 2 12.8 2H11.2C10.0799 2 9.51984 2 9.09202 2.21799C8.71569 2.40973 8.40973 2.71569 8.21799 3.09202C8 3.51984 8 4.0799 8 5.2V6M10 11.5V16.5M14 11.5V16.5M3 6H21M19 6V17.2C19 18.8802 19 19.7202 18.673 20.362C18.3854 20.9265 17.9265 21.3854 17.362 21.673C16.7202 22 15.8802 22 14.2 22H9.8C8.11984 22 7.27976 22 6.63803 21.673C6.07354 21.3854 5.6146 20.9265 5.32698 20.362C5 19.7202 5 18.8802 5 17.2V6"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',N="tagImage",P="tags",U="imageGallery",D="context-menu-icon",Z="tag__inactive",z="image-container",F="image-container__hover",J="image-container__loaded",Y="image-container__skeleton",W=[];let X=[],K=!1;async function Q(e=!1){const t=document.querySelector("#"+U);e&&(t.innerHTML=`<div class="${z} ${Y}"></div>`.repeat(15));const[n,o]=await Promise.all([S(),T()]),a=Object.keys(n).filter((e=>X.includes(e))).reduce(((e,t)=>e.filter((e=>n[t].tweets.includes(e)))),Object.keys(o)).reverse().filter((e=>e in o)).flatMap((e=>o[e].images.map(((t,n)=>({tweetId:e,image:t,index:n,element:s(`\n                    <a id="${N}__${e}__${n}" class="${z} ${J}" href="/poohcom1/status/${e}" target="_blank">\n                        <img src="${t}" />\n                    </a>`)})))));t.innerHTML="",t.append(...a.map((e=>e.element))),0===Object.keys(n).length?t.innerHTML='<h3>No tags yet!<br>Add one by clicking on the "..." menu of a tweet with images and selected "Tag Tweet"</h3>':0===a.length&&(t.innerHTML="<h3>Nothing to see here!</h3>"),a.forEach((e=>{const t=a.filter((t=>t.tweetId===e.tweetId)),o=()=>{K||(document.querySelectorAll("."+F).forEach((e=>e.classList.remove(F))),t.forEach((e=>e.element.classList.add(F))))},i=()=>{K||t.forEach((e=>e.element.classList.remove(F)))},s=()=>{K=!1,i()};e.element.addEventListener("mouseover",o),e.element.addEventListener("mouseout",i),e.element.addEventListener("contextmenu",(()=>{K=!1,o(),K=!0})),document.addEventListener("click",s),W.push((()=>{document.removeEventListener("click",s)}));const l=[{label:"Open image",iconHTML:ne(q),callback:t=>{console.log(t.target),window.open(e.image,"_blank")}},{label:"Open tweet",iconHTML:ne(O),callback:()=>{window.open(`/poohcom1/status/${e.tweetId}`,"_blank")}}],c=[],d=Object.keys(n).filter((t=>!n[t].tweets.includes(e.tweetId))),u=Object.keys(n).filter((t=>n[t].tweets.includes(e.tweetId)));d.length>0&&c.push({label:"Add to",iconHTML:ne(I),preventCloseOnClick:!0,nestedMenu:d.map((t=>({label:r(t),iconHTML:ne(I),callback:async()=>{await L(e.tweetId,t,[]),te()}})))}),u.length>0&&c.push({label:"Remove from",iconHTML:ne(R),preventCloseOnClick:!0,nestedMenu:u.map((t=>({label:r(t),iconHTML:ne(R),callback:async()=>{await V(e.tweetId,t),te()}})))}),c.push({label:"Remove tweet",iconHTML:ne(B),callback:async()=>{confirm("Are you sure you want to remove this tweet from all tags?")&&(await async function(e){const t=await GM.getValue(b,{});for(const n of Object.values(t))n.tweets=n.tweets.filter((t=>t!==e));await GM.setValue(b,t);const n=await GM.getValue(M,{});delete n[e],await GM.setValue(M,n)}(e.tweetId),te())}});const p=Object.keys(n).filter((t=>n[t].tweets.includes(e.tweetId))).map((e=>({label:r(e),iconHTML:ne(E),callback:()=>{X=[e],te()}}))),g=[...l,"hr",...c];p.length>0&&(g.push("hr"),g.push(...p)),new VanillaContextMenu({scope:e.element,transitionDuration:0,menuItems:g,normalizePosition:!0,customNormalizeScope:document.querySelector("main").firstElementChild,openSubMenuOnHover:!0})}))}async function ee(){const[t,n]=await Promise.all([S(),T()]),a=Object.keys(t),i=document.querySelector("#"+P);a.sort(((e,t)=>e.localeCompare(t)));const l=a.map((a=>{const i=X.includes(a),l=t[a].tweets.map((e=>n[e].images.length)).reduce(((e,t)=>e+t),0),c=s(`<button class="tag ${!i&&Z}">\n                ${i?H:G}\n                <div class="text">${r(a)} (${l})</div>\n            </button>`);return c.addEventListener("click",(()=>{i?X=X.filter((e=>e!==a)):X.push(a),te(!0)})),new VanillaContextMenu({scope:c,normalizePosition:!1,transitionDuration:0,menuItems:[{label:"Rename",iconHTML:ne(A),callback:async()=>{let t;for(;;){if(t=prompt("Enter new tag name:",r(a)),!t)return;if((n=t)&&e.allowedChars.test(n))break;alert("Invalid tag name! Tag names can only contain letters, numbers, and spaces.")}var n;await async function(e,t){if(e=o(e),t=o(t),""===e||""===t)return void console.error("Invalid tag name");if(e===t)return;const n=await GM.getValue(b,{});e in n&&(t in n?alert("Tag already exists"):(n[t]=n[e],delete n[e],await GM.setValue(b,n)))}(a,t),te()}},{label:"Delete",iconHTML:ne(B),callback:async()=>{confirm("Are you sure you want to delete this tag?")&&(await async function(e){const t=await GM.getValue(b,{});e in t&&(delete t[e],await GM.setValue(b,t))}(a),te())}}]}),c}));i.innerHTML="",i.append(...l)}function te(e=!1){ee(),Q(e),W.forEach((e=>e())),W.length=0}function ne(e){const t=s(e);return t.classList.add(D),t.outerHTML}var oe=n(376);GM.registerMenuCommand("Twitter Art Tags - View tags",(()=>window.location.href=window.location.origin+_)),GM.registerMenuCommand("Twitter Art Tags - Clear all tags",(async function(){confirm("Are you sure you want to delete all tags?")&&await GM.deleteValue(b)})),function(e){const t=document.getElementsByTagName("head")[0];if(t){const n=document.createElement("style");n.setAttribute("type","text/css"),n.textContent=e,t.appendChild(n)}}(oe.A),async function(){let n=null,o=null,i=null,l=null;const c=document.createElement("div");c.innerHTML='<input id="tagInput" type="text" placeholder="Add a tag..." /><hr style="width: 100%" /><div id="tagsContainer"/>',c.classList.add("tag-dropdown"),document.body.appendChild(c);const d=c.querySelector("#tagInput");d.maxLength=e.maxLength,d.addEventListener("keydown",(async e=>{const r=e.target;if(t(e))if("Enter"===e.key){if(null===n)return void console.error("No tweet selected");await L(n,r.value,$(n)),r.value="",null==o||o()}else null==o||o();else e.preventDefault()}));const u=c.querySelector("#tagsContainer");function p(){u.innerHTML=""}await a("#layers"),new MutationObserver((e=>{e.forEach((async e=>{if(e.addedNodes.length>0){a('div[role="menu"]',e.addedNodes[0]).then((e=>{const t=window.getComputedStyle(e);c.style.border=t.border,c.style.borderRadius=t.borderRadius,c.style.backgroundColor=t.backgroundColor,c.style.color=t.color,c.style.boxShadow=t.boxShadow}));const t=await a('div[data-testid="Dropdown"]',e.addedNodes[0]);if(!t)return;let g="";const w=t.querySelectorAll("a");for(const e of w)if(e.href.includes("/status/")){g=e.href.split("/")[5];break}if(!g)return;n=g;const y=g+"_tagButton";if(document.getElementById(y))return;const h=$(g);if(0===h.length)return;!function(e,t,a){if(null===i||null===l){i=e.childNodes[0].cloneNode(!0),l=i.cloneNode(!0);const t=i.querySelector("svg").classList;i.querySelector("span").innerText="Tag Tweet",i.querySelector("svg").outerHTML=E,i.querySelector("svg").classList.add(...t),i.querySelector("svg").style.stroke="currentColor",i.querySelector("svg").style.fill="transparent",l.querySelector("span").innerText="View Tags",l.querySelector("svg").outerHTML='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\r\n    \x3c!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --\x3e\r\n    <path\r\n        d="M21 11L13.4059 3.40589C12.887 2.88703 12.6276 2.6276 12.3249 2.44208C12.0564 2.27759 11.7638 2.15638 11.4577 2.08289C11.1124 2 10.7455 2 10.0118 2L6 2M3 8.7L3 10.6745C3 11.1637 3 11.4083 3.05526 11.6385C3.10425 11.8425 3.18506 12.0376 3.29472 12.2166C3.4184 12.4184 3.59136 12.5914 3.93726 12.9373L11.7373 20.7373C12.5293 21.5293 12.9253 21.9253 13.382 22.0737C13.7837 22.2042 14.2163 22.2042 14.618 22.0737C15.0747 21.9253 15.4707 21.5293 16.2627 20.7373L18.7373 18.2627C19.5293 17.4707 19.9253 17.0747 20.0737 16.618C20.2042 16.2163 20.2042 15.7837 20.0737 15.382C19.9253 14.9253 19.5293 14.5293 18.7373 13.7373L11.4373 6.43726C11.0914 6.09136 10.9184 5.9184 10.7166 5.79472C10.5376 5.68506 10.3425 5.60425 10.1385 5.55526C9.90829 5.5 9.6637 5.5 9.17452 5.5H6.2C5.0799 5.5 4.51984 5.5 4.09202 5.71799C3.7157 5.90973 3.40973 6.21569 3.21799 6.59202C3 7.01984 3 7.57989 3 8.7Z"\r\n        stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\r\n</svg>',l.querySelector("svg").classList.add(...t),l.querySelector("svg").style.stroke="currentColor",l.querySelector("svg").style.fill="transparent",l.querySelector("svg").style.height="20px",l.querySelector("svg").style.width="20px",l.addEventListener("click",(async()=>{window.location.href=window.location.origin+_}))}i.id=t,i.addEventListener("click",(async()=>{const e=i.getBoundingClientRect();async function t(){if(null===n)return;const e=await S(),o=await async function(e){if(null===e)return console.error("No tweet selected"),[];const t=await S(),n=Object.keys(t);return n.sort(((e,t)=>e.localeCompare(t))),n}(n),i=o.filter((e=>e.toLowerCase().includes(d.value.toLowerCase())));if(p(),0===o.length)return void(u.innerHTML="No tags yet!");if(0===i.length)return void(u.innerHTML=`<div style="overflow: hidden; text-overflow: ellipsis; max-width: 200px">Create a new tag: ${d.value}</div>`);const l=i.map((t=>function(e,t){return s(`<button id="${e}" class="tag ${!t&&"tag__inactive"}">\n            ${t?H:G}\n            <div class="text">${r(e)}</div>\n        </button>`)}(t,e[t].tweets.includes(null!=n?n:""))));u.append(...l);for(const o of c.querySelectorAll(".tag"))o.addEventListener("click",(async()=>{if(null===n)return void console.error("No tweet selected");const r=o.id;r in e&&e[r].tweets.includes(n)?await V(n,r):await L(n,r,a),t()}))}c.style.top=`${e.top+window.scrollY}px`,c.style.left=`${e.right+10}px`,c.style.display="block",t(),o=t,d.focus()})),e.prepend(l),e.prepend(i)}(t,y,h)}else e.removedNodes.length>0&&(c.style.display="none",p(),o=null)}))})).observe(document.getElementById("layers"),{childList:!0})}(),window.location.href.includes(_)&&async function(){const n=(await a('div[data-testid="error-detail"]')).parentElement;n.style.maxWidth="100%",n.innerHTML='<div> <style>.root{padding:40px 0;font-family:TwitterChirp;stroke:currentcolor}.title{display:flex;align-items:center}.title div{margin-left:auto;width:50px;height:50px;display:flex;align-items:center;justify-content:center}.title div svg{width:20px;height:20px;opacity:.5}#imageGallery{display:flex;flex-wrap:wrap;gap:10px}@keyframes fadeIn{from{opacity:.2}to{opacity:1}}.image-container{width:200px;height:200px;overflow:hidden;padding:0;margin:0;border-radius:5px}.image-container__loaded{background-color:#aaa;animation:.1s fadeIn ease-in-out}.image-container img{object-fit:cover;width:100%;height:100%}.image-container__hover{outline:4px solid #8c8cff}@keyframes loading{to{background-position-x:-20%}}.image-container__skeleton{background-color:#aaa;background:linear-gradient(100deg,rgba(255,255,255,0) 0,rgba(255,255,255,.7) 50%,rgba(255,255,255,0) 70%) #aaa;background-size:200% 100%;background-position-x:180%;animation:.5s loading linear infinite;opacity:.2}#tagSelect{width:200px}#tags{display:flex;flex-wrap:wrap;gap:10px;margin:10px 0}#addTag{width:200px}button{display:flex;align-items:center}svg.button-icon{display:inline-block;stroke:currentcolor;fill:none;width:20px;height:20px;margin-right:5px}.context-menu-icon{width:14px;height:14px;stroke:currentcolor;opacity:.8;margin-right:2px}</style> <div class="root"> <div class="title"> <h1>Tag Gallery</h1> <div title="Right click to view more options for tags and images"> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M9.09 9C9.3251 8.33167 9.78915 7.76811 10.4 7.40913C11.0108 7.05016 11.7289 6.91894 12.4272 7.03871C13.1255 7.15849 13.7588 7.52152 14.2151 8.06353C14.6713 8.60553 14.9211 9.29152 14.92 10C14.92 12 11.92 13 11.92 13M12 17H12.01M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </div> </div> <div style="display:flex;gap:10px"> <svg style="width:20px;stroke:currentcolor;fill:none" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M8 8H8.01M2 5.2L2 9.67451C2 10.1637 2 10.4083 2.05526 10.6385C2.10425 10.8425 2.18506 11.0376 2.29472 11.2166C2.4184 11.4184 2.59135 11.5914 2.93726 11.9373L10.6059 19.6059C11.7939 20.7939 12.388 21.388 13.0729 21.6105C13.6755 21.8063 14.3245 21.8063 14.927 21.6105C15.612 21.388 16.2061 20.7939 17.3941 19.6059L19.6059 17.3941C20.7939 16.2061 21.388 15.612 21.6105 14.927C21.8063 14.3245 21.8063 13.6755 21.6105 13.0729C21.388 12.388 20.7939 11.7939 19.6059 10.6059L11.9373 2.93726C11.5914 2.59135 11.4184 2.4184 11.2166 2.29472C11.0376 2.18506 10.8425 2.10425 10.6385 2.05526C10.4083 2 10.1637 2 9.67452 2L5.2 2C4.0799 2 3.51984 2 3.09202 2.21799C2.7157 2.40973 2.40973 2.71569 2.21799 3.09202C2 3.51984 2 4.07989 2 5.2ZM8.5 8C8.5 8.27614 8.27614 8.5 8 8.5C7.72386 8.5 7.5 8.27614 7.5 8C7.5 7.72386 7.72386 7.5 8 7.5C8.27614 7.5 8.5 7.72386 8.5 8Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> <input id="addTag" type="text" placeholder="Press enter to add a tag..."/> <button id="tagExport" style="margin-left:auto"> <svg class="button-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M15 8H8.6C8.03995 8 7.75992 8 7.54601 7.89101C7.35785 7.79513 7.20487 7.64215 7.10899 7.45399C7 7.24008 7 6.96005 7 6.4V3M17 21V14.6C17 14.0399 17 13.7599 16.891 13.546C16.7951 13.3578 16.6422 13.2049 16.454 13.109C16.2401 13 15.9601 13 15.4 13H8.6C8.03995 13 7.75992 13 7.54601 13.109C7.35785 13.2049 7.20487 13.3578 7.10899 13.546C7 13.7599 7 14.0399 7 14.6V21M21 9.32548V16.2C21 17.8802 21 18.7202 20.673 19.362C20.3854 19.9265 19.9265 20.3854 19.362 20.673C18.7202 21 17.8802 21 16.2 21H7.8C6.11984 21 5.27976 21 4.63803 20.673C4.07354 20.3854 3.6146 19.9265 3.32698 19.362C3 18.7202 3 17.8802 3 16.2V7.8C3 6.11984 3 5.27976 3.32698 4.63803C3.6146 4.07354 4.07354 3.6146 4.63803 3.32698C5.27976 3 6.11984 3 7.8 3H14.6745C15.1637 3 15.4083 3 15.6385 3.05526C15.8425 3.10425 16.0376 3.18506 16.2166 3.29472C16.4184 3.4184 16.5914 3.59135 16.9373 3.93726L20.0627 7.06274C20.4086 7.40865 20.5816 7.5816 20.7053 7.78343C20.8149 7.96237 20.8957 8.15746 20.9447 8.36154C21 8.59171 21 8.8363 21 9.32548Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Export Tags </button> <button id="tagImport"> <svg class="button-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M21 12V16.2C21 17.8802 21 18.7202 20.673 19.362C20.3854 19.9265 19.9265 20.3854 19.362 20.673C18.7202 21 17.8802 21 16.2 21H7.8C6.11984 21 5.27976 21 4.63803 20.673C4.07354 20.3854 3.6146 19.9265 3.32698 19.362C3 18.7202 3 17.8802 3 16.2V12M16 7L12 3M12 3L8 7M12 3V15" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Import Tags </button> </div> <hr/> <div id="tags"></div> <div id="imageGallery"></div> </div> </div> ',document.title=j,new MutationObserver((e=>{e.forEach((e=>{"childList"===e.type&&document.title!==j&&(document.title=j)}))})).observe(document.querySelector("title"),{childList:!0});const r=document.querySelector("#addTag");r.maxLength=e.maxLength,r.addEventListener("keydown",(async e=>{const n=e.target;t(e)?"Enter"===e.key&&(console.log(n.value),await async function(e){if(""===(e=o(e)))return void console.error("Invalid tag name");const t=await GM.getValue(b,{});e in t?alert("Tag already exists"):(t[e]={tweets:[],lastUpdated:Date.now()},await GM.setValue(b,t))}(n.value),n.value="",ee()):e.preventDefault()})),document.querySelector("#tagExport").addEventListener("click",(async()=>{const e=await async function(){const e={tags:await S(),tweets:await T()};return JSON.stringify(e,null,2)}(),t=new Blob([e],{type:"application/json"}),n=URL.createObjectURL(t),o=document.createElement("a");o.href=n,o.download=function(){const e=new Date;return`twitter-art-tag_data_${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}_${String(e.getHours()).padStart(2,"0")}.json`}(),o.click(),URL.revokeObjectURL(n)})),document.querySelector("#tagImport").addEventListener("click",(async()=>{const e=document.createElement("input");e.type="file",e.accept="application/json",e.style.display="none",e.addEventListener("change",(async()=>{const t=e.files[0],n=new FileReader;n.onload=async()=>{confirm("Are you sure you want to overwrite all tags?")&&(await async function(e){const t=JSON.parse(e),n=f(k,t);n.success?(await GM.setValue(b,n.output.tags),await GM.setValue(M,n.output.tweets)):(console.error(n.issues),alert("Failed to import data due to potentially corrupted file. Check the console for more information."))}(n.result),te())},n.readAsText(t)})),e.click()})),await ee(),Q(!0)}()})()})();
twitter-art-tags - Release v0.1.0

Published by poohcom1 4 months ago

Changelog

  • Add tag options to the tweet menu
  • Add tag page

Script

// ==UserScript==
// @name        Twitter Art Collection
// @description Tag artwork on twitter and view it in a gallery
// @version     0.1.0
// @match       https://x.com/*
// @grant       GM.setValue
// @grant       GM.getValue
// @grant       GM.deleteValue
// @grant       GM.listValues
// @grant       GM.registerMenuCommand
// @require     https://unpkg.com/[email protected]/dist/vanilla-context-menu.js
// ==/UserScript==

(()=>{"use strict";var e={379:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(601),r=n.n(a),s=n(314),i=n.n(s)()(r());i.push([e.id,"/* Drop down */\n#tagInput {\n    width: 100%;\n    height: 20px;\n    font-family: TwitterChirp;\n}\n\n.tag-dropdown {\n    padding: 10px;\n    display: none;\n    position: absolute;\n    padding: 10px 20px;\n    color: inherit;\n    white-space: nowrap;\n    z-index: 1000; /* Ensure it is above other content */\n    font-family: TwitterChirp;\n    font-weight: 700;\n    max-width: 300px;\n}\n\n.tag {\n    height: 30px;\n    font-family: TwitterChirp;\n    font-weight: 700;\n    max-width: 300px;\n}\n\n.tag__inactive {\n    opacity: 0.75;\n}\n\n#tagsContainer {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 8px;\n}\n",""]);const o=i},314:e=>{e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",a=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),a&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),a&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,a,r,s){"string"==typeof e&&(e=[[null,e,void 0]]);var i={};if(a)for(var o=0;o<this.length;o++){var l=this[o][0];null!=l&&(i[l]=!0)}for(var c=0;c<e.length;c++){var u=[].concat(e[c]);a&&i[u[0]]||(void 0!==s&&(void 0===u[5]||(u[1]="@layer".concat(u[5].length>0?" ".concat(u[5]):""," {").concat(u[1],"}")),u[5]=s),n&&(u[2]?(u[1]="@media ".concat(u[2]," {").concat(u[1],"}"),u[2]=n):u[2]=n),r&&(u[4]?(u[1]="@supports (".concat(u[4],") {").concat(u[1],"}"),u[4]=r):u[4]="".concat(r)),t.push(u))}},t}},601:e=>{e.exports=function(e){return e[1]}}},t={};function n(a){var r=t[a];if(void 0!==r)return r.exports;var s=t[a]={id:a,exports:{}};return e[a](s,s.exports,n),s.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{function e(e){return e.trim().toLowerCase()}function t(e){return e.split(" ").map((e=>e.charAt(0).toUpperCase()+e.slice(1))).join(" ")}async function a(e,t=document){return new Promise((n=>{const a=t.querySelector(e);a&&n(a);const r=setInterval((()=>{const a=t.querySelector(e);a&&(clearInterval(r),n(a))}),100)}))}const r=new DOMParser;var s,i,o,l;function c(e,t,n,a,r){const s=r&&"input"in r?r.input:n.value,c=r?.expected??e.expects,u=r?.received??function(e){let t=typeof e;return"object"===t&&(t=(e&&Object.getPrototypeOf(e)?.constructor?.name)??"null"),"string"===t?`"${e}"`:"number"===t||"bigint"===t||"boolean"===t?`${e}`:t}(s),d={kind:e.kind,type:e.type,input:s,expected:c,received:u,message:`Invalid ${t}: ${c?`Expected ${c} but r`:"R"}eceived ${u}`,requirement:e.requirement,path:r?.path,issues:r?.issues,lang:a.lang,abortEarly:a.abortEarly,abortPipeEarly:a.abortPipeEarly,skipPipe:a.skipPipe},p="schema"===e.kind,y=e.message??(g=e.reference,f=d.lang,l?.get(g)?.get(f))??(p?function(e){return o?.get(e)}(d.lang):null)??a.message??function(e){return i?.get(e)}(d.lang);var g,f;y&&(d.message="function"==typeof y?y(d):y),p&&(n.typed=!1),n.issues?n.issues.push(d):n.issues=[d]}function u(e){return"__proto__"!==e&&"prototype"!==e&&"constructor"!==e}function d(e,t){return{kind:"schema",type:"array",reference:d,expects:"Array",async:!1,item:e,message:t,_run(e,t){const n=e.value;if(Array.isArray(n)){e.typed=!0,e.value=[];for(let a=0;a<n.length;a++){const r=n[a],s=this.item._run({typed:!1,value:r},t);if(s.issues){const i={type:"array",origin:"value",input:n,key:a,value:r};for(const t of s.issues)t.path?t.path.unshift(i):t.path=[i],e.issues?.push(t);if(e.issues||(e.issues=s.issues),t.abortEarly){e.typed=!1;break}}s.typed||(e.typed=!1),e.value.push(s.value)}}else c(this,"type",e,t);return e}}}function p(e,t){return{kind:"schema",type:"object",reference:p,expects:"Object",async:!1,entries:e,message:t,_run(e,t){const n=e.value;if(n&&"object"==typeof n){e.typed=!0,e.value={};for(const a in this.entries){const r=n[a],s=this.entries[a]._run({typed:!1,value:r},t);if(s.issues){const i={type:"object",origin:"value",input:n,key:a,value:r};for(const t of s.issues)t.path?t.path.unshift(i):t.path=[i],e.issues?.push(t);if(e.issues||(e.issues=s.issues),t.abortEarly){e.typed=!1;break}}s.typed||(e.typed=!1),(void 0!==s.value||a in n)&&(e.value[a]=s.value)}}else c(this,"type",e,t);return e}}}function y(e,t,n){return{kind:"schema",type:"record",reference:y,expects:"Object",async:!1,key:e,value:t,message:n,_run(e,t){const n=e.value;if(n&&"object"==typeof n){e.typed=!0,e.value={};for(const a in n)if(u(a)){const r=n[a],s=this.key._run({typed:!1,value:a},t);if(s.issues){const i={type:"record",origin:"key",input:n,key:a,value:r};for(const t of s.issues)t.path=[i],e.issues?.push(t);if(e.issues||(e.issues=s.issues),t.abortEarly){e.typed=!1;break}}const i=this.value._run({typed:!1,value:r},t);if(i.issues){const s={type:"record",origin:"value",input:n,key:a,value:r};for(const t of i.issues)t.path?t.path.unshift(s):t.path=[s],e.issues?.push(t);if(e.issues||(e.issues=i.issues),t.abortEarly){e.typed=!1;break}}s.typed&&i.typed||(e.typed=!1),s.typed&&(e.value[s.value]=i.value)}}else c(this,"type",e,t);return e}}}function g(e){return{kind:"schema",type:"string",reference:g,expects:"string",async:!1,message:e,_run(e,t){return"string"==typeof e.value?e.typed=!0:c(this,"type",e,t),e}}}function f(e,t,n){const a=e._run({typed:!1,value:t},function(e){return{lang:e?.lang??s?.lang,message:e?.message,abortEarly:e?.abortEarly??s?.abortEarly,abortPipeEarly:e?.abortPipeEarly??s?.abortPipeEarly,skipPipe:e?.skipPipe}}(n));return{typed:a.typed,success:!a.issues,output:a.value,issues:a.issues}}Error;const w=p({images:d(g())}),m=y(g(),w),h=p({tweets:d(g()),lastUpdated:function e(t){return{kind:"schema",type:"number",reference:e,expects:"number",async:!1,message:t,_run(e,t){return"number"!=typeof e.value||isNaN(e.value)?c(this,"type",e,t):e.typed=!0,e}}}()}),v=p({tweets:m,tags:y(g(),h)}),b="tags",x="tweets";async function k(t,n,a){if(null===t)return void console.error("No tweet selected");if(""===(n=e(n)))return void console.error("Invalid tag name");const r=await GM.getValue(b,{});let s={tweets:[],lastUpdated:Date.now()};n in r?s=r[n]:r[n]=s,s.lastUpdated=Date.now(),s.tweets.includes(t)||s.tweets.push(t),await GM.setValue(b,r);const i=await GM.getValue(x,{});a.length>0&&(i[t]={images:a}),await GM.setValue(x,i)}async function E(t,n){if(null===t)return void console.error("No tweet selected");if(""===(n=e(n)))return void console.error("Invalid tag name");const a=await GM.getValue(b,{});n in a&&(a[n].tweets=a[n].tweets.filter((e=>e!=e)),await GM.setValue(b,a))}async function M(){return GM.getValue(b,{})}async function _(){return GM.getValue(x,{})}const C='<svg viewBox="0 0 24 24" aria-hidden="true"\r\n    class="r-4qtqp9 r-yyyyoo r-dnmrzs r-bnwqim r-lrvibr r-m6rgpd r-1xvli5t r-1hdv0qi">\r\n    <g>\r\n        <path\r\n            d="M4 4.5C4 3.12 5.119 2 6.5 2h11C18.881 2 20 3.12 20 4.5v18.44l-8-5.71-8 5.71V4.5zM6.5 4c-.276 0-.5.22-.5.5v14.56l6-4.29 6 4.29V4.5c0-.28-.224-.5-.5-.5h-11z">\r\n        </path>\r\n    </g>\r\n</svg>',S="/twitter-art-collections/tags",T="Tags / X",I=new DOMParser;function L(e){return Array.from(document.querySelectorAll("a")).filter((t=>t.href.includes(e))).flatMap((e=>Array.from(e.querySelectorAll("img")))).map((e=>e.src))}const O="tagImage";var j=n(379);GM.registerMenuCommand("View tags",(()=>window.location.href=window.location.origin+S)),GM.registerMenuCommand("Clear all tags",(async function(){confirm("Are you sure you want to delete all tags?")&&await GM.deleteValue(b)})),function(e){let t=document.getElementsByTagName("head")[0];if(t){let n=document.createElement("style");n.setAttribute("type","text/css"),n.textContent=e,t.appendChild(n)}}(j.A),async function(){let e=null,n=null;const r=document.createElement("div");r.innerHTML='<input id="tagInput" type="text" placeholder="Add a tag..." /><hr style="width: 100%" /><div id="tagsContainer"/>',r.classList.add("tag-dropdown"),document.body.appendChild(r),r.querySelector("#tagInput").addEventListener("keydown",(async t=>{if(/^[a-zA-Z0-9 ]+$/.test(t.key)||"Enter"===t.key){if("Enter"===t.key){if(null===e)return void console.error("No tweet selected");const a=t.target;await k(e,a.value,L(e)),a.value="",null==n||n()}}else t.preventDefault()}));const s=r.querySelector("#tagsContainer");function i(){s.innerHTML=""}await a("#layers"),new MutationObserver(((o,l)=>{o.forEach((async o=>{if(o.addedNodes.length>0){a('div[role="menu"]',o.addedNodes[0]).then((e=>{const t=window.getComputedStyle(e);r.style.border=t.border,r.style.borderRadius=t.borderRadius,r.style.backgroundColor=t.backgroundColor,r.style.color=t.color,r.style.boxShadow=t.boxShadow}));const l=await a('div[data-testid="Dropdown"]',o.addedNodes[0]);if(!l)return;let c="";const u=l.querySelectorAll("a");for(const e of u)if(e.href.includes("/status/")){c=e.href.split("/")[5];break}if(!c)return;e=c;const d=c+"_tagButton";if(document.getElementById(d))return;const p=L(c);if(0===p.length)return;const y=l.childNodes[0].cloneNode(!0),g=y.cloneNode(!0);l.prepend(g),l.prepend(y),y.querySelector("span").innerText="Tag Tweet",y.querySelector("svg").outerHTML=C,y.id=d,y.addEventListener("click",(async()=>{const a=y.getBoundingClientRect();async function o(){if(null===e)return;const n=await M(),a=await async function(e){if(null===e)return console.error("No tweet selected"),[];const t=await M(),n=Object.keys(t);return n.sort(((e,t)=>e.localeCompare(t))),n}(e);i();const l=a.map((a=>function(e,n){return I.parseFromString(`<button id="${e}" class="tag ${!n&&"tag__inactive"}">${n?"":""}${t(e)}</button>`,"text/html").body.firstChild}(a,n[a].tweets.includes(null!=e?e:""))));s.append(...l);for(const t of r.querySelectorAll(".tag"))t.addEventListener("click",(async()=>{if(null===e)return void console.error("No tweet selected");const a=t.id;a in n&&n[a].tweets.includes(e)?await E(e,a):await k(e,a,p),o()}))}r.style.top=`${a.top+window.scrollY}px`,r.style.left=`${a.right+10}px`,r.style.display="block",o(),n=o})),g.querySelector("span").innerText="View Tags",g.querySelector("svg").outerHTML=C,g.addEventListener("click",(async()=>{window.location.href=window.location.origin+S}))}else o.removedNodes.length>0&&(r.style.display="none",i(),n=null)}))})).observe(document.getElementById("layers"),{childList:!0})}(),window.location.href.includes(S)&&async function(){let n=[];const s=(await a('div[data-testid="error-detail"]')).parentElement;async function i(){const e=document.querySelector(".images-container");e.innerHTML="";const[a,r]=await Promise.all([M(),_()]),s=Object.keys(a).filter((e=>n.includes(e))).reduce(((e,t)=>e.filter((e=>a[t].tweets.includes(e)))),Object.keys(r)).reverse().filter((e=>e in r)).flatMap((e=>r[e].images.map(((t,n)=>({tweetId:e,image:t,index:n})))));e.innerHTML=s.map((e=>`\n                    <a id="${O}__${e.tweetId}__${e.index}" class="image-container" href="${e.image}" target="_blank">\n                        <img src="${e.image}" />\n                    </a>\n                    `)).join(""),0===Object.keys(a).length?e.innerHTML="<h3>No tweets yet!</h3>":0===s.length&&(e.innerHTML="<h3>Nothing to see here!</h3>"),s.forEach((e=>{new VanillaContextMenu({scope:document.querySelector(`#${O}__${e.tweetId}__${e.index}`),normalizePosition:!1,transitionDuration:0,menuItems:[{label:"Open image",callback:()=>{window.open(e.image,"_blank")}},{label:"Open tweet",callback:()=>{window.open(`/poohcom1/status/${e.tweetId}`,"_blank")}},"hr",{label:"Add to",preventCloseOnClick:!0,nestedMenu:Object.keys(a).filter((t=>!a[t].tweets.includes(e.tweetId))).map((n=>({label:" + "+t(n),callback:async()=>{await k(e.tweetId,n,[]),i()}})))},{label:"Remove from",preventCloseOnClick:!0,nestedMenu:Object.keys(a).filter((t=>a[t].tweets.includes(e.tweetId))).map((n=>({label:" - "+t(n),callback:async()=>{await E(e.tweetId,n),i()}})))},"hr",...Object.keys(a).filter((t=>a[t].tweets.includes(e.tweetId))).map((e=>({label:t(e),callback:()=>{document.querySelector("#tagSelect").value=e,i()}})))]})}))}async function o(){const a=await M(),s=Object.keys(a),l=document.querySelector("#tags");s.sort(((e,t)=>e.localeCompare(t)));const c=s.map((s=>{const l=n.includes(s),c=(u=`<button class="tag">${l?"":""}${t(s)} (${a[s].tweets.length})</button>`,r.parseFromString(u,"text/html").body.firstChild);var u;return c.addEventListener("click",(()=>{l?n=n.filter((e=>e!==s)):n.push(s),o(),i()})),new VanillaContextMenu({scope:c,normalizePosition:!1,transitionDuration:0,menuItems:[{label:"Rename",callback:async()=>{const t=prompt("Enter new tag name:",s);t&&(await async function(t,n){if(t=e(t),n=e(n),""===t||""===n)return void console.error("Invalid tag name");const a=await GM.getValue(b,{});t in a&&(a[n]=a[t],delete a[t],await GM.setValue(b,a))}(s,t),o(),i())}},{label:"Delete",callback:async()=>{confirm("Are you sure you want to delete this tag?")&&(await async function(e){const t=await GM.getValue(b,{});e in t&&(delete t[e],await GM.setValue(b,t))}(s),o(),i())}}]}),c}));l.innerHTML="",l.append(...c)}s.style.maxWidth="100%",s.innerHTML='<div> <style>.root{padding:40px 0;font-family:TwitterChirp}.images-container{display:flex;flex-wrap:wrap;gap:10px}.image-container{max-width:200px;overflow:hidden}.image-container img{object-fit:cover;width:200px;height:200px}.image-skeleton{width:200px;height:200px;background-color:#6666;opacity:0}#tagSelect{width:200px}#tags{display:flex;flex-wrap:wrap;gap:10px;margin:20px 0}#addTag{width:200px}</style> <div class="root"> <h2>Tags</h2> <div style="display:flex;gap:10px"> <input id="addTag" type="text" placeholder="Add a tag..."/> <button id="tagExport" style="margin-left:auto">Export Tags</button> <button id="tagImport">Import Tags</button> </div> <div id="tags"></div> <hr/> <div class="images-container"></div> </div> </div> ',document.title=T,new MutationObserver((e=>{e.forEach((e=>{"childList"===e.type&&document.title!==T&&(document.title=T)}))})).observe(document.querySelector("title"),{childList:!0}),i(),document.querySelector("#addTag").addEventListener("keydown",(async t=>{if(/^[a-zA-Z0-9 ]+$/.test(t.key)||"Enter"===t.key){if("Enter"===t.key){const n=t.target;console.log(n.value),await async function(t){if(""===(t=e(t)))return void console.error("Invalid tag name");const n=await GM.getValue(b,{});t in n?alert("Tag already exists"):(n[t]={tweets:[],lastUpdated:Date.now()},await GM.setValue(b,n))}(n.value),n.value="",o()}}else t.preventDefault()})),o(),document.querySelector("#tagExport").addEventListener("click",(async()=>{const e=await async function(){const e={tags:await M(),tweets:await _()};return JSON.stringify(e,null,2)}(),t=new Blob([e],{type:"application/json"}),n=URL.createObjectURL(t),a=document.createElement("a");a.href=n;const r=(new Date).toISOString().split("T")[0];a.download=`tags_${r}.json`,a.click(),URL.revokeObjectURL(n)})),document.querySelector("#tagImport").addEventListener("click",(async()=>{const e=document.createElement("input");e.type="file",e.accept="application/json",e.style.display="none",e.addEventListener("change",(async()=>{const t=e.files[0],n=new FileReader;n.onload=async()=>{confirm("Are you sure you want to overwrite all tags?")&&(await async function(e){const t=JSON.parse(e),n=f(v,t);n.success?(await GM.setValue(b,n.output.tags),await GM.setValue(x,n.output.tweets)):(console.error(n.issues),alert("Failed to import data due to potentially corrupted file. Check the console for more information."))}(n.result),o(),i())},n.readAsText(t)})),e.click()}))}()})()})();