Files
2026-04-28 11:14:32 -04:00

36838 lines
1.1 MiB
Plaintext

/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
// src/resources/flags.js
var flags;
var init_flags = __esm({
"src/resources/flags.js"() {
flags = {
"red": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH6QMBFCMRzbWhYAAAAm9JREFUWIXt1Eto03AcwPHfPy21r6WZ2JOKrN2jrV3b6ZAWHxQcXpwOvDkUD4MdnDfdSPAQi+JBUG8TRMGDrOrwAeKiUCHIYBE8zR3s1sdBkYEwpgOXf5v272WW1dKtaZMepH8IhF8eny9/SIAQAoQQGDh0IPz3vBkHih4Mhe+cOzXnpO2rP3+vM1efvY28+jAngc6LunTisOCk7QAAjMNqgfHBqKA3CgBA7d7pYDYP9u1qZya9HvZIR0dYV9hAoYrhiMXMJxhaSPh97EhXpy4BVJW5GSHEHDWZ+Mk2u/AtFCB3vR42ouEuVINLAQDAOCkKLlrM/HuGFmb8PvZ8Z+O7sB1cFmFAiDluMvEPaLuQCvayN3q66w5QA5cCAIDZazDw4zarsNYXJNP7fewZt1tVRD1wWYAJITi9w8THHW3CfMB/hatxFxqBKyJ6jMbr12xWYaUvSB75POyg21U1Qiu4LMCGEJw1m/nnDlr4FPCzl7u7KgK0hisieo1G/qbdJvwIBcl9r4eNbnySesJlATSF4ILFzL9rdwgnXa6wsQlwaSUVJfZYxuKbTEbSHV4nBGZyeS4uy+LrdEa6tTHXDc4UCtwTGYux5KI0DADD/1zXFFYAIIFz3JSMxafptBTb4l5N4O+FAjeNc+LEl6Q0BABDNTxTN0wIgdl8novjnPhwKSVNqHxeNbxSLMILnOPiMhZns1lpQO0L1MIf8woXl2Xx3lJKGgOAsTrBmuBfRQIvMeamZCyK2ax0rEFsW/izokBcxpHbi0vSKACMaghuCffPL6B+HbDNqxn/6hbcglvwfwobF74uw/LqWtlwTxPgP3n3GrlhLVHIAAAAAElFTkSuQmCC",
"checkers": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAC5klEQVRYhe2WS0hUYRTHf8fX+EpnfPQwKXIT9iKLYsSNRLZSgkoICgKJWiTVJopaRFRCENFrkVEbF0G1yAiyaGMGWZARPcQMsjDKMHN0SjOz0+J+l3vV66Q4uvLAcL//+ebO77zudwdVRVVZv3ZV0F5Px0eKV68Mntle1pidlhrq6ev3H7lxr7C2ofEJU2wxlRuK6rLTUgH86clJHCgtrptqKEDM/Ix0v9uxMCvgR+QQIsEpBcfGyChnHlQVwaOpDCDGy9kF8g3igKNA3XkRPSdyJZpBxHk5e8w1AIk5kPjGktuuw9ZmkdpjcBHVSQ2gZ8a2hQF1ZOIySCmHLUDdZNvgmXE6kAb8sYnALKDTkr7T4HsJJ2ogjIj/BbR9grulqpXjBXtm/AvIdGk/kOPSYeAnxJotPsCiftiDSCciV8cD9sx4wFzjTWS2zgJ6gd9G+4BB4K/R+ZCVDBVNIhXAyztQX6a6z4sRscdJQKpLx464IdkEZ1sr8N6RK17B3nqR74hUjwucg9XXXqDLrDOAr1htyDNBdWNVY44JYMj4/EAQOAwUQ2AT7Lotoog8R2Q/jFHqNKDfpRMB9zEzaCDufXcGfcBHl35ogt0IBUABIsme4BZXAAlAyGifqUa70cvN2t6PB1JwZiAfKMSqGsAD6A1BfblqlSfYtniskto2G1js0l+AHy6dy/DpH8KqDtAMXCtRPWnveYIzgYAr0gScnts3peCccHOBeUCb0ceBEgi3wi1Ud3gxPMEhnPJhgoh1aWH4NHfjHDbAuyXQEFTd6fXbEcH24ASwnlE7s2ysqTbl4ylWjwesM+U+quUAmyMRI4Ft0xF6AU65ARLgcxc8toETMU/wUuAtTvkuYQ1Nk/OVZ0BNgeqFgokSI4EHsV4KthUDPmuIbzLGERgVcKu5ngLWQcsaqEb1bDSAEcHGLh9U3R1N2P/BqqP/iEXZIr6dZsAz4BnwDDiSxb1u76AjFB7mzJ0G8D/kvRAp7yYAQQAAAABJRU5ErkJggg==",
"blue": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACMElEQVRYhWP4//8/w////xlczIwsYGx6YEYHYwOLvhjf46J8PB8+fvsuUL1qh+WGQ8dPMNAYMOW4WW8X5eNhYGBgEODn4mQo9XHYTmtLGRgYGJikhfgFkAXkRQQFfCLX7tD3n55CU4uZmRgxBB9IOLozKQTOiopau8MhcCZNHMCCTfAnExvDTyY2xkviju6s/365qRZ/ma36bOfOJ99erbm0MXMONSxmwif5k4mN4QsLDyMDAwPDIwlHd2aFwFkRUWt32AVQHgpYfYzLET+Z2BivQEMhNXp9yKvvr9dsXJdGVggQbTG6A46L2cOjQf/5zp1Pv71ec3xDBtGOINlidAcwMDAw3ICGQlL0Wv8X399u3EZEKJBtMTZHnBJz9GH998tbtfjLbO3nO3c++/Z6zSkcoYA3cZHjAFhivCPu6P5bPmBWfPS6He5YsiRVfIzLET+Z2BjPijnA04Lms507H397teb8xsw5NLMY3QEMDAwM9yQgacHMfwbtfIwNmL46sPPFt9drtm/MoL2PGRgYGHSh2W0+UkKjmcV2L3bvfPX99ZrN69LmMDAEY8hT3WJDqO/mEihMqGKx44vdO19/f71mPQ7fUd1i0+c7dz77/mbNrPXpJJfXZFmsDs2PyyioIom22PzFrp3Pv71es3d9OtHBSZHFsNJmCZUaAAQtVrq3NHUnlXxHtMW3e3kYGRjSaWIhDFC1dhq1eNTiUYtHlsUsVx6/YHjx4TOKoAwdLAYATv0+zO4Wc9kAAAAASUVORK5CYII=",
"green": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACOUlEQVRYhe2VzWsTQRjG39kMQaW0UcxJQWIbQSQbtVprE6RKmz3Y4kkQ8ag3r0Vt7+1uD/4BaY6CKAMqHrpDBSWI8ZJEiIjamBz8aEqVrBaVhd0ZL90S7SZu404v5oWF5Z3D73medz6Acw6ccxgZODro/G/Fh4b7Dw/evDSeC3d3GV9//AxN3dVP3s/mnoPgkq6mEvPh7i4AgFDPju0wMTY8LxoKACDt2dUTamzs270zdEbt1Q9ci1wWCg5IaEPzbZwpVhKlz2l9+okb+4UIwG5NEwOYmKO8bCtBC6XCC5G5Q3mJfqwzsqhVM36ApVaLJgZY3cYRAEA5zhQ7idLjWp9+3IcUWoLdRBRlW/kyBOmLs1E9NdnbtgDXqP8mwMQcZWPW+hiOFQJ0qc7IC7XieQyeHbsJcMZQkm3lWwLSF7Tow9MeU9i042YiTMzRU9kaC1robHghMnckL9Elg5GS6r4Z23bcTICTwqs4U74nUPr8bFQ/5ZKCL46biTAxR7mGvSCvHcnXWjUjDPynAACAN3GmBC2Uil2PiHPsVokSpssGI0/UinjHAAD9hQD9VLfJHXVxfaMJA48UMV0xGHk0/S4DoxvXfQcPrF0mtxvcuZUvYKWI6WeDE3267OrOd/BQIUBrBiO3Zlq78w0cy0v0Q52RB1q57SfSMzhZwHTZsMmzmYrnOP8J7Nw297TNx9kW+OBj6Uq2yVEQBl4ZrSJRQKd8fZ064A64A/6/wPjl+xrUjNXfmnu3APwLyT02r9kBUpoAAAAASUVORK5CYII=",
"pink": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABSUlEQVRYhe2WsUrEQBCGv4tXRDnuVvEqBV9BFCSHzRVipXB18BEsFRWLQ+x9jCus0sVSbM4H8A1srjwR1G5tNqC4e9kkuykkAwvDpPj+f3bYCVJKpJQc7O1EWV7HaQ13t6O7k+Npv9uZv318iuv7h0HyNH3GcwSnh/tpv9sBEL2VZc6PhqlvKECwsdYTPwtb66uCOLkkTiKv4KWgpauPgdSngLahHqozBi6IEwFcAY9MRk7uP8j5HgJC5U67kAfWiXAiwNTqPAGVr6GIY50AofKsC2e2XSjj2CQiBG6BL5suVHFsEiBUvnAWXDk2iTDOgmvHJgFC5VkXIp+OdXGDclwX+M+g+QQvnGofYKvHxBW48MtVFVx6Y5UFV16RRcBO97EN2CnQBjxwDcsHT0baHzGXUcdb3YAbcAP+p+D2y+uM2fz9V3GzBvA3IcG57wVt5SkAAAAASUVORK5CYII=",
"yellow": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABPklEQVRYhe3Wv0rDQBzA8W9jKVVKexE7KfgKoiApLiWIk76Bj+CooDiIuPsgTm5xKuJSH8A3cOmWiqCS5VwqxHhJ8+d3GaQHgeOWzze54whaa7TW7O9uez/zOp7GcGfLuz0+Gve7nenbx6e6vHsY3D+Nn7E8nJODvaDf7QCo3soyZ4fDwDYK4Kyv9lR8YXPNVWHkn4eR71mFl5yGaf0KCGwGOCnrbUDFArR0RBqcDADhrzAPNkWIBBSB0wJKbUMZOBlALOI0b0CzApyMaAM3wFcY+Qq4AB7d1sh4GVV547QANZtnngVp2BRhPAs24WQAsQhPao/zjmtm+14X/Oeg2YQzT7UNOBOUhnNhknBhsCpcGiwDV8aKwqJgHnggjc2F3dbI+CMmOeq4qxfwAl7A/xRuvrxOmEzffy1u1AB/A5cP1WBzW154AAAAAElFTkSuQmCC",
"orange": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABTElEQVRYhWP4//8/w////xlczIwsYGx6YEYHYwOLvhjf46J8PB8+fvsuUL1qh+WGQ8dPMNAYMOW4WW8X5eNhYGBgEODn4mQo9XHYTmtLGRgYGJikhfgFkAXkRQQFflSqVvyoVLWgqcXMTIzYxOsZGBi209IBLDjEOaC4noGBofxHpaoAAwNDJQMDwwGO9ttUiX8mAvIcDAwMAlA2VUOBkMXYHEEVB+AKakIOoDgaSPExNgcIQNmwUCghNhTI8TEuR3AwMDA0MzAw/CAmFCjxMS4HCEDZeNMCtXyMyxE40wK1fYzLAQJQNiwULGjpY2ygkQHqY3pZjJHQaGkx3lRNC4uJKkyoZTHJJRelFpNdY5FrMcVVJCkWU7U+JsZiqlpIjMWW1LaMoMUc7bexNsSoCehRVo9aPGrxqMXD1GKWK49fMLz48BlFUIYOFgMAyz/KBzK6zGkAAAAASUVORK5CYII=",
"purple": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABPElEQVRYhe3WPUvDQByA8aexQ5TSnmInBafsomBTXDqIk34DP4KjguIg4u4HcXKLo7jU7G4dXTpWBM12LhVivKR5+V8GyUHguOX3JHccQWuN1pqDvR3/Z17H0xrtbvt3J8fjfrcze//8Ulf3j8OH5/ELlodzergf9LsdANVbWeb8aBTYRgGcjbWeii9sra+q0IsuQi/yrcJLTsu0fg0ENgOclHUXULEALR2RBicDQPgrLIJNESIBReC0gFLbUAZOBhCLOMsb0K4AJyNc4BaIQi9SwCXwNJi4xsuoyhunBaj5PPMsSMOmCONZsAknA4hF+FJ7nHfcMN/3uuA/B80mnHmqbcCZoDScC5OEC4NV4dJgGbgyVhQWBfPAQ2lsITyYuMYfMclRx13dwA3cwP8Ubr++TZnOPn4tbtYAfwMBYNJpfZpdcwAAAABJRU5ErkJggg==",
"white": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABJklEQVRYhe3WsUrDQBjA8X9jB5XSnmImBV9BLEiKSwdxsm/gIzgqKA4i7j6IU7d0LC7tA/gGLh0rgo7nFDjjxV6S724o+SAQvuX3JzlC0Fqjtebs5DjJ7kNcrWH/KHm+HM3ibmf58fWt7l8mg/HrbI7nia7OT9O42wFQve0tbi6GqW8UINrf7Slzcbi3o4BbIPEKb0Qt2/4BSH0GRAX7TUAZAVo6ogjOB4DwU1gF2yJEAsrARQGVXkMVOB+AEXHtGlAHtkU84fgUpOB8AKw4C9KwLcJ6FnzC+QCMiKQdADbnEZgC81DwXQZmC5/wH8wcH/C/oDTshEnCpcG6cGWwClwbKwuLgi7wQBpzga0/YpIT4lvdwA3cwGsKt9/eFyyWn7+WBwHgH/UUmOp6KmdeAAAAAElFTkSuQmCC",
"black": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABLElEQVRYhe3Wv2rCUBTH8d9NHbSI3oqZWvAVpEKJuDiIk76Bj+DYQksHKe59kE5ucZQu+gB9AxdHi6DjcQpc05vm37l3kBwIhLN8viSXEBARiAiDp0cvuLdxiX6n7X1Oxmu3Vt3/Hk/y/WvZXXyvNzA8znTY891aFQBk/baCl1HfN40CgHPfqEt10WreSSHEqxDCMwrfOEK3nwHwTQY4EfsyAKkEEHdEFBwOAJifQhysi2AJSANHBWR6DVngcACUiOekAXlgXcQcCZ8CFxwOAGLOAjesi9CeBZNwOABKhFeyAKvzAWBFRBtb8FsABguT8B9MHRPwvyA3nAjjhFODeeHMYBY4N5YWZgWTwF1uLBYmIu2PGOfY+FYXcAEX8JXCpZ/tDrv94WL5YAE+A0hLuhQ62ZsRAAAAAElFTkSuQmCC",
"finish-line": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAADyklEQVRYhe2WTUgjZxjHf0mcfJqYafyciqW5aIyWpsUS8SLS9FIloBUWWhCWsj10aU/FQg9FbBcEKavtwS3txUPB9tAFobZ40S10FRoq2hVNwQ8izYImxkRN4pikBzuzys4Gt9U9+cDA/J933vf3vM/7zDNDoVCgUCjw+muv+JX7Z3Hp2l992f/FO133Kxylib3DtPOT739uvXvv/hyXbPqbb7RNVThKAZxlVgsfdbZPXTYUQP/8c2XO044XykUn8DHgv1SwQa97zOl2u2+1tbX9epkB6LWcsVhMt7OzUwJ8CkyNjo4WRkZGvrnIIEq0nHt7ewCIomiWJMn84MEDgLcnJiauLS8v3x0YGPgK+F8FqAlWLJVKUSgUFGluamrC6/W+BbwJDAEz/zUATXBZWRkOh4Pj4+MTotmM3W5ne3sbwDQ8PGxaXFz8bHx8PAU4FxYW1re2tn7q7Oy8eV6w5hlnMhlcLpeqnU4nkiSpOpVKcXBwYACcABsbGy+m0+n3gW3g2/OANXeczWYBEAQBvV6v6vLycpLJJEdHRwCYTCZkWSafzwPg8XjKrVbr9VAodB1YnJycnOnq6vrw3DtWzGKxUFpaqmqDwYBe/2iK1WpFEARVh8Nh1tbWFPnS0tLSBzMzM3Hgzrl2LEkS8XicTCYDnJyx1WolHo8D4Ha7iUajpNNpAKqqqojH4+RyOeDkaBoaGpibmwMQu7u7b/T19d0IBoN/AOPAbU2ww+FQF1XAOt2jRiPLsgpRxk9n4vDwkM3NTVXPzs7idrsJBoM+wAdYNcErKytqAEajkUQiAZycqSRJRCIRAJqbm4lEIuq4IAjYbDa1BjweD62trcRiMQCmp6eTiURipre391bR91gQBCwWi6orKyupr69XdTQaZX9/X9W1tbVnqj+XyyHLMsAy8F0gEPhcGdMEu1wuRFFUIzUajZjNZpLJ5MmkkhJsNpva4aqrq6mpqWF9fR2AwcFBAoFAKhwO/wj0aTE0wYlEQk0fgCiKGAwGVet0ujPVvLu7qzYb4K/GxsZ7fr//Xa21i4KVwhFFkXw+r+6soqKCTCajpI/5+XkikQjZbDYF/AL0AvT09BRjPhms2Kk+DUBdXZ2abgCj0fh3LBb7TQE+jWmCvV4vq6uravrGxsaQJIlQKKQ88jsw7vP5vvT5fE/LfDJYlmXsdruq29vbMZlMUeAHQLMFXgg4HA4DMDQ0REdHx0pLS8sd4PZFAIuC/7Wv+/v737tI2HnAj/+IXbAV/Tpdga/AV+ArcDEr+TPykIeJ1Bln7TMA/wOG1Kb5w4e1ggAAAABJRU5ErkJggg==",
"bee": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAD+UlEQVRYhe2WXUxbZRjHf+f0uytQoB2sEvEjMTEmaOfYSgyOGVyauO4CcXPOj2wxbGTLZqJbjMZwMbcLLwwGL0RYXFhC4mAbyjaqF0pYIqC4JroYFxIxOvvByRBaKF/tOV6ctYXspLKs7Ip/0qT/5+05v/6f9zlvi6IoKIpC7eaNntT7+/ESap5+yvPxq75BZ75tcio+a3//nL+qZ2BwiFWWeHj7M33OfBuAvcBq4diOmr7VhgKIDxQV2JcWyh2F9oDf9W7A7/KsJlivE4U7ir5946ccRbrkF7g+APrd3mDOWy9qFSejsjA5JeuBJqDveGOBcuxgQXsuu6DXKl49XwrA1vqI2VksmrtbnQB7vx2Yffmzs3k9B1/L+/Reu6CZOKWZWRlFSVvzo+WGdbXVlnqg717nQDNx9YthbFaRkcsbAPDsDGO1CHz3ZQmAqeP8jGl0bPHDE+/YYwG/y37jj8WxcUm+cqTp1uGVgjUTLyxAQX5mKc8m4CzWpX08LjM7p+gAO0Awknx4bkE5FPC7pIDfdXolYM3Ew73qHlfuCCEKQtpv2x1hJi7zY6/aiS2+MImkws9XVF/XIDnMJmF/Z4trP/DLwPBc/9GmiaMrTpyS2ShgtWQeN50IopDxZpOAXpfxf91M8E8ombIVo2OJI20fOSYCflfrihJv3xshGlMY+lpN6tkZxmwS6O8qAcC3bxxpQmboK3W9dk+EqZjMyO3kz9aHeahMT0ezA6Dw7RP/NvgGixpqqswBoMPtDTZrgm1Wkfl5Oe2NRlgSlEQCZFlZsi4s68TcvEJoPJ2ca78uULZBR02V2Q24A36XVRN8oc0JQHVdGL1BYKBbTbbFF8ZZLNJ3dj0AuxolwlIyvV65I4TFLKRnoK5BouJxI9+fUzs1dG0+GptR+o+fnDilCU5JrxcwGzNJiuwi5WWZS6QJmfhsJnmJQ7ds+mUZEgkF4Degs/G9WyfT99YCbtsVIc8mpL/pZl8Ik0Hg6gU12aYX1GSpE+75VyI4CnVcOqN2oq1zGs9GU6yn3XnR7Q2+oRlKqxiblolNZ3y+TURcMv+CoHYjpWhMIZFM7+noIw/qB15/S3pT695Zwanp3FofQVaUdLLndkdYWFT46ZK6fv3GImEpyXBvaQz4xu0NvgTg9mZDZgGnpKAs86XrdczEMzW9nuBUVP4hBbwbaYLrD0j8eTORPqu7L8dxFot0tjhSHxkBOvYcklruFpgVnEjCOktmUzdVGDEYhBDQ5fYGNY/AnIB72tXn+EzXNJVPmn5/4jFDq9sbbM4FMCv4tj7/5HT0QC5h/wt2e4N3/hHLsbL+Oq2B18Br4DVwNumv/x0mPBlbViy7D+D/APJpt+laKOEtAAAAAElFTkSuQmCC",
"red-fade-light": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACmElEQVRYhe2VS2sTURSAv5NJm7bWvNrYYiv5ByIVpFIXFWpBUNGVLsSdICq6UTddCIorwYUIIv4D3eimdVEQRGgXQheKC0ERWmkCsaaW2qRJ5riYRyaPThNtusodZrhz7p3zndc9g6qiqkwcGRl15rtxy/jhQ6OPLp6eS4R7s6t/NqJTL94cffVubp4Wj8D1ybGZRLgXIBrp6eb2qfGZVkMBAkPxSNQrSPbHokuDg6nlkZGnLQUbAakRltLpgc2FhSuLw32aPjE23RKw36ICxfTyydSxg/rz0tkfq1M3nrQc3DUxgQgIgAilTGZ/8euXa9mrF/LrD+68bhk4NzsLCOzpARRsI6RQ6tSVzJmNuzc1//je938FB31XOzoJBIKAYvQPQH7dDgEgApuFZOHZQzUiMQLxviKT5zoaBfvmmEIeM58jEItDIWfJ1L6tB4iiAqoaZP6t8nlB/xssoS4oljBXViitZl0u4QjEE0hiEMSrwmYuflN+ZZT8Ru6fwJrPoWbJzq8BhmFFen0NYyiJZlJgKqJuGByTrXclhCdGDYNREOcTNcEsWXPTpPjxgxtydVuBtyfU9AfHgN/be4xUpBO19KmrVsu1hmejKKjWYQOwF8j551hsjVJ23dGnKiBiG1GJdz/eusy6tg21o7Ami6K2QFDUXlN3f52x6bgB251j8RzbKp1lI7TCwOrVrazwB1folLK5HvfFvqowJrF+w0+1P7hOkhSsKhYPX+wiGz1ev5yaBatUVSt2nVkz+weiBM5fbhjYEFjc41RboRLqzIZu3Y81C2wI7Emky408f9m0d82D7ZYk3T1r+6bfh3cC2BD4wFJmR7xrGJxUbRnQGf6dqw1ug9vgNthnBD8tpkhl1yqEw7sA/gs0rjmZ1qf2MwAAAABJRU5ErkJggg==",
"blue-fade-light": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACwElEQVRYhe2Vy08TURTGvzPToYXwmFYQBSLRuHGlQUMwLsQETYyPxL1/gIlsXLhy4daFceFGl65d+NiIC1csxEQWmmhiSAxECUKIUKlCO+3M52LundvSMmkJZdWTTHvvmen5ne+cc6cgCZKYGB0Z0+v9uGT89KmxRzevzfR1d2b/bG65956/PftqeuYDmmzW5KVzU33dnQDg9nS04+7V8almQwHAGsz0uOWO4d60e+j94vLI7K8nTQXbllQ5Vzy//9Nf79ax6R+88nHpTTPAiVpOAUB1rXj+5YmZRR5td5aGkomX908cmNwLsFXLSQAX0ymzIbDm+QPzm97tO59XCw/n1l43BQwA79bzAICOhAUIQAIiAp9B23rRv/7g228+/Z5d2C04ttRtlsAWABT0JW14PsO7DO+XyOFnC1n2ODZcxy5dONjh1AveUTEAeAGR9wnXERQCqqozyk4ACCRMlEjMruU5l/O4a7D+ZUoEAYn1YoBcKQBAiABdjoWMY6M3acNSbQAAUQdkaavIbDFgIWC+IbCoKx8QPqE0ISw7gH+lAIdTCawWfHUfpj8qgoTlScIckPoUc9uaAAKG5IDA141ClCD1q0CVHyAoOqOqsBuxircHigQpin4m7LkhUMkXIFRdbV0A8rE9NnwJfTqamPRFnzUw/NaDzxr1NZbacaqNolBVhQKKKXMUXtRTelvRdADwYMYn/hxHIk3nQFhRVlqx6KMlyhv2BCJWuYYKqwneXiJqzaKn1fQ1GsSotAICgevYdq3YseDyDAhAygfKfKB8RSHOpFM11dUNjkotqswSKqSWFk1y2OEbg111A2PBrNpVx3UsyU4ez6QbBcaCtZkCG/Djk/0Nq2sYrEvbblu5F6MD3XsBrAs8f/7InqirG8zx4aYBtcX+H7fALXAL3ALHWeLLz2UsZ3MVzqF9AP8HvhlcMbOYSC0AAAAASUVORK5CYII=",
"red-fade-dark": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACWElEQVRYhe2UzWsTURTFz518zCSZSVMxtdWQUBDcSiTS4qaLICrqP6B7Bd24cKP/gyuxiPsuXOnGiBQEXdRFoQRKQdwEYjDNQtIkZmYyyVwXyWgymUw+yHSVAwkz9713f+e8eTwwM5gZ2avpDev5NH60deXyxov7d/biUbl60lRjz99+3Hz3Ze8bPJbw+Pq1XDwqA0BsKRzC09tbOa+hACBcOLMU6y+kzi7Hfq6uln+l09uegn0CDRU7x8fnWgcHD78T8WEy+cET8LgJRrF48ysRf04kSp8ymZeeg6VsduBdLZXO/97ff/RGFPXX6+vvPQNru7vdCYoCALA+iNlqBU8KhbvPiPhJJFKYOxgAIIogvw8AICWTAwYAQG82U/eI+AYRbwYCxvzAug6zqSKwsoKOqgIAuDdkP5Kddtt/kYjjRIwJ5HcbpFAIbBgwKhW0+ury2hp8soxIOIzG0RHIMAbMSERsAAiIoq5pmuTU2zUxqyrQbnebCgLI3/X5p1JBIpNBKZ9HxzD+7YI9qq7rIhExOeyCO7ivGZsmuGeCzQ7yOztDILKtHRjrGVAUpTYWPHy1/K+RQ23U9+9Xo9FQJEnSxl4gdhD3/hjuCd2kaZo0Fmx1d2tsmXDaBUvBYLDFzMTMBIw51S4+RhqwP1sguxwTD8x0iep0uAiAIAim2ZfOSY6JnVhuSam35ocLaCKwmxknU7kpgFOD7ZIUpfqqVluedf3U4AczpJsZ7JPl+q16PToP4ETgS3NKNzE45SHQ0vibawFegBfgBXiE/IfFMsrV+kAxcQrgv2BEIMc8VIzqAAAAAElFTkSuQmCC",
"blue-fade-dark": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAC0klEQVRYhe2Uu08UURTGvzM77A6wu+waUVEQLUwsjUSDscFoiBr0H8DCCk2ksbDRxMpY2hlDrLWwURvZxsZHMGJMSNRoYoGCumqCCzvAvI/F7B1mhtlxMLtUe5J93DNn5vd959y5YGYwM04cPjgo/m/Gh4YGDgzeOndmqjufrSyurBauPSgdefRs6hWaHNL48NHJ7nwWAApdHe24MjI02WwoAEi7tnQV/In+rcVCz8v58sD0jztNBackWpcsG/b2t6p+kUqfeHfp/ZNmgOWoJDEAIjAYc6vGKbr3nHthft8B6+H06PB4I8BSVJIJOF5UXAG1mF/Wdr75uXApc+OuvvfmxOOmgAHg6R8NTEBedkvEQAzbSc8uLJ6lC1e58/zl2f8Fx7QayEgSUjViX7YdX9UlnwRgxTD6aWSUUVmAXK1Y5sxUW1Jw3VYzA7rtYMVmbFPasGrZYXkAxCwYlm3J1LePqdDNSBCRjgGACFBkgqkDvzQT0AwP0pPPItueQkexEx8+qjCJABDc7QhQWmFYJjLpNl3TNCWxY8B1rFmAxa4BiQiyJAFE+K0u49CeXsx8+QbTsgFmDwqqqQZD1/UMETERretCJFiU+asdZliOA4BhsYP7r2cCFSxmz2IPBM8HISCXyy0B9Vrt3Rs1LgLVCtiXgeeYg4pDoapqTlEUrW6rKaTaWzODxbOFSQH1In5/aZqmRO9q/73ky9W+KVAYFAhGuMsAgHQ6bTAzMbuzqLur3cf5cSLnU0IUuOKtee1VE6BwxB4grvNQE8XcKSyJvVpJkhzbcVJxpiLBnkYm+KfNCO94rxAAgec+R7pLDBZGyNfVtTOK1tXxi1JiYCyYyA8KX3R/ckqmsjRxu7hRYCw4zGHfiq+PbdhdcjAL1y4yK6eq1bHT+UYAY8FMNacn9zfEXXLwsf6mAUXUPTJb4Ba4BW6B/xXyu7kyypVqINm7CeC/dmFnE+0zAOgAAAAASUVORK5CYII="
};
}
});
// src/utils/semver.js
var semver;
var init_semver = __esm({
"src/utils/semver.js"() {
semver = {
parse: (versionString) => {
const [major, minor, patch] = versionString.split(".").map(Number);
return { major, minor, patch };
},
gt: (v1, v2) => {
const ver1 = semver.parse(v1);
const ver2 = semver.parse(v2);
if (ver1.major > ver2.major) return true;
if (ver1.major < ver2.major) return false;
if (ver1.minor > ver2.minor) return true;
if (ver1.minor < ver2.minor) return false;
return ver1.patch > ver2.patch;
}
};
}
});
// src/resources/constants.js
var PIXEL_BANNER_PLUS;
var init_constants = __esm({
"src/resources/constants.js"() {
PIXEL_BANNER_PLUS = {
API_URL: "https://pixel-banner.online/",
// API_URL: 'http://localhost:3000/',
ENDPOINTS: {
PING: "ping",
VERIFY: "verify",
TEXT_TO_IMAGE_MODELS: "text-to-image-models",
UPLOAD_TEMP_IMAGE: "/upload-temp-image",
GENERATE: "generatev2",
GENERATE_BANNER_IDEA: "generate-banner-idea",
GENERATE_BANNER_IDEA_FROM_SEED: "generate-banner-idea-from-seed",
REWRITE_BANNER_IDEA: "rewrite-banner-idea",
HISTORY: "history",
HISTORY_COUNT: "history/count",
HISTORY_PAGE: "history/page",
HISTORY_DELETE: "history/image",
STORE_CATEGORIES: "store-categories",
STORE_CATEGORY_IMAGES: "store-category-images",
STORE_IMAGE_BY_ID: "store-image-by-id",
STORE_IMAGE_SEARCH: "store-search",
SIGNUP: "signup",
BANNER_VOTES_STATS: "api/banner-votes/:id/stats",
BANNER_VOTES_USER_VOTE: "api/banner-votes/:id/user-vote",
BANNER_VOTES_UPVOTE: "api/banner-votes/:id/upvote",
BANNER_VOTES_DOWNVOTE: "api/banner-votes/:id/downvote",
INFO: "api/pixel-banner-info",
DAILY_GAME: "games/daily-embed",
BANNER_ICON_CATEGORIES: "api/icon-categories",
BANNER_ICONS: "api/banner-icons",
BANNER_ICONS_SEARCH: "api/banner-icons/search",
BANNER_ICONS_ID: "api/banner-icons/:id"
},
SHOP_URL: "https://ko-fi.com/s/7ce609ff2c",
DONATE_URL: "https://ko-fi.com/jparkerweb",
BANNER_ICON_KEY: "pixel-banner-icons"
};
}
});
// src/modal/modals/releaseNotesModal.js
var import_obsidian7, ReleaseNotesModal;
var init_releaseNotesModal = __esm({
"src/modal/modals/releaseNotesModal.js"() {
import_obsidian7 = require("obsidian");
ReleaseNotesModal = class extends import_obsidian7.Modal {
constructor(app, version, releaseNotes2) {
super(app);
this.version = version;
this.releaseNotes = releaseNotes2;
}
onOpen() {
const { contentEl } = this;
contentEl.empty();
this.modalEl.addClass("pixel-banner-release-notes-modal");
this.addStyles();
contentEl.createEl("h2", { text: `Welcome to \u{1F6A9} Pixel Banner v${this.version}` });
contentEl.createEl("p", {
text: "After each update you'll be prompted with the release notes. You can disable this in the plugin settings General tab.",
cls: "release-notes-instructions"
});
const promotionalLinks = contentEl.createEl("div", { cls: "promotional-links" });
const equilllabsLink = promotionalLinks.createEl("a", {
href: "https://www.equilllabs.com",
target: "equilllabs"
});
equilllabsLink.createEl("img", {
attr: {
height: "36",
src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/equilllabs.png?raw=true",
alt: "eQuill Labs"
}
});
const discordLink = promotionalLinks.createEl("a", {
href: "https://discord.gg/sp8AQQhMJ7",
target: "discord"
});
discordLink.createEl("img", {
attr: {
height: "36",
src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/discord.png?raw=true",
alt: "Discord"
}
});
const kofiLink = promotionalLinks.createEl("a", {
href: "https://ko-fi.com/Z8Z212UMBI",
target: "kofi"
});
kofiLink.createEl("img", {
attr: {
height: "36",
src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/support.png?raw=true",
alt: "Buy Me a Coffee at ko-fi.com"
}
});
const notesContainer = contentEl.createDiv("release-notes-container");
notesContainer.innerHTML = this.releaseNotes;
contentEl.createEl("div", { cls: "release-notes-spacer" });
new import_obsidian7.Setting(contentEl).addButton((btn) => btn.setButtonText("Close").onClick(() => this.close()));
const modalEl = this.modalEl;
modalEl.style.position = "absolute";
modalEl.style.left = `${modalEl.getBoundingClientRect().left}px`;
modalEl.style.top = `${modalEl.getBoundingClientRect().top}px`;
}
addStyles() {
const styleEl = document.createElement("style");
styleEl.id = "pixel-banner-release-notes-styles";
const existingStyle = document.getElementById(styleEl.id);
if (existingStyle) {
existingStyle.remove();
}
styleEl.textContent = `
.pixel-banner-release-notes-modal {
max-width: 600px;
width: 100%;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%);
}
.promotional-links {
display: flex;
flex-direction: row;
justify-content: space-around;
margin: 15px 0;
}
.promotional-links img {
border: 0;
height: 36px;
transition: transform 0.2s ease;
}
.promotional-links a:hover img {
transform: scale(1.1);
}
.release-notes-instructions {
font-size: 0.75em;
font-weight: 300;
letter-spacing: 2px;
text-align: center;
margin: 10px 0;
}
.release-notes-container {
max-height: 400px;
overflow-y: auto;
padding: 10px;
margin: 10px 0;
border-radius: 5px;
background-color: var(--background-secondary);
}
.release-notes-container img {
max-width: 100%;
}
.release-notes-section {
margin: 10px 0;
}
.release-notes-section h4 {
margin: 15px 0 5px 0;
color: var(--text-accent-hover);
}
.release-notes-container > ul {
padding-left: 25px;
}
.release-notes-container ul:not(:last-of-type) {
padding-bottom: 20px;
}
.release-notes-section ul {
margin: 0;
padding-left: 20px;
}
.release-notes-container li,
.release-notes-section li {
margin: 5px 0;
}
.release-notes-spacer {
margin: 10px 0;
height: 20px;
}
`;
document.head.appendChild(styleEl);
}
onClose() {
const { contentEl } = this;
contentEl.empty();
const styleEl = document.getElementById("pixel-banner-release-notes-styles");
if (styleEl) {
styleEl.remove();
}
}
};
}
});
// src/modal/modals/imageViewModal.js
var import_obsidian8, ImageViewModal;
var init_imageViewModal = __esm({
"src/modal/modals/imageViewModal.js"() {
import_obsidian8 = require("obsidian");
ImageViewModal = class extends import_obsidian8.Modal {
constructor(app, imageUrl, bannerPath = "") {
super(app);
this.imageUrl = imageUrl;
this.bannerPath = bannerPath;
}
onOpen() {
this.modalEl.addClass("pixel-banner-image-modal");
this.addStyles();
const { contentEl } = this;
contentEl.empty();
contentEl.addClass("pixel-banner-image-view-modal");
const actualUrl = this.getActualUrl(this.imageUrl);
const imageContainer = contentEl.createDiv("image-container");
const isVideo = this.isVideoUrl(actualUrl);
let mediaElement;
if (isVideo) {
mediaElement = imageContainer.createEl("video", {
attr: {
src: actualUrl,
controls: true,
autoplay: false,
preload: "metadata"
}
});
} else {
mediaElement = imageContainer.createEl("img", {
attr: {
src: actualUrl,
alt: "Banner Image"
}
});
}
if (this.bannerPath) {
const pathContainer = contentEl.createEl("div", { cls: "path-container" });
const pathDisplay = pathContainer.createEl("div", {
text: this.bannerPath,
cls: "banner-path"
});
const copyButton = pathContainer.createEl("button", {
text: "Copy Path",
cls: "mod-cta"
});
copyButton.addEventListener("click", () => {
navigator.clipboard.writeText(this.bannerPath).then(() => {
const originalText = copyButton.textContent;
copyButton.textContent = "Copied!";
setTimeout(() => {
copyButton.textContent = originalText;
}, 2e3);
});
});
}
const closeButton = contentEl.createEl("button", {
text: "Close",
cls: "mod-cta close-button"
});
closeButton.addEventListener("click", () => this.close());
this.scope.register([], "Escape", () => this.close());
const modalEl = this.modalEl;
modalEl.style.position = "absolute";
modalEl.style.left = `${modalEl.getBoundingClientRect().left}px`;
modalEl.style.top = `${modalEl.getBoundingClientRect().top}px`;
}
addStyles() {
const styleEl = document.createElement("style");
styleEl.id = "pixel-banner-image-view-styles";
const existingStyle = document.getElementById(styleEl.id);
if (existingStyle) {
existingStyle.remove();
}
styleEl.textContent = `
.pixel-banner-image-modal {
max-width: 80vw;
max-height: 90vh;
width: 100%;
height: auto;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%);
}
.pixel-banner-image-view-modal {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
}
.pixel-banner-image-view-modal img {
max-width: 100%;
max-height: 90vh;
object-fit: contain;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.pixel-banner-image-view-modal button {
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
}
.pixel-banner-image-view-modal .image-container {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 10px;
}
.pixel-banner-image-view-modal .image-container img,
.pixel-banner-image-view-modal .image-container video {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
max-width: 100%;
max-height: 90vh;
object-fit: contain;
}
.pixel-banner-image-view-modal .path-container {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 10px;
margin-bottom: 10px;
}
.pixel-banner-image-view-modal .banner-path {
flex-grow: 1;
margin-right: 10px;
font-family: var(--font-monospace);
font-size: 0.9em;
overflow-x: auto;
padding: 4px;
background-color: var(--background-secondary);
border-radius: 4px;
word-break: break-word;
}
.pixel-banner-image-view-modal .close-button {
margin-top: 10px;
width: 150px;
}
`;
document.head.appendChild(styleEl);
}
onClose() {
const { contentEl } = this;
contentEl.empty();
const styleEl = document.getElementById("pixel-banner-image-view-styles");
if (styleEl) {
styleEl.remove();
}
}
isVideoUrl(url) {
if (!url) return false;
const videoExtensions = [".mp4", ".mov", ".webm", ".ogg"];
const pathWithoutQuery = url.split("?")[0].toLowerCase();
return videoExtensions.some((ext) => pathWithoutQuery.endsWith(ext));
}
getActualUrl(imageUrl) {
if (typeof imageUrl === "object" && imageUrl !== null) {
return imageUrl.url || imageUrl.src || "";
}
return imageUrl;
}
};
}
});
// src/modal/modals/folderSelectionModal.js
var folderSelectionModal_exports = {};
__export(folderSelectionModal_exports, {
FolderSelectionModal: () => FolderSelectionModal
});
var import_obsidian9, FolderSelectionModal;
var init_folderSelectionModal = __esm({
"src/modal/modals/folderSelectionModal.js"() {
import_obsidian9 = require("obsidian");
FolderSelectionModal = class extends import_obsidian9.FuzzySuggestModal {
constructor(app, defaultFolder, onChoose) {
super(app);
this.defaultFolder = defaultFolder;
this.onChoose = onChoose;
this.modalEl.addClass("pixel-banner-folder-select-modal");
const titleDiv = document.createElement("p");
titleDiv.innerHTML = "\u{1F4BE} Choose a <strong>Folder</strong> to save the selected Banner in your vault";
titleDiv.style.margin = "10px 0";
titleDiv.style.padding = "20px 20px 0";
titleDiv.style.color = "var(--text-accent)";
titleDiv.style.borderTop = "1px dashed var(--modal-border-color)";
this.modalEl.appendChild(titleDiv);
const descriptionDiv = document.createElement("p");
descriptionDiv.innerHTML = "If you set a default value in settings it will be pre-filled above.<br>Select any folder in your vault to continue.";
descriptionDiv.style.margin = "0 0 20px 0";
descriptionDiv.style.padding = "0 20px";
descriptionDiv.style.color = "var(--text-muted)";
descriptionDiv.style.fontSize = ".9em";
this.modalEl.appendChild(descriptionDiv);
this.setPlaceholder("Select or type folder path to save Banner Image");
this.addStyle();
}
addStyle() {
const style = document.createElement("style");
style.textContent = `
.prompt.pixel-banner-folder-select-modal {
top: unset !important;
padding: 20px !important;
}
.pixel-banner-folder-select-modal .prompt {
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
}
`;
document.head.appendChild(style);
this.style = style;
}
onClose() {
if (this.style) {
this.style.remove();
}
}
getItems() {
const folderPaths = this.app.vault.getAllLoadedFiles().filter((file) => file.children).map((folder) => folder.path);
if (!folderPaths.includes(this.defaultFolder)) {
folderPaths.unshift(this.defaultFolder);
}
return folderPaths;
}
getItemText(item) {
return item;
}
onChooseItem(item) {
this.onChoose(item);
}
onOpen() {
super.onOpen();
const inputEl = this.inputEl;
inputEl.addClass("prompt-input");
inputEl.value = this.defaultFolder;
inputEl.focus();
inputEl.select();
this.updateSuggestions();
}
};
}
});
// src/modal/modals/saveImageModal.js
var saveImageModal_exports = {};
__export(saveImageModal_exports, {
SaveImageModal: () => SaveImageModal
});
var import_obsidian10, SaveImageModal;
var init_saveImageModal = __esm({
"src/modal/modals/saveImageModal.js"() {
import_obsidian10 = require("obsidian");
SaveImageModal = class extends import_obsidian10.Modal {
constructor(app, suggestedName, onSubmit) {
super(app);
this.suggestedName = suggestedName;
this.onSubmit = onSubmit;
this.useAsBanner = true;
}
onOpen() {
const { contentEl } = this;
contentEl.empty();
contentEl.createEl("h2", { text: "Save Image", cls: "margin-top-0" });
contentEl.createEl("p", { text: "Enter a name for the media file." });
const fileNameSetting = new import_obsidian10.Setting(contentEl).setName("File name").addText((text) => {
this.textInput = text;
text.setValue(this.suggestedName).onChange((value) => {
this.suggestedName = value;
}).inputEl.style.width = "100%";
text.inputEl.addEventListener("keydown", (e) => {
if (e.key === "Enter" && this.suggestedName) {
this.onSubmit(this.suggestedName, this.useAsBanner);
this.close();
}
});
});
new import_obsidian10.Setting(contentEl).setName("Use Saved Image as Banner").setDesc("If disabled, the saved image will be saved to your vault, but not applied to the current note.").addToggle((toggle) => {
toggle.setValue(this.useAsBanner).onChange((value) => {
this.useAsBanner = value;
});
});
const buttonContainer = contentEl.createDiv();
buttonContainer.style.display = "flex";
buttonContainer.style.justifyContent = "flex-end";
buttonContainer.style.gap = "8px";
buttonContainer.style.marginTop = "1em";
const cancelButton = buttonContainer.createEl("button", { text: "Cancel" });
const saveButton = buttonContainer.createEl("button", {
text: "Save",
cls: "mod-cta"
});
cancelButton.addEventListener("click", () => this.close());
saveButton.addEventListener("click", () => {
if (this.suggestedName) {
this.onSubmit(this.suggestedName, this.useAsBanner);
this.close();
} else {
new import_obsidian10.Notice("Please enter a file name");
}
});
const modalEl = this.modalEl;
modalEl.style.position = "absolute";
modalEl.style.left = `${modalEl.getBoundingClientRect().left}px`;
modalEl.style.top = `${modalEl.getBoundingClientRect().top}px`;
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
};
}
});
// src/modal/modals/pinChoiceModal.js
var import_obsidian11, PinChoiceModal;
var init_pinChoiceModal = __esm({
"src/modal/modals/pinChoiceModal.js"() {
import_obsidian11 = require("obsidian");
PinChoiceModal = class extends import_obsidian11.Modal {
constructor(app, onChoice) {
super(app);
this.onChoice = onChoice;
}
onOpen() {
const { contentEl } = this;
contentEl.empty();
contentEl.createEl("h2", { text: "Pin Image", cls: "margin-top-0" });
contentEl.createEl("p", {
text: "How would you like to pin this image?",
cls: "setting-item-description"
});
const localOptionContainer = contentEl.createDiv({
cls: "pin-choice-option"
});
localOptionContainer.style.cssText = `
border: 1px solid var(--background-modifier-border);
border-radius: 6px;
padding: 1em;
margin: 0.5em 0;
cursor: pointer;
transition: background-color 0.2s ease;
`;
const localTitle = localOptionContainer.createEl("h3", {
text: "\u{1F4BE} Save Image Locally",
cls: "margin-top-0"
});
localTitle.style.marginBottom = "0.5em";
localOptionContainer.createEl("p", {
text: "Download and save the image to your vault. The image will be stored locally and referenced by file path.",
cls: "setting-item-description"
});
localOptionContainer.createEl("p", {
text: "\u2713 Image remains available even if original source is removed",
cls: "setting-item-description"
});
localOptionContainer.createEl("p", {
text: "\u2713 Works offline",
cls: "setting-item-description"
});
const urlOptionContainer = contentEl.createDiv({
cls: "pin-choice-option"
});
urlOptionContainer.style.cssText = `
border: 1px solid var(--background-modifier-border);
border-radius: 6px;
padding: 1em;
margin: 0.5em 0;
cursor: pointer;
transition: background-color 0.2s ease;
`;
const urlTitle = urlOptionContainer.createEl("h3", {
text: "\u{1F517} Pin Image URL",
cls: "margin-top-0"
});
urlTitle.style.marginBottom = "0.5em";
urlOptionContainer.createEl("p", {
text: "Save only the image URL to frontmatter. The image will be loaded from the original source each time.",
cls: "setting-item-description"
});
urlOptionContainer.createEl("p", {
text: "\u2713 No storage space used in vault",
cls: "setting-item-description"
});
urlOptionContainer.createEl("p", {
text: "\u26A0 Requires internet connection to display",
cls: "setting-item-description"
});
const addHoverEffect = (element) => {
element.addEventListener("mouseenter", () => {
element.style.backgroundColor = "var(--background-modifier-hover)";
});
element.addEventListener("mouseleave", () => {
element.style.backgroundColor = "";
});
};
addHoverEffect(localOptionContainer);
addHoverEffect(urlOptionContainer);
localOptionContainer.addEventListener("click", () => {
this.onChoice("local");
this.close();
});
urlOptionContainer.addEventListener("click", () => {
this.onChoice("url");
this.close();
});
const buttonContainer = contentEl.createDiv();
buttonContainer.style.cssText = `
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 1em;
`;
const cancelButton = buttonContainer.createEl("button", { text: "Cancel" });
cancelButton.addEventListener("click", () => {
this.onChoice(null);
this.close();
});
const modalEl = this.modalEl;
modalEl.style.position = "absolute";
modalEl.style.left = `${modalEl.getBoundingClientRect().left}px`;
modalEl.style.top = `${modalEl.getBoundingClientRect().top}px`;
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
};
}
});
// src/utils/frontmatterUtils.js
var frontmatterUtils_exports = {};
__export(frontmatterUtils_exports, {
getFrontmatterValue: () => getFrontmatterValue,
getValueWithZeroCheck: () => getValueWithZeroCheck,
updateNoteFrontmatter: () => updateNoteFrontmatter,
updateNoteFrontmatterWithUrl: () => updateNoteFrontmatterWithUrl
});
function getFrontmatterValue(frontmatter, fieldNames) {
if (!frontmatter || !fieldNames) return null;
const fields = Array.isArray(fieldNames) ? fieldNames : [fieldNames];
const processedFields = fields.flatMap((field) => {
if (typeof field === "string" && field.includes(",")) {
return field.split(",").map((f) => f.trim()).filter((f) => f.length > 0);
}
return field;
});
for (const field of processedFields) {
if (frontmatter.hasOwnProperty(field)) {
const value = frontmatter[field];
if (value === 0) {
return 0;
}
if (typeof value === "string" && (value.toLowerCase() === "true" || value.toLowerCase() === "false")) {
return value.toLowerCase() === "true";
}
return value;
}
}
return null;
}
function getValueWithZeroCheck(values) {
if (!Array.isArray(values)) {
console.warn("getValueWithZeroCheck expects an array of values");
return null;
}
for (const value of values) {
if (value === 0) return 0;
if (value !== null && value !== void 0) return value;
}
return values[values.length - 1];
}
async function updateNoteFrontmatter(imagePath, plugin, usedField = null) {
const activeFile = plugin.app.workspace.getActiveFile();
if (!activeFile) return;
let imageReference = imagePath;
if (plugin.settings.useShortPath) {
const imageFile = plugin.app.vault.getAbstractFileByPath(imagePath);
if (imageFile) {
const allFiles = plugin.app.vault.getFiles();
const matchingFiles = allFiles.filter((f) => f.name === imageFile.name);
imageReference = matchingFiles.length === 1 ? imageFile.name : imageFile.path;
}
}
const bannerField = usedField || (Array.isArray(plugin.settings.customBannerField) && plugin.settings.customBannerField.length > 0 ? plugin.settings.customBannerField[0] : "banner");
const format = plugin.settings.imagePropertyFormat;
let bannerValue;
if (format === "image") {
bannerValue = imageReference;
} else if (format === "[[image]]") {
bannerValue = `[[${imageReference}]]`;
} else {
bannerValue = `![[${imageReference}]]`;
}
await plugin.app.fileManager.processFrontMatter(activeFile, (frontmatter) => {
if (Array.isArray(plugin.settings.customBannerField)) {
for (const field of plugin.settings.customBannerField) {
if (field !== bannerField && field in frontmatter) {
delete frontmatter[field];
}
}
}
frontmatter[bannerField] = bannerValue;
});
if (plugin.settings.useShortPath && imageReference === imagePath) {
new import_obsidian12.Notice("Banner image pinned (full path used due to duplicate filenames)");
} else {
new import_obsidian12.Notice("Banner image pinned");
}
}
async function updateNoteFrontmatterWithUrl(imageUrl, plugin, usedField = null) {
const activeFile = plugin.app.workspace.getActiveFile();
if (!activeFile) return;
const bannerField = usedField || (Array.isArray(plugin.settings.customBannerField) && plugin.settings.customBannerField.length > 0 ? plugin.settings.customBannerField[0] : "banner");
await plugin.app.fileManager.processFrontMatter(activeFile, (frontmatter) => {
if (Array.isArray(plugin.settings.customBannerField)) {
for (const field of plugin.settings.customBannerField) {
if (field !== bannerField && field in frontmatter) {
delete frontmatter[field];
}
}
}
frontmatter[bannerField] = imageUrl;
});
new import_obsidian12.Notice("Banner image URL pinned");
}
var import_obsidian12;
var init_frontmatterUtils = __esm({
"src/utils/frontmatterUtils.js"() {
import_obsidian12 = require("obsidian");
}
});
// src/utils/handlePinIconClick.js
async function handlePinIconClick(imageUrl, plugin, usedField = null, suggestedFilename = null, showChoiceModal = true) {
let choice = "local";
if (showChoiceModal) {
choice = await new Promise((resolve) => {
const modal = new PinChoiceModal(plugin.app, (result) => {
resolve(result);
});
modal.open();
});
if (!choice) {
return null;
}
}
if (choice === "url") {
await updateNoteFrontmatterWithUrl(imageUrl, plugin, usedField);
hidePinIcon();
return imageUrl;
} else {
const imageBlob = await fetchImage(imageUrl);
const { file, useAsBanner } = await saveImageLocally(imageBlob, plugin, suggestedFilename);
const finalPath = await waitForFileRename(file, plugin);
if (!finalPath) {
console.error("\u274C Failed to resolve valid file path");
new Notice("Failed to save image - file not found");
return null;
}
if (useAsBanner) {
await updateNoteFrontmatter(finalPath, plugin, usedField);
hidePinIcon();
}
return finalPath;
}
}
async function fetchImage(url) {
const response = await fetch(url);
if (!response.ok) throw new Error("Image download failed");
return await response.arrayBuffer();
}
async function saveImageLocally(arrayBuffer, plugin, suggestedFilename = null) {
const format = detectImageFormat(arrayBuffer);
const vault = plugin.app.vault;
const defaultFolderPath = plugin.settings.pinnedImageFolder;
const folderPath = await new Promise((resolve) => {
const modal = new FolderSelectionModal(plugin.app, defaultFolderPath, (result) => {
resolve(result);
});
modal.open();
});
if (!folderPath) {
throw new Error("No folder selected");
}
if (!await vault.adapter.exists(folderPath)) {
await vault.createFolder(folderPath);
}
const suggestedName = (suggestedFilename == null ? void 0 : suggestedFilename.toLowerCase()) || plugin.settings.pinnedImageFilename;
const userInput = await new Promise((resolve) => {
const modal = new SaveImageModal(plugin.app, suggestedName, (name, useAsBanner) => {
resolve({ name, useAsBanner });
});
modal.open();
});
if (!userInput) {
throw new Error("No filename provided");
}
let baseName = userInput.name.replace(/[^a-zA-Z0-9-_ ]/g, "").trim();
if (!baseName) baseName = "banner";
if (!baseName.toLowerCase().endsWith(`.${format}`)) baseName += `.${format}`;
let fileName = baseName;
let counter = 1;
while (await vault.adapter.exists(`${folderPath}/${fileName}`)) {
const nameWithoutExt = baseName.slice(0, -4);
fileName = `${nameWithoutExt}-${counter}.${format}`;
counter++;
}
const filePath = `${folderPath}/${fileName}`;
const savedFile = await vault.createBinary(filePath, arrayBuffer);
return {
initialPath: filePath,
file: savedFile,
useAsBanner: userInput.useAsBanner
};
}
function detectImageFormat(arrayBuffer) {
const uint8arr = new Uint8Array(arrayBuffer);
const signatures = {
jpeg: [255, 216, 255],
png: [137, 80, 78, 71],
gif: [71, 73, 70, 56],
webp: [82, 73, 70, 70]
};
for (const [format, signature] of Object.entries(signatures)) {
if (signature.every((byte, i) => uint8arr[i] === byte)) {
return format === "jpeg" ? "jpg" : format;
}
}
return "jpg";
}
function hidePinIcon() {
const pinIcon = document.querySelector(".pin-icon");
if (pinIcon) pinIcon.style.display = "none";
}
async function waitForFileRename(file, plugin) {
return new Promise((resolve) => {
const initialPath = file.path;
let timeoutId;
let renamedPath = null;
const validatePath = async (path) => {
if (!path) return false;
return await plugin.app.vault.adapter.exists(path);
};
const handleRename = async (theFile) => {
if (theFile == null ? void 0 : theFile.path) {
renamedPath = theFile == null ? void 0 : theFile.path;
}
};
const cleanup = () => {
plugin.app.vault.off("rename", handleRename);
};
plugin.app.vault.on("rename", handleRename);
timeoutId = setTimeout(async () => {
cleanup();
if (renamedPath) {
const exists = await validatePath(renamedPath);
if (exists) {
return resolve(renamedPath);
}
}
const initialExists = await validatePath(initialPath);
if (initialExists) {
return resolve(initialPath);
}
resolve(null);
}, 100);
});
}
var init_handlePinIconClick = __esm({
"src/utils/handlePinIconClick.js"() {
init_folderSelectionModal();
init_saveImageModal();
init_pinChoiceModal();
init_frontmatterUtils();
}
});
// src/utils/fractionTextDisplay.js
function decimalToFractionString(num) {
const fractionMap = {
0.25: "\xBC",
0.5: "\xBD",
0.75: "\xBE"
};
const whole = Math.floor(num);
const decimal = +(num - whole).toFixed(2);
let fraction = fractionMap[decimal] || "";
if (whole === 0 && fraction) return fraction;
if (whole === 0 && !fraction) return num.toString();
return fraction ? `${whole} ${fraction}` : `${whole}`;
}
var init_fractionTextDisplay = __esm({
"src/utils/fractionTextDisplay.js"() {
}
});
// src/utils/downloadHistory.js
var DownloadHistory;
var init_downloadHistory = __esm({
"src/utils/downloadHistory.js"() {
DownloadHistory = class {
constructor() {
this.maxHistory = 50;
this.loadHistory();
}
loadHistory() {
const saved = localStorage.getItem("pixel-banner-download-history");
this.history = saved ? JSON.parse(saved) : [];
}
saveHistory() {
localStorage.setItem("pixel-banner-download-history", JSON.stringify(this.history));
}
addImage(imageId) {
if (!this.history.includes(imageId)) {
this.history.unshift(imageId);
if (this.history.length > this.maxHistory) {
this.history.pop();
}
}
this.saveHistory();
}
hasImage(imageId) {
return this.history.includes(imageId);
}
};
}
});
// src/modal/modals/selectPixelBannerModal.js
var import_obsidian13, SelectPixelBannerModal;
var init_selectPixelBannerModal = __esm({
"src/modal/modals/selectPixelBannerModal.js"() {
import_obsidian13 = require("obsidian");
init_modals();
init_flags();
init_semver();
init_fractionTextDisplay();
init_constants();
SelectPixelBannerModal = class extends import_obsidian13.Modal {
constructor(app, plugin) {
super(app);
this.plugin = plugin;
this.isLoading = false;
this.isVerifyingAPI = true;
}
async onOpen() {
const { contentEl } = this;
contentEl.empty();
await this.initializeBasicUI();
if (this.plugin.settings.pixelBannerPlusEnabled) {
this.initializeAPIDependentSections().catch((error) => {
console.error("Error initializing API-dependent sections:", error);
this.updateAPIStatusUI(false);
});
}
}
// Create a loading spinner element
createLoadingSpinner() {
const spinner = document.createElement("div");
spinner.classList.add("pixel-banner-section-spinner");
spinner.innerHTML = `
<div class="pixel-banner-spinner" style="
width: 20px;
height: 20px;
border: 2px solid var(--background-modifier-border);
border-top: 2px solid var(--text-accent);
border-radius: 50%;
animation: pixel-banner-spin 1s linear infinite;
"></div>
`;
return spinner;
}
// Refresh the modal when Pixel Banner Plus enabled state changes
async refreshModal() {
this.contentEl.empty();
await this.onOpen();
}
// Initialize the basic UI (non-API dependent)
async initializeBasicUI() {
var _a;
const { contentEl } = this;
const titleContainer = contentEl.createEl("h2", {
cls: "pixel-banner-selector-title",
attr: {
style: `
display: flex;
align-items: center;
justify-content: flex-start;
margin-top: 5px;
`
}
});
const flagImg = titleContainer.createEl("img", {
attr: {
src: flags[this.plugin.settings.selectImageIconFlag] || flags["red"],
alt: "Pixel Banner",
style: `
width: 20px;
height: 25px;
vertical-align: middle;
margin: -5px 10px 0 20px;
`
}
});
titleContainer.appendChild(document.createTextNode("Pixel Banner"));
const versionText = titleContainer.createEl("span", {
text: `v${this.plugin.settings.lastVersion}`,
attr: {
style: `
font-size: 12px;
opacity: 0.7;
margin-left: 10px;
font-weight: normal;
`
}
});
const settingsButton = titleContainer.createEl("button", {
cls: "pixel-banner-settings-button",
attr: {
style: `
margin-left: auto;
margin-right: 20px;
padding: 4px 10px;
// background: transparent;
border: none;
cursor: pointer;
font-size: 14px;
text-transform: uppercase;
`
}
});
settingsButton.innerHTML = "\u2699\uFE0F Plugin Settings";
settingsButton.title = "Open Pixel Banner Plugin Settings";
settingsButton.addEventListener("click", () => {
this.close();
const openSettings = async () => {
await this.app.setting.open();
await new Promise((resolve) => setTimeout(resolve, 300));
const settingsTabs = document.querySelectorAll(".vertical-tab-header-group .vertical-tab-nav-item");
for (const tab of settingsTabs) {
if (tab.textContent.includes("Pixel Banner")) {
tab.click();
break;
}
}
};
openSettings();
});
const activeFile = this.app.workspace.getActiveFile();
const hasBanner = activeFile ? this.plugin.hasBannerFrontmatter(activeFile) || ((_a = this.plugin.app.metadataCache.getFileCache(activeFile)) == null ? void 0 : _a.frontmatter) && this.plugin.settings.customBannerShuffleField.some(
(field) => {
var _a2, _b;
return (_b = (_a2 = this.plugin.app.metadataCache.getFileCache(activeFile)) == null ? void 0 : _a2.frontmatter) == null ? void 0 : _b[field];
}
) : false;
const mainContainer = contentEl.createDiv({ cls: "pixel-banner-main-container" });
const bannerSourceSection = mainContainer.createDiv({ cls: "pixel-banner-section" });
bannerSourceSection.createEl("h3", {
text: "Choose a Banner",
cls: "pixel-banner-section-title",
attr: {
style: `
margin: 0;
`
}
});
const bannerSourceButtons = bannerSourceSection.createDiv({
cls: "pixel-banner-source-buttons"
});
const vaultButton = bannerSourceButtons.createEl("button", {
cls: "pixel-banner-source-button"
});
const vaultButtonContent = vaultButton.createDiv({ cls: "pixel-banner-button-content" });
vaultButtonContent.createEl("span", { text: "\u{1F4BE}", cls: "pixel-banner-button-icon" });
vaultButtonContent.createEl("div", { cls: "pixel-banner-button-text-container" }).createEl("span", {
text: "Your Vault",
cls: "pixel-banner-button-text"
});
vaultButton.addEventListener("click", () => {
this.close();
new ImageSelectionModal(
this.app,
this.plugin,
async (file) => {
const activeFile2 = this.app.workspace.getActiveFile();
if (activeFile2) {
await this.plugin.app.fileManager.processFrontMatter(activeFile2, (frontmatter) => {
const bannerField = this.plugin.settings.customBannerField[0];
const format = this.plugin.settings.imagePropertyFormat;
let bannerValue;
if (format === "image") {
bannerValue = file.path;
} else if (format === "[[image]]") {
bannerValue = `[[${file.path}]]`;
} else {
bannerValue = `![[${file.path}]]`;
}
frontmatter[bannerField] = bannerValue;
});
if (this.plugin.settings.openTargetingModalAfterSelectingBannerOrIcon) {
new TargetPositionModal(this.app, this.plugin).open();
}
}
},
this.plugin.settings.defaultSelectImagePath
).open();
});
const webAddressButton = bannerSourceButtons.createEl("button", {
cls: "pixel-banner-source-button"
});
const webAddressButtonContent = webAddressButton.createDiv({ cls: "pixel-banner-button-content" });
webAddressButtonContent.createEl("span", { text: "\u{1F310}", cls: "pixel-banner-button-icon" });
webAddressButtonContent.createEl("div", { cls: "pixel-banner-button-text-container" }).createEl("span", {
text: "URL",
cls: "pixel-banner-button-text"
});
webAddressButton.addEventListener("click", () => {
this.close();
new WebAddressModal(this.app, this.plugin).open();
});
if (this.plugin.settings.pixelBannerPlusEnabled) {
const aiButton = bannerSourceButtons.createEl("button", {
cls: "pixel-banner-source-button pixel-banner-api-dependent",
attr: {
id: "pixel-banner-plus-ai-button",
style: `
position: relative;
`
}
});
const aiButtonContent = aiButton.createDiv({ cls: "pixel-banner-button-content" });
aiButtonContent.createEl("span", { text: "\u2728", cls: "pixel-banner-button-icon" });
aiButtonContent.createEl("div", { cls: "pixel-banner-button-text-container" }).createEl("span", {
text: "AI Banner",
cls: "pixel-banner-button-text"
});
const aiLoadingOverlay = aiButton.createDiv({
cls: "pixel-banner-button-loading-overlay",
attr: {
style: `
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--background-primary);
opacity: 0.8;
z-index: 10;
`
}
});
aiLoadingOverlay.appendChild(this.createLoadingSpinner());
const storeButton = bannerSourceButtons.createEl("button", {
cls: "pixel-banner-source-button pixel-banner-api-dependent",
attr: {
id: "pixel-banner-plus-store-button",
style: `
position: relative;
`
}
});
const storeButtonContent = storeButton.createDiv({ cls: "pixel-banner-button-content" });
storeButtonContent.createEl("span", { text: "\u{1F3EA}", cls: "pixel-banner-button-icon" });
storeButtonContent.createEl("div", { cls: "pixel-banner-button-text-container" }).createEl("span", {
text: "Plus Collection",
cls: "pixel-banner-button-text"
});
const storeLoadingOverlay = storeButton.createDiv({
cls: "pixel-banner-button-loading-overlay",
attr: {
style: `
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--background-primary);
opacity: 0.8;
z-index: 10;
`
}
});
storeLoadingOverlay.appendChild(this.createLoadingSpinner());
storeButton.addEventListener("click", () => {
this.close();
new PixelBannerStoreModal(this.app, this.plugin).open();
});
}
const customizationSection = mainContainer.createDiv({ cls: "pixel-banner-section" });
customizationSection.createEl("h3", {
text: "Customize Banner",
cls: "pixel-banner-section-title",
attr: {
style: `
margin: 0;
`
}
});
const customizationOptions = customizationSection.createDiv({ cls: "pixel-banner-customization-options" });
const bannerIconImageButton = customizationOptions.createEl("button", {
cls: "pixel-banner-customize-button"
});
const bannerIconImageContent = bannerIconImageButton.createDiv({ cls: "pixel-banner-button-content" });
bannerIconImageContent.createEl("span", { text: "\u2B50", cls: "pixel-banner-button-icon" });
bannerIconImageContent.createEl("div", { cls: "pixel-banner-button-text-container" }).createEl("span", {
text: "Icon Image",
cls: "pixel-banner-button-text"
});
if (!hasBanner) {
bannerIconImageButton.disabled = true;
bannerIconImageButton.classList.add("pixel-banner-button-disabled");
bannerIconImageButton.title = "You need to add a banner first";
}
bannerIconImageButton.addEventListener("click", () => {
this.close();
const onChooseBannerIconImage = async (filePath) => {
if (!filePath) {
return;
}
let pathString = filePath;
if (typeof filePath === "object" && filePath.path) {
pathString = filePath.path;
} else if (typeof filePath !== "string") {
return;
}
const activeFile2 = this.app.workspace.getActiveFile();
if (!activeFile2) return;
const file = this.app.vault.getAbstractFileByPath(pathString);
if (typeof pathString === "string" && (pathString.startsWith("http://") || pathString.startsWith("https://"))) {
this.app.fileManager.processFrontMatter(activeFile2, (fm) => {
const iconImageField = Array.isArray(this.plugin.settings.customBannerIconImageField) ? this.plugin.settings.customBannerIconImageField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageField;
fm[iconImageField] = pathString;
});
new TargetPositionModal(this.app, this.plugin).open();
return;
}
const extensionPart = pathString.split(".").pop();
const fileExtension = extensionPart ? extensionPart.toLowerCase() : "";
if (fileExtension && fileExtension.match(/^(jpg|jpeg|png|gif|bmp|svg|webp|avif)$/)) {
try {
const imageUrl = await this.plugin.getVaultImageUrl(pathString);
if (imageUrl) {
this.plugin.loadedImages.set(pathString, imageUrl);
const preloadImg = new Image();
preloadImg.src = imageUrl;
}
} catch (error) {
console.error("Error preloading icon image:", error);
}
}
this.app.fileManager.processFrontMatter(activeFile2, (fm) => {
const iconImageField = Array.isArray(this.plugin.settings.customBannerIconImageField) ? this.plugin.settings.customBannerIconImageField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageField;
const format = this.plugin.settings.imagePropertyFormat;
let iconValue;
if (format === "image") {
iconValue = pathString;
} else if (format === "[[image]]") {
iconValue = `[[${pathString}]]`;
} else {
iconValue = `![[${pathString}]]`;
}
fm[iconImageField] = iconValue;
});
new TargetPositionModal(this.app, this.plugin).open();
};
new IconImageSelectionModal(
this.app,
this.plugin,
onChooseBannerIconImage,
this.plugin.settings.defaultSelectIconPath
).open();
});
const bannerIconButton = customizationOptions.createEl("button", {
cls: "pixel-banner-customize-button"
});
const bannerIconContent = bannerIconButton.createDiv({ cls: "pixel-banner-button-content" });
bannerIconContent.createEl("span", { text: "\u{1F4F0}", cls: "pixel-banner-button-icon" });
bannerIconContent.createEl("div", { cls: "pixel-banner-button-text-container" }).createEl("span", {
text: "Icon Emoji & Text",
cls: "pixel-banner-button-text"
});
if (!hasBanner) {
bannerIconButton.disabled = true;
bannerIconButton.classList.add("pixel-banner-button-disabled");
bannerIconButton.title = "You need to add a banner first";
}
bannerIconButton.addEventListener("click", () => {
if (!hasBanner) return;
this.close();
new EmojiSelectionModal(
this.app,
this.plugin,
async (emoji) => {
const activeFile2 = this.app.workspace.getActiveFile();
if (activeFile2) {
await this.plugin.app.fileManager.processFrontMatter(activeFile2, (frontmatter) => {
const iconField = this.plugin.settings.customBannerIconField[0];
if (emoji) {
frontmatter[iconField] = emoji;
} else {
delete frontmatter[iconField];
}
});
}
}
).open();
});
const targetingIconButton = customizationOptions.createEl("button", {
cls: "pixel-banner-customize-button"
});
const targetingIconContent = targetingIconButton.createDiv({ cls: "pixel-banner-button-content" });
targetingIconContent.createEl("span", { text: "\u{1F3AF}", cls: "pixel-banner-button-icon" });
targetingIconContent.createEl("div", { cls: "pixel-banner-button-text-container" }).createEl("span", {
text: "Position, Size, & Style",
cls: "pixel-banner-button-text"
});
if (!hasBanner) {
targetingIconButton.disabled = true;
targetingIconButton.classList.add("pixel-banner-button-disabled");
targetingIconButton.title = "You need to add a banner first";
}
targetingIconButton.addEventListener("click", () => {
this.close();
new TargetPositionModal(this.app, this.plugin).open();
});
setTimeout(() => {
if (hasBanner && targetingIconButton) {
targetingIconButton.focus();
}
}, 1e3);
if (!hasBanner) {
const noBannerMessage = customizationSection.createDiv({ cls: "pixel-banner-no-banner-message" });
noBannerMessage.createEl("p", {
text: "Add a banner first to enable customization options.",
cls: "pixel-banner-message-text"
});
}
const accountSection = mainContainer.createDiv({
cls: "pixel-banner-section pixel-banner-api-dependent",
attr: {
style: `
gap: 5px;
position: relative;
min-height: ${this.plugin.settings.pixelBannerPlusEnabled ? "97px" : "60px"};
`
}
});
const accountTitleContainer = accountSection.createDiv({
attr: {
style: `
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: ${this.plugin.settings.pixelBannerPlusEnabled ? "10px" : "0"};
`
}
});
const accountTitle = accountTitleContainer.createEl("h3", {
text: "Pixel Banner Plus",
cls: "pixel-banner-section-title",
attr: {
style: `
margin: 0;
cursor: help;
width: max-content;
`
}
});
const toggleContainer = accountTitleContainer.createDiv({
cls: "pixel-banner-plus-toggle-container",
attr: {
style: `
display: flex;
align-items: center;
gap: 8px;
`
}
});
toggleContainer.createEl("span", {
text: "Enabled",
attr: {
style: `
font-size: 12px;
opacity: 0.8;
font-weight: normal;
`
}
});
const toggleSettingContainer = toggleContainer.createDiv({
attr: {
style: `
display: inline-flex;
align-items: center;
`
}
});
new import_obsidian13.Setting(toggleSettingContainer).addToggle(
(toggle) => toggle.setValue(this.plugin.settings.pixelBannerPlusEnabled).onChange(async (value) => {
this.plugin.pixelBannerPlusEnabled = value;
this.plugin.settings.pixelBannerPlusEnabled = value;
await this.plugin.saveSettings();
this.refreshModal();
})
).then((setting) => {
if (setting.nameEl) setting.nameEl.style.display = "none";
if (setting.descEl) setting.descEl.style.display = "none";
if (setting.settingEl) {
setting.settingEl.style.border = "none";
setting.settingEl.style.padding = "0";
setting.settingEl.style.margin = "0";
}
});
if (this.plugin.settings.pixelBannerPlusEnabled) {
const accountInfo = accountSection.createDiv({
cls: "pixel-banner-account-info",
attr: {
style: "visibility: hidden;"
}
});
const accountLoadingOverlay = accountSection.createDiv({
cls: "pixel-banner-section-loading-overlay",
attr: {
style: `
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--background-primary);
opacity: 0.8;
z-index: 10;
min-height: 50px;
`
}
});
accountLoadingOverlay.appendChild(this.createLoadingSpinner());
}
this.addStyle();
}
// Initialize the API-dependent UI sections
async initializeAPIDependentSections() {
try {
await this.plugin.verifyPixelBannerPlusCredentials();
await this.plugin.getPixelBannerInfo();
this.updateAPIStatusUI(true);
} catch (error) {
console.error("Error initializing API-dependent sections:", error);
this.plugin.pixelBannerPlusServerOnline = false;
this.updateAPIStatusUI(false);
}
}
// Update UI elements that depend on API status
updateAPIStatusUI(isOnline) {
this.isVerifyingAPI = false;
const { contentEl } = this;
if (!isOnline) {
this.plugin.pixelBannerPlusServerOnline = false;
}
const apiButtons = contentEl.querySelectorAll(".pixel-banner-api-dependent");
apiButtons.forEach((element) => {
const loadingOverlay = element.querySelector(".pixel-banner-button-loading-overlay, .pixel-banner-section-loading-overlay");
if (loadingOverlay) {
loadingOverlay.remove();
}
if (element.tagName === "BUTTON") {
if (isOnline && this.plugin.pixelBannerPlusServerOnline) {
element.style.display = "flex";
element.classList.remove("pixel-banner-button-disabled");
element.disabled = false;
if (element.textContent.includes("AI Banner")) {
element.addEventListener("click", () => {
this.close();
new GenerateAIBannerModal(this.app, this.plugin).open();
});
} else if (element.textContent.includes("Store")) {
element.addEventListener("click", () => {
this.close();
new PixelBannerStoreModal(this.app, this.plugin).open();
});
}
} else {
element.style.display = "none";
}
}
});
if (this.plugin.settings.pixelBannerPlusEnabled) {
const accountSection = contentEl.querySelector(".pixel-banner-section.pixel-banner-api-dependent");
if (accountSection) {
const accountInfo = accountSection.querySelector(".pixel-banner-account-info");
if (accountInfo) {
accountInfo.style.visibility = "visible";
accountInfo.empty();
const statusContainer = accountInfo.createDiv({
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
cursor: help;
`
}
});
const openPlusSettings = async () => {
this.close();
await this.app.setting.open();
await new Promise((resolve) => setTimeout(resolve, 300));
const settingsTabs = document.querySelectorAll(".vertical-tab-header-group .vertical-tab-nav-item");
for (const tab of settingsTabs) {
if (tab.textContent.includes("Pixel Banner")) {
tab.click();
break;
}
}
const pixelBannerSettingsTabs = document.querySelectorAll(".pixel-banner-settings-tabs > button.pixel-banner-settings-tab");
for (const tab of pixelBannerSettingsTabs) {
if (tab.textContent.includes("Plus")) {
tab.click();
break;
}
}
};
const accountTitle = accountSection.querySelector(".pixel-banner-section-title");
if (accountTitle) accountTitle.addEventListener("click", openPlusSettings);
const isConnected = this.plugin.pixelBannerPlusEnabled;
const pixelBannerPlusServerOnline = this.plugin.pixelBannerPlusServerOnline;
if (!isConnected) {
const aiButton = document.getElementById("pixel-banner-plus-ai-button");
const storeButton = document.getElementById("pixel-banner-plus-store-button");
if (aiButton) {
aiButton.disabled = true;
aiButton.classList.add("pixel-banner-button-disabled");
aiButton.title = "You need an authorize Pixel Banner Plus account to use this feature";
}
if (storeButton) {
storeButton.disabled = true;
storeButton.classList.add("pixel-banner-button-disabled");
storeButton.title = "You need an authorize Pixel Banner Plus account to use this feature";
}
}
const statusText = !isOnline || !pixelBannerPlusServerOnline ? "\u{1F6A8} Servers Offline \u{1F6A8}" : isConnected ? "\u2705 Authorized" : "\u274C Not Authorized";
const statusBorderColor = !isOnline || !pixelBannerPlusServerOnline ? "#FF6B6B" : isConnected ? "#177d47" : "#FF0000";
const statusEl = statusContainer.createEl("span", {
text: statusText,
cls: "pixel-banner-status-value",
attr: {
style: `border: 1px dashed ${statusBorderColor};`
}
});
statusEl.addEventListener("click", openPlusSettings);
const isMobileDevice = window.navigator.userAgent.includes("Android") || window.navigator.userAgent.includes("iPhone") || window.navigator.userAgent.includes("iPad") || window.navigator.userAgent.includes("iPod");
if (isOnline && pixelBannerPlusServerOnline) {
const tokenCount = this.plugin.pixelBannerPlusBannerTokens !== void 0 ? `\u{1FA99} ${decimalToFractionString(this.plugin.pixelBannerPlusBannerTokens)} Tokens` : "\u2753 Unknown";
const tokenCountEl = statusContainer.createEl("span", {
text: tokenCount,
cls: "pixel-banner-status-value",
attr: {
style: `
border: 1px dashed #bba00f;
display: ${pixelBannerPlusServerOnline && this.plugin.pixelBannerPlusEnabled ? "inline-flex" : "none"};
`
}
});
tokenCountEl.addEventListener("click", openPlusSettings);
if (!isMobileDevice && isConnected) {
const gameButton = statusContainer.createEl("button", {
cls: "pixel-banner-game-button",
attr: {
style: `
margin-left: auto;
margin-right: 10px;
padding: 4px 10px;
background: transparent;
border: none;
box-shadow: none;
cursor: pointer;
font-size: 14px;
display: none;
text-transform: uppercase;
`
}
});
gameButton.innerHTML = "\u{1F579}\uFE0F";
gameButton.title = "Play Daily Game (optional)... chance to win Banner Tokens";
gameButton.addEventListener("click", () => {
this.close();
new DailyGameModal(this.app, this.plugin.settings.pixelBannerPlusEmail, this.plugin.settings.pixelBannerPlusApiKey, this.plugin).open();
});
const showGameButton = isOnline && this.plugin.pixelBannerPlusServerOnline && this.plugin.pixelBannerPlusEnabled && !this.plugin.settings.enableDailyGame;
gameButton.style.display = showGameButton ? "inline-block" : "none";
}
if (!isMobileDevice && this.plugin.settings.enableDailyGame && isConnected) {
const dailyGameContainer = accountInfo.createDiv({
cls: "pixel-banner-daily-game-container",
attr: {
style: `
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
justify-content: space-between;
width: 100%;
border-top: 1px solid var(--modal-border-color);
padding-top: 20px;
margin-top: 10px;
`
}
});
const dailyGameInfoBlock = dailyGameContainer.createDiv({
attr: {
style: `
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 5px;
width: 100%;
`
}
});
const infoBlockRow1 = dailyGameInfoBlock.createEl("div");
infoBlockRow1.createEl("span", { text: "\u{1F3AE} Daily Game " });
infoBlockRow1.createEl("span", {
text: this.plugin.pixelBannerPlusDailyGameName,
attr: {
style: `
font-style: italic;
padding: 0px 8px;
border-radius: 7px;
line-height: 1.38;
color: var(--text-color);
background-color: var(--interactive-normal);
box-shadow: var(--input-shadow);
`
}
});
const infoBlockRow2 = dailyGameInfoBlock.createEl("div");
infoBlockRow2.createEl("span", { text: "\u{1F3C6} High Score " });
infoBlockRow2.createEl("span", {
text: this.plugin.pixelBannerPlusHighScore,
attr: { style: `
font-style: italic;
padding: 0px 8px;
border-radius: 7px;
line-height: 1.38;
color: var(--text-color);
background-color: var(--interactive-normal);
box-shadow: var(--input-shadow);
` }
});
const infoBlockRow3 = dailyGameInfoBlock.createEl("div");
infoBlockRow3.createEl("span", { text: "\u{1F4B0} Current Jackpot " });
infoBlockRow3.createEl("span", {
text: `\u{1FA99} ${decimalToFractionString(this.plugin.pixelBannerPlusJackpot)} Tokens`,
attr: { style: `
font-style: italic;
padding: 0px 8px;
border-radius: 7px;
line-height: 1.38;
color: var(--text-color);
background-color: var(--interactive-normal);
box-shadow: var(--input-shadow);
` }
});
const infoBlockRow4 = dailyGameInfoBlock.createEl("div");
infoBlockRow4.createEl("span", { text: "\u23F0 Time Left " });
infoBlockRow4.createEl("span", {
text: this.plugin.pixelBannerPlusTimeLeft,
attr: { style: `
font-style: italic;
padding: 0px 8px;
border-radius: 7px;
line-height: 1.38;
color: var(--text-color);
background-color: var(--interactive-normal);
box-shadow: var(--input-shadow);
` }
});
const infoBlockRow5 = dailyGameInfoBlock.createEl("div");
infoBlockRow5.createEl("span", {
text: "3 FREE",
attr: {
style: `
background: darkgreen;
color: white;
padding: 0px 4px;
border-radius: 5px;
letter-spacing: 1px;
`
}
});
infoBlockRow5.createEl("span", { text: " plays per day!" });
const dailyGameButton = dailyGameContainer.createEl("button", {
cls: "pixel-banner-account-button pixel-banner-daily-game-button",
attr: {
style: `
min-width: 110px;
`
}
});
const dailyGameContent = dailyGameButton.createDiv({
cls: "pixel-banner-button-content",
attr: {
style: `
display: flex;
align-items: center;
gap: 10px;
`
}
});
dailyGameContent.createEl("span", { text: "\u{1F579}\uFE0F", cls: "pixel-banner-button-icon pixel-banner-twinkle-animation" });
dailyGameContent.createEl("div", { cls: "pixel-banner-button-text-container" }).createEl("span", {
text: "Play Daily Game",
cls: "pixel-banner-button-text"
});
dailyGameButton.addEventListener("click", () => {
this.close();
new DailyGameModal(this.app, this.plugin.settings.pixelBannerPlusEmail, this.plugin.settings.pixelBannerPlusApiKey, this.plugin).open();
});
}
} else {
const retryButton = statusContainer.createEl("button", {
text: "\u{1F504} Try Again",
cls: "pixel-banner-account-button pixel-banner-retry-button",
attr: {
style: `
background-color: var(--background-accent) !important;
color: var(--text-on-accent) !important;
margin-left: 10px;
`
}
});
retryButton.addEventListener("click", async () => {
statusContainer.empty();
statusContainer.createEl("span", {
text: "Connecting...",
cls: "pixel-banner-status-value"
});
const tempSpinner = this.createLoadingSpinner();
statusContainer.appendChild(tempSpinner);
try {
await this.plugin.verifyPixelBannerPlusCredentials();
await this.plugin.getPixelBannerInfo();
this.updateAPIStatusUI(true);
} catch (error) {
console.error("Error reconnecting:", error);
this.plugin.pixelBannerPlusServerOnline = false;
this.updateAPIStatusUI(false);
}
});
}
if (pixelBannerPlusServerOnline && isConnected && this.plugin.pixelBannerPlusBannerTokens === 0) {
const buyTokensButton = accountInfo.createEl("button", {
cls: "pixel-banner-account-button pixel-banner-buy-tokens-button",
text: "\u{1F4B5} Buy More Tokens"
});
buyTokensButton.addEventListener("click", (event) => {
event.preventDefault();
window.open(PIXEL_BANNER_PLUS.SHOP_URL, "_blank");
});
} else if (pixelBannerPlusServerOnline && !isConnected) {
const signupButton = accountInfo.createEl("button", {
cls: "pixel-banner-account-button pixel-banner-signup-button",
text: "\u{1F6A9} Signup for Free!"
});
signupButton.addEventListener("click", (event) => {
event.preventDefault();
const signupUrl = PIXEL_BANNER_PLUS.API_URL + PIXEL_BANNER_PLUS.ENDPOINTS.SIGNUP;
window.open(signupUrl, "_blank");
});
}
const cloudVersion = this.plugin.pixelBannerVersion;
const currentVersion = this.plugin.settings.lastVersion;
const isCloudVersionGreater = semver.gt(cloudVersion, currentVersion);
let versionText, cursor;
if (isCloudVersionGreater) {
versionText = `\u{1F504} Update Available!`;
cursor = "pointer";
} else {
versionText = ``;
cursor = "default";
}
const versionInfo = accountInfo.createDiv({
text: versionText,
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
cursor: ${cursor};
margin-left: auto;
animation: pixel-banner-scale-up-down 3s ease-in-out infinite;
`
}
});
if (isCloudVersionGreater) {
const openCommunityPlugins = async () => {
this.close();
await this.app.setting.open();
await new Promise((resolve) => setTimeout(resolve, 300));
const settingsTabs = document.querySelectorAll(".vertical-tab-header-group .vertical-tab-nav-item");
for (const tab of settingsTabs) {
if (tab.textContent.includes("Community plugins")) {
tab.click();
break;
}
}
await new Promise((resolve) => setTimeout(resolve, 500));
const allTheButtons = document.querySelectorAll("button.mod-cta");
for (const button of allTheButtons) {
if (button.textContent.includes("Check for updates")) {
button.click();
break;
}
}
};
versionInfo.addEventListener("click", openCommunityPlugins);
}
}
}
}
}
addStyle() {
const style = document.createElement("style");
style.textContent = `
.pixel-banner-main-container {
display: flex;
flex-direction: column;
gap: 15px;
padding: 0 16px 16px;
max-height: 80vh;
width: 100%;
box-sizing: border-box;
}
.pixel-banner-section {
display: flex;
flex-direction: column;
gap: 16px;
width: 100%;
padding: 14px;
}
.pixel-banner-section-title {
font-size: 16px;
margin: 0;
color: var(--text-normal);
font-weight: 600;
}
.pixel-banner-source-buttons {
display: flex;
flex-wrap: wrap;
gap: 12px;
width: 100%;
justify-content: space-between;
}
.pixel-banner-source-button {
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 8px;
border-radius: 8px;
border: 1px solid var(--background-modifier-border);
background: var(--background-primary);
cursor: pointer;
transition: all 0.2s ease;
flex: auto;
min-width: 80px;
height: 100%;
box-sizing: border-box;
overflow: hidden;
}
.pixel-banner-source-button:hover {
background: var(--background-modifier-hover);
transform: translateY(-2px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.pixel-banner-button-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
width: 100%;
height: 100%;
}
.pixel-banner-button-icon {
font-size: 24px;
line-height: 1;
flex-shrink: 0;
}
.pixel-banner-button-text-container {
text-align: center;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-grow: 1;
overflow: hidden;
}
.pixel-banner-button-text {
font-size: 13px;
font-weight: 500;
white-space: normal;
word-break: break-word;
line-height: 1.2;
hyphens: auto;
overflow-wrap: break-word;
max-width: 100%;
}
.pixel-banner-customization-options {
display: flex;
flex-wrap: wrap;
gap: 12px;
width: 100%;
justify-content: space-between;
}
.pixel-banner-customize-button {
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 8px;
border-radius: 8px;
border: 1px solid var(--background-modifier-border);
background: var(--background-primary);
cursor: pointer;
transition: all 0.2s ease;
flex: auto;
min-width: 80px;
height: 100%;
box-sizing: border-box;
overflow: hidden;
}
.pixel-banner-customize-button:hover {
background: var(--background-modifier-hover);
transform: translateY(-2px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.pixel-banner-button-disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pixel-banner-button-disabled:hover {
background: var(--background-primary);
transform: none;
box-shadow: none;
}
.pixel-banner-no-banner-message {
background: var(--background-modifier-error-rgb);
border-radius: 8px;
width: 100%;
box-sizing: border-box;
}
.pixel-banner-message-text {
margin: 0;
color: var(--text-accent-hover);
font-size: 14px;
text-align: center;
text-transform: uppercase;
}
.pixel-banner-settings-button:hover {
opacity: 0.8;
}
/* Pixel Banner Plus Account styles */
.pixel-banner-account-info {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
flex-wrap: wrap;
gap: 10px;
width: 100%;
}
.pixel-banner-status-value {
padding: 3px 7px;
border-radius: 15px;
font-size: .8em;
letter-spacing: 1px;
background-color: var(--background-primary);
display: inline-flex;
align-items: center;
}
.pixel-banner-account-button {
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 8px;
border-radius: 8px;
border: 1px solid var(--background-modifier-border);
background: var(--background-primary);
cursor: pointer;
transition: all 0.2s ease;
flex: 1;
min-width: 80px;
height: 100%;
box-sizing: border-box;
overflow: hidden;
}
.pixel-banner-account-button:hover {
opacity: 0.9;
transform: translateY(-2px);
}
.pixel-banner-buy-tokens-button {
background-color: darkgreen !important;
color: papayawhip !important;
opacity: 0.7;
}
.pixel-banner-signup-button {
background-color: var(--interactive-accent) !important;
color: var(--text-on-accent) !important;
}
.pixel-banner-retry-button {
background-color: var(--background-accent) !important;
color: var(--text-on-accent) !important;
font-size: 0.8em !important;
padding: 4px 8px !important;
animation: pixel-banner-pulse 2s infinite;
}
@keyframes pixel-banner-pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
/* Loading spinner styles */
@keyframes pixel-banner-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes pixel-banner-fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes pixel-banner-scale-up-down {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
@media (min-width: 400px) {
.pixel-banner-source-button,
.pixel-banner-customize-button {
padding: 16px 8px;
}
}
@media (max-width: 590px) {
.pixel-banner-daily-game-container {
flex-direction: column !important;
align-items: flex-start !important;
}
}
@media (max-width: 399px) {
.pixel-banner-source-button,
.pixel-banner-customize-button {
min-height: 90px;
}
.pixel-banner-button-icon {
font-size: 20px;
}
.pixel-banner-button-text {
font-size: 12px;
}
}
`;
document.head.appendChild(style);
this.style = style;
}
onClose() {
this.contentEl.empty();
if (this.style) {
this.style.remove();
}
}
};
}
});
// src/modal/modals/generateAIBannerModal.js
var import_obsidian14, GenerateAIBannerModal;
var init_generateAIBannerModal = __esm({
"src/modal/modals/generateAIBannerModal.js"() {
import_obsidian14 = require("obsidian");
init_constants();
init_handlePinIconClick();
init_fractionTextDisplay();
init_downloadHistory();
init_modals();
init_selectPixelBannerModal();
GenerateAIBannerModal = class extends import_obsidian14.Modal {
constructor(app, plugin) {
super(app);
this.plugin = plugin;
this.prompt = "";
this.imageContainer = null;
this.modalEl.addClass("pixel-banner-ai-modal");
this.downloadHistory = new DownloadHistory();
this.isLoading = true;
this.availableModels = {};
this.selectedModelId = null;
this.controlValues = {};
this.controlsContainer = null;
this.currentPage = 1;
this.totalPages = 1;
this.itemsPerPage = 9;
this.totalItems = 0;
this.addStyles();
}
addStyles() {
const styleEl = document.createElement("style");
styleEl.textContent = `
.ai-banner-pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-top: 10px;
position: sticky;
bottom: -20px;
background-color: var(--modal-background);
padding: 10px 20px;
border-radius: 7px;
z-index: 2;
}
.ai-banner-pagination button {
padding: 4px 8px;
border-radius: 4px;
}
.ai-banner-pagination button:hover:not(.disabled) {
background-color: var(--interactive-accent-hover);
cursor: pointer;
}
.ai-banner-pagination button.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.ai-banner-pagination span {
color: var(--text-muted);
font-size: 0.9em;
}
.pixel-banner-history-container {
transition: min-height 0.3s ease-out;
}
.pixel-banner-history-container.loading {
opacity: 0.7;
position: relative;
}
.pixel-banner-history-container .pixel-banner-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.pixel-banner-loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--background-primary);
z-index: 100;
animation: pixel-banner-fade-in 0.3s ease-in-out;
}
.pixel-banner-spinner {
width: 40px;
height: 40px;
border: 4px solid var(--background-modifier-border);
border-top: 4px solid var(--text-accent);
border-radius: 50%;
animation: pixel-banner-spin 1s linear infinite;
}
/* Dynamic controls styles */
.pixel-banner-dynamic-controls .setting-item {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0;
border-bottom: 1px solid var(--background-modifier-border);
}
.pixel-banner-dynamic-controls .setting-item:last-child {
border-bottom: none;
}
.pixel-banner-dynamic-controls input[type="range"] {
width: 100%;
}
.pixel-banner-dynamic-controls select.dropdown {
width: 100%;
padding: 4px 8px;
border-radius: 4px;
background-color: var(--background-secondary);
color: var(--text-normal);
border: 1px solid var(--background-modifier-border);
}
.pixel-banner-ai-modal select.dropdown:hover {
border-color: var(--interactive-accent-hover);
}
.pixel-banner-ai-modal select.dropdown:focus {
border-color: var(--interactive-accent);
outline: none;
}
`;
document.head.appendChild(styleEl);
this.styleEl = styleEl;
}
// Show loading spinner
showLoadingSpinner(container) {
this.isLoading = true;
this.loadingOverlay = container.createDiv({
cls: "pixel-banner-loading-overlay"
});
this.loadingOverlay.createDiv({
cls: "pixel-banner-spinner"
});
}
// Hide loading spinner
hideLoadingSpinner() {
this.isLoading = false;
if (this.loadingOverlay) {
this.loadingOverlay.remove();
this.loadingOverlay = null;
}
}
// Fetch available AI models from the API
async fetchAvailableModels() {
try {
const modelUrl = new URL(PIXEL_BANNER_PLUS.ENDPOINTS.TEXT_TO_IMAGE_MODELS, PIXEL_BANNER_PLUS.API_URL).toString();
const response = await (0, import_obsidian14.requestUrl)({
url: modelUrl,
method: "GET",
headers: {
"X-User-Email": this.plugin.settings.pixelBannerPlusEmail,
"X-API-Key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json"
}
});
if (response.status === 200) {
this.availableModels = response.json;
return true;
} else {
throw new Error("Failed to fetch models");
}
} catch (error) {
console.error("Error fetching models:", error);
return false;
}
}
// Helper method to capitalize first letter of a string
capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
// Render model selection UI
renderModelSelection(container) {
container.empty();
const modelInfo = container.createDiv({ cls: "setting-item-info" });
modelInfo.createDiv({
cls: "setting-item-name",
text: "AI Model",
attr: {
style: `
min-width: 100px;
`
}
});
const modelControl = container.createDiv({
cls: "setting-item-control",
attr: { style: "width: 100%;" }
});
const modelSelect = modelControl.createEl("select", {
cls: "dropdown",
attr: {
id: "ai-model-select",
style: "width: 100%; padding: 4px 8px; border-radius: 4px; background-color: var(--background-secondary); color: var(--text-normal); border: 1px solid var(--background-modifier-border);"
}
});
let hasEnabledModels = false;
let firstEnabledModelId = null;
Object.entries(this.availableModels).forEach(([modelId, modelData]) => {
if (modelData.enabled === false) {
return;
}
hasEnabledModels = true;
const tokenCost = modelData.tokens || 1;
const optionText = `${modelData.name} \u22C5 \u{1FA99} ${decimalToFractionString(tokenCost)}`;
const option = modelSelect.createEl("option", {
text: optionText,
attr: {
value: modelId,
title: modelData.description || ""
}
});
if (!firstEnabledModelId) {
firstEnabledModelId = modelId;
this.selectedModelId = modelId;
option.selected = true;
}
});
modelSelect.addEventListener("change", (e) => {
const previousModelId = this.selectedModelId;
this.selectedModelId = e.target.value;
if (previousModelId !== this.selectedModelId) {
this.renderModelControls();
}
});
return hasEnabledModels;
}
// Render dynamic controls based on selected model
renderModelControls() {
if (!this.selectedModelId || !this.availableModels[this.selectedModelId]) {
return;
}
this.controlsContainer.empty();
this.controlValues = {};
const modelData = this.availableModels[this.selectedModelId];
const controls = modelData.controls;
const modelDescription = this.controlsContainer.createDiv({ cls: "setting-item-info" });
modelDescription.createDiv({
cls: "setting-item-name",
text: modelData.description,
attr: {
style: `
border: 1px solid var(--text-accent);
border-radius: 5px;
padding: 5px 10px;
font-size: 0.9em;
color: var(--text-muted);
font-style: italic;
text-align: justify;
`
}
});
if (!controls || Object.keys(controls).length === 0) {
return;
}
const formElements = [];
Object.entries(controls).forEach(([controlKey, control]) => {
const controlContainer = this.controlsContainer.createDiv({
cls: "setting-item pixel-banner-ai-control-row"
});
const controlInfo = controlContainer.createDiv({ cls: "setting-item-info" });
controlInfo.createDiv({
cls: "setting-item-name",
text: this.capitalizeFirstLetter(controlKey)
});
this.controlValues[controlKey] = control.defaultValue;
const controlValueDisplay = controlInfo.createDiv({
cls: "setting-item-description",
text: control.defaultValue
});
const controlElement = controlContainer.createDiv({ cls: "setting-item-control" });
if (control.type === "slider") {
const slider = controlElement.createEl("input", {
type: "range",
attr: {
id: `control-${this.selectedModelId}-${controlKey}`,
min: control.min,
max: control.max,
step: control.step,
value: control.defaultValue
}
});
slider.addEventListener("input", (e) => {
const value = parseInt(e.target.value);
this.controlValues[controlKey] = value;
controlValueDisplay.textContent = value;
});
formElements.push({
element: slider,
type: "slider",
value: control.defaultValue
});
} else if (control.type === "select") {
const select = controlElement.createEl("select", {
cls: "dropdown",
attr: {
id: `control-${this.selectedModelId}-${controlKey}`,
style: "width: 100%;"
}
});
control.options.forEach((option) => {
const optElement = select.createEl("option", {
text: option,
attr: {
value: option,
selected: option === control.defaultValue
}
});
});
select.addEventListener("change", (e) => {
this.controlValues[controlKey] = e.target.value;
controlValueDisplay.textContent = e.target.value;
});
formElements.push({
element: select,
type: "select",
value: control.defaultValue
});
} else if (control.type === "image_file") {
const fileContainer = controlElement.createDiv({
attr: { style: "display: flex; flex-direction: column; gap: 10px;" }
});
const fileInput = fileContainer.createEl("input", {
type: "file",
attr: {
id: `control-${this.selectedModelId}-${controlKey}`,
accept: "image/jpeg,image/jpg,image/png,image/webp,image/gif"
}
});
const previewImage = fileContainer.createEl("img", {
attr: {
id: `${fileInput.id}-preview`,
style: "max-width: 200px; max-height: 200px; display: none; border-radius: 4px; border: 1px solid var(--background-modifier-border);"
}
});
const fileInfo = fileContainer.createDiv({
attr: {
id: `${fileInput.id}-info`,
style: "font-size: 12px; color: var(--text-muted);"
},
text: "No file selected (max 10MB)"
});
fileInput.addEventListener("change", (e) => {
const file = e.target.files[0];
if (file) {
const maxFileSize = 10 * 1024 * 1024;
const fileSizeMB = (file.size / (1024 * 1024)).toFixed(1);
if (file.size > maxFileSize) {
fileInfo.innerHTML = `<span style="color: var(--text-error);">\u274C File too large: ${fileSizeMB}MB (max 10MB)</span>`;
previewImage.style.display = "none";
this.controlValues[controlKey] = null;
controlValueDisplay.textContent = "File too large";
console.error(`File too large: ${fileSizeMB}MB`);
return;
}
fileInfo.innerHTML = `\u{1F4C1} ${file.name} (${fileSizeMB}MB)`;
const reader = new FileReader();
reader.onload = (e2) => {
previewImage.src = e2.target.result;
previewImage.style.display = "block";
};
reader.onerror = (e2) => {
console.error("File reader error:", e2);
};
reader.readAsDataURL(file);
this.controlValues[controlKey] = "FILE_SELECTED";
controlValueDisplay.textContent = "File selected";
} else {
fileInfo.textContent = "No file selected (max 10MB)";
previewImage.style.display = "none";
this.controlValues[controlKey] = null;
controlValueDisplay.textContent = "No file";
}
});
} else if (control.type === "image_files") {
const fileContainer = controlElement.createDiv({
attr: { style: "display: flex; flex-direction: column; gap: 10px;" }
});
if (!this.selectedFiles) this.selectedFiles = {};
this.selectedFiles[controlKey] = [];
const fileInput = fileContainer.createEl("input", {
type: "file",
attr: {
id: `control-${this.selectedModelId}-${controlKey}`,
accept: "image/jpeg,image/jpg,image/png,image/webp,image/gif",
multiple: true
}
});
const previewContainer = fileContainer.createDiv({
attr: {
id: `${fileInput.id}-previews`,
style: "display: flex; flex-wrap: wrap; gap: 10px; display: none;"
}
});
const fileInfo = fileContainer.createDiv({
attr: {
id: `${fileInput.id}-info`,
style: "font-size: 12px; color: var(--text-muted);"
},
text: "No files selected (max 10MB each)"
});
const updatePreviewDisplay = () => {
previewContainer.empty();
const validFiles = this.selectedFiles[controlKey];
if (validFiles.length > 0) {
validFiles.forEach((file, index) => {
const previewItem = previewContainer.createDiv({
attr: {
style: "display: flex; flex-direction: column; align-items: center; position: relative;"
}
});
const removeButton = previewItem.createEl("button", {
text: "X",
attr: {
style: "position: absolute; top: -5px; right: -5px; width: 20px; height: 20px; border-radius: 50%; background: maroon; color: white; border: none; font-weight: bold; cursor: pointer; z-index: 1; line-height: 1; font-size: 14px;",
title: "Remove this image"
}
});
const previewImage = previewItem.createEl("img", {
attr: {
style: "max-width: 100px; max-height: 100px; border-radius: 4px; border: 1px solid var(--background-modifier-border);"
}
});
const fileName = previewItem.createDiv({
text: file.name,
attr: { style: "font-size: 10px; color: var(--text-muted); text-align: center; max-width: 100px; word-break: break-word;" }
});
const reader = new FileReader();
reader.onload = (e) => {
previewImage.src = e.target.result;
};
reader.onerror = (e) => {
console.error("File reader error:", e);
};
reader.readAsDataURL(file);
removeButton.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
this.selectedFiles[controlKey] = this.selectedFiles[controlKey].filter((_, i) => i !== index);
updatePreviewDisplay();
});
});
const totalSize = validFiles.reduce((sum, file) => sum + file.size, 0);
const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(1);
let infoText = `\u{1F4C1} ${validFiles.length} file${validFiles.length !== 1 ? "s" : ""} selected (${totalSizeMB}MB total)`;
fileInfo.textContent = infoText;
previewContainer.style.display = "flex";
this.controlValues[controlKey] = "FILES_SELECTED";
controlValueDisplay.textContent = `${validFiles.length} files selected`;
} else {
fileInfo.textContent = "No files selected (max 10MB each)";
previewContainer.style.display = "none";
this.controlValues[controlKey] = null;
controlValueDisplay.textContent = "No files";
}
};
fileInput.addEventListener("change", (e) => {
const newFiles = Array.from(e.target.files);
if (newFiles.length > 0) {
let errors = [];
newFiles.forEach((file) => {
const maxFileSize = 10 * 1024 * 1024;
const fileSizeMB = (file.size / (1024 * 1024)).toFixed(1);
const isDuplicate = this.selectedFiles[controlKey].some(
(existingFile) => existingFile.name === file.name && existingFile.size === file.size
);
if (file.size > maxFileSize) {
errors.push(`${file.name}: ${fileSizeMB}MB (too large)`);
return;
}
if (isDuplicate) {
errors.push(`${file.name}: already selected`);
return;
}
this.selectedFiles[controlKey].push(file);
});
if (errors.length > 0) {
const errorMsg = `\u274C ${errors.length} file${errors.length !== 1 ? "s" : ""} rejected: ${errors.join(", ")}`;
fileInfo.innerHTML = errorMsg;
setTimeout(() => updatePreviewDisplay(), 3e3);
} else {
updatePreviewDisplay();
}
}
fileInput.value = "";
});
}
});
setTimeout(() => {
formElements.forEach((item) => {
if (item.type === "slider") {
item.element.value = item.value;
const event = new Event("input", { bubbles: true });
item.element.dispatchEvent(event);
} else if (item.type === "select") {
item.element.value = item.value;
const event = new Event("change", { bubbles: true });
item.element.dispatchEvent(event);
}
});
}, 50);
}
// Helper method to collect control values
async collectControlValues() {
var _a;
if (!this.selectedModelId || !this.availableModels[this.selectedModelId]) {
console.error("No selected model or model data not found");
return {};
}
const controlValues = {};
const controls = this.availableModels[this.selectedModelId].controls;
for (const controlKey in controls) {
const controlId = `control-${this.selectedModelId}-${controlKey}`;
const controlElement = this.contentEl.querySelector(`#${controlId}`);
if (controlElement) {
if (controls[controlKey].type === "slider") {
controlValues[controlKey] = parseInt(controlElement.value, 10);
} else if (controls[controlKey].type === "image_file") {
const file = controlElement.files[0];
controlValues[controlKey] = file ? "FILE_SELECTED" : null;
} else if (controls[controlKey].type === "image_files") {
const selectedFiles = ((_a = this.selectedFiles) == null ? void 0 : _a[controlKey]) || [];
controlValues[controlKey] = selectedFiles.length > 0 ? "FILES_SELECTED" : null;
} else {
controlValues[controlKey] = controlElement.value;
}
} else {
console.warn(`Control element not found for ${controlKey} (ID: ${controlId})`);
}
}
return controlValues;
}
// Helper method to upload image files
async uploadImageFiles(controlValues) {
var _a, _b, _c;
const controls = this.availableModels[this.selectedModelId].controls;
const updatedControlValues = { ...controlValues };
for (const controlKey in controlValues) {
if (((_a = controls[controlKey]) == null ? void 0 : _a.type) === "image_file" && controlValues[controlKey] === "FILE_SELECTED") {
const controlId = `control-${this.selectedModelId}-${controlKey}`;
const fileInput = this.contentEl.querySelector(`#${controlId}`);
const file = fileInput == null ? void 0 : fileInput.files[0];
if (file) {
try {
const maxFileSize = 10 * 1024 * 1024;
if (file.size > maxFileSize) {
throw new Error(`Image file too large. Maximum size is ${maxFileSize / (1024 * 1024)}MB, but file is ${(file.size / (1024 * 1024)).toFixed(1)}MB`);
}
const formData = new FormData();
formData.append("image", file);
const uploadUrl = new URL(PIXEL_BANNER_PLUS.ENDPOINTS.UPLOAD_TEMP_IMAGE, PIXEL_BANNER_PLUS.API_URL).toString();
const response = await fetch(uploadUrl, {
method: "POST",
headers: {
"X-User-Email": this.plugin.settings.pixelBannerPlusEmail,
"X-API-Key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion
// Don't set Content-Type - let browser set it with boundary for FormData
},
body: formData
});
if (!response.ok) {
const errorText = await response.text();
console.error(`Upload failed with status ${response.status}:`, errorText);
throw new Error(`Failed to upload ${controlKey} image: HTTP ${response.status}`);
}
const responseData = await response.json();
if (!(responseData == null ? void 0 : responseData.imageId)) {
console.error("Upload response missing imageId:", responseData);
throw new Error(`Upload response missing imageId for ${controlKey}`);
}
updatedControlValues[controlKey] = responseData.imageId;
} catch (error) {
console.error(`Error uploading ${controlKey}:`, error);
throw new Error(`Failed to upload ${controlKey}: ${error.message}`);
}
} else {
updatedControlValues[controlKey] = null;
}
} else if (((_b = controls[controlKey]) == null ? void 0 : _b.type) === "image_files" && controlValues[controlKey] === "FILES_SELECTED") {
const files = ((_c = this.selectedFiles) == null ? void 0 : _c[controlKey]) || [];
if (files.length > 0) {
try {
const uploadedImageIds = [];
for (const file of files) {
const maxFileSize = 10 * 1024 * 1024;
if (file.size > maxFileSize) {
throw new Error(`Image file too large. Maximum size is ${maxFileSize / (1024 * 1024)}MB, but file is ${(file.size / (1024 * 1024)).toFixed(1)}MB`);
}
const formData = new FormData();
formData.append("image", file);
const uploadUrl = new URL(PIXEL_BANNER_PLUS.ENDPOINTS.UPLOAD_TEMP_IMAGE, PIXEL_BANNER_PLUS.API_URL).toString();
const response = await fetch(uploadUrl, {
method: "POST",
headers: {
"X-User-Email": this.plugin.settings.pixelBannerPlusEmail,
"X-API-Key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion
// Don't set Content-Type - let browser set it with boundary for FormData
},
body: formData
});
if (!response.ok) {
const errorText = await response.text();
console.error(`Upload failed with status ${response.status}:`, errorText);
throw new Error(`Failed to upload ${controlKey} image: HTTP ${response.status}`);
}
const responseData = await response.json();
if (!(responseData == null ? void 0 : responseData.imageId)) {
console.error("Upload response missing imageId:", responseData);
throw new Error(`Upload response missing imageId for ${controlKey}`);
}
uploadedImageIds.push(responseData.imageId);
}
updatedControlValues[controlKey] = uploadedImageIds;
} catch (error) {
console.error(`Error uploading ${controlKey}:`, error);
throw new Error(`Failed to upload ${controlKey}: ${error.message}`);
}
} else {
updatedControlValues[controlKey] = null;
}
}
}
return updatedControlValues;
}
async generateImage() {
if (!this.imageContainer) return;
if (!this.prompt) {
new import_obsidian14.Notice("Please enter a prompt");
return;
}
if (!this.selectedModelId && Object.keys(this.availableModels).length > 0) {
new import_obsidian14.Notice("Please select an AI model");
return;
}
const existingImage = this.imageContainer.querySelector(".pixel-banner-generated-image");
const existingImageData = existingImage ? {
base64Image: existingImage.src,
imageId: existingImage.getAttribute("imageId")
} : null;
this.imageContainer.empty();
const loadingContainer = this.imageContainer.createDiv({ cls: "pixel-banner-loading" });
loadingContainer.createDiv({ cls: "dot-pulse" });
try {
if (existingImageData) {
await this.refreshHistoryContainer();
}
let controlValues = await this.collectControlValues();
const hasImageFiles = Object.values(controlValues).includes("FILE_SELECTED") || Object.values(controlValues).includes("FILES_SELECTED");
if (hasImageFiles) {
loadingContainer.empty();
const uploadingDiv = loadingContainer.createDiv({ text: "Uploading images..." });
try {
controlValues = await this.uploadImageFiles(controlValues);
} catch (uploadError) {
console.error("Image upload failed:", uploadError);
throw uploadError;
}
loadingContainer.empty();
loadingContainer.createDiv({ cls: "dot-pulse" });
}
const generateUrl = new URL(PIXEL_BANNER_PLUS.ENDPOINTS.GENERATE, PIXEL_BANNER_PLUS.API_URL).toString();
const modelData = this.availableModels[this.selectedModelId];
const modelControlValues = {};
if (modelData.controls) {
Object.keys(modelData.controls).forEach((controlKey) => {
if (controlValues[controlKey] !== void 0) {
modelControlValues[controlKey] = controlValues[controlKey];
} else {
modelControlValues[controlKey] = modelData.controls[controlKey].defaultValue;
}
});
}
const requestBody = {
prompt: this.prompt,
textToImageModel: this.selectedModelId,
controlValues: modelControlValues
};
if (modelData.appendToPrompt) {
requestBody.prompt += " " + modelData.appendToPrompt;
}
const response = await (0, import_obsidian14.requestUrl)({
url: generateUrl,
method: "POST",
headers: {
"X-User-Email": this.plugin.settings.pixelBannerPlusEmail,
"X-API-Key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(requestBody)
});
if (response.status === 200 && response.json.image) {
this.plugin.pixelBannerPlusBannerTokens = response.json.balance;
const pixelBannerPlusBalanceEl = document.querySelector(".modal.pixel-banner-ai-modal .pixel-banner-plus-token-balance");
const tokenCountSpan = pixelBannerPlusBalanceEl.querySelector("span") || document.createElement("span");
tokenCountSpan.style.color = "var(--text-accent)";
tokenCountSpan.innerText = decimalToFractionString(this.plugin.pixelBannerPlusBannerTokens);
if (!tokenCountSpan.parentElement) {
pixelBannerPlusBalanceEl.innerText = "\u{1FA99} Remaining Tokens: ";
pixelBannerPlusBalanceEl.appendChild(tokenCountSpan);
}
tokenCountSpan.classList.remove("token-balance-animation");
void tokenCountSpan.offsetWidth;
tokenCountSpan.classList.add("token-balance-animation");
this.imageContainer.empty();
const imgWrapper = this.imageContainer.createDiv({ cls: "pixel-banner-generated-image-wrapper" });
const img = imgWrapper.createEl("img", {
cls: "pixel-banner-generated-image",
attr: {
src: `data:image/jpeg;base64,${response.json.image}`,
"imageId": response.json.imageId
}
});
const controls = this.imageContainer.createDiv({ cls: "pixel-banner-image-controls" });
const useAsButton = controls.createEl("button", {
cls: "mod-cta cursor-pointer",
text: "\u{1F3F7}\uFE0F Download and Use as Banner"
});
const handleUseImage = async () => {
var _a;
const imageUrl = `data:image/jpeg;base64,${response.json.image}`;
let filename = ((_a = this.prompt) == null ? void 0 : _a.toLowerCase().replace(/[^a-zA-Z0-9-_ ]/g, "").trim()) || "banner";
filename = filename.replace(/\s+/g, "-").substring(0, 47);
const savedPath = await handlePinIconClick(imageUrl, this.plugin, null, filename, false);
this.downloadHistory.addImage(response.json.imageId);
this.close();
const activeFile = this.plugin.app.workspace.getActiveFile();
if (!activeFile || !savedPath) return;
await this.plugin.app.fileManager.processFrontMatter(activeFile, (frontmatter) => {
const bannerField = this.plugin.settings.customBannerField[0];
const format = this.plugin.settings.imagePropertyFormat;
let bannerValue;
if (format === "image") {
bannerValue = savedPath;
} else if (format === "[[image]]") {
bannerValue = `[[${savedPath}]]`;
} else {
bannerValue = `![[${savedPath}]]`;
}
frontmatter[bannerField] = bannerValue;
});
if (this.plugin.settings.openTargetingModalAfterSelectingBannerOrIcon) {
new TargetPositionModal(this.app, this.plugin).open();
}
};
img.addEventListener("click", handleUseImage);
useAsButton.addEventListener("click", handleUseImage);
} else {
throw new Error("Failed to generate image");
}
} catch (error) {
console.error("Failed to generate image:", error);
this.imageContainer.empty();
const errorDiv = this.imageContainer.createDiv({ cls: "pixel-banner-error" });
errorDiv.innerHTML = `
<p>\u{1F62D} Failed to generate image, please try again.</p>
<p style="font-size: 0.8em; color: var(--text-muted);">Note that NSFW images are not allowed. You were not charged for this request.</p>
`;
}
}
async checkDownloadHistory(img) {
const imageId = img.getAttribute("imageid");
if (this.downloadHistory.hasImage(imageId)) {
return new Promise((resolve) => {
const modal = new import_obsidian14.Modal(this.app);
modal.contentEl.createEl("h2", { text: "Image Already Downloaded" });
modal.contentEl.createEl("p", { text: "You have already downloaded this image. Do you want to download it again?" });
const buttonContainer = modal.contentEl.createDiv();
buttonContainer.style.display = "flex";
buttonContainer.style.justifyContent = "flex-end";
buttonContainer.style.gap = "10px";
const cancelButton = buttonContainer.createEl("button", { text: "Cancel" });
const confirmButton = buttonContainer.createEl("button", { text: "Download Again", cls: "mod-cta" });
cancelButton.onclick = () => {
modal.close();
resolve(false);
};
confirmButton.onclick = () => {
modal.close();
resolve(true);
};
modal.open();
});
}
return true;
}
async confirmDelete(prompt) {
return new Promise((resolve) => {
let imgDescription = prompt;
imgDescription = imgDescription.replace(/\s/g, "-").toLowerCase();
imgDescription = imgDescription.replace(/[^a-zA-Z0-9-_ ]/g, "").trim();
if (imgDescription.length > 47) {
imgDescription = imgDescription.substring(0, 47);
imgDescription = imgDescription + "...";
}
const modal = new import_obsidian14.Modal(this.app);
modal.contentEl.createEl("h2", { text: "Delete Image", cls: "margin-top-0" });
const deletePrompt = modal.contentEl.createEl("p", { text: `Please confirm you want to delete "IMGDESCRIPTION" from your AI Generated Banner History. This will not delete any images you have previously downloaded to your vault.` });
deletePrompt.innerHTML = deletePrompt.innerHTML.replace("IMGDESCRIPTION", `<span style="color: var(--text-accent);">${imgDescription}</span>`);
const buttonContainer = modal.contentEl.createDiv();
buttonContainer.style.display = "flex";
buttonContainer.style.justifyContent = "flex-end";
buttonContainer.style.gap = "10px";
const cancelButton = buttonContainer.createEl("button", { text: "Cancel" });
const deleteButton = buttonContainer.createEl("button", {
text: "Delete",
cls: "mod-warning"
});
cancelButton.onclick = () => {
modal.close();
resolve(false);
};
deleteButton.onclick = () => {
modal.close();
resolve(true);
};
modal.open();
});
}
async deleteImage(imageId, imgDescription) {
const confirmed = await this.confirmDelete(imgDescription);
if (!confirmed) return;
const deleteUrl = new URL(PIXEL_BANNER_PLUS.ENDPOINTS.HISTORY_DELETE, PIXEL_BANNER_PLUS.API_URL).toString();
const response = await (0, import_obsidian14.requestUrl)({
url: `${deleteUrl}/${imageId}`,
method: "DELETE",
headers: {
"X-User-Email": this.plugin.settings.pixelBannerPlusEmail,
"X-API-Key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json"
}
});
if (response.status === 200) {
new import_obsidian14.Notice("Image deleted successfully");
this.refreshHistoryContainer();
} else {
new import_obsidian14.Notice("Failed to delete image");
}
}
async onOpen() {
const { contentEl } = this;
contentEl.empty();
this.showLoadingSpinner(contentEl);
this.initializeModal().catch((error) => {
console.error("Error initializing modal:", error);
this.hideLoadingSpinner();
contentEl.createEl("p", {
text: "Failed to load AI banner generator. Please try again later.",
cls: "pixel-banner-error"
});
});
}
// Initialize modal content
async initializeModal() {
await this.plugin.verifyPixelBannerPlusCredentials();
const { contentEl } = this;
let modelsLoaded = false;
if (this.plugin.pixelBannerPlusEnabled) {
modelsLoaded = await this.fetchAvailableModels();
}
const styleTag = contentEl.createEl("style", {
text: `
/* AI Banner Generation Modal */
.pixel-banner-ai-modal {
display: flex;
flex-direction: column;
align-items: stretch;
padding: 20px;
width: var(--dialog-max-width);
top: unset !important;
overflow-x: hidden;
}
.pixel-banner-ai-modal .modal-content {
display: flex;
flex-direction: column;
align-items: center;
}
.pixel-banner-ai-modal .setting-item {
border: none;
padding: 0.75rem 0;
}
.pixel-banner-ai-modal .full-width-input {
width: 100%;
}
.pixel-banner-ai-modal input[type="range"] {
width: 100%;
}
.pixel-banner-ai-modal .pixel-banner-ai-control-row {
display: flex;
flex-direction: row;
gap: 5px;
justify-content: space-between;
max-width: 500px;
width: 100%;
}
.pixel-banner-generate-btn-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 1rem;
gap: 1rem;
}
.pixel-banner-token-balance {
color: var(--text-muted);
font-size: 0.9em;
}
.pixel-banner-generate-btn-container button {
min-width: 150px;
}
.pixel-banner-image-controls {
margin-top: 10px;
display: flex;
justify-content: center;
gap: 10px;
}
.pixel-banner-generated-image-wrapper {
width: 100%;
border-radius: 8px;
overflow: hidden;
background: var(--background-secondary);
}
.pixel-banner-generated-image {
width: inherit;
height: auto;
display: block;
animation: pixel-banner-fade-in 1300ms ease-in-out;
margin: 0 auto;
}
/* History container styles */
.pixel-banner-history-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 1rem;
margin-top: 2rem;
padding: 1rem;
border-top: 1px solid var(--background-modifier-border);
place-items: start center; /* Ensures the grid is centered while items align naturally */
}
/* Image wrapper with masonry alignment */
.pixel-banner-history-image-wrapper {
position: relative;
cursor: pointer;
border-radius: 8px;
border: 5px solid transparent;
overflow: hidden;
transition: transform 0.2s ease;
display: flex;
justify-content: center; /* Centers image inside the wrapper */
align-items: center;
max-width: 300px;
max-height: 300px;
animation: pixel-banner-fade-in 1300ms ease-in-out;
}
/* Hover effect */
.pixel-banner-history-image-wrapper:hover {
transform: scale(1.25);
z-index: 2;
border-color: var(--modal-border-color) !important;
}
/* Ensuring images keep aspect ratio */
.pixel-banner-history-image {
width: 100%;
height: auto; /* Maintain height for masonry */
object-fit: cover;
border-radius: 8px;
}
/* Tooltip styles */
.pixel-banner-history-image-wrapper.has-tooltip {
position: relative;
}
/* Tooltip on hover */
.pixel-banner-history-image-wrapper.has-tooltip:hover::after {
content: attr(aria-label);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: var(--background-primary);
color: var(--text-normal);
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
z-index: 100;
box-shadow: var(--shadow-s);
margin-bottom: 4px;
}
/* Error message styling */
.pixel-banner-history-container .pixel-banner-error {
color: var(--text-accent);
font-size: 12px;
text-align: center;
padding: 8px;
}
.pixel-banner-input-container {
display: flex;
gap: 8px;
width: 100%;
}
.pixel-banner-input-container input {
flex: 1;
}
.pixel-banner-prompt-inspiration-container {
display: flex;
flex-direction: row;
gap: 5px;
align-items: center;
justify-content: center;
width: 100%;
}
.pixel-banner-prompt-inspiration-container > button {
margin: 0;
flex: 1;
border: 1px solid var(--modal-border-color);
text-transform: uppercase;
font-size: 0.8em;
letter-spacing: 1px;
}
.pixel-banner-inspiration-button,
.pixel-banner-inspiration-from-seed-button,
.pixel-banner-rewrite-button {
padding: 4px 12px;
border-radius: 4px;
cursor: pointer;
background-color: var(--interactive-accent);
color: var(--text-on-accent);
border: none;
font-size: 16px;
margin-left: 10px;
}
.pixel-banner-inspiration-button:hover,
.pixel-banner-inspiration-from-seed-button:hover,
.pixel-banner-rewrite-button:hover {
background-color: var(--interactive-accent-hover);
}
.pixel-banner-inspiration-button:disabled,
.pixel-banner-inspiration-from-seed-button:disabled,
.pixel-banner-rewrite-button:disabled {
opacity: 0.7;
cursor: not-allowed;
background-color: var(--interactive-accent);
}
/* ------------------- */
/* -- mobile layout -- */
/* ------------------- */
@media screen and (max-width: 550px) {
.pixel-banner-prompt-description { display: none; }
.setting-item.pixel-banner-ai-prompt-container {
max-width: var(--dialog-max-width) !important;
padding-left: 20px;
padding-right: 20px;
}
.pixel-banner-prompt-inspiration-container { flex-direction: column !important; }
.pixel-banner-prompt-inspiration-container button { width: 100% !important; }
.pixel-banner-generate-btn-container { flex-direction: column !important; }
.pixel-banner-generate-btn-container button { width: 100% !important; }
}
/* ------------------- */
`
});
contentEl.createEl("h2", {
text: "\u2728 Generate a Banner with AI",
cls: "margin-top-0",
attr: {
"style": `
margin-bottom: 0px;
text-align: center;
`
}
});
const promptAllowedSection = contentEl.createDiv({
cls: "prompt-allowed-section",
attr: {
"style": `
display: ${this.plugin.pixelBannerPlusBannerTokens === 0 ? "none" : "block"};
`
}
});
const promptContainer = promptAllowedSection.createDiv({
cls: "setting-item pixel-banner-ai-prompt-container",
attr: {
style: `
display: flex;
flex-direction: column;
align-items: flex-start;
max-width: 500px;
width: 100%;
`
}
});
promptContainer.createDiv({ cls: "setting-item-name", text: "\u{1F58B}\uFE0F Creative Banner Prompt" });
promptContainer.createDiv({
cls: "setting-item-description",
text: 'TIP \u21E2 Type a few words and then press the "\u{1F331} GROW IDEA" button to transform your basic idea into something special! Select a Model and then click "\u2728 Generate Banner".',
attr: {
"style": `
color: var(--text-muted);
font-size: .8em;
`
}
});
const promptControl = promptContainer.createDiv({
cls: "setting-item-control",
attr: {
"style": `
display: flex;
flex-direction: column;
align-items: flex-start;
margin-top: 10px !important;
width: 100% !important;
`
}
});
const promptInput = promptControl.createEl("textarea", {
cls: "full-width-input",
attr: {
id: "ai-banner-prompt",
rows: 7
}
});
promptInput.value = this.prompt;
promptInput.addEventListener("input", (e) => {
this.prompt = e.target.value;
});
const promptInspirationContainer = promptControl.createDiv({ cls: "pixel-banner-prompt-inspiration-container" });
const inspirationButton = promptInspirationContainer.createEl("button", {
cls: "pixel-banner-inspiration-button",
text: "\u{1F4A1} INSPIRATION"
});
inspirationButton.addEventListener("click", () => this.getPromptInspiration());
const inspirationFromSeedButton = promptInspirationContainer.createEl("button", {
cls: "pixel-banner-inspiration-from-seed-button",
text: "\u{1F331} GROW IDEA",
attr: {
style: `
border-bottom: 1px solid var(--interactive-accent-hover);
`
}
});
inspirationFromSeedButton.addEventListener("click", () => this.getPromptInspirationFromSeed());
const rewritePromptButton = promptInspirationContainer.createEl("button", {
cls: "pixel-banner-rewrite-button",
text: "\u270F\uFE0F REWRITE",
attr: {
style: `
border-bottom: 1px solid var(--interactive-accent-hover);
`
}
});
rewritePromptButton.addEventListener("click", () => this.rewritePrompt());
const inspirationClearButton = promptInspirationContainer.createEl("button", {
cls: "pixel-banner-inspiration-clear-button",
text: "\u{1F5D1}\uFE0F CLEAR"
});
inspirationClearButton.addEventListener("click", () => this.clearPromptInspiration());
const modelContainer = promptAllowedSection.createDiv({
cls: "setting-item pixel-banner-ai-control-row",
attr: {
style: `
align-items: center;
padding-bottom: 0;
`
}
});
if (!modelsLoaded || Object.keys(this.availableModels).length === 0) {
modelContainer.empty();
modelContainer.createEl("p", {
text: "No AI models available. Please try again later.",
cls: "pixel-banner-error"
});
return;
}
const hasModels = this.renderModelSelection(modelContainer);
if (!hasModels) {
modelContainer.empty();
modelContainer.createEl("p", {
text: "No enabled AI models available.",
cls: "pixel-banner-error"
});
return;
}
this.controlsContainer = promptAllowedSection.createDiv({
cls: "pixel-banner-dynamic-controls",
attr: {
style: `
display: flex;
flex-direction: column;
gap: 15px;
margin-top: 10px;
width: 100%;
max-width: 500px;
`
}
});
this.renderModelControls();
const promptDisallowedSection = contentEl.createDiv({
cls: "prompt-disallowed-section",
attr: {
"style": `
display: ${this.plugin.pixelBannerPlusBannerTokens === 0 || !this.plugin.pixelBannerPlusEnabled ? "block" : "none"};
max-width: 500px;
margin-top: 20px;
`
}
});
promptDisallowedSection.createEl("p", {
text: "You have no remaining banner tokens \u{1F62D}. Please purchase more tokens to generate a banner. Your previous banners (if any) will still be available for download below.",
attr: {
"style": `
display: ${this.plugin.pixelBannerPlusEnabled ? "block" : "none"};
color: var(--text-muted);
font-size: 1.1em;
text-align: center;
`
}
});
promptDisallowedSection.createEl("p", {
attr: {
"style": `
display: ${!this.plugin.pixelBannerPlusEnabled ? "block" : "none"};
color: var(--text-muted);
font-size: 1.1em;
text-align: center;
`
}
}).innerHTML = 'You do not have an active <span style="color: var(--text-accent); font-weight: bold;">Pixel Banner Plus</span> account. Please <span style="color: var(--text-accent); font-weight: bold;">Signup for Free</span> or connect to your account in Settings to generate awesome banners with AI \u{1F916}.';
const buttonContainer = contentEl.createDiv({
cls: "setting-item pixel-banner-generate-btn-container pixel-banner-ai-control-row",
attr: {
"style": `
justify-content: ${!this.plugin.pixelBannerPlusEnabled ? "center !important" : "space-between"};
`
}
});
const tokenBalance = buttonContainer.createDiv({ cls: "pixel-banner-plus-token-balance" });
tokenBalance.style.display = `${this.plugin.pixelBannerPlusEnabled ? "inline-block" : "none"}`;
const tokenCountSpan = document.createElement("span");
tokenCountSpan.style.color = "var(--text-accent)";
tokenCountSpan.style.fontWeight = "bold";
tokenCountSpan.style.letterSpacing = "1px";
tokenCountSpan.innerText = decimalToFractionString(this.plugin.pixelBannerPlusBannerTokens);
tokenBalance.setText("\u{1FA99} Remaining Tokens: ");
tokenBalance.appendChild(tokenCountSpan);
tokenCountSpan.classList.add("token-balance-animation");
const generateButton = buttonContainer.createEl("button", {
cls: "mod-cta cursor-pointer radial-pulse-animation",
text: "\u2728 Generate Banner",
attr: {
"style": `
display: ${this.plugin.pixelBannerPlusEnabled && this.plugin.pixelBannerPlusBannerTokens > 0 ? "block" : "none"};
`
}
});
generateButton.addEventListener("click", async () => {
if (!this.prompt) {
new import_obsidian14.Notice("Please enter a prompt");
return;
}
await this.generateImage();
});
const buyTokensButton = buttonContainer.createEl("button", {
cls: "mod-cta cursor-pointer radial-pulse-animation",
text: "\u{1F4B5} Buy More Tokens",
attr: {
"style": `
display: ${this.plugin.pixelBannerPlusEnabled && this.plugin.pixelBannerPlusBannerTokens === 0 ? "block" : "none"};
`
}
});
buyTokensButton.addEventListener("click", (event) => {
event.preventDefault();
window.open(PIXEL_BANNER_PLUS.SHOP_URL, "_blank");
});
const signupButton = buttonContainer.createEl("button", {
cls: "mod-cta cursor-pointer radial-pulse-animation",
text: "\u{1F6A9} Signup for Free!",
attr: {
"style": `
display: ${!this.plugin.pixelBannerPlusEnabled ? "block" : "none"};
`
}
});
signupButton.addEventListener("click", (event) => {
event.preventDefault();
const signupUrl = PIXEL_BANNER_PLUS.API_URL + PIXEL_BANNER_PLUS.ENDPOINTS.SIGNUP;
window.open(signupUrl, "_blank");
});
const backToMainButton = buttonContainer.createEl("button", {
text: "\u21E0 Main Menu",
cls: "cursor-pointer",
attr: {
style: `
width: max-content;
min-width: auto;
`
}
});
backToMainButton.addEventListener("click", () => {
this.close();
new SelectPixelBannerModal(this.app, this.plugin).open();
});
this.imageContainer = contentEl.createDiv({
cls: "pixel-banner-image-container",
attr: {
"style": `
display: ${this.plugin.pixelBannerPlusEnabled && this.plugin.pixelBannerPlusBannerTokens > 0 ? "block" : "none"};
`
}
});
contentEl.createEl("h5", {
text: "\u23F3 Previous AI Generated Banners",
attr: {
"style": `
margin-bottom: -20px;
display: ${this.plugin.pixelBannerPlusEnabled ? "block" : "none"};
`
}
});
const historyContainerDescription = contentEl.createEl("p", {
text: `Click an image to download and use as a banner. These downloads are always FREE as you have already paid to generate them. Images older than one month are subject to deletion from our servers.`,
cls: "pixel-banner-history-description",
attr: {
style: `
font-size: 12px;
color: var(--text-muted);
padding-top: 10px;
margin-bottom: -10px;
display: ${this.plugin.pixelBannerPlusEnabled ? "block" : "none"};
`
}
});
historyContainerDescription.innerHTML = historyContainerDescription.innerHTML.replace(/FREE/g, '<span style="color: var(--color-green); font-weight: bold;">FREE</span>');
const historyContainer = contentEl.createDiv({
cls: "pixel-banner-history-container",
attr: {
"style": `
display: ${this.plugin.pixelBannerPlusEnabled ? "flex" : "none"};
`
}
});
const paginationContainer = contentEl.createDiv({
cls: "ai-banner-pagination",
attr: {
"style": `
display: ${this.plugin.pixelBannerPlusEnabled ? "flex" : "none"};
`
}
});
if (this.plugin.pixelBannerPlusEnabled) {
await this.refreshHistoryContainer();
}
this.hideLoadingSpinner();
if (promptInput) {
setTimeout(() => {
promptInput.focus();
}, 100);
}
this.hideLoadingSpinner();
}
async getPromptInspiration() {
var _a;
const inspirationButton = this.contentEl.querySelector(".pixel-banner-inspiration-button");
const originalText = inspirationButton.textContent;
const promptTextarea = this.contentEl.querySelector("#ai-banner-prompt");
try {
inspirationButton.textContent = "\u23F3";
inspirationButton.disabled = true;
const inspirationUrl = new URL(PIXEL_BANNER_PLUS.ENDPOINTS.GENERATE_BANNER_IDEA, PIXEL_BANNER_PLUS.API_URL).toString();
const response = await (0, import_obsidian14.requestUrl)({
url: inspirationUrl,
method: "GET",
headers: {
"X-User-Email": this.plugin.settings.pixelBannerPlusEmail,
"X-API-Key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json"
}
});
if (response.status === 200 && response.json.bannerIdea) {
const promptInput = this.contentEl.querySelector("#ai-banner-prompt");
if (promptInput) {
let promptIdea = (_a = response.json.bannerIdea) == null ? void 0 : _a.toLowerCase();
promptIdea = promptIdea.trim();
promptInput.value = promptIdea;
this.prompt = promptIdea;
}
}
} catch (error) {
console.error("Failed to get prompt inspiration:", error);
new import_obsidian14.Notice("Failed to get prompt inspiration. Please try again.");
} finally {
inspirationButton.textContent = originalText;
inspirationButton.disabled = false;
}
}
async getPromptInspirationFromSeed() {
var _a;
const inspirationFromSeedButton = this.contentEl.querySelector(".pixel-banner-inspiration-from-seed-button");
const originalText = inspirationFromSeedButton.textContent;
const promptTextarea = this.contentEl.querySelector("#ai-banner-prompt");
let seed = promptTextarea.value.trim();
if (seed.length === 0) {
new import_obsidian14.Notice("Please enter at lease one word in the Prompt box to grow your banner idea from.");
return;
}
try {
inspirationFromSeedButton.textContent = "\u23F3";
inspirationFromSeedButton.disabled = true;
const inspirationUrl = new URL(PIXEL_BANNER_PLUS.ENDPOINTS.GENERATE_BANNER_IDEA_FROM_SEED, PIXEL_BANNER_PLUS.API_URL).toString();
const response = await (0, import_obsidian14.requestUrl)({
url: inspirationUrl + `/${seed}`,
method: "GET",
headers: {
"X-User-Email": this.plugin.settings.pixelBannerPlusEmail,
"X-API-Key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json"
}
});
if (response.status === 200 && response.json.bannerIdea) {
const promptInput = this.contentEl.querySelector("#ai-banner-prompt");
if (promptInput) {
let promptIdea = (_a = response.json.bannerIdea) == null ? void 0 : _a.toLowerCase();
promptIdea = promptIdea.trim();
promptInput.value = promptIdea;
this.prompt = promptIdea;
}
}
} catch (error) {
console.error("Failed to get prompt inspiration:", error);
new import_obsidian14.Notice("Failed to get prompt inspiration. Please try again.");
} finally {
inspirationFromSeedButton.textContent = originalText;
inspirationFromSeedButton.disabled = false;
}
}
async rewritePrompt() {
var _a;
const rewritePromptButton = this.contentEl.querySelector(".pixel-banner-rewrite-button");
const originalText = rewritePromptButton.textContent;
const promptTextarea = this.contentEl.querySelector("#ai-banner-prompt");
let seed = promptTextarea.value.trim();
if (seed.length === 0) {
new import_obsidian14.Notice("Please enter at lease one word in the Prompt box to generate a rewritten prompt.");
return;
}
try {
rewritePromptButton.textContent = "\u23F3";
rewritePromptButton.disabled = true;
const inspirationUrl = new URL(PIXEL_BANNER_PLUS.ENDPOINTS.REWRITE_BANNER_IDEA, PIXEL_BANNER_PLUS.API_URL).toString();
const response = await (0, import_obsidian14.requestUrl)({
url: inspirationUrl + `/${seed}`,
method: "GET",
headers: {
"X-User-Email": this.plugin.settings.pixelBannerPlusEmail,
"X-API-Key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json"
}
});
if (response.status === 200 && response.json.bannerIdea) {
const promptInput = this.contentEl.querySelector("#ai-banner-prompt");
if (promptInput) {
let promptIdea = (_a = response.json.bannerIdea) == null ? void 0 : _a.toLowerCase();
promptIdea = promptIdea.trim();
promptInput.value = promptIdea;
this.prompt = promptIdea;
}
}
} catch (error) {
console.error("Failed to rewrite prompt:", error);
new import_obsidian14.Notice("Failed to rewrite prompt. Please try again.");
} finally {
rewritePromptButton.textContent = originalText;
rewritePromptButton.disabled = false;
}
}
async clearPromptInspiration() {
const promptTextarea = this.contentEl.querySelector("#ai-banner-prompt");
if (promptTextarea) {
promptTextarea.value = "";
promptTextarea.focus();
}
}
async refreshHistoryContainer() {
const styleTag = this.contentEl.createEl("style", {
text: `
/* Delete icon for history images */
.pixel-banner-history-container .pixel-banner-image-delete {
position: absolute;
top: 8px;
right: 8px;
width: 24px;
height: 24px;
background-color: var(--background-secondary);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
opacity: .5;
transition: opacity 0.2s ease, background-color 0.2s ease;
cursor: pointer;
z-index: 2;
}
.pixel-banner-history-container pixel-banner-history-image-wrapper:hover .pixel-banner-image-delete {
opacity: 1;
}
.pixel-banner-history-container .pixel-banner-image-delete:hover {
background-color: red;
color: white;
opacity: 1;
}
.pixel-banner-history-container .pixel-banner-image-delete svg {
width: 16px;
height: 16px;
}
`
});
this.contentEl.appendChild(styleTag);
const historyContainer = this.contentEl.querySelector(".pixel-banner-history-container");
if (!historyContainer) return;
historyContainer.empty();
try {
const countUrl = new URL(PIXEL_BANNER_PLUS.ENDPOINTS.HISTORY_COUNT, PIXEL_BANNER_PLUS.API_URL).toString();
const countResponse = await (0, import_obsidian14.requestUrl)({
url: countUrl,
method: "GET",
headers: {
"X-User-Email": this.plugin.settings.pixelBannerPlusEmail,
"X-API-Key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json"
}
});
const countData = JSON.parse(new TextDecoder().decode(countResponse.arrayBuffer));
if (countResponse.status === 200 && countData.count !== void 0) {
this.totalItems = countData.count;
this.totalPages = Math.ceil(this.totalItems / this.itemsPerPage);
}
const historyUrl = new URL(`${PIXEL_BANNER_PLUS.ENDPOINTS.HISTORY_PAGE}/${this.currentPage}?limit=${this.itemsPerPage}`, PIXEL_BANNER_PLUS.API_URL).toString();
const response = await (0, import_obsidian14.requestUrl)({
url: historyUrl,
method: "GET",
headers: {
"X-User-Email": this.plugin.settings.pixelBannerPlusEmail,
"X-API-Key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json"
}
});
if (response.status === 200 && response.json.images) {
response.json.images.forEach((imageData) => {
const imgWrapper = this.renderImageItem(imageData, historyContainer);
});
this.updatePaginationUI();
}
} catch (error) {
console.error("Failed to fetch history:", error);
const errorDiv = historyContainer.createDiv({ cls: "pixel-banner-error" });
errorDiv.setText("Failed to load history. Please try again later.");
}
}
renderImageItem(imageData, container) {
const imgWrapper = container.createDiv({ cls: "pixel-banner-history-image-wrapper" });
const img = imgWrapper.createEl("img", {
cls: "pixel-banner-history-image",
attr: {
src: imageData.base64Image,
"imageId": imageData.imageId,
"filename": imageData.prompt.trim().substr(0, 47).replace(/\s/g, "-").toLowerCase()
}
});
const deleteBtn = imgWrapper.createDiv({ cls: "pixel-banner-image-delete" });
const trashIcon = `<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>`;
deleteBtn.innerHTML = trashIcon;
deleteBtn.addEventListener("click", async (e) => {
e.stopPropagation();
e.preventDefault();
await this.deleteImage(imageData.imageId, imageData.prompt);
});
let promptText = imageData.prompt;
if (promptText.length > 70) {
promptText = promptText.substring(0, 70) + "...";
}
imgWrapper.setAttribute("aria-label", `Download \u21E2 ${promptText}`);
imgWrapper.addClass("has-tooltip");
imgWrapper.addEventListener("click", async () => {
const shouldDownload = await this.checkDownloadHistory(img);
if (!shouldDownload) return;
const filename = img.getAttribute("filename");
const savedPath = await handlePinIconClick(imageData.base64Image, this.plugin, null, filename, false);
this.downloadHistory.addImage(img.getAttribute("imageid"));
this.close();
const activeFile = this.plugin.app.workspace.getActiveFile();
if (!activeFile || !savedPath) return;
await this.plugin.app.fileManager.processFrontMatter(activeFile, (frontmatter) => {
const bannerField = this.plugin.settings.customBannerField[0];
const format = this.plugin.settings.imagePropertyFormat;
let bannerValue;
if (format === "image") {
bannerValue = savedPath;
} else if (format === "[[image]]") {
bannerValue = `[[${savedPath}]]`;
} else {
bannerValue = `![[${savedPath}]]`;
}
frontmatter[bannerField] = bannerValue;
});
if (this.plugin.settings.openTargetingModalAfterSelectingBannerOrIcon) {
new TargetPositionModal(this.app, this.plugin).open();
}
});
return imgWrapper;
}
async updateHistoryContent() {
const historyContainer = this.contentEl.querySelector(".pixel-banner-history-container");
if (!historyContainer) return;
const scrollPos = this.contentEl.scrollTop;
const containerHeight = historyContainer.offsetHeight;
historyContainer.style.minHeight = `${containerHeight}px`;
historyContainer.addClass("loading");
historyContainer.empty();
const loadingDiv = historyContainer.createDiv({ cls: "pixel-banner-loading" });
loadingDiv.createDiv({ cls: "dot-pulse" });
try {
const historyUrl = new URL(`${PIXEL_BANNER_PLUS.ENDPOINTS.HISTORY_PAGE}/${this.currentPage}?limit=${this.itemsPerPage}`, PIXEL_BANNER_PLUS.API_URL).toString();
const response = await (0, import_obsidian14.requestUrl)({
url: historyUrl,
method: "GET",
headers: {
"X-User-Email": this.plugin.settings.pixelBannerPlusEmail,
"X-API-Key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json"
}
});
if (response.status === 200 && response.json.images) {
historyContainer.empty();
response.json.images.forEach((imageData) => {
const imgWrapper = this.renderImageItem(imageData, historyContainer);
});
}
this.updatePaginationUI();
this.contentEl.scrollTo({
top: this.contentEl.scrollHeight,
behavior: "smooth"
});
} catch (error) {
console.error("Failed to fetch history:", error);
const errorDiv = historyContainer.createDiv({ cls: "pixel-banner-error" });
errorDiv.setText("Failed to load history. Please try again later.");
} finally {
historyContainer.removeClass("loading");
setTimeout(() => {
historyContainer.style.minHeight = "";
}, 300);
}
}
updatePaginationUI() {
let paginationContainer = this.contentEl.querySelector(".ai-banner-pagination");
if (!paginationContainer) {
paginationContainer = this.contentEl.createDiv({ cls: "ai-banner-pagination" });
}
paginationContainer.empty();
const firstButton = paginationContainer.createEl("button", {
text: "\xAB"
});
firstButton.disabled = this.currentPage <= 1;
if (firstButton.disabled) {
firstButton.addClass("disabled");
}
const prevButton = paginationContainer.createEl("button", {
text: "\u2039"
});
prevButton.disabled = this.currentPage <= 1;
if (prevButton.disabled) {
prevButton.addClass("disabled");
}
const pageInfo = paginationContainer.createSpan({
text: `${this.currentPage} of ${this.totalPages}`
});
const nextButton = paginationContainer.createEl("button", {
text: "\u203A"
});
nextButton.disabled = this.currentPage >= this.totalPages;
if (nextButton.disabled) {
nextButton.addClass("disabled");
}
const lastButton = paginationContainer.createEl("button", {
text: "\xBB"
});
lastButton.disabled = this.currentPage >= this.totalPages;
if (lastButton.disabled) {
lastButton.addClass("disabled");
}
firstButton.addEventListener("click", async (e) => {
if (this.currentPage > 1) {
this.currentPage = 1;
await this.updateHistoryContent();
}
});
prevButton.addEventListener("click", async (e) => {
if (this.currentPage > 1) {
this.currentPage--;
await this.updateHistoryContent();
}
});
nextButton.addEventListener("click", async (e) => {
if (this.currentPage < this.totalPages) {
this.currentPage++;
await this.updateHistoryContent();
}
});
lastButton.addEventListener("click", async (e) => {
if (this.currentPage < this.totalPages) {
this.currentPage = this.totalPages;
await this.updateHistoryContent();
}
});
}
onClose() {
const { contentEl } = this;
contentEl.empty();
if (this.styleEl) {
this.styleEl.remove();
}
if (this.loadingOverlay) {
this.loadingOverlay.remove();
}
}
};
}
});
// src/modal/modals/imageSelectionModal.js
var import_obsidian15, ImageSelectionModal;
var init_imageSelectionModal = __esm({
"src/modal/modals/imageSelectionModal.js"() {
import_obsidian15 = require("obsidian");
init_generateAIBannerModal();
init_folderSelectionModal();
init_saveImageModal();
init_selectPixelBannerModal();
ImageSelectionModal = class extends import_obsidian15.Modal {
constructor(app, plugin, onChoose, defaultPath = "") {
super(app);
this.plugin = plugin;
this.onChoose = onChoose;
this.defaultPath = defaultPath;
this.searchQuery = defaultPath.toLowerCase();
this.currentPage = 1;
this.imagesPerPage = 20;
this.sortOrder = "name-asc";
this.imageFiles = this.app.vault.getFiles().filter((file) => file.extension.toLowerCase().match(/^(jpg|jpeg|png|gif|bmp|svg|webp|avif|mp4|mov)$/));
}
debounce(func, wait) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
async confirmDelete(file) {
return new Promise((resolve) => {
const modal = new import_obsidian15.Modal(this.app);
modal.contentEl.createEl("h2", { text: "Delete Image" });
modal.contentEl.createEl("p", { text: `Are you sure you want to delete "${file.name}"?` });
const buttonContainer = modal.contentEl.createDiv();
buttonContainer.style.display = "flex";
buttonContainer.style.justifyContent = "flex-end";
buttonContainer.style.gap = "10px";
const cancelButton = buttonContainer.createEl("button", { text: "Cancel" });
const deleteButton = buttonContainer.createEl("button", {
text: "Delete",
cls: "mod-warning"
});
cancelButton.onclick = () => {
modal.close();
resolve(false);
};
deleteButton.onclick = () => {
modal.close();
resolve(true);
};
modal.open();
});
}
async deleteImage(file) {
const confirmed = await this.confirmDelete(file);
if (!confirmed) return;
try {
await this.app.vault.delete(file);
this.imageFiles = this.imageFiles.filter((f) => f.path !== file.path);
this.updateImageGrid();
} catch (error) {
new import_obsidian15.Notice(`Failed to delete image: ${error.message}`);
}
}
onOpen() {
this.modalEl.addClass("pixel-banner-image-select-modal");
const { contentEl } = this;
contentEl.empty();
const style = document.createElement("style");
style.textContent = `
.pixel-banner-image-modal {
width: var(--dialog-max-width);
top: unset !important;
}
.pixel-banner-image-select-modal {
top: unset !important;
width: var(--dialog-max-width);
max-width: 1100px;
}
.pixel-banner-image-select-modal .pixel-banner-image-delete {
position: absolute;
top: 8px;
right: 8px;
width: 24px;
height: 24px;
background-color: var(--background-secondary);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
opacity: .5;
transition: opacity 0.2s ease, background-color 0.2s ease;
cursor: pointer;
z-index: 2;
}
.pixel-banner-image-select-modal .pixel-banner-image-wrapper:hover .pixel-banner-image-delete {
opacity: 1;
}
.pixel-banner-image-select-modal .pixel-banner-image-delete:hover {
background-color: red;
color: white;
opacity: 1;
}
.pixel-banner-image-select-modal .pixel-banner-image-delete svg {
width: 16px;
height: 16px;
}
.pixel-banner-image-select-description {
margin-top: -15px;
font-size: 0.8em;
word-break: break-all;
color: var(--text-muted);
margin-bottom: 15px;
}
.pixel-banner-search-container {
margin-bottom: 1rem;
}
.pixel-banner-search-container input {
width: 100%;
padding: 8px;
border-radius: 4px;
border: 1px solid var(--background-modifier-border);
}
.pixel-banner-search-container .search-row {
flex: 1;
display: flex;
gap: 8px;
margin: 0;
}
.pixel-banner-search-container .controls-row {
flex: 0 auto;
display: flex;
gap: 8px;
margin: 0;
}
.pixel-banner-image-thumbnail {
width: 100%;
height: 150px;
object-fit: cover;
border-radius: 2px;
}
.pixel-banner-image-path {
margin-top: 8px;
font-size: 0.8em;
word-break: break-all;
color: var(--text-muted);
}
.pixel-banner-image-error {
height: 150px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--background-modifier-error);
color: var(--text-error);
border-radius: 2px;
}
.pixel-banner-image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
padding: 0 1rem;
overflow-y: auto;
max-height: 60vh;
}
.pixel-banner-pagination-button {
padding: 4px 8px;
border-radius: 4px;
background: var(--background-secondary);
border: 1px solid var(--background-modifier-border);
cursor: pointer;
font-size: 14px;
line-height: 1;
}
.pixel-banner-pagination-button:hover:not(.disabled) {
background: var(--background-modifier-hover);
}
.pixel-banner-pagination-button.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pixel-banner-pagination-info {
font-size: 14px;
color: var(--text-muted);
}
.pixel-banner-image-container {
cursor: pointer;
border-radius: 6px;
border: 1px solid var(--background-modifier-border);
transition: transform 0.2s ease, box-shadow 0.2s ease;
position: relative;
}
.pixel-banner-image-container:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.pixel-banner-image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
margin-top: 15px;
max-height: 60vh;
overflow-y: auto;
padding: 5px;
}
.pixel-banner-no-images {
width: 100%;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2em;
color: var(--text-muted);
border: 1px dashed var(--background-modifier-border);
border-radius: 8px;
background-color: var(--background-secondary);
grid-column: 1 / -1;
text-align: center;
padding: 20px;
}
.pixel-banner-image-thumbnail {
width: 100%;
height: 150px;
object-fit: cover;
display: block;
}
.pixel-banner-image-info {
padding: 8px;
font-size: 12px;
background: var(--background-secondary);
}
.pixel-banner-image-path {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 4px;
}
.pixel-banner-image-delete {
position: absolute;
top: 5px;
right: 5px;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s ease;
}
.pixel-banner-image-container:hover .pixel-banner-image-delete {
opacity: 1;
}
.pixel-banner-image-delete svg {
width: 16px;
height: 16px;
color: white;
}
/* ------------------- */
/* -- mobile layout -- */
/* ------------------- */
@media screen and (max-width: 550px) {
.pixel-banner-pagination { flex-direction: column !important; }
.pixel-banner-pagination .pixel-banner-controls { flex-direction: column !important; }
}
@media screen and (max-width: 775px) {
.pixel-banner-search-container {
flex-direction: column !important;
gap: 8px !important;
}
.pixel-banner-search-container .search-row {
display: flex;
width: 100%;
gap: 8px;
}
.pixel-banner-search-container .controls-row {
display: flex;
width: 100%;
gap: 8px;
align-items: center;
}
.pixel-banner-search-container input[type="text"] {
flex: 1;
}
}
`;
document.head.appendChild(style);
this.style = style;
contentEl.createEl("h2", { text: "\u{1F4BE} Select Banner Image", cls: "margin-top-0" });
contentEl.createEl("div", {
text: "Select an image from your vault or upload a new one.",
cls: "pixel-banner-image-select-description"
});
const searchContainer = contentEl.createDiv({ cls: "pixel-banner-search-container" });
searchContainer.style.display = "flex";
searchContainer.style.gap = "8px";
searchContainer.style.alignItems = "center";
searchContainer.style.marginBottom = "1em";
const searchRow = searchContainer.createDiv({ cls: "search-row" });
const searchInput = searchRow.createEl("input", {
type: "text",
placeholder: "Search images...",
value: this.defaultPath
});
searchInput.style.flex = "1";
const clearButton = searchRow.createEl("button", {
text: "Clear"
});
const controlsRow = searchContainer.createDiv({ cls: "controls-row" });
if (this.plugin.pixelBannerPlusEnabled && this.plugin.pixelBannerPlusServerOnline) {
const pixelBannerPlusGenAIButton = controlsRow.createEl("button");
pixelBannerPlusGenAIButton.addClass("radial-pulse-animation");
const sparkleSpan = pixelBannerPlusGenAIButton.createSpan({ cls: "pixel-banner-twinkle-animation", text: "\u2728 " });
pixelBannerPlusGenAIButton.createSpan({ cls: "margin-left-5", text: "AI" });
pixelBannerPlusGenAIButton.addEventListener("click", () => {
this.close();
new GenerateAIBannerModal(this.app, this.plugin).open();
});
}
const uploadButton = controlsRow.createEl("button", {
text: "\u{1F4E4} Upload"
});
uploadButton.addEventListener("click", () => {
fileInput.click();
});
const toggleContainer = controlsRow.createDiv({
cls: "pixel-banner-path-toggle",
attr: {
style: "display: flex; align-items: center; gap: 8px;"
}
});
const toggleLabel = toggleContainer.createSpan({
text: "Use short path",
attr: {
style: "font-size: 12px; color: var(--text-muted);"
}
});
const toggle = new import_obsidian15.Setting(toggleContainer).addToggle((cb) => {
cb.setValue(this.plugin.settings.useShortPath).onChange(async (value) => {
this.plugin.settings.useShortPath = value;
await this.plugin.saveSettings();
});
});
toggle.settingEl.style.border = "none";
toggle.settingEl.style.padding = "0";
toggle.settingEl.style.margin = "0";
toggle.infoEl.remove();
const fileInput = searchContainer.createEl("input", {
type: "file",
attr: {
accept: "image/*,video/mp4,video/quicktime",
style: "display: none;"
}
});
fileInput.addEventListener("change", async (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = async () => {
const arrayBuffer = reader.result;
const defaultFolder = this.plugin.settings.pinnedImageFolder || "";
const folderPath = await new Promise((resolve) => {
new FolderSelectionModal(this.app, defaultFolder, (result) => {
resolve(result);
}).open();
});
if (!folderPath) {
new import_obsidian15.Notice("No folder selected");
return;
}
if (!await this.app.vault.adapter.exists(folderPath)) {
await this.app.vault.createFolder(folderPath);
}
const suggestedName = file.name;
const fileName = await new Promise((resolve) => {
new SaveImageModal(this.app, suggestedName, (result) => {
resolve(result);
}).open();
});
if (!fileName) {
new import_obsidian15.Notice("No file name provided");
return;
}
try {
const fullPath = `${folderPath}/${fileName}`.replace(/\/+/g, "/");
const newFile = await this.app.vault.createBinary(fullPath, arrayBuffer);
this.onChoose(newFile);
this.close();
} catch (error) {
new import_obsidian15.Notice("Failed to save image: " + error.message);
}
};
reader.readAsArrayBuffer(file);
}
});
clearButton.addEventListener("click", () => {
searchInput.value = "";
this.searchQuery = "";
this.updateImageGrid();
});
searchInput.addEventListener("input", this.debounce(() => {
this.searchQuery = searchInput.value.toLowerCase();
this.updateImageGrid();
}, 500));
this.gridContainer = contentEl.createDiv({ cls: "pixel-banner-image-grid" });
this.paginationContainer = contentEl.createDiv({
cls: "pixel-banner-pagination",
attr: {
style: `
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
flex-wrap: wrap;
margin-top: 15px;
`
}
});
this.updateImageGrid();
const modalEl = this.modalEl;
modalEl.style.position = "absolute";
modalEl.style.left = `${modalEl.getBoundingClientRect().left}px`;
modalEl.style.top = `${modalEl.getBoundingClientRect().top}px`;
}
updateImageGrid() {
this.gridContainer.empty();
this.paginationContainer.empty();
let filteredFiles = this.imageFiles.filter((file) => {
const filePath = file.path.toLowerCase();
const fileName = file.name.toLowerCase();
return filePath.includes(this.searchQuery) || fileName.includes(this.searchQuery);
});
filteredFiles = this.sortFiles(filteredFiles);
const totalImages = filteredFiles.length;
const totalPages = Math.ceil(totalImages / this.imagesPerPage);
const startIndex = (this.currentPage - 1) * this.imagesPerPage;
const endIndex = Math.min(startIndex + this.imagesPerPage, totalImages);
const currentFiles = filteredFiles.slice(startIndex, endIndex);
if (currentFiles.length === 0) {
const noImagesMessage = this.gridContainer.createEl("div", {
cls: "pixel-banner-no-images",
text: filteredFiles.length === 0 ? "\u{1F50D} No images found matching your search." : "No images on this page."
});
}
currentFiles.forEach((file) => {
const imageContainer = this.gridContainer.createDiv({ cls: "pixel-banner-image-container" });
const thumbnailContainer = imageContainer.createDiv();
const fileExt = file.extension.toLowerCase();
if (fileExt === "svg") {
this.app.vault.read(file).then((content) => {
const parser = new DOMParser();
const svgDoc = parser.parseFromString(content, "image/svg+xml");
const svgElement = svgDoc.documentElement;
svgElement.classList.add("pixel-banner-image-thumbnail");
svgElement.style.width = "100%";
svgElement.style.height = "100%";
thumbnailContainer.empty();
thumbnailContainer.appendChild(svgElement);
}).catch(() => {
thumbnailContainer.createEl("div", {
cls: "pixel-banner-image-error",
text: "Error loading SVG"
});
});
} else if (fileExt === "mp4" || fileExt === "mov") {
const resourcePath = this.app.vault.getResourcePath(file);
const video = thumbnailContainer.createEl("video", {
cls: "pixel-banner-video-thumbnail",
attr: {
src: resourcePath,
preload: "metadata",
muted: true
}
});
video.style.width = "100%";
video.style.height = "100%";
video.style.objectFit = "cover";
video.addEventListener("loadedmetadata", () => {
video.currentTime = Math.min(1, video.duration / 4);
});
const videoOverlay = thumbnailContainer.createDiv({ cls: "pixel-banner-video-overlay" });
videoOverlay.innerHTML = "\u25B6\uFE0F";
videoOverlay.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
background: rgba(0, 0, 0, 0.7);
border-radius: 50%;
padding: 8px;
pointer-events: none;
`;
} else {
this.app.vault.readBinary(file).then((arrayBuffer) => {
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
const img = thumbnailContainer.createEl("img", {
cls: "pixel-banner-image-thumbnail",
attr: { src: url }
});
const cleanup = () => URL.revokeObjectURL(url);
img.addEventListener("load", cleanup);
img.addEventListener("error", cleanup);
}).catch(() => {
thumbnailContainer.createEl("div", {
cls: "pixel-banner-image-error",
text: "Error loading image"
});
});
}
const infoContainer = imageContainer.createDiv("pixel-banner-image-info");
infoContainer.createEl("div", {
cls: "pixel-banner-image-path",
text: file.path
});
const statsContainer = infoContainer.createDiv("pixel-banner-image-stats");
statsContainer.style.fontSize = "0.8em";
statsContainer.style.color = "var(--text-muted)";
const fileSize = this.formatFileSize(file.stat.size);
const modifiedDate = this.formatDate(file.stat.mtime);
statsContainer.createEl("span", {
text: `${fileSize} \u2022 ${modifiedDate}`
});
const deleteBtn = imageContainer.createDiv({ cls: "pixel-banner-image-delete" });
const trashIcon = `<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>`;
deleteBtn.innerHTML = trashIcon;
deleteBtn.addEventListener("click", (e) => {
e.stopPropagation();
this.deleteImage(file);
});
imageContainer.addEventListener("click", () => {
this.onChoose(file);
this.close();
});
});
const paginationContainer = this.paginationContainer;
paginationContainer.innerHTML = "";
paginationContainer.style.display = "flex";
paginationContainer.style.alignItems = "center";
paginationContainer.style.justifyContent = "center";
paginationContainer.style.gap = "10px";
paginationContainer.style.marginTop = "15px";
const controlsContainer = paginationContainer.createDiv({
cls: "pixel-banner-controls",
attr: {
style: `
display: flex;
justify-content: center;
gap: 20px;
align-items: center;
margin-left: auto;
margin-right: auto;
`
}
});
const sortContainer = controlsContainer.createDiv({ cls: "pixel-banner-sort-container" });
sortContainer.style.display = "flex";
sortContainer.style.alignItems = "center";
sortContainer.style.gap = "5px";
const sortLabel = sortContainer.createEl("span", {
text: "Sort by:",
attr: {
style: "font-size: 14px; color: var(--text-muted);"
}
});
const sortSelect = sortContainer.createEl("select", { cls: "dropdown" });
const sortOptions = [
{ value: "name-asc", label: "Name (A-Z)" },
{ value: "name-desc", label: "Name (Z-A)" },
{ value: "date-desc", label: "Date (Newest)" },
{ value: "date-asc", label: "Date (Oldest)" },
{ value: "size-desc", label: "Size (Largest)" },
{ value: "size-asc", label: "Size (Smallest)" }
];
sortOptions.forEach((option) => {
const optionEl = sortSelect.createEl("option", {
value: option.value,
text: option.label
});
if (option.value === this.sortOrder) {
optionEl.selected = true;
}
});
sortSelect.addEventListener("change", () => {
this.sortOrder = sortSelect.value;
this.currentPage = 1;
this.updateImageGrid();
});
const paginationDiv = controlsContainer.createDiv({ cls: "pixel-banner-pagination-buttons" });
paginationDiv.style.display = "flex";
paginationDiv.style.gap = "10px";
paginationDiv.style.alignItems = "center";
const firstButton = paginationDiv.createEl("button", {
text: "\xAB",
cls: "pixel-banner-pagination-button",
attr: {
"aria-label": "First page"
}
});
firstButton.disabled = this.currentPage === 1 || totalImages === 0;
if (firstButton.disabled) {
firstButton.addClass("disabled");
}
firstButton.onclick = () => {
if (this.currentPage !== 1) {
this.currentPage = 1;
this.updateImageGrid();
}
};
const prevButton = paginationDiv.createEl("button", {
text: "\u2039",
cls: "pixel-banner-pagination-button",
attr: {
"aria-label": "Previous page"
}
});
prevButton.disabled = this.currentPage === 1 || totalImages === 0;
if (prevButton.disabled) {
prevButton.addClass("disabled");
}
prevButton.onclick = () => {
if (this.currentPage > 1) {
this.currentPage--;
this.updateImageGrid();
}
};
const pageInfo = paginationDiv.createEl("span", {
text: `${this.currentPage} / ${totalPages}`,
cls: "pixel-banner-pagination-info",
attr: {
style: "white-space: nowrap;"
}
});
const nextButton = paginationDiv.createEl("button", {
text: "\u203A",
cls: "pixel-banner-pagination-button",
attr: {
"aria-label": "Next page"
}
});
nextButton.disabled = this.currentPage === totalPages || totalImages === 0;
if (nextButton.disabled) {
nextButton.addClass("disabled");
}
nextButton.onclick = () => {
if (this.currentPage < totalPages) {
this.currentPage++;
this.updateImageGrid();
}
};
const lastButton = paginationDiv.createEl("button", {
text: "\xBB",
cls: "pixel-banner-pagination-button",
attr: {
"aria-label": "Last page"
}
});
lastButton.disabled = this.currentPage === totalPages || totalImages === 0;
if (lastButton.disabled) {
lastButton.addClass("disabled");
}
lastButton.onclick = () => {
if (this.currentPage !== totalPages) {
this.currentPage = totalPages;
this.updateImageGrid();
}
};
const backToMainButton = paginationContainer.createEl("button", {
text: "\u21E0 Main Menu",
cls: "pixel-banner-image-select-back-to-main",
attr: {
style: `
margin-right: 20px;
cursor: pointer;
`
}
});
backToMainButton.addEventListener("click", () => {
this.close();
new SelectPixelBannerModal(this.app, this.plugin).open();
});
}
sortFiles(files) {
return files.sort((a, b) => {
switch (this.sortOrder) {
case "name-asc":
return a.name.localeCompare(b.name);
case "name-desc":
return b.name.localeCompare(a.name);
case "date-desc":
return b.stat.mtime - a.stat.mtime;
case "date-asc":
return a.stat.mtime - b.stat.mtime;
case "size-desc":
return b.stat.size - a.stat.size;
case "size-asc":
return a.stat.size - b.stat.size;
default:
return 0;
}
});
}
formatFileSize(bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
return (bytes / (1024 * 1024)).toFixed(1) + " MB";
}
formatDate(timestamp) {
const date = new Date(timestamp);
return date.toLocaleDateString();
}
onClose() {
const { contentEl } = this;
contentEl.empty();
if (this.style) {
this.style.remove();
}
}
};
}
});
// src/modal/modals/iconFolderSelectionModal.js
var import_obsidian16, IconFolderSelectionModal;
var init_iconFolderSelectionModal = __esm({
"src/modal/modals/iconFolderSelectionModal.js"() {
import_obsidian16 = require("obsidian");
IconFolderSelectionModal = class extends import_obsidian16.FuzzySuggestModal {
constructor(app, defaultFolder, onChoose) {
super(app);
this.defaultFolder = defaultFolder;
this.onChoose = onChoose;
this.modalEl.addClass("pixel-banner-icon-image-folder-select-modal");
const titleDiv = document.createElement("p");
titleDiv.innerHTML = "\u{1F4BE} Choose a <strong>Folder</strong> to save the selected Icon in your vault";
titleDiv.style.margin = "10px 0";
titleDiv.style.padding = "20px 20px 0";
titleDiv.style.color = "var(--text-accent)";
titleDiv.style.borderTop = "1px dashed var(--modal-border-color)";
this.modalEl.appendChild(titleDiv);
const descriptionDiv = document.createElement("p");
descriptionDiv.innerHTML = "If you set a default value in settings it will be pre-filled above.<br>Select any folder in your vault to continue.";
descriptionDiv.style.margin = "0 0 20px 0";
descriptionDiv.style.padding = "0 20px";
descriptionDiv.style.color = "var(--text-muted)";
descriptionDiv.style.fontSize = ".9em";
this.modalEl.appendChild(descriptionDiv);
this.setPlaceholder("Select or type folder path to save the Icon Image");
this.addStyle();
}
addStyle() {
const style = document.createElement("style");
style.textContent = `
.prompt.pixel-banner-icon-image-folder-select-modal {
top: unset !important;
padding: 20px !important;
}
.pixel-banner-icon-image-folder-select-modal .prompt {
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
}
`;
document.head.appendChild(style);
this.style = style;
}
onClose() {
if (this.style) {
this.style.remove();
}
}
getItems() {
const folderPaths = this.app.vault.getAllLoadedFiles().filter((file) => file.children).map((folder) => folder.path);
if (!folderPaths.includes(this.defaultFolder)) {
folderPaths.unshift(this.defaultFolder);
}
return folderPaths;
}
getItemText(item) {
return item;
}
onChooseItem(item) {
this.onChoose(item);
}
onOpen() {
super.onOpen();
const inputEl = this.inputEl;
inputEl.value = this.defaultFolder;
inputEl.focus();
this.updateSuggestions();
}
};
}
});
// src/utils/getCurrentTheme.js
function getCurrentTheme() {
return document.body.classList.contains("theme-dark") ? "dark" : "light";
}
var getCurrentTheme_default;
var init_getCurrentTheme = __esm({
"src/utils/getCurrentTheme.js"() {
getCurrentTheme_default = getCurrentTheme;
}
});
// src/modal/modals/targetPositionModal.js
var targetPositionModal_exports = {};
__export(targetPositionModal_exports, {
TargetPositionModal: () => TargetPositionModal
});
var import_obsidian17, TargetPositionModal;
var init_targetPositionModal = __esm({
"src/modal/modals/targetPositionModal.js"() {
import_obsidian17 = require("obsidian");
init_getCurrentTheme();
init_modals();
init_selectPixelBannerModal();
init_flags();
init_frontmatterUtils();
TargetPositionModal = class _TargetPositionModal extends import_obsidian17.Modal {
constructor(app, plugin, onPositionChange) {
var _a;
super(app);
this.plugin = plugin;
this.onPositionChange = onPositionChange;
this.isDragging = false;
const activeFile = this.app.workspace.getActiveFile();
const frontmatter = (_a = this.app.metadataCache.getFileCache(activeFile)) == null ? void 0 : _a.frontmatter;
const alignmentField = Array.isArray(this.plugin.settings.customBannerAlignmentField) ? this.plugin.settings.customBannerAlignmentField[0].split(",")[0].trim() : this.plugin.settings.customBannerAlignmentField;
this.currentAlignment = (frontmatter == null ? void 0 : frontmatter[alignmentField]) || "center";
const displayField = Array.isArray(this.plugin.settings.customImageDisplayField) ? this.plugin.settings.customImageDisplayField[0].split(",")[0].trim() : this.plugin.settings.customImageDisplayField;
this.currentDisplay = (frontmatter == null ? void 0 : frontmatter[displayField]) || this.plugin.settings.imageDisplay;
const xField = Array.isArray(this.plugin.settings.customXPositionField) ? this.plugin.settings.customXPositionField[0].split(",")[0].trim() : this.plugin.settings.customXPositionField;
this.currentX = getValueWithZeroCheck([
frontmatter == null ? void 0 : frontmatter[xField],
this.plugin.settings.xPosition
]);
const yField = Array.isArray(this.plugin.settings.customYPositionField) ? this.plugin.settings.customYPositionField[0].split(",")[0].trim() : this.plugin.settings.customYPositionField;
this.currentY = getValueWithZeroCheck([
frontmatter == null ? void 0 : frontmatter[yField],
this.plugin.settings.yPosition
]);
const heightField = Array.isArray(this.plugin.settings.customBannerHeightField) ? this.plugin.settings.customBannerHeightField[0].split(",")[0].trim() : this.plugin.settings.customBannerHeightField;
this.currentHeight = (frontmatter == null ? void 0 : frontmatter[heightField]) || this.plugin.settings.bannerHeight;
const maxWidthField = Array.isArray(this.plugin.settings.customBannerMaxWidthField) ? this.plugin.settings.customBannerMaxWidthField[0].split(",")[0].trim() : this.plugin.settings.customBannerMaxWidthField;
const maxWidthValue = frontmatter == null ? void 0 : frontmatter[maxWidthField];
const directMaxWidth = frontmatter == null ? void 0 : frontmatter["banner-max-width"];
const maxWidthExists = maxWidthValue !== void 0 && maxWidthValue !== null || directMaxWidth !== void 0 && directMaxWidth !== null;
const isMaxWidthUnset = !maxWidthExists;
this.currentMaxWidth = isMaxWidthUnset ? 1928 : parseInt(maxWidthValue || directMaxWidth) || 1928;
const contentStartPositionField = Array.isArray(this.plugin.settings.customContentStartField) ? this.plugin.settings.customContentStartField[0].split(",")[0].trim() : this.plugin.settings.customContentStartField;
this.currentContentStartPosition = (frontmatter == null ? void 0 : frontmatter[contentStartPositionField]) || this.plugin.settings.contentStartPosition;
const bannerIconXPositionField = Array.isArray(this.plugin.settings.customBannerIconXPositionField) ? this.plugin.settings.customBannerIconXPositionField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconXPositionField;
this.currentBannerIconXPosition = (frontmatter == null ? void 0 : frontmatter[bannerIconXPositionField]) || this.plugin.settings.bannerIconXPosition;
const bannerIconImageAlignmentField = Array.isArray(this.plugin.settings.customBannerIconImageAlignmentField) ? this.plugin.settings.customBannerIconImageAlignmentField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageAlignmentField;
const bannerIconRotateField = Array.isArray(this.plugin.settings.customBannerIconRotateField) ? this.plugin.settings.customBannerIconRotateField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconRotateField;
this.currentBannerIconRotate = getValueWithZeroCheck([
frontmatter == null ? void 0 : frontmatter[bannerIconRotateField],
0
]);
const frontmatterValue = frontmatter == null ? void 0 : frontmatter[bannerIconImageAlignmentField];
const defaultValue = this.plugin.settings.bannerIconImageAlignment;
this.currentBannerIconImageAlignment = frontmatterValue === "right" ? "right" : "left";
const repeatField = Array.isArray(this.plugin.settings.customImageRepeatField) ? this.plugin.settings.customImageRepeatField[0].split(",")[0].trim() : this.plugin.settings.customImageRepeatField;
this.currentRepeat = (frontmatter == null ? void 0 : frontmatter[repeatField]) !== void 0 ? frontmatter[repeatField] : this.plugin.settings.imageRepeat;
const fadeField = Array.isArray(this.plugin.settings.customFadeField) ? this.plugin.settings.customFadeField[0].split(",")[0].trim() : this.plugin.settings.customFadeField;
this.currentFade = (frontmatter == null ? void 0 : frontmatter[fadeField]) !== void 0 ? frontmatter[fadeField] : this.plugin.settings.fade;
const borderRadiusField = Array.isArray(this.plugin.settings.customBorderRadiusField) ? this.plugin.settings.customBorderRadiusField[0].split(",")[0].trim() : this.plugin.settings.customBorderRadiusField;
this.currentBorderRadius = (frontmatter == null ? void 0 : frontmatter[borderRadiusField]) !== void 0 ? frontmatter[borderRadiusField] : this.plugin.settings.borderRadius || 0;
this.currentZoom = 100;
if (this.currentDisplay && this.currentDisplay.endsWith("%")) {
this.currentZoom = parseInt(this.currentDisplay) || 100;
this.currentDisplay = "cover-zoom";
}
}
// Helper to update frontmatter with new display value
updateDisplayMode(mode, zoom = null) {
const displayField = Array.isArray(this.plugin.settings.customImageDisplayField) ? this.plugin.settings.customImageDisplayField[0].split(",")[0].trim() : this.plugin.settings.customImageDisplayField;
const repeatField = Array.isArray(this.plugin.settings.customImageRepeatField) ? this.plugin.settings.customImageRepeatField[0].split(",")[0].trim() : this.plugin.settings.customImageRepeatField;
let newValue = mode;
if (mode === "cover-zoom") {
newValue = `${zoom}%`;
}
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (fm) => {
fm[displayField] = newValue;
if (mode === "contain" || mode === "auto") {
fm[repeatField] = this.currentRepeat;
} else {
if (repeatField in fm) {
delete fm[repeatField];
}
}
});
}
updateBannerMaxWidth(maxWidth) {
const maxWidthField = Array.isArray(this.plugin.settings.customBannerMaxWidthField) ? this.plugin.settings.customBannerMaxWidthField[0].split(",")[0].trim() : this.plugin.settings.customBannerMaxWidthField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
if (maxWidth === "unset") {
if (maxWidthField in frontmatter) {
delete frontmatter[maxWidthField];
}
} else {
frontmatter[maxWidthField] = maxWidth;
}
});
setTimeout(() => {
const view = this.app.workspace.getActiveViewOfType(import_obsidian17.MarkdownView);
if (view) {
this.plugin.updateBanner(view, true);
}
}, 350);
}
updateBannerHeight(height) {
const heightField = Array.isArray(this.plugin.settings.customBannerHeightField) ? this.plugin.settings.customBannerHeightField[0].split(",")[0].trim() : this.plugin.settings.customBannerHeightField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[heightField] = height;
});
}
updateBannerContentStartPosition(position) {
const contentStartPositionField = Array.isArray(this.plugin.settings.customContentStartField) ? this.plugin.settings.customContentStartField[0].split(",")[0].trim() : this.plugin.settings.customContentStartField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[contentStartPositionField] = position;
});
}
updateBannerIconXPosition(position) {
const bannerIconXPositionField = Array.isArray(this.plugin.settings.customBannerIconXPositionField) ? this.plugin.settings.customBannerIconXPositionField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconXPositionField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[bannerIconXPositionField] = position;
});
}
updateBannerIconSize(size) {
const bannerIconSizeField = Array.isArray(this.plugin.settings.customBannerIconSizeField) ? this.plugin.settings.customBannerIconSizeField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconSizeField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[bannerIconSizeField] = size;
});
}
updateBannerIconImageSizeMultiplier(sizeMultiplier) {
const bannerIconImageSizeMultiplierField = Array.isArray(this.plugin.settings.customBannerIconImageSizeMultiplierField) ? this.plugin.settings.customBannerIconImageSizeMultiplierField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageSizeMultiplierField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[bannerIconImageSizeMultiplierField] = sizeMultiplier;
});
}
updateBannerIconRotate(rotate) {
const bannerIconRotateField = Array.isArray(this.plugin.settings.customBannerIconRotateField) ? this.plugin.settings.customBannerIconRotateField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconRotateField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[bannerIconRotateField] = rotate;
});
}
updateBannerIconColor(color) {
const bannerIconColorField = Array.isArray(this.plugin.settings.customBannerIconColorField) ? this.plugin.settings.customBannerIconColorField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconColorField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[bannerIconColorField] = color;
});
}
updateBannerIconFontWeight(fontWeight) {
const bannerIconFontWeightField = Array.isArray(this.plugin.settings.customBannerIconFontWeightField) ? this.plugin.settings.customBannerIconFontWeightField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconFontWeightField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[bannerIconFontWeightField] = fontWeight;
});
}
updateBannerIconBgColor(color, alpha) {
const bannerIconBgColorField = Array.isArray(this.plugin.settings.customBannerIconBackgroundColorField) ? this.plugin.settings.customBannerIconBackgroundColorField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconBackgroundColorField;
let finalColor = color;
if (alpha < 100) {
if (color.startsWith("#")) {
const r = parseInt(color.slice(1, 3), 16);
const g = parseInt(color.slice(3, 5), 16);
const b = parseInt(color.slice(5, 7), 16);
finalColor = `rgba(${r}, ${g}, ${b}, ${alpha / 100})`;
} else if (color && !color.startsWith("rgb")) {
const tempEl = document.createElement("div");
tempEl.style.color = color;
document.body.appendChild(tempEl);
const computedColor = window.getComputedStyle(tempEl).color;
document.body.removeChild(tempEl);
const rgbMatch = computedColor.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/);
if (rgbMatch) {
const r = parseInt(rgbMatch[1]);
const g = parseInt(rgbMatch[2]);
const b = parseInt(rgbMatch[3]);
finalColor = `rgba(${r}, ${g}, ${b}, ${alpha / 100})`;
}
}
}
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[bannerIconBgColorField] = finalColor;
});
}
updateBannerIconPaddingX(paddingX) {
const bannerIconPaddingXField = Array.isArray(this.plugin.settings.customBannerIconPaddingXField) ? this.plugin.settings.customBannerIconPaddingXField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconPaddingXField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[bannerIconPaddingXField] = paddingX;
});
}
updateBannerIconPaddingY(paddingY) {
const bannerIconPaddingYField = Array.isArray(this.plugin.settings.customBannerIconPaddingYField) ? this.plugin.settings.customBannerIconPaddingYField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconPaddingYField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[bannerIconPaddingYField] = paddingY;
});
}
updateBannerIconBorderRadius(borderRadius) {
const bannerIconBorderRadiusField = Array.isArray(this.plugin.settings.customBannerIconBorderRadiusField) ? this.plugin.settings.customBannerIconBorderRadiusField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconBorderRadiusField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[bannerIconBorderRadiusField] = borderRadius;
});
}
updateBannerIconVerticalOffset(verticalOffset) {
const bannerIconVerticalOffsetField = Array.isArray(this.plugin.settings.customBannerIconVerticalOffsetField) ? this.plugin.settings.customBannerIconVerticalOffsetField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconVerticalOffsetField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[bannerIconVerticalOffsetField] = verticalOffset;
});
}
updateBannerIconTextVerticalOffset(textVerticalOffset) {
const bannerIconTextVerticalOffsetField = Array.isArray(this.plugin.settings.customBannerIconTextVerticalOffsetField) ? this.plugin.settings.customBannerIconTextVerticalOffsetField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconTextVerticalOffsetField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[bannerIconTextVerticalOffsetField] = textVerticalOffset;
});
}
updateBannerIconImageAlignment(alignment) {
const bannerIconImageAlignmentField = Array.isArray(this.plugin.settings.customBannerIconImageAlignmentField) ? this.plugin.settings.customBannerIconImageAlignmentField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageAlignmentField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[bannerIconImageAlignmentField] = alignment;
});
setTimeout(() => {
const view = this.app.workspace.getActiveViewOfType(import_obsidian17.MarkdownView);
if (view) {
this.plugin.updateBanner(view, true);
}
}, 350);
}
updateTitleColor(color) {
const titleColorField = Array.isArray(this.plugin.settings.customTitleColorField) ? this.plugin.settings.customTitleColorField[0].split(",")[0].trim() : this.plugin.settings.customTitleColorField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[titleColorField] = color;
});
}
updateRepeatMode(repeat) {
const repeatField = Array.isArray(this.plugin.settings.customImageRepeatField) ? this.plugin.settings.customImageRepeatField[0].split(",")[0].trim() : this.plugin.settings.customImageRepeatField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (fm) => {
fm[repeatField] = repeat;
});
}
updateBannerAlignment(alignment) {
const alignmentField = Array.isArray(this.plugin.settings.customBannerAlignmentField) ? this.plugin.settings.customBannerAlignmentField[0].split(",")[0].trim() : this.plugin.settings.customBannerAlignmentField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (fm) => {
fm[alignmentField] = alignment;
});
}
updateBannerFade(fade) {
const fadeField = Array.isArray(this.plugin.settings.customFadeField) ? this.plugin.settings.customFadeField[0].split(",")[0].trim() : this.plugin.settings.customFadeField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[fadeField] = fade;
});
}
updateBannerBorderRadius(borderRadius) {
const borderRadiusField = Array.isArray(this.plugin.settings.customBorderRadiusField) ? this.plugin.settings.customBorderRadiusField[0].split(",")[0].trim() : this.plugin.settings.customBorderRadiusField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter) => {
frontmatter[borderRadiusField] = borderRadius;
});
setTimeout(() => {
const view = this.app.workspace.getActiveViewOfType(import_obsidian17.MarkdownView);
if (view) {
this.plugin.updateBanner(view, true);
}
}, 350);
}
onPositionChange(x, y) {
const activeFile = this.app.workspace.getActiveFile();
if (!activeFile) return;
this.app.fileManager.processFrontMatter(activeFile, (frontmatter) => {
frontmatter.bannerTargetX = x;
frontmatter.bannerTargetY = y;
});
}
onOpen() {
const { contentEl } = this;
contentEl.empty();
this.setupUI(contentEl);
}
async setupUI(contentEl) {
var _a;
const { modalEl, bgEl } = this;
contentEl.empty();
contentEl.addClass("target-position-modal");
modalEl.style.opacity = "0.8";
modalEl.style.width = "max-content";
modalEl.style.height = "max-content";
bgEl.style.opacity = "0";
const activeFile = this.app.workspace.getActiveFile();
const frontmatter = activeFile ? ((_a = this.app.metadataCache.getFileCache(activeFile)) == null ? void 0 : _a.frontmatter) || {} : {};
const dragHandle = contentEl.createDiv({
cls: "drag-handle",
attr: {
style: `
cursor: move;
position: absolute;
left: 50%;
transform: translateX(-50%);
top: 7px;
opacity: .8;
`
}
});
dragHandle.setText("\u22EE\u22EE\u22EE\u22EE\u22EE\u22EE\u22EE\u22EE\u22EE\u22EE");
const bannerImageHeader = contentEl.createEl("div", {
text: "\u{1F5BC}\uFE0F Banner Image Settings",
cls: "banner-image-header",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
justify-content: space-between;
color: var(--text-accent);
font-size: 0.9em;
font-weight: 600;
letter-spacing: 1px;
text-transform: uppercase;
margin-top: 15px;
margin-bottom: 10px;
`
}
});
const bannerImageHeaderButtons = bannerImageHeader.createEl("div", {
cls: "banner-image-header-buttons",
attr: {
style: `
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 5px;
`
}
});
const bannerImageHeaderChangeButton = bannerImageHeaderButtons.createEl("button", {
text: "\u270F\uFE0F Change Banner",
cls: "banner-image-header-button cursor-pointer",
attr: {
style: `
text-transform: uppercase;
font-size: .8em;
`
}
});
bannerImageHeaderChangeButton.addEventListener("click", () => {
this.close();
new SelectPixelBannerModal(this.app, this.plugin).open();
});
const bannerImageHeaderRemoveButton = bannerImageHeaderButtons.createEl("button", {
text: "\u{1F5D1}\uFE0F Remove Banner",
cls: "banner-image-header-button cursor-pointer",
attr: {
style: `
text-transform: uppercase;
font-size: .8em;
`
}
});
bannerImageHeaderRemoveButton.addEventListener("click", () => {
this.resetPixelBannerNoteSettings(true);
});
const mainContainer = contentEl.createDiv({
cls: "main-container--banner-image",
attr: {
style: `
position: relative;
display: flex;
flex-direction: row;
gap: 20px;
align-items: stretch;
justify-content: space-between;
`
}
});
let isVideoFile = false;
const bannerField = Array.isArray(this.plugin.settings.customBannerField) ? this.plugin.settings.customBannerField[0].split(",")[0].trim() : this.plugin.settings.customBannerField;
const bannerValue = frontmatter == null ? void 0 : frontmatter[bannerField];
if (bannerValue) {
if (typeof bannerValue === "string") {
const lowerBanner = bannerValue.toLowerCase();
isVideoFile = lowerBanner.endsWith(".mp4") || lowerBanner.endsWith(".mov");
}
if (!isVideoFile && (bannerValue.includes("[[") || bannerValue.includes("![["))) {
const linkMatch = bannerValue.match(/\[\[(.*?)\]\]/) || bannerValue.match(/!\[\[(.*?)\]\]/);
if (linkMatch && linkMatch[1]) {
const linkPath = linkMatch[1].toLowerCase();
isVideoFile = linkPath.endsWith(".mp4") || linkPath.endsWith(".mov");
}
}
}
const controlPanel = mainContainer.createDiv({
cls: "control-panel",
id: "display-mode-panel",
attr: {
style: `
display: ${isVideoFile ? "none" : "flex"};
flex-direction: column;
gap: 10px;
flex: 0 auto;
`
}
});
const displaySelect = controlPanel.createEl("select", { cls: "display-mode-select" });
["cover", "auto", "contain", "cover-zoom"].forEach((mode) => {
const option = displaySelect.createEl("option", {
text: mode.replace("-", " "),
value: mode
});
if (mode === this.currentDisplay) {
option.selected = true;
}
});
const zoomContainer = controlPanel.createDiv({
cls: "zoom-container",
attr: {
style: `
display: ${this.currentDisplay === "cover-zoom" ? "flex" : "none"};
flex-direction: column;
gap: 5px;
align-items: center;
margin-top: 10px;
height: 100%;
`
}
});
const zoomValue = zoomContainer.createDiv({
cls: "zoom-value",
attr: {
style: `
font-family: var(--font-monospace);
font-size: 0.9em;
`
}
});
zoomValue.setText(`${this.currentZoom}%`);
const zoomSlider = zoomContainer.createEl("input", {
type: "range",
cls: "zoom-slider",
attr: {
min: "0",
max: "500",
step: "10",
value: this.currentZoom,
style: `
flex: 1;
writing-mode: vertical-lr;
direction: rtl;
`
}
});
displaySelect.addEventListener("change", () => {
const mode = displaySelect.value;
zoomContainer.style.display = mode === "cover-zoom" ? "flex" : "none";
this.updateDisplayMode(mode, mode === "cover-zoom" ? this.currentZoom : null);
});
zoomSlider.addEventListener("input", () => {
this.currentZoom = parseInt(zoomSlider.value);
zoomValue.setText(`${this.currentZoom}%`);
this.updateDisplayMode("cover-zoom", this.currentZoom);
});
const maxWidthContainer = mainContainer.createDiv({
cls: "max-width-container",
attr: {
style: `
display: flex;
flex-direction: column;
gap: 10px;
align-items: center;
min-width: 60px;
flex: 0 auto;
`
}
});
const maxWidthLabel = maxWidthContainer.createEl("div", {
text: "Max Width",
cls: "max-width-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const maxWidthField = Array.isArray(this.plugin.settings.customBannerMaxWidthField) ? this.plugin.settings.customBannerMaxWidthField[0].split(",")[0].trim() : this.plugin.settings.customBannerMaxWidthField;
const maxWidthValue = frontmatter == null ? void 0 : frontmatter[maxWidthField];
const directMaxWidth = frontmatter == null ? void 0 : frontmatter["banner-max-width"];
const maxWidthExists = maxWidthValue !== void 0 && maxWidthValue !== null || directMaxWidth !== void 0 && directMaxWidth !== null;
const isMaxWidthUnset = !maxWidthExists;
this.currentMaxWidth = isMaxWidthUnset ? 1928 : parseInt(maxWidthValue || directMaxWidth) || 1928;
const unsetContainer = maxWidthContainer.createDiv({
cls: "unset-container",
attr: {
style: `
display: flex;
align-items: center;
gap: 5px;
margin-bottom: 5px;
`
}
});
const unsetCheckbox = unsetContainer.createEl("input", {
type: "checkbox",
cls: "unset-checkbox"
});
const shouldBeChecked = isMaxWidthUnset;
unsetCheckbox.checked = shouldBeChecked;
unsetContainer.createEl("span", {
text: "unset"
});
const maxWidthValueDisplay = maxWidthContainer.createDiv({
cls: "max-width-value",
attr: {
style: `
font-family: var(--font-monospace);
font-size: 0.9em;
${isMaxWidthUnset ? "color: var(--text-muted);" : ""}
`
}
});
maxWidthValueDisplay.setText(isMaxWidthUnset ? "unset" : `${this.currentMaxWidth}px`);
const maxWidthSlider = maxWidthContainer.createEl("input", {
type: "range",
cls: "max-width-slider",
attr: {
min: "100",
max: "2560",
step: "10",
value: this.currentMaxWidth,
disabled: isMaxWidthUnset,
draggable: false,
style: `
width: 15px;
height: 30px;
flex: 1;
writing-mode: vertical-lr;
direction: rtl;
${isMaxWidthUnset ? "opacity: 0.5;" : ""}
`
}
});
if (!isMaxWidthUnset) {
maxWidthSlider.disabled = false;
maxWidthSlider.style.opacity = "1";
}
unsetCheckbox.addEventListener("change", () => {
const isUnset = unsetCheckbox.checked;
maxWidthSlider.disabled = isUnset;
maxWidthSlider.style.opacity = isUnset ? "0.5" : "1";
maxWidthValueDisplay.style.color = isUnset ? "var(--text-muted)" : "";
maxWidthValueDisplay.setText(isUnset ? "unset" : `${this.currentMaxWidth}px`);
if (isUnset) {
this.updateBannerMaxWidth("unset");
} else {
this.updateBannerMaxWidth(this.currentMaxWidth);
}
});
maxWidthSlider.addEventListener("input", () => {
this.currentMaxWidth = parseInt(maxWidthSlider.value);
maxWidthValueDisplay.setText(`${this.currentMaxWidth}px`);
this.updateBannerMaxWidth(this.currentMaxWidth);
});
const heightContainer = mainContainer.createDiv({
cls: "height-container",
attr: {
style: `
display: flex;
flex-direction: column;
gap: 10px;
align-items: center;
min-width: 60px;
flex: 0 auto;
`
}
});
const heightLabel = heightContainer.createEl("div", {
text: "Height",
cls: "height-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const heightValue = heightContainer.createDiv({
cls: "height-value",
attr: {
style: `
font-family: var(--font-monospace);
font-size: 0.9em;
`
}
});
heightValue.setText(`${this.currentHeight}px`);
const heightSlider = heightContainer.createEl("input", {
type: "range",
cls: "height-slider",
attr: {
min: "0",
max: "1280",
step: "10",
value: this.currentHeight,
style: `
flex: 1;
writing-mode: vertical-lr;
direction: rtl;
`
}
});
heightSlider.addEventListener("input", () => {
this.currentHeight = parseInt(heightSlider.value);
heightValue.setText(`${this.currentHeight}px`);
this.updateBannerHeight(this.currentHeight);
});
const targetContainer = mainContainer.createDiv({
cls: "target-container",
attr: {
style: `
display: flex;
flex-direction: column;
gap: 10px;
`
}
});
const targetArea = targetContainer.createDiv({
cls: "target-area",
attr: {
style: `
width: 200px;
height: 200px;
border: 2px solid var(--background-modifier-border);
position: relative;
background-color: var(--background-primary);
cursor: crosshair;
flex-grow: 1;
`
}
});
const verticalLine = targetArea.createDiv({ cls: "crosshair-line vertical" });
const horizontalLine = targetArea.createDiv({ cls: "crosshair-line horizontal" });
const positionIndicator = targetContainer.createEl("div", {
cls: "position-indicator",
attr: {
style: `
text-align: center;
font-family: var(--font-monospace);
font-size: 0.9em;
color: var(--text-muted);
width: 200px;
`
}
});
positionIndicator.setText(`X: ${this.currentX}%, Y: ${this.currentY}%`);
const updatePositionIndicator = () => {
positionIndicator.setText(`X: ${this.currentX}%, Y: ${this.currentY}%`);
};
this.addStyle();
const updatePosition = (e) => {
const rect = targetArea.getBoundingClientRect();
const x = Math.max(0, Math.min(100, (e.clientX - rect.left) / rect.width * 100));
const y = Math.max(0, Math.min(100, (e.clientY - rect.top) / rect.height * 100));
verticalLine.style.left = `${x}%`;
horizontalLine.style.top = `${y}%`;
this.currentX = Math.round(x);
this.currentY = Math.round(y);
const xField = Array.isArray(this.plugin.settings.customXPositionField) ? this.plugin.settings.customXPositionField[0].split(",")[0].trim() : this.plugin.settings.customXPositionField;
const yField = Array.isArray(this.plugin.settings.customYPositionField) ? this.plugin.settings.customYPositionField[0].split(",")[0].trim() : this.plugin.settings.customYPositionField;
this.app.fileManager.processFrontMatter(this.app.workspace.getActiveFile(), (frontmatter2) => {
frontmatter2[xField] = this.currentX;
frontmatter2[yField] = this.currentY;
});
updatePositionIndicator();
};
targetArea.addEventListener("click", updatePosition);
verticalLine.style.left = `${this.currentX}%`;
horizontalLine.style.top = `${this.currentY}%`;
const contentStartPositionContainer = mainContainer.createDiv({
cls: "content-start-position-container",
attr: {
style: `
display: flex;
flex-direction: column;
gap: 10px;
align-items: center;
min-width: 60px;
flex: 0 auto;
`
}
});
const contentStartPositionLabel = contentStartPositionContainer.createEl("div", {
text: "Content Start Position",
cls: "content-start-position-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
text-align: center;
width: 60px;
`
}
});
const contentStartPositionValue = contentStartPositionContainer.createDiv({
cls: "content-start-position-value",
attr: {
style: `
font-family: var(--font-monospace);
font-size: 0.9em;
`
}
});
contentStartPositionValue.setText(`${this.currentContentStartPosition}px`);
const contentStartPositionSlider = contentStartPositionContainer.createEl("input", {
type: "range",
cls: "content-start-position-slider",
attr: {
min: "1",
max: "800",
step: "5",
value: this.currentContentStartPosition,
style: `
flex: 1;
writing-mode: vertical-lr;
direction: rtl;
`
}
});
contentStartPositionSlider.addEventListener("input", () => {
this.currentContentStartPosition = parseInt(contentStartPositionSlider.value);
contentStartPositionValue.setText(`${this.currentContentStartPosition}px`);
this.updateBannerContentStartPosition(this.currentContentStartPosition);
});
const bannerSettingsRow2 = contentEl.createDiv({
attr: {
style: `
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 10px;
align-items: center;
flex: 0 auto;
margin-top: 10px;
`
}
});
bannerSettingsRow2.createEl("div", {
text: "Banner Alignment",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const alignmentSelect = bannerSettingsRow2.createEl("select", { cls: "alignment-select" });
[
{ value: "left", text: "Left" },
{ value: "center", text: "Center" },
{ value: "right", text: "Right" }
].forEach((option) => {
const optionEl = alignmentSelect.createEl("option", {
text: option.text,
value: option.value
});
if (option.value === this.currentAlignment) {
optionEl.selected = true;
}
});
alignmentSelect.addEventListener("change", () => {
this.currentAlignment = alignmentSelect.value;
this.updateBannerAlignment(this.currentAlignment);
});
const bannerFadeContainer = bannerSettingsRow2.createDiv({
cls: "setting-item",
attr: {
style: `
flex: 1;
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
`
}
});
const bannerFadeHeader = bannerFadeContainer.createDiv({
text: "Banner Fade",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const bannerFadeSliderContainer = bannerFadeContainer.createDiv({
attr: {
style: `
flex: 1;
display: flex;
gap: 10px;
align-items: center;
`
}
});
const bannerFadeSlider = bannerFadeSliderContainer.createEl("input", {
type: "range",
cls: "slider",
attr: {
min: "-300",
max: "100",
step: "5",
value: this.currentFade,
style: `
flex: 1;
`
}
});
const bannerFadeValue = bannerFadeSliderContainer.createDiv({
text: this.currentFade,
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
min-width: 45px;
text-align: right;
`
}
});
bannerFadeSlider.addEventListener("input", (e) => {
const value = parseInt(e.target.value);
this.updateBannerFade(value);
bannerFadeValue.setText(value.toString());
});
const borderRadiusContainer = bannerSettingsRow2.createDiv({
cls: "setting-item",
attr: {
style: `
flex: 1;
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
`
}
});
const borderRadiusHeader = borderRadiusContainer.createDiv({
text: "Border Radius",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const borderRadiusSliderContainer = borderRadiusContainer.createDiv({
attr: {
style: `
flex: 1;
display: flex;
gap: 10px;
align-items: center;
`
}
});
const borderRadiusSlider = borderRadiusSliderContainer.createEl("input", {
type: "range",
cls: "slider",
attr: {
min: "0",
max: "50",
step: "1",
value: this.currentBorderRadius,
style: `
flex: 1;
`
}
});
const borderRadiusValue = borderRadiusSliderContainer.createDiv({
text: this.currentBorderRadius.toString(),
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
min-width: 45px;
text-align: right;
`
}
});
borderRadiusSlider.addEventListener("input", (e) => {
const value = parseInt(e.target.value);
this.updateBannerBorderRadius(value);
borderRadiusValue.setText(`${value}`);
});
const openEmojiPicker = () => {
this.close();
new EmojiSelectionModal(
this.app,
this.plugin,
async (emoji) => {
const activeFile2 = this.app.workspace.getActiveFile();
if (activeFile2) {
await this.plugin.app.fileManager.processFrontMatter(activeFile2, (frontmatter2) => {
const iconField = this.plugin.settings.customBannerIconField[0];
if (emoji) {
frontmatter2[iconField] = emoji;
} else {
delete frontmatter2[iconField];
}
});
}
}
).open();
};
const openIconImagePicker = () => {
const defaultIconImageFolder = this.plugin.settings.defaultSelectIconPath || "";
this.close();
new IconImageSelectionModal(
this.app,
this.plugin,
async (file) => {
if (!file) return;
const activeFile2 = this.app.workspace.getActiveFile();
if (!activeFile2) return;
if (file.isWebUrl) {
this.app.fileManager.processFrontMatter(activeFile2, (fm) => {
const iconImageField = Array.isArray(this.plugin.settings.customBannerIconImageField) ? this.plugin.settings.customBannerIconImageField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageField;
fm[iconImageField] = file.path;
});
new _TargetPositionModal(this.app, this.plugin).open();
return;
}
let fileExtension = "";
let filePath = "";
if (typeof file === "string") {
filePath = file;
const extensionPart = file.split(".").pop();
fileExtension = extensionPart ? extensionPart.toLowerCase() : "";
} else if (file && file.extension) {
filePath = file.path;
fileExtension = file.extension.toLowerCase();
}
if (fileExtension && fileExtension.match(/^(jpg|jpeg|png|gif|bmp|svg|webp|avif)$/)) {
try {
const imageUrl = await this.plugin.getVaultImageUrl(filePath);
if (imageUrl) {
this.plugin.loadedImages.set(filePath, imageUrl);
const preloadImg = new Image();
preloadImg.src = imageUrl;
}
} catch (error) {
console.error("Error preloading icon image:", error);
}
}
this.app.fileManager.processFrontMatter(activeFile2, (fm) => {
const iconImageField = Array.isArray(this.plugin.settings.customBannerIconImageField) ? this.plugin.settings.customBannerIconImageField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageField;
const format = this.plugin.settings.imagePropertyFormat;
let iconImageValue;
if (format === "image") {
iconImageValue = filePath;
} else if (format === "[[image]]") {
iconImageValue = `[[${filePath}]]`;
} else {
iconImageValue = `![[${filePath}]]`;
}
fm[iconImageField] = iconImageValue;
});
new _TargetPositionModal(this.app, this.plugin).open();
},
defaultIconImageFolder
).open();
};
const bannerIconField = Array.isArray(this.plugin.settings.customBannerIconField) ? this.plugin.settings.customBannerIconField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconField;
const bannerIconImageField = Array.isArray(this.plugin.settings.customBannerIconImageField) ? this.plugin.settings.customBannerIconImageField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageField;
const getStringValue = (value) => {
if (value === void 0 || value === null) return "";
if (typeof value === "string") return value;
if (Array.isArray(value)) {
let current = value;
while (Array.isArray(current) && current.length > 0) {
current = current[0];
}
return typeof current === "string" ? current : "";
}
return String(value);
};
let hasBannerIcon = frontmatter && (frontmatter[bannerIconField] && getStringValue(frontmatter[bannerIconField]).trim() !== "" || frontmatter[bannerIconImageField] && getStringValue(frontmatter[bannerIconImageField]).trim() !== "");
const hasBannerIconImage = frontmatter && frontmatter[bannerIconImageField] && getStringValue(frontmatter[bannerIconImageField]).trim() !== "";
if (!hasBannerIcon) {
await new Promise((resolve) => {
setTimeout(async () => {
var _a2;
const refreshedFrontmatter = (_a2 = this.app.metadataCache.getFileCache(activeFile)) == null ? void 0 : _a2.frontmatter;
if (refreshedFrontmatter && (refreshedFrontmatter[bannerIconField] && getStringValue(refreshedFrontmatter[bannerIconField]).trim() !== "" || refreshedFrontmatter[bannerIconImageField] && getStringValue(refreshedFrontmatter[bannerIconImageField]).trim() !== "")) {
hasBannerIcon = true;
}
resolve();
}, 400);
});
}
const addBannerIconContainer = contentEl.createDiv({
cls: "main-container--banner-icon",
attr: {
style: `
display: flex;
flex-direction: row;
margin-top: 30px;
margin-bottom: 10px;
justify-content: space-between;
align-items: center;
`
}
});
const bannerIconHeader = addBannerIconContainer.createEl("div", {
text: hasBannerIcon ? "\u2B50 Banner Icon Settings" : "",
cls: "banner-icon-header",
attr: {
style: `
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
color: var(--text-accent);
font-size: 0.9em;
font-weight: 600;
letter-spacing: 1px;
text-transform: uppercase;
`
}
});
const bannerIconHeaderButtons = addBannerIconContainer.createDiv({
cls: "banner-icon-header-buttons",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
justify-content: flex-end;
`
}
});
const bannerIconHeaderButtonIcon = bannerIconHeaderButtons.createEl("button", {
text: hasBannerIconImage ? "\u270F\uFE0F Edit Icon Image" : "\u2B50 Add Icon Image",
cls: "banner-icon-header-button cursor-pointer",
attr: {
style: `
text-transform: uppercase;
font-size: .8em;
`
}
});
bannerIconHeaderButtonIcon.addEventListener("click", openIconImagePicker);
const hasBannerIconText = frontmatter && frontmatter[bannerIconField] && getStringValue(frontmatter[bannerIconField]).trim() !== "";
const bannerIconHeaderButtonText = bannerIconHeaderButtons.createEl("button", {
text: hasBannerIconText ? "\u{1F4DD} Edit Icon Text & Emoji" : "\u{1F4F0} Add Icon Text & Emoji",
cls: "banner-icon-header-button cursor-pointer",
attr: {
style: `
text-transform: uppercase;
font-size: .8em;
`
}
});
bannerIconHeaderButtonText.addEventListener("click", openEmojiPicker);
const bannerIconControlsContainer = contentEl.createDiv({
cls: "main-container--banner-icon",
attr: {
style: `
margin-top: 20px;
display: ${hasBannerIcon ? "block" : "none"};
`
}
});
const bannerIconImageAlignmentContainer = bannerIconControlsContainer.createDiv({
cls: "banner-icon-image-alignment-container",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
min-width: 60px;
flex: 0 auto;
margin-bottom: 15px;
`
}
});
const bannerIconImageAlignmentLabel = bannerIconImageAlignmentContainer.createEl("div", {
text: "Icon Image Alignment",
cls: "banner-icon-image-alignment-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
min-width: 120px;
`
}
});
const bannerIconImageAlignmentRadioContainer = bannerIconImageAlignmentContainer.createDiv({
cls: "banner-icon-image-alignment-radio-container",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 20px;
align-items: center;
`
}
});
const leftRadioContainer = bannerIconImageAlignmentRadioContainer.createDiv({
cls: "radio-container",
attr: {
style: `
display: flex;
align-items: center;
gap: 5px;
`
}
});
const isRightAlignment = this.currentBannerIconImageAlignment === "right";
const isLeftAlignment = !isRightAlignment;
const leftRadio = leftRadioContainer.createEl("input", {
type: "radio",
attr: {
id: "icon-placement-left",
name: "icon-image-placement",
value: "left",
style: `cursor: pointer;`
}
});
leftRadioContainer.createEl("label", {
text: "Left",
attr: {
for: "icon-placement-left",
style: `
cursor: pointer;
font-size: 0.9em;
`
}
});
const rightRadioContainer = bannerIconImageAlignmentRadioContainer.createDiv({
cls: "radio-container",
attr: {
style: `
display: flex;
align-items: center;
gap: 5px;
`
}
});
const rightRadio = rightRadioContainer.createEl("input", {
type: "radio",
attr: {
id: "icon-placement-right",
name: "icon-image-placement",
value: "right",
style: `cursor: pointer;`
}
});
rightRadioContainer.createEl("label", {
text: "Right",
attr: {
for: "icon-placement-right",
style: `
cursor: pointer;
font-size: 0.9em;
`
}
});
setTimeout(() => {
leftRadio.checked = isLeftAlignment;
rightRadio.checked = isRightAlignment;
}, 50);
leftRadio.addEventListener("change", () => {
if (leftRadio.checked) {
this.currentBannerIconImageAlignment = "left";
this.updateBannerIconImageAlignment(this.currentBannerIconImageAlignment);
}
});
rightRadio.addEventListener("change", () => {
if (rightRadio.checked) {
this.currentBannerIconImageAlignment = "right";
this.updateBannerIconImageAlignment(this.currentBannerIconImageAlignment);
}
});
const bannerIconXPositionContainer = bannerIconControlsContainer.createDiv({
cls: "banner-icon-x-position-container",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
min-width: 60px;
flex: 0 auto;
`
}
});
const bannerIconXPositionLabel = bannerIconXPositionContainer.createEl("div", {
text: "Icon X Position",
cls: "banner-icon-x-position-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const bannerIconXPositionSlider = bannerIconXPositionContainer.createEl("input", {
type: "range",
cls: "banner-icon-x-position-slider",
attr: {
min: "1",
max: "100",
step: "1",
value: this.currentBannerIconXPosition,
style: `
flex: 1;
writing-mode: horizontal-tb;
direction: ltr;
`
}
});
const bannerIconXPositionValue = bannerIconXPositionContainer.createDiv({
cls: "banner-icon-x-position-value",
attr: {
style: `
font-family: var(--font-monospace);
font-size: 0.9em;
`
}
});
bannerIconXPositionValue.setText(`${this.currentBannerIconXPosition}`);
bannerIconXPositionSlider.addEventListener("input", () => {
this.currentBannerIconXPosition = parseInt(bannerIconXPositionSlider.value);
bannerIconXPositionValue.setText(`${this.currentBannerIconXPosition}`);
this.updateBannerIconXPosition(this.currentBannerIconXPosition);
});
const bannerIconVerticalOffsetContainer = bannerIconControlsContainer.createDiv({
cls: "banner-icon-vertical-offset-container",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
min-width: 60px;
flex: 0 auto;
margin-top: 10px;
`
}
});
const bannerIconVerticalOffsetLabel = bannerIconVerticalOffsetContainer.createEl("div", {
text: "Icon Y Position",
cls: "banner-icon-vertical-offset-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const iconVerticalOffsetField = Array.isArray(this.plugin.settings.customBannerIconVerticalOffsetField) ? this.plugin.settings.customBannerIconVerticalOffsetField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconVerticalOffsetField;
this.currentBannerIconVerticalOffset = (frontmatter == null ? void 0 : frontmatter[iconVerticalOffsetField]) || this.plugin.settings.bannerIconVerticalOffset;
const bannerIconVerticalOffsetSlider = bannerIconVerticalOffsetContainer.createEl("input", {
type: "range",
cls: "banner-icon-vertical-offset-slider",
attr: {
min: "-100",
max: "100",
step: "1",
value: this.currentBannerIconVerticalOffset,
style: `
flex: 1;
writing-mode: horizontal-tb;
direction: ltr;
`
}
});
const bannerIconVerticalOffsetValue = bannerIconVerticalOffsetContainer.createDiv({
cls: "banner-icon-vertical-offset-value",
attr: {
style: `
font-family: var(--font-monospace);
font-size: 0.9em;
`
}
});
bannerIconVerticalOffsetValue.setText(`${this.currentBannerIconVerticalOffset}`);
bannerIconVerticalOffsetSlider.addEventListener("input", () => {
this.currentBannerIconVerticalOffset = parseInt(bannerIconVerticalOffsetSlider.value);
bannerIconVerticalOffsetValue.setText(`${this.currentBannerIconVerticalOffset}`);
this.updateBannerIconVerticalOffset(this.currentBannerIconVerticalOffset);
});
const bannerIconSizeContainer = bannerIconControlsContainer.createDiv({
cls: "banner-icon-size-container",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
min-width: 60px;
flex: 0 auto;
margin-top: 10px;
`
}
});
const bannerIconSizeLabel = bannerIconSizeContainer.createEl("div", {
text: "Icon Size",
cls: "banner-icon-size-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const iconSizeField = Array.isArray(this.plugin.settings.customBannerIconSizeField) ? this.plugin.settings.customBannerIconSizeField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconSizeField;
this.currentBannerIconSize = (frontmatter == null ? void 0 : frontmatter[iconSizeField]) || this.plugin.settings.bannerIconSize;
const bannerIconSizeSlider = bannerIconSizeContainer.createEl("input", {
type: "range",
cls: "banner-icon-size-slider",
attr: {
min: "10",
max: "200",
step: "1",
value: this.currentBannerIconSize,
style: `
flex: 1;
writing-mode: horizontal-tb;
direction: ltr;
`
}
});
const bannerIconSizeValue = bannerIconSizeContainer.createDiv({
cls: "banner-icon-size-value",
attr: {
style: `
font-family: var(--font-monospace);
font-size: 0.9em;
`
}
});
bannerIconSizeValue.setText(`${this.currentBannerIconSize}`);
bannerIconSizeSlider.addEventListener("input", () => {
this.currentBannerIconSize = parseInt(bannerIconSizeSlider.value);
bannerIconSizeValue.setText(`${this.currentBannerIconSize}`);
this.updateBannerIconSize(this.currentBannerIconSize);
});
const bannerIconImageSizeMultiplierContainer = bannerIconControlsContainer.createDiv({
cls: "banner-icon-image-size-multiplier-container",
attr: {
style: `
display: ${hasBannerIconImage ? "flex" : "none"};
flex-direction: row;
gap: 10px;
align-items: center;
min-width: 60px;
flex: 0 auto;
margin-top: 10px;
`
}
});
const bannerIconImageSizeMultiplierLabel = bannerIconImageSizeMultiplierContainer.createEl("div", {
text: "Icon Image Size Multiplier",
cls: "banner-icon-image-size-multiplier-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const iconImageSizeMultiplierField = Array.isArray(this.plugin.settings.customBannerIconImageSizeMultiplierField) ? this.plugin.settings.customBannerIconImageSizeMultiplierField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageSizeMultiplierField;
this.currentBannerIconImageSizeMultiplier = (frontmatter == null ? void 0 : frontmatter[iconImageSizeMultiplierField]) || this.plugin.settings.bannerIconImageSizeMultiplier;
const bannerIconImageSizeMultiplierSlider = bannerIconImageSizeMultiplierContainer.createEl("input", {
type: "range",
cls: "banner-icon-image-size-multiplier-slider",
attr: {
min: ".2",
max: "3",
step: "0.1",
value: this.currentBannerIconImageSizeMultiplier,
style: `
flex: 1;
writing-mode: horizontal-tb;
direction: ltr;
`
}
});
const bannerIconImageSizeMultiplierValue = bannerIconImageSizeMultiplierContainer.createDiv({
cls: "banner-icon-image-size-multiplier-value",
attr: {
style: `
font-family: var(--font-monospace);
font-size: 0.9em;
`
}
});
bannerIconImageSizeMultiplierValue.setText(`${this.currentBannerIconImageSizeMultiplier}`);
bannerIconImageSizeMultiplierSlider.addEventListener("input", () => {
this.currentBannerIconImageSizeMultiplier = parseFloat(bannerIconImageSizeMultiplierSlider.value);
bannerIconImageSizeMultiplierValue.setText(`${this.currentBannerIconImageSizeMultiplier}`);
this.updateBannerIconImageSizeMultiplier(this.currentBannerIconImageSizeMultiplier);
});
const bannerIconTextVerticalOffsetContainer = bannerIconControlsContainer.createDiv({
cls: "banner-icon-text-vertical-offset-container",
attr: {
style: `
display: ${hasBannerIconText ? "flex" : "none"};
flex-direction: row;
gap: 10px;
align-items: center;
min-width: 60px;
flex: 0 auto;
margin-top: 10px;
`
}
});
const bannerIconTextVerticalOffsetLabel = bannerIconTextVerticalOffsetContainer.createEl("div", {
text: "Icon Text Vertical Offset",
cls: "banner-icon-text-vertical-offset-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const iconTextVerticalOffsetField = Array.isArray(this.plugin.settings.customBannerIconTextVerticalOffsetField) ? this.plugin.settings.customBannerIconTextVerticalOffsetField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconTextVerticalOffsetField;
this.currentBannerIconTextVerticalOffset = (frontmatter == null ? void 0 : frontmatter[iconTextVerticalOffsetField]) || this.plugin.settings.bannerIconTextVerticalOffset || 0;
const bannerIconTextVerticalOffsetSlider = bannerIconTextVerticalOffsetContainer.createEl("input", {
type: "range",
cls: "banner-icon-text-vertical-offset-slider",
attr: {
min: "-50",
max: "50",
step: "1",
value: this.currentBannerIconTextVerticalOffset,
style: `
flex: 1;
writing-mode: horizontal-tb;
direction: ltr;
`
}
});
const bannerIconTextVerticalOffsetValue = bannerIconTextVerticalOffsetContainer.createDiv({
cls: "banner-icon-text-vertical-offset-value",
attr: {
style: `
font-family: var(--font-monospace);
font-size: 0.9em;
`
}
});
bannerIconTextVerticalOffsetValue.setText(`${this.currentBannerIconTextVerticalOffset}`);
bannerIconTextVerticalOffsetSlider.addEventListener("input", () => {
this.currentBannerIconTextVerticalOffset = parseInt(bannerIconTextVerticalOffsetSlider.value);
bannerIconTextVerticalOffsetValue.setText(`${this.currentBannerIconTextVerticalOffset}`);
this.updateBannerIconTextVerticalOffset(this.currentBannerIconTextVerticalOffset);
});
const bannerIconRotateContainer = bannerIconControlsContainer.createDiv({
cls: "banner-icon-rotate-container",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
min-width: 60px;
flex: 0 auto;
margin-top: 10px;
`
}
});
const bannerIconRotateLabel = bannerIconRotateContainer.createEl("div", {
text: "Icon Rotation",
cls: "banner-icon-rotate-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const iconRotateField = Array.isArray(this.plugin.settings.customBannerIconRotateField) ? this.plugin.settings.customBannerIconRotateField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconRotateField;
this.currentBannerIconRotate = (frontmatter == null ? void 0 : frontmatter[iconRotateField]) || 0;
const bannerIconRotateSlider = bannerIconRotateContainer.createEl("input", {
type: "range",
cls: "banner-icon-rotate-slider",
attr: {
min: "0",
max: "360",
step: "5",
value: this.currentBannerIconRotate,
style: `
flex: 1;
writing-mode: horizontal-tb;
direction: ltr;
`
}
});
const bannerIconRotateValue = bannerIconRotateContainer.createDiv({
cls: "banner-icon-rotate-value",
attr: {
style: `
font-family: var(--font-monospace);
font-size: 0.9em;
`
}
});
bannerIconRotateValue.setText(`${this.currentBannerIconRotate}`);
bannerIconRotateSlider.addEventListener("input", () => {
this.currentBannerIconRotate = parseInt(bannerIconRotateSlider.value);
bannerIconRotateValue.setText(`${this.currentBannerIconRotate}`);
this.updateBannerIconRotate(this.currentBannerIconRotate);
});
const bannerIconColorContainer = bannerIconControlsContainer.createDiv({
cls: "banner-icon-color-container",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
flex: 0 auto;
margin-top: 10px;
`
}
});
const bannerIconColorLabel = bannerIconColorContainer.createEl("div", {
text: "Icon Text Color",
cls: "banner-icon-color-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const iconColorField = Array.isArray(this.plugin.settings.customBannerIconColorField) ? this.plugin.settings.customBannerIconColorField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconColorField;
let currentIconColor = this.plugin.settings.bannerIconColor;
if ((frontmatter == null ? void 0 : frontmatter[iconColorField]) || this.plugin.settings.bannerIconColor) {
const colorValue = (frontmatter == null ? void 0 : frontmatter[iconColorField]) || this.plugin.settings.bannerIconColor;
if (typeof colorValue === "string" && colorValue.startsWith("#")) {
currentIconColor = colorValue;
} else if (typeof colorValue === "string" && colorValue.trim() !== "") {
currentIconColor = colorValue;
}
}
this.currentBannerIconColor = currentIconColor;
const ensureValidHexColor = (color) => {
if (!color || !/^#[0-9A-F]{6}$/i.test(color)) {
return "#000000";
}
return color;
};
const bannerIconColorPicker = bannerIconColorContainer.createEl("input", {
type: "color",
cls: "banner-icon-color-picker",
attr: {
value: ensureValidHexColor(this.currentBannerIconColor),
style: `
width: 30px;
height: 30px;
cursor: pointer;
padding: 0;
background-color: transparent;
margin-left: 5px;
border: 1px solid;
border-radius: 50%;
`
}
});
const bannerIconColorInput = bannerIconColorContainer.createEl("input", {
type: "text",
cls: "banner-icon-color-input",
attr: {
value: this.currentBannerIconColor || "",
placeholder: "#RRGGBB or color name",
style: `
flex: 1;
max-width: 120px;
`
}
});
bannerIconColorInput.addEventListener("change", () => {
this.currentBannerIconColor = bannerIconColorInput.value;
if (this.currentBannerIconColor.startsWith("#")) {
bannerIconColorPicker.value = this.currentBannerIconColor;
}
this.updateBannerIconColor(this.currentBannerIconColor);
});
bannerIconColorPicker.addEventListener("input", () => {
this.currentBannerIconColor = bannerIconColorPicker.value;
bannerIconColorInput.value = this.currentBannerIconColor;
this.updateBannerIconColor(this.currentBannerIconColor);
});
const bannerIconFontWeightContainer = bannerIconControlsContainer.createDiv({
cls: "banner-icon-font-weight-container",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
min-width: 60px;
flex: 0 auto;
margin-top: 10px;
`
}
});
const bannerIconFontWeightLabel = bannerIconFontWeightContainer.createEl("div", {
text: "Icon Text Font Weight",
cls: "banner-icon-font-weight-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const iconFontWeightField = Array.isArray(this.plugin.settings.customBannerIconFontWeightField) ? this.plugin.settings.customBannerIconFontWeightField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconFontWeightField;
this.currentBannerIconFontWeight = (frontmatter == null ? void 0 : frontmatter[iconFontWeightField]) || this.plugin.settings.bannerIconFontWeight;
const bannerIconFontWeightSelect = bannerIconFontWeightContainer.createEl("select", {
cls: "banner-icon-font-weight-select"
});
["lighter", "normal", "bold"].forEach((weight) => {
const option = bannerIconFontWeightSelect.createEl("option", {
text: weight.charAt(0).toUpperCase() + weight.slice(1),
value: weight
});
if (weight === this.currentBannerIconFontWeight) {
option.selected = true;
}
});
bannerIconFontWeightSelect.addEventListener("change", () => {
this.currentBannerIconFontWeight = bannerIconFontWeightSelect.value;
this.updateBannerIconFontWeight(this.currentBannerIconFontWeight);
});
const bannerIconBgColorContainer = bannerIconControlsContainer.createDiv({
cls: "banner-icon-bg-color-container",
attr: {
style: `
display: flex;
flex-direction: column;
gap: 10px;
min-width: 60px;
flex: 0 auto;
margin-top: 10px;
`
}
});
const bannerIconBgColorLabel = bannerIconBgColorContainer.createEl("div", {
text: "Icon Background Color",
cls: "banner-icon-bg-color-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const colorPickerAndAlphaSliderRow = bannerIconBgColorContainer.createDiv({
cls: "color-picker-and-alpha-slider-row",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
width: 100%;
`
}
});
const colorPickerRow = colorPickerAndAlphaSliderRow.createDiv({
cls: "color-picker-row",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
width: 100%;
`
}
});
const iconBgColorField = Array.isArray(this.plugin.settings.customBannerIconBackgroundColorField) ? this.plugin.settings.customBannerIconBackgroundColorField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconBackgroundColorField;
let currentColor = this.plugin.settings.bannerIconBackgroundColor;
let currentAlpha = 100;
if ((frontmatter == null ? void 0 : frontmatter[iconBgColorField]) || this.plugin.settings.bannerIconBackgroundColor) {
const colorValue = (frontmatter == null ? void 0 : frontmatter[iconBgColorField]) || this.plugin.settings.bannerIconBackgroundColor;
const rgbaMatch = colorValue == null ? void 0 : colorValue.match(/rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d.]+)\s*\)/);
if (rgbaMatch) {
const r = parseInt(rgbaMatch[1]);
const g = parseInt(rgbaMatch[2]);
const b = parseInt(rgbaMatch[3]);
currentColor = `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
currentAlpha = Math.round(parseFloat(rgbaMatch[4]) * 100);
} else if (colorValue == null ? void 0 : colorValue.startsWith("#")) {
currentColor = colorValue;
} else if (colorValue) {
currentColor = colorValue;
}
}
this.currentBannerIconBgColor = currentColor;
this.currentBannerIconBgAlpha = currentAlpha;
const bannerIconBgColorInput = colorPickerAndAlphaSliderRow.createEl("input", {
type: "text",
cls: "banner-icon-bg-color-input",
attr: {
value: this.currentBannerIconBgColor || "",
placeholder: "#RRGGBB or color name",
style: `
flex: 1;
max-width: 120px;
`
}
});
const bannerIconBgColorPicker = colorPickerAndAlphaSliderRow.createEl("input", {
type: "color",
cls: "banner-icon-bg-color-picker",
attr: {
value: this.currentBannerIconBgColor && this.currentBannerIconBgColor.startsWith("#") ? this.currentBannerIconBgColor : "",
style: `
width: 30px;
height: 30px;
cursor: pointer;
padding: 0;
background-color: transparent;
margin-left: 5px;
border: 1px solid;
border-radius: 50%;
`
}
});
const alphaSliderRow = colorPickerAndAlphaSliderRow.createDiv({
cls: "alpha-slider-row",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
width: 100%;
margin-top: 5px;
`
}
});
const alphaLabel = colorPickerAndAlphaSliderRow.createEl("div", {
text: "Opacity:",
cls: "alpha-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
min-width: 60px;
`
}
});
const alphaSlider = colorPickerAndAlphaSliderRow.createEl("input", {
type: "range",
cls: "alpha-slider",
attr: {
min: "0",
max: "100",
step: "1",
value: this.currentBannerIconBgAlpha,
style: `
flex: 1;
`
}
});
const alphaValue = colorPickerAndAlphaSliderRow.createDiv({
cls: "alpha-value",
attr: {
style: `
font-family: var(--font-monospace);
font-size: 0.9em;
min-width: 40px;
text-align: right;
`
}
});
alphaValue.setText(`${this.currentBannerIconBgAlpha}%`);
const colorPreview = colorPickerAndAlphaSliderRow.createDiv({
cls: "color-preview",
attr: {
style: `
width: 100%;
height: 20px;
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
background-color: ${this.currentBannerIconBgColor};
opacity: ${this.currentBannerIconBgAlpha / 100};
`
}
});
const updateColorPreview = () => {
colorPreview.style.backgroundColor = this.currentBannerIconBgColor;
colorPreview.style.opacity = this.currentBannerIconBgAlpha / 100;
this.updateBannerIconBgColor(this.currentBannerIconBgColor, this.currentBannerIconBgAlpha);
};
bannerIconBgColorInput.addEventListener("change", () => {
this.currentBannerIconBgColor = bannerIconBgColorInput.value;
if (this.currentBannerIconBgColor.startsWith("#")) {
bannerIconBgColorPicker.value = this.currentBannerIconBgColor;
}
updateColorPreview();
});
bannerIconBgColorPicker.addEventListener("input", () => {
this.currentBannerIconBgColor = bannerIconBgColorPicker.value;
bannerIconBgColorInput.value = this.currentBannerIconBgColor;
updateColorPreview();
});
alphaSlider.addEventListener("input", () => {
this.currentBannerIconBgAlpha = parseInt(alphaSlider.value);
alphaValue.setText(`${this.currentBannerIconBgAlpha}%`);
updateColorPreview();
});
const bannerIconPaddingXContainer = bannerIconControlsContainer.createDiv({
cls: "banner-icon-padding-x-container",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
min-width: 60px;
flex: 0 auto;
margin-top: 10px;
`
}
});
const bannerIconPaddingXLabel = bannerIconPaddingXContainer.createEl("div", {
text: "Icon Padding X",
cls: "banner-icon-padding-x-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const iconPaddingXField = Array.isArray(this.plugin.settings.customBannerIconPaddingXField) ? this.plugin.settings.customBannerIconPaddingXField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconPaddingXField;
this.currentBannerIconPaddingX = (frontmatter == null ? void 0 : frontmatter[iconPaddingXField]) || this.plugin.settings.bannerIconPaddingX;
const bannerIconPaddingXSlider = bannerIconPaddingXContainer.createEl("input", {
type: "range",
cls: "banner-icon-padding-x-slider",
attr: {
min: "0",
max: "100",
step: "1",
value: this.currentBannerIconPaddingX,
style: `
flex: 1;
writing-mode: horizontal-tb;
direction: ltr;
`
}
});
const bannerIconPaddingXValue = bannerIconPaddingXContainer.createDiv({
cls: "banner-icon-padding-x-value",
attr: {
style: `
font-family: var(--font-monospace);
font-size: 0.9em;
`
}
});
bannerIconPaddingXValue.setText(`${this.currentBannerIconPaddingX}`);
bannerIconPaddingXSlider.addEventListener("input", () => {
this.currentBannerIconPaddingX = parseInt(bannerIconPaddingXSlider.value);
bannerIconPaddingXValue.setText(`${this.currentBannerIconPaddingX}`);
this.updateBannerIconPaddingX(this.currentBannerIconPaddingX);
});
const bannerIconPaddingYContainer = bannerIconControlsContainer.createDiv({
cls: "banner-icon-padding-y-container",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
min-width: 60px;
flex: 0 auto;
margin-top: 10px;
`
}
});
const bannerIconPaddingYLabel = bannerIconPaddingYContainer.createEl("div", {
text: "Icon Padding Y",
cls: "banner-icon-padding-y-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const iconPaddingYField = Array.isArray(this.plugin.settings.customBannerIconPaddingYField) ? this.plugin.settings.customBannerIconPaddingYField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconPaddingYField;
this.currentBannerIconPaddingY = (frontmatter == null ? void 0 : frontmatter[iconPaddingYField]) || this.plugin.settings.bannerIconPaddingY;
const bannerIconPaddingYSlider = bannerIconPaddingYContainer.createEl("input", {
type: "range",
cls: "banner-icon-padding-y-slider",
attr: {
min: "0",
max: "100",
step: "1",
value: this.currentBannerIconPaddingY,
style: `
flex: 1;
writing-mode: horizontal-tb;
direction: ltr;
`
}
});
const bannerIconPaddingYValue = bannerIconPaddingYContainer.createDiv({
cls: "banner-icon-padding-y-value",
attr: {
style: `
font-family: var(--font-monospace);
font-size: 0.9em;
`
}
});
bannerIconPaddingYValue.setText(`${this.currentBannerIconPaddingY}`);
bannerIconPaddingYSlider.addEventListener("input", () => {
this.currentBannerIconPaddingY = parseInt(bannerIconPaddingYSlider.value);
bannerIconPaddingYValue.setText(`${this.currentBannerIconPaddingY}`);
this.updateBannerIconPaddingY(this.currentBannerIconPaddingY);
});
const bannerIconBorderRadiusContainer = bannerIconControlsContainer.createDiv({
cls: "banner-icon-border-radius-container",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
min-width: 60px;
flex: 0 auto;
margin-top: 10px;
`
}
});
const bannerIconBorderRadiusLabel = bannerIconBorderRadiusContainer.createEl("div", {
text: "Icon Border Radius",
cls: "banner-icon-border-radius-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const iconBorderRadiusField = Array.isArray(this.plugin.settings.customBannerIconBorderRadiusField) ? this.plugin.settings.customBannerIconBorderRadiusField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconBorderRadiusField;
this.currentBannerIconBorderRadius = getValueWithZeroCheck([
frontmatter == null ? void 0 : frontmatter[iconBorderRadiusField],
this.plugin.settings.bannerIconBorderRadius
]);
const bannerIconBorderRadiusSlider = bannerIconBorderRadiusContainer.createEl("input", {
type: "range",
cls: "banner-icon-border-radius-slider",
attr: {
min: "0",
max: "200",
step: "1",
value: this.currentBannerIconBorderRadius,
style: `
flex: 1;
writing-mode: horizontal-tb;
direction: ltr;
`
}
});
const bannerIconBorderRadiusValue = bannerIconBorderRadiusContainer.createDiv({
cls: "banner-icon-border-radius-value",
attr: {
style: `
font-family: var(--font-monospace);
font-size: 0.9em;
`
}
});
bannerIconBorderRadiusValue.setText(`${this.currentBannerIconBorderRadius}`);
bannerIconBorderRadiusSlider.addEventListener("input", () => {
this.currentBannerIconBorderRadius = parseInt(bannerIconBorderRadiusSlider.value);
bannerIconBorderRadiusValue.setText(`${this.currentBannerIconBorderRadius}`);
this.updateBannerIconBorderRadius(this.currentBannerIconBorderRadius);
});
const flagColorSection = contentEl.createDiv({
cls: "flag-color-section",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 5px;
margin-top: 20px;
padding: 15px;
border-radius: 5px;
background-color: var(--background-secondary);
max-width: 600px;
`
}
});
flagColorSection.createEl("span", {
text: "Flag Color",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
`
}
});
const flagRadioContainer = flagColorSection.createDiv({
cls: "pixel-banner-flag-radio-container",
attr: {
style: `
max-width: 440px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 10px;
`
}
});
const currentFlagColor = getFrontmatterValue(frontmatter, this.plugin.settings.customFlagColorField) || this.plugin.settings.selectImageIconFlag;
Object.keys(flags).forEach((color) => {
const radioContainer = flagRadioContainer.createDiv({
cls: "pixel-banner-flag-radio",
attr: {
style: `
display: flex;
align-items: center;
gap: 5px;
`
}
});
const radio = radioContainer.createEl("input", {
type: "radio",
attr: {
id: `flag-${color}`,
name: "pixel-banner-flag",
value: color,
style: `
margin-right: 5px;
cursor: pointer;
`
}
});
radio.checked = currentFlagColor === color;
const label = radioContainer.createEl("label", {
attr: {
for: `flag-${color}`,
style: `
display: flex;
align-items: center;
cursor: pointer;
`
}
});
label.createEl("img", {
attr: {
src: flags[color],
alt: color,
style: `
width: 15px;
height: 20px;
margin-right: 3px;
`
}
});
label.createEl("span", {
text: color.charAt(0).toUpperCase() + color.slice(1),
attr: {
style: `
display: none;
font-size: 12px;
`
}
});
radio.addEventListener("change", async () => {
if (radio.checked) {
const activeFile2 = this.app.workspace.getActiveFile();
if (activeFile2) {
await this.plugin.app.fileManager.processFrontMatter(activeFile2, (frontmatter2) => {
const flagColorField = this.plugin.settings.customFlagColorField[0];
frontmatter2[flagColorField] = color;
});
const view = this.plugin.app.workspace.getActiveViewOfType(import_obsidian17.MarkdownView);
if (view) {
await this.plugin.updateBanner(view, true);
}
}
}
});
});
const inlineTitleEnabled = this.app.vault.config.showInlineTitle;
const titleColorSection = contentEl.createDiv({
cls: "title-color-section",
attr: {
style: `
display: ${inlineTitleEnabled ? "flex" : "none"};
flex-direction: row;
gap: 10px;
margin-top: 20px;
padding: 15px;
border-radius: 5px;
background-color: var(--background-secondary);
max-width: 510px;
align-items: center;
`
}
});
titleColorSection.createEl("span", {
text: "Inline Title Color",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
min-width: 90px;
`
}
});
const titleColorField = Array.isArray(this.plugin.settings.customTitleColorField) ? this.plugin.settings.customTitleColorField[0].split(",")[0].trim() : this.plugin.settings.customTitleColorField;
let currentTitleColor = (frontmatter == null ? void 0 : frontmatter[titleColorField]) || this.plugin.settings.titleColor;
if (currentTitleColor && currentTitleColor.startsWith("var(--")) {
const tempEl = document.createElement("div");
tempEl.style.color = currentTitleColor;
document.body.appendChild(tempEl);
const computedColor = window.getComputedStyle(tempEl).color;
document.body.removeChild(tempEl);
const rgbMatch = computedColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (rgbMatch) {
const [_, r, g, b] = rgbMatch;
currentTitleColor = "#" + parseInt(r).toString(16).padStart(2, "0") + parseInt(g).toString(16).padStart(2, "0") + parseInt(b).toString(16).padStart(2, "0");
}
}
this.currentTitleColor = currentTitleColor || (getCurrentTheme_default() === "dark" ? "#ffffff" : "#000000");
const titleColorInput = titleColorSection.createEl("input", {
type: "text",
cls: "title-color-input",
attr: {
value: this.currentTitleColor || "",
placeholder: "#RRGGBB or color name",
style: `
flex: 1;
max-width: 90px;
`
}
});
const titleColorPicker = titleColorSection.createEl("input", {
type: "color",
cls: "title-color-picker",
attr: {
value: this.currentTitleColor && this.currentTitleColor.startsWith("#") ? this.currentTitleColor : getCurrentTheme_default() === "dark" ? "#ffffff" : "#000000",
style: `
width: 30px;
height: 30px;
cursor: pointer;
padding: 0;
background-color: transparent;
margin-left: 5px;
`
}
});
titleColorInput.addEventListener("change", () => {
this.currentTitleColor = titleColorInput.value;
if (this.currentTitleColor.startsWith("#")) {
titleColorPicker.value = this.currentTitleColor;
}
this.updateTitleColor(this.currentTitleColor);
});
titleColorPicker.addEventListener("input", () => {
this.currentTitleColor = titleColorPicker.value;
titleColorInput.value = this.currentTitleColor;
this.updateTitleColor(this.currentTitleColor);
});
const buttonContainer = contentEl.createDiv({
cls: "button-container",
attr: {
style: `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
justify-content: center;
position: sticky;
bottom: -20px;
background: var(--modal-background);
padding: 20px 0;
`
}
});
const resetButton = buttonContainer.createEl("button", {
text: "Reset to Defaults",
cls: "reset-button",
attr: {
style: `
flex: 1;
`
}
});
const closeSettingsButton = buttonContainer.createEl("button", {
text: "Close Settings",
cls: "mod-cta close-settings-button",
attr: {
style: `
flex: 1;
`
}
});
closeSettingsButton.addEventListener("click", () => {
this.close();
});
this.resetPixelBannerNoteSettings = (deleteBannerAndIcon = false) => {
displaySelect.value = "cover";
zoomContainer.style.display = "none";
repeatContainer.style.display = "none";
zoomSlider.value = 100;
heightSlider.value = this.plugin.settings.bannerHeight;
contentStartPositionSlider.value = this.plugin.settings.contentStartPosition;
bannerIconXPositionSlider.value = this.plugin.settings.bannerIconXPosition;
bannerFadeSlider.value = this.plugin.settings.fade;
bannerFadeValue.setText(this.plugin.settings.fade.toString());
if (borderRadiusSlider) borderRadiusSlider.value = this.plugin.settings.borderRadius;
if (borderRadiusValue) borderRadiusValue.setText(this.plugin.settings.borderRadius.toString());
this.updateBannerBorderRadius(this.plugin.settings.borderRadius);
const currentTheme = getCurrentTheme_default();
let defaultColor = currentTheme === "dark" ? "#ffffff" : "#000000";
if (bannerIconSizeSlider) bannerIconSizeSlider.value = this.plugin.settings.bannerIconSize;
if (bannerIconImageSizeMultiplierSlider) bannerIconImageSizeMultiplierSlider.value = this.plugin.settings.bannerIconImageSizeMultiplier;
if (bannerIconRotateSlider) bannerIconRotateSlider.value = 0;
if (bannerIconTextVerticalOffsetSlider) bannerIconTextVerticalOffsetSlider.value = this.plugin.settings.bannerIconTextVerticalOffset;
if (bannerIconColorInput) {
const defaultIconColor = this.plugin.settings.bannerIconColor || defaultColor;
bannerIconColorInput.value = defaultIconColor;
if (bannerIconColorPicker) {
bannerIconColorPicker.value = defaultIconColor.startsWith("#") ? defaultIconColor : defaultColor;
}
this.updateBannerIconColor(defaultIconColor);
}
if (bannerIconFontWeightSelect) bannerIconFontWeightSelect.value = this.plugin.settings.bannerIconFontWeight;
if (bannerIconBgColorInput) {
let defaultAlpha = 100;
if (this.plugin.settings.bannerIconBackgroundColor) {
const colorValue = this.plugin.settings.bannerIconBackgroundColor;
const rgbaMatch = colorValue == null ? void 0 : colorValue.match(/rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d.]+)\s*\)/);
if (rgbaMatch) {
const r = parseInt(rgbaMatch[1]);
const g = parseInt(rgbaMatch[2]);
const b = parseInt(rgbaMatch[3]);
defaultColor = `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
defaultAlpha = Math.round(parseFloat(rgbaMatch[4]) * 100);
} else if (colorValue == null ? void 0 : colorValue.startsWith("#")) {
defaultColor = colorValue;
} else if (colorValue) {
defaultColor = colorValue;
}
}
bannerIconBgColorInput.value = defaultColor;
if (bannerIconBgColorPicker) bannerIconBgColorPicker.value = defaultColor.startsWith("#") ? defaultColor : "#ffffff";
if (alphaSlider) alphaSlider.value = defaultAlpha;
if (alphaValue) alphaValue.setText(`${defaultAlpha}%`);
if (colorPreview) {
colorPreview.style.backgroundColor = defaultColor;
colorPreview.style.opacity = defaultAlpha / 100;
}
this.updateBannerIconBgColor(defaultColor, defaultAlpha);
}
if (bannerIconPaddingXSlider) bannerIconPaddingXSlider.value = this.plugin.settings.bannerIconPaddingX;
if (bannerIconPaddingYSlider) bannerIconPaddingYSlider.value = this.plugin.settings.bannerIconPaddingY;
if (bannerIconBorderRadiusSlider) bannerIconBorderRadiusSlider.value = this.plugin.settings.bannerIconBorderRadius;
if (bannerIconVerticalOffsetSlider) bannerIconVerticalOffsetSlider.value = this.plugin.settings.bannerIconVerticalOffset;
if (bannerIconSizeSlider) bannerIconSizeSlider.value = this.plugin.settings.bannerIconSize;
if (bannerIconRotateSlider) bannerIconRotateSlider.value = 0;
zoomValue.setText("100%");
heightValue.setText(`${this.plugin.settings.bannerHeight}px`);
contentStartPositionValue.setText(`${this.plugin.settings.contentStartPosition}px`);
bannerIconXPositionValue.setText(`${this.plugin.settings.bannerIconXPosition}`);
if (unsetCheckbox) {
unsetCheckbox.checked = true;
maxWidthValueDisplay.style.color = "var(--text-muted)";
maxWidthValueDisplay.setText("unset");
}
if (alignmentSelect) {
alignmentSelect.value = "center";
}
if (bannerIconSizeValue) bannerIconSizeValue.setText(`${this.plugin.settings.bannerIconSize}`);
if (bannerIconImageSizeMultiplierValue) bannerIconImageSizeMultiplierValue.setText(`${this.plugin.settings.bannerIconImageSizeMultiplier}`);
if (bannerIconTextVerticalOffsetValue) bannerIconTextVerticalOffsetValue.setText(`${this.plugin.settings.bannerIconTextVerticalOffset}`);
if (bannerIconPaddingXValue) bannerIconPaddingXValue.setText(`${this.plugin.settings.bannerIconPaddingX}`);
if (bannerIconPaddingYValue) bannerIconPaddingYValue.setText(`${this.plugin.settings.bannerIconPaddingY}`);
if (bannerIconBorderRadiusValue) bannerIconBorderRadiusValue.setText(`${this.plugin.settings.bannerIconBorderRadius}`);
if (bannerIconVerticalOffsetValue) bannerIconVerticalOffsetValue.setText(`${this.plugin.settings.bannerIconVerticalOffset}`);
if (bannerIconRotateValue) bannerIconRotateValue.setText(`${this.plugin.settings.bannerIconRotate}`);
toggleInput.checked = false;
this.currentX = this.plugin.settings.xPosition;
this.currentY = this.plugin.settings.yPosition;
verticalLine.style.left = `${this.currentX}%`;
horizontalLine.style.top = `${this.currentY}%`;
updatePositionIndicator();
const activeFile2 = this.app.workspace.getActiveFile();
this.app.fileManager.processFrontMatter(activeFile2, (frontmatter2) => {
const displayField = Array.isArray(this.plugin.settings.customImageDisplayField) ? this.plugin.settings.customImageDisplayField[0].split(",")[0].trim() : this.plugin.settings.customImageDisplayField;
const heightField = Array.isArray(this.plugin.settings.customBannerHeightField) ? this.plugin.settings.customBannerHeightField[0].split(",")[0].trim() : this.plugin.settings.customBannerHeightField;
const maxWidthField2 = Array.isArray(this.plugin.settings.customBannerMaxWidthField) ? this.plugin.settings.customBannerMaxWidthField[0].split(",")[0].trim() : this.plugin.settings.customBannerMaxWidthField;
const xField = Array.isArray(this.plugin.settings.customXPositionField) ? this.plugin.settings.customXPositionField[0].split(",")[0].trim() : this.plugin.settings.customXPositionField;
const yField = Array.isArray(this.plugin.settings.customYPositionField) ? this.plugin.settings.customYPositionField[0].split(",")[0].trim() : this.plugin.settings.customYPositionField;
const contentStartPositionField = Array.isArray(this.plugin.settings.customContentStartField) ? this.plugin.settings.customContentStartField[0].split(",")[0].trim() : this.plugin.settings.customContentStartField;
const bannerIconXPositionField = Array.isArray(this.plugin.settings.customBannerIconXPositionField) ? this.plugin.settings.customBannerIconXPositionField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconXPositionField;
const bannerIconImageAlignmentField = Array.isArray(this.plugin.settings.customBannerIconImageAlignmentField) ? this.plugin.settings.customBannerIconImageAlignmentField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageAlignmentField;
const repeatField = Array.isArray(this.plugin.settings.customImageRepeatField) ? this.plugin.settings.customImageRepeatField[0].split(",")[0].trim() : this.plugin.settings.customImageRepeatField;
const fadeField = Array.isArray(this.plugin.settings.customFadeField) ? this.plugin.settings.customFadeField[0].split(",")[0].trim() : this.plugin.settings.customFadeField;
const borderRadiusField = Array.isArray(this.plugin.settings.customBorderRadiusField) ? this.plugin.settings.customBorderRadiusField[0].split(",")[0].trim() : this.plugin.settings.customBorderRadiusField;
const bannerIconColorField = Array.isArray(this.plugin.settings.customBannerIconColorField) ? this.plugin.settings.customBannerIconColorField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconColorField;
const bannerIconFontWeightField = Array.isArray(this.plugin.settings.customBannerIconFontWeightField) ? this.plugin.settings.customBannerIconFontWeightField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconFontWeightField;
const bannerIconBgColorField = Array.isArray(this.plugin.settings.customBannerIconBackgroundColorField) ? this.plugin.settings.customBannerIconBackgroundColorField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconBackgroundColorField;
const bannerIconPaddingXField = Array.isArray(this.plugin.settings.customBannerIconPaddingXField) ? this.plugin.settings.customBannerIconPaddingXField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconPaddingXField;
const bannerIconPaddingYField = Array.isArray(this.plugin.settings.customBannerIconPaddingYField) ? this.plugin.settings.customBannerIconPaddingYField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconPaddingYField;
const bannerIconBorderRadiusField = Array.isArray(this.plugin.settings.customBannerIconBorderRadiusField) ? this.plugin.settings.customBannerIconBorderRadiusField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconBorderRadiusField;
const bannerIconVerticalOffsetField = Array.isArray(this.plugin.settings.customBannerIconVerticalOffsetField) ? this.plugin.settings.customBannerIconVerticalOffsetField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconVerticalOffsetField;
const bannerIconSizeField = Array.isArray(this.plugin.settings.customBannerIconSizeField) ? this.plugin.settings.customBannerIconSizeField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconSizeField;
const bannerIconTextVerticalOffsetField = Array.isArray(this.plugin.settings.customBannerIconTextVerticalOffsetField) ? this.plugin.settings.customBannerIconTextVerticalOffsetField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconTextVerticalOffsetField;
const bannerIconImageSizeMultiplierField = Array.isArray(this.plugin.settings.customBannerIconImageSizeMultiplierField) ? this.plugin.settings.customBannerIconImageSizeMultiplierField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageSizeMultiplierField;
const bannerIconRotateField = Array.isArray(this.plugin.settings.customBannerIconRotateField) ? this.plugin.settings.customBannerIconRotateField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconRotateField;
delete frontmatter2[displayField];
delete frontmatter2[heightField];
delete frontmatter2[maxWidthField2];
delete frontmatter2[xField];
delete frontmatter2[yField];
delete frontmatter2[contentStartPositionField];
delete frontmatter2[repeatField];
delete frontmatter2[fadeField];
delete frontmatter2[borderRadiusField];
delete frontmatter2[bannerIconXPositionField];
delete frontmatter2[bannerIconImageAlignmentField];
delete frontmatter2[bannerIconColorField];
delete frontmatter2[bannerIconFontWeightField];
delete frontmatter2[bannerIconBgColorField];
delete frontmatter2[bannerIconPaddingXField];
delete frontmatter2[bannerIconPaddingYField];
delete frontmatter2[bannerIconBorderRadiusField];
delete frontmatter2[bannerIconVerticalOffsetField];
delete frontmatter2[bannerIconSizeField];
delete frontmatter2[bannerIconTextVerticalOffsetField];
delete frontmatter2[bannerIconImageSizeMultiplierField];
delete frontmatter2[bannerIconRotateField];
const alignmentField = Array.isArray(this.plugin.settings.customBannerAlignmentField) ? this.plugin.settings.customBannerAlignmentField[0].split(",")[0].trim() : this.plugin.settings.customBannerAlignmentField;
delete frontmatter2[alignmentField];
const flagColorField = Array.isArray(this.plugin.settings.customFlagColorField) ? this.plugin.settings.customFlagColorField[0].split(",")[0].trim() : this.plugin.settings.customFlagColorField;
delete frontmatter2[flagColorField];
if (deleteBannerAndIcon) {
const bannerField2 = Array.isArray(this.plugin.settings.customBannerField) ? this.plugin.settings.customBannerField[0].split(",")[0].trim() : this.plugin.settings.customBannerField;
const bannerIconImageField2 = Array.isArray(this.plugin.settings.customBannerIconImageField) ? this.plugin.settings.customBannerIconImageField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageField;
const bannerIconField2 = Array.isArray(this.plugin.settings.customBannerIconField) ? this.plugin.settings.customBannerIconField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconField;
delete frontmatter2[bannerField2];
delete frontmatter2[bannerIconImageField2];
delete frontmatter2[bannerIconField2];
}
});
if (flagRadioContainer) {
const flagRadios = flagRadioContainer.querySelectorAll('input[type="radio"]');
flagRadios.forEach((radio) => {
radio.checked = radio.value === this.plugin.settings.selectImageIconFlag;
});
}
if (bannerIconImageAlignmentRadioContainer) {
const bannerIconImageAlignmentRadios = bannerIconImageAlignmentRadioContainer.querySelectorAll('input[type="radio"]');
bannerIconImageAlignmentRadios.forEach((radio) => {
radio.checked = radio.value === this.plugin.settings.bannerIconImageAlignment;
});
}
if (inlineTitleEnabled && titleColorInput) {
let defaultTitleColor = this.plugin.settings.titleColor;
if (defaultTitleColor.startsWith("var(--")) {
console.log("defaultTitleColor", defaultTitleColor);
const tempEl = document.createElement("div");
tempEl.style.color = defaultTitleColor;
document.body.appendChild(tempEl);
const computedColor = window.getComputedStyle(tempEl).color;
document.body.removeChild(tempEl);
const rgbMatch = computedColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (rgbMatch) {
const [_, r, g, b] = rgbMatch;
defaultTitleColor = "#" + parseInt(r).toString(16).padStart(2, "0") + parseInt(g).toString(16).padStart(2, "0") + parseInt(b).toString(16).padStart(2, "0");
}
}
titleColorInput.value = defaultTitleColor;
if (titleColorPicker) {
titleColorPicker.value = defaultTitleColor.startsWith("#") ? defaultTitleColor : getCurrentTheme_default() === "dark" ? "#ffffff" : "#000000";
}
}
const titleColorField2 = Array.isArray(this.plugin.settings.customTitleColorField) ? this.plugin.settings.customTitleColorField[0].split(",")[0].trim() : this.plugin.settings.customTitleColorField;
if (activeFile2) {
this.app.fileManager.processFrontMatter(activeFile2, (fm) => {
delete fm[titleColorField2];
});
}
this.currentX = this.plugin.settings.xPosition;
this.currentY = this.plugin.settings.yPosition;
verticalLine.style.left = `${this.currentX}%`;
horizontalLine.style.top = `${this.currentY}%`;
updatePositionIndicator();
setTimeout(() => {
const view = this.app.workspace.getActiveViewOfType(import_obsidian17.MarkdownView);
if (view) {
this.plugin.updateBanner(view, true);
}
}, 750);
if (deleteBannerAndIcon) {
this.close();
}
};
resetButton.addEventListener("click", () => {
this.resetPixelBannerNoteSettings();
});
const repeatContainer = controlPanel.createDiv({
cls: "repeat-container",
attr: {
style: `
display: ${this.currentDisplay === "contain" || this.currentDisplay === "auto" ? "flex" : "none"};
flex-direction: column;
gap: 5px;
align-items: center;
justify-content: flex-start;
margin-top: 10px;
max-width: 70px;
text-align: center;
`
}
});
const repeatLabel = repeatContainer.createEl("div", {
text: "repeat banner image?",
cls: "repeat-label",
attr: {
style: `
color: var(--text-muted);
font-size: 0.9em;
margin-bottom: 20px;
`
}
});
const repeatToggle = repeatContainer.createEl("div", {
cls: "repeat-toggle",
attr: {
style: `
margin-top: 10px;
`
}
});
const toggleInput = repeatToggle.createEl("input", {
type: "checkbox",
cls: "repeat-checkbox",
attr: {
checked: this.currentDisplay === "contain" || this.currentDisplay === "auto" ? this.currentRepeat : this.plugin.settings.imageRepeat
}
});
displaySelect.addEventListener("change", () => {
const mode = displaySelect.value;
zoomContainer.style.display = mode === "cover-zoom" ? "flex" : "none";
repeatContainer.style.display = mode === "contain" || mode === "auto" ? "flex" : "none";
if (mode === "contain" || mode === "auto") {
toggleInput.checked = this.currentRepeat;
} else {
toggleInput.checked = this.plugin.settings.imageRepeat;
this.currentRepeat = this.plugin.settings.imageRepeat;
}
this.updateDisplayMode(mode, mode === "cover-zoom" ? this.currentZoom : null);
});
toggleInput.addEventListener("change", () => {
this.currentRepeat = toggleInput.checked;
this.updateRepeatMode(this.currentRepeat);
});
let isDragging = false;
let offsetX, offsetY;
let isCrosshairDragging = false;
modalEl.addEventListener("mousedown", (e) => {
if (e.target === zoomSlider || e.target === heightSlider || e.target === maxWidthSlider || e.target === contentStartPositionSlider || e.target === bannerFadeSlider || e.target === borderRadiusSlider || e.target === bannerIconXPositionSlider || e.target === bannerIconSizeSlider || e.target === bannerIconImageSizeMultiplierSlider || e.target === bannerIconTextVerticalOffsetSlider || e.target === bannerIconRotateSlider || e.target === bannerIconColorPicker || e.target === bannerIconColorInput || e.target === bannerIconPaddingXSlider || e.target === bannerIconPaddingYSlider || e.target === bannerIconBorderRadiusSlider || e.target === bannerIconVerticalOffsetSlider || e.target === alphaSlider || e.target === bannerIconBgColorPicker || e.target === bannerIconBgColorInput || e.target === titleColorPicker || e.target === titleColorInput || e.target === targetArea || e.target === verticalLine || e.target === horizontalLine) return;
isDragging = true;
offsetX = e.clientX - modalEl.getBoundingClientRect().left;
offsetY = e.clientY - modalEl.getBoundingClientRect().top;
modalEl.style.cursor = "grabbing";
});
document.addEventListener("mousemove", (e) => {
if (isDragging) {
modalEl.style.left = `${e.clientX - offsetX}px`;
modalEl.style.top = `${e.clientY - offsetY}px`;
}
if (isCrosshairDragging) {
updatePosition(e);
}
});
document.addEventListener("mouseup", () => {
isDragging = false;
isCrosshairDragging = false;
modalEl.style.cursor = "default";
targetArea.style.cursor = "crosshair";
});
targetArea.addEventListener("mousedown", (e) => {
isCrosshairDragging = true;
targetArea.style.cursor = "move";
updatePosition(e);
e.preventDefault();
});
verticalLine.addEventListener("mousedown", (e) => {
isCrosshairDragging = true;
targetArea.style.cursor = "move";
e.preventDefault();
});
horizontalLine.addEventListener("mousedown", (e) => {
isCrosshairDragging = true;
targetArea.style.cursor = "move";
e.preventDefault();
});
modalEl.style.position = "absolute";
modalEl.style.left = `${modalEl.getBoundingClientRect().left}px`;
modalEl.style.top = `${modalEl.getBoundingClientRect().top}px`;
}
addStyle() {
const style = document.createElement("style");
style.textContent = `
/* --------------------------- */
/* -- Target Position Modal -- */
/* --------------------------- */
.target-position-modal {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.target-position-modal .target-container {
display: flex;
flex-direction: column;
gap: 10px;
min-width: 200px;
}
.target-position-modal .target-area {
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
border-radius: 4px;
}
.target-position-modal .position-indicator {
padding: 4px;
border-radius: 4px;
background-color: var(--background-secondary);
}
.target-position-modal .crosshair-line {
position: absolute;
background-color: var(--text-accent);
pointer-events: none;
}
.target-position-modal .vertical {
width: 1px;
height: 100%;
}
.target-position-modal .horizontal {
width: 100%;
height: 1px;
}
.target-position-modal .control-panel {
background-color: var(--background-secondary);
padding: 15px;
border-radius: 8px;
}
.target-position-modal .display-mode-select {
width: 100%;
min-width: max-content;
padding: 6px;
border-radius: 4px;
border: 1px solid var(--background-modifier-border);
background-color: var(--background-primary);
color: var(--text-normal);
}
.target-position-modal .zoom-container {
position: relative;
}
.target-position-modal .zoom-slider {
width: 15px;
background-color: var(--background-primary);
border-radius: 5px;
cursor: pointer;
margin: 10px auto;
appearance: none;
}
.target-position-modal .zoom-slider::-webkit-slider-runnable-track {
width: 100%;
height: 200px;
background: var(--background-modifier-border);
border-radius: 5px;
border: none;
}
.target-position-modal .zoom-slider::-moz-range-track {
width: 100%;
height: 200px;
background: var(--background-modifier-border);
border-radius: 5px;
border: none;
}
.target-position-modal .zoom-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--text-accent);
cursor: pointer;
border: none;
margin-top: 90px;
position: relative;
left: -2px;
}
.target-position-modal .zoom-slider::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--text-accent);
cursor: pointer;
border: none;
}
.target-position-modal .zoom-value {
color: var(--text-muted);
text-align: center;
}
.target-position-modal .height-slider,
.target-position-modal .content-start-position-slider {
width: 15px;
background-color: var(--background-primary);
border-radius: 5px;
cursor: pointer;
margin: 10px auto;
appearance: none;
}
.target-position-modal .height-slider::-webkit-slider-runnable-track,
.target-position-modal .content-start-position-slider::-webkit-slider-runnable-track {
width: 100%;
height: 200px;
background: var(--background-modifier-border);
border-radius: 5px;
border: none;
}
.target-position-modal .height-slider::-moz-range-track,
.target-position-modal .content-start-position-slider::-moz-range-track {
width: 100%;
height: 200px;
background: var(--background-modifier-border);
border-radius: 5px;
border: none;
}
.target-position-modal .max-width-slider::-webkit-slider-runnable-track,
.target-position-modal .height-slider::-webkit-slider-runnable-track,
.target-position-modal .content-start-position-slider::-webkit-slider-runnable-track {
width: 100%;
height: 200px;
background: var(--background-modifier-border);
border-radius: 5px;
border: none;
}
.target-position-modal .max-width-slider::-moz-range-track,
.target-position-modal .height-slider::-moz-range-track,
.target-position-modal .content-start-position-slider::-moz-range-track {
width: 100%;
height: 200px;
background: var(--background-modifier-border);
border-radius: 5px;
border: none;
}
.target-position-modal .max-width-slider::-webkit-slider-thumb,
.target-position-modal .height-slider::-webkit-slider-thumb,
.target-position-modal .content-start-position-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--text-accent);
cursor: pointer;
border: none;
margin-top: 90px;
position: relative;
left: -2px;
}
.target-position-modal .max-width-slider:disabled {
opacity: 0;
cursor: not-allowed;
}
.target-position-modal .max-width-slider::-moz-range-thumb,
.target-position-modal .height-slider::-moz-range-thumb,
.target-position-modal .content-start-position-slider::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--text-accent);
cursor: pointer;
border: none;
}
.target-position-modal .height-value,
.target-position-modal .content-start-position-value {
color: var(--text-muted);
text-align: center;
}
.target-position-modal .max-width-container,
.target-position-modal .height-container,
.target-position-modal .content-start-position-container {
display: flex;
flex-direction: column;
gap: 5px;
align-items: center;
background-color: var(--background-secondary);
padding: 15px;
border-radius: 4px;
}
.target-position-modal .height-label,
.target-position-modal .content-start-position-label {
color: var(--text-muted);
font-size: 0.9em;
}
.target-position-modal .height-value,
.target-position-modal .content-start-position-value {
font-family: var(--font-monospace);
font-size: 0.9em;
color: var(--text-muted);
}
.target-position-modal .reset-button {
padding: 8px;
width: 100%;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s ease;
}
.target-position-modal .reset-button:hover {
background-color: var(--interactive-accent-hover);
}
.target-position-modal .target-area {
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.target-position-modal .vertical {
position: absolute;
background-color: var(--text-accent);
pointer-events: none;
width: 1px;
height: 100%;
left: ${this.currentX}%;
pointer-events: auto;
cursor: move;
}
.target-position-modal .horizontal {
position: absolute;
background-color: var(--text-accent);
pointer-events: none;
width: 100%;
height: 1px;
top: ${this.currentY}%;
pointer-events: auto;
cursor: move;
}
.target-position-modal .position-indicator {
text-align: center;
margin-top: 10px;
font-family: var(--font-monospace);
}
.target-position-modal .repeat-container {
min-height: 120px;
display: flex;
justify-content: center;
}
.target-position-modal .repeat-checkbox {
transform: scale(1.2);
cursor: pointer;
}
/* ------------------- */
/* -- mobile layout -- */
/* ------------------- */
@media screen and (max-width: 550px) {
.banner-image-header { flex-direction: column !important; }
.banner-icon-header { flex-direction: column !important; }
.main-container--banner-image { flex-direction: column !important; }
.main-container--banner-icon { flex-direction: column !important; }
.target-container { order: -1 !important; align-items: center !important; }
.height-slider,
.content-start-position-slider {
rotate: 90deg !important;
flex: 0 auto !important;
writing-mode: unset !important;
direction: unset !important;
}
.color-picker-and-alpha-slider-row { flex-wrap: wrap !important; }
}
`;
document.head.appendChild(style);
}
onClose() {
const style = document.head.querySelector("style:last-child");
if (style) {
style.remove();
}
}
};
}
});
// src/modal/modals/iconImageSelectionModal.js
var import_obsidian18, IconImageSelectionModal;
var init_iconImageSelectionModal = __esm({
"src/modal/modals/iconImageSelectionModal.js"() {
import_obsidian18 = require("obsidian");
init_iconFolderSelectionModal();
init_saveImageModal();
init_modals();
init_constants();
IconImageSelectionModal = class extends import_obsidian18.Modal {
constructor(app, plugin, onChoose, defaultPath = "") {
super(app);
this.plugin = plugin;
this.onChoose = onChoose;
this.defaultPath = defaultPath || "";
this.searchQuery = defaultPath && typeof defaultPath === "string" ? defaultPath.toLowerCase() : "";
this.currentPage = 1;
this.imagesPerPage = 20;
this.sortOrder = "name-asc";
this.imageFiles = this.app.vault.getFiles().filter((file) => file && file.extension && file.extension.toLowerCase && file.extension.toLowerCase().match(/^(jpg|jpeg|png|gif|bmp|svg|webp|avif)$/));
this.iconCategories = [];
this.selectedIconCategory = null;
this.selectedIconCategoryIndex = 0;
this.iconsCurrentPage = 1;
this.iconsTotalPages = 1;
this.iconsSearchTerm = "";
this.isIconsSearchMode = false;
this.iconsPerPage = 10;
this._isPaginating = false;
this.targetingModalOpened = false;
const originalOnChoose = this.onChoose;
this.onChoose = (file) => {
this.targetingModalOpened = true;
originalOnChoose(file);
};
}
debounce(func, wait) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
async confirmDelete(file) {
return new Promise((resolve) => {
const modal = new import_obsidian18.Modal(this.app);
modal.contentEl.createEl("h2", { text: "Delete Image" });
modal.contentEl.createEl("p", { text: `Are you sure you want to delete "${file.name}"?` });
const buttonContainer = modal.contentEl.createDiv();
buttonContainer.style.display = "flex";
buttonContainer.style.justifyContent = "flex-end";
buttonContainer.style.gap = "10px";
const cancelButton = buttonContainer.createEl("button", { text: "Cancel" });
const deleteButton = buttonContainer.createEl("button", {
text: "Delete",
cls: "mod-warning"
});
cancelButton.onclick = () => {
modal.close();
resolve(false);
};
deleteButton.onclick = () => {
modal.close();
resolve(true);
};
modal.open();
});
}
async deleteImage(file) {
const confirmed = await this.confirmDelete(file);
if (!confirmed) return;
try {
await this.app.vault.delete(file);
this.imageFiles = this.imageFiles.filter((f) => f.path !== file.path);
this.updateImageGrid();
} catch (error) {
new import_obsidian18.Notice(`Failed to delete image: ${error.message}`);
}
}
onOpen() {
this.modalEl.addClass("pixel-banner-image-select-modal");
const { contentEl } = this;
contentEl.empty();
const style = document.createElement("style");
style.textContent = `
.pixel-banner-image-modal {
width: var(--dialog-max-width);
top: unset !important;
}
.pixel-banner-image-select-modal {
top: unset !important;
width: var(--dialog-max-width);
max-width: 1100px;
min-height: 50vh;
max-height: 95vh;
}
.pixel-banner-image-select-modal .pixel-banner-image-delete {
position: absolute;
top: 8px;
right: 8px;
width: 24px;
height: 24px;
background-color: var(--background-secondary);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
opacity: .5;
transition: opacity 0.2s ease, background-color 0.2s ease;
cursor: pointer;
z-index: 2;
}
.pixel-banner-image-select-modal .pixel-banner-image-wrapper:hover .pixel-banner-image-delete {
opacity: 1;
}
.pixel-banner-image-select-modal .pixel-banner-image-delete:hover {
background-color: red;
color: white;
opacity: 1;
}
.pixel-banner-image-select-modal .pixel-banner-image-delete svg {
width: 16px;
height: 16px;
}
.pixel-banner-image-select-description {
margin-top: -15px;
font-size: 0.8em;
word-break: break-all;
color: var(--text-muted);
margin-bottom: 15px;
}
.pixel-banner-search-container {
margin-bottom: 1rem;
}
.pixel-banner-search-container input {
width: 100%;
padding: 8px;
border-radius: 4px;
border: 1px solid var(--background-modifier-border);
}
.pixel-banner-search-container .search-row {
flex: 1;
display: flex;
gap: 8px;
margin: 0;
}
.pixel-banner-search-container .controls-row {
flex: 0 auto;
display: flex;
gap: 8px;
margin: 0;
}
.pixel-banner-image-path {
margin-top: 8px;
font-size: 0.8em;
word-break: break-all;
color: var(--text-muted);
}
.pixel-banner-image-error {
height: 150px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--background-modifier-error);
color: var(--text-error);
border-radius: 2px;
}
.pixel-banner-image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
padding: 0 1rem;
overflow-y: auto;
max-height: 60vh;
}
.pixel-banner-pagination-button {
padding: 4px 8px;
border-radius: 4px;
background: var(--background-secondary);
border: 1px solid var(--background-modifier-border);
cursor: pointer;
font-size: 14px;
line-height: 1;
}
button.pixel-banner-pagination-button:not([disabled]),
.pixel-banner-pagination-button:hover:not(.disabled) {
background-color: var(--interactive-accent);
color: var(--text-on-accent);
}
.pixel-banner-pagination-button.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pixel-banner-pagination-info {
font-size: 14px;
color: var(--text-muted);
}
.pixel-banner-image-container {
cursor: pointer;
border-radius: 6px;
border: 1px solid var(--background-modifier-border);
transition: transform 0.2s ease, box-shadow 0.2s ease;
position: relative;
}
.pixel-banner-image-container:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.pixel-banner-image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
margin-top: 15px;
max-height: 60vh;
overflow-y: auto;
padding: 5px;
}
.pixel-banner-no-images {
width: 100%;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2em;
color: var(--text-muted);
border: 1px dashed var(--background-modifier-border);
border-radius: 8px;
background-color: var(--background-secondary);
grid-column: 1 / -1;
text-align: center;
padding: 20px;
}
.pixel-banner-image-thumbnail {
width: 100%;
max-width: fit-content;
height: auto;
max-height: 150px;
object-fit: cover;
display: block;
}
.pixel-banner-image-info {
padding: 8px;
font-size: 12px;
background: var(--background-secondary);
}
.pixel-banner-image-path {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 4px;
}
.pixel-banner-image-delete {
position: absolute;
top: 5px;
right: 5px;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s ease;
}
.pixel-banner-image-container:hover .pixel-banner-image-delete {
opacity: 1;
}
.pixel-banner-image-delete svg {
width: 16px;
height: 16px;
color: white;
}
/* ------------------- */
/* -- mobile layout -- */
/* ------------------- */
@media screen and (max-width: 550px) {
.pixel-banner-pagination { flex-direction: column !important; }
.pixel-banner-pagination .pixel-banner-controls { flex-direction: column !important; }
}
@media screen and (max-width: 775px) {
.pixel-banner-search-container {
flex-direction: column !important;
gap: 8px !important;
}
.pixel-banner-search-container .search-row {
display: flex;
width: 100%;
gap: 8px;
}
.pixel-banner-search-container .controls-row {
display: flex;
width: 100%;
gap: 8px;
align-items: center;
}
.pixel-banner-search-container input[type="text"] {
flex: 1;
}
}
`;
document.head.appendChild(style);
this.style = style;
contentEl.createEl("h2", { text: "\u2B50 Select Banner Icon Image", cls: "margin-top-0" });
const titleDescriptionRow = contentEl.createEl("div", {
text: "Select an image to use as a banner icon.",
cls: "pixel-banner-image-select-description",
attr: {
style: `
display: flex;
align-items: center;
justify-content: space-between;
`
}
});
const removeIconImageButton = titleDescriptionRow.createEl("button", {
text: "\u{1F5D1}\uFE0F Remove",
cls: "remove-icon-image-button cursor-pointer"
});
removeIconImageButton.addEventListener("click", async () => {
const activeFile = this.app.workspace.getActiveFile();
if (activeFile) {
await this.app.fileManager.processFrontMatter(activeFile, (frontmatter) => {
const bannerIconField = Array.isArray(this.plugin.settings.customBannerIconField) ? this.plugin.settings.customBannerIconField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconField;
const bannerIconImageAlignmentField = Array.isArray(this.plugin.settings.customBannerIconImageAlignmentField) ? this.plugin.settings.customBannerIconImageAlignmentField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageAlignmentField;
const iconSizeField = Array.isArray(this.plugin.settings.customBannerIconSizeField) ? this.plugin.settings.customBannerIconSizeField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconSizeField;
const iconImageSizeMultiplierField = Array.isArray(this.plugin.settings.customBannerIconImageSizeMultiplierField) ? this.plugin.settings.customBannerIconImageSizeMultiplierField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageSizeMultiplierField;
const iconRotateField = Array.isArray(this.plugin.settings.customBannerIconRotateField) ? this.plugin.settings.customBannerIconRotateField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconRotateField;
const iconYPositionField = Array.isArray(this.plugin.settings.customBannerIconVerticalOffsetField) ? this.plugin.settings.customBannerIconVerticalOffsetField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconVerticalOffsetField;
const iconXPositionField = Array.isArray(this.plugin.settings.customBannerIconXPositionField) ? this.plugin.settings.customBannerIconXPositionField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconXPositionField;
const iconColorField = Array.isArray(this.plugin.settings.customBannerIconColorField) ? this.plugin.settings.customBannerIconColorField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconColorField;
const iconBgColorField = Array.isArray(this.plugin.settings.customBannerIconBackgroundColorField) ? this.plugin.settings.customBannerIconBackgroundColorField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconBackgroundColorField;
const iconXPaddingField = Array.isArray(this.plugin.settings.customBannerIconPaddingXField) ? this.plugin.settings.customBannerIconPaddingXField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconPaddingXField;
const iconYPaddingField = Array.isArray(this.plugin.settings.customBannerIconPaddingYField) ? this.plugin.settings.customBannerIconPaddingYField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconPaddingYField;
const iconBorderRadiusField = Array.isArray(this.plugin.settings.customBannerIconBorderRadiusField) ? this.plugin.settings.customBannerIconBorderRadiusField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconBorderRadiusField;
const bannerIconImageField = Array.isArray(this.plugin.settings.customBannerIconImageField) ? this.plugin.settings.customBannerIconImageField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageField;
const hasTextEmoji = frontmatter[bannerIconField] !== void 0 && frontmatter[bannerIconField] !== null && frontmatter[bannerIconField] !== "";
if (!hasTextEmoji) {
delete frontmatter[bannerIconField];
delete frontmatter[bannerIconImageField];
delete frontmatter[bannerIconImageAlignmentField];
delete frontmatter[iconSizeField];
delete frontmatter[iconImageSizeMultiplierField];
delete frontmatter[iconRotateField];
delete frontmatter[iconYPositionField];
delete frontmatter[iconXPositionField];
delete frontmatter[iconColorField];
delete frontmatter[iconBgColorField];
delete frontmatter[iconXPaddingField];
delete frontmatter[iconYPaddingField];
delete frontmatter[iconBorderRadiusField];
} else {
delete frontmatter[bannerIconImageField];
delete frontmatter[iconImageSizeMultiplierField];
delete frontmatter[bannerIconImageAlignmentField];
}
});
this.onChoose(null);
this.close();
const activeView = this.app.workspace.getActiveViewOfType(import_obsidian18.MarkdownView);
if (activeView) {
const contentEl2 = activeView.contentEl;
if (contentEl2) {
const existingOverlays = contentEl2.querySelectorAll(".banner-icon-overlay");
existingOverlays.forEach((overlay) => {
this.plugin.returnIconOverlay(overlay);
});
}
await this.plugin.updateBanner(activeView, true, this.plugin.UPDATE_MODE.FULL_UPDATE);
}
if (!this.skipTargetingModal && this.plugin.settings.openTargetingModalAfterSelectingBannerOrIcon) {
await new Promise((resolve) => {
var _a;
const initialFrontmatter = JSON.stringify(
((_a = this.app.metadataCache.getFileCache(activeFile)) == null ? void 0 : _a.frontmatter) || {}
);
const eventRef = this.app.metadataCache.on("changed", (file) => {
var _a2;
if (file.path !== activeFile.path) return;
const updatedFrontmatter = JSON.stringify(
((_a2 = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a2.frontmatter) || {}
);
if (updatedFrontmatter !== initialFrontmatter) {
this.app.metadataCache.off("changed", eventRef);
resolve();
}
});
setTimeout(() => {
this.app.metadataCache.off("changed", eventRef);
resolve();
}, 500);
});
new TargetPositionModal(this.app, this.plugin).open();
}
}
});
const tabContainer = contentEl.createDiv({
cls: "pixel-banner-tabs-container",
attr: {
style: `
display: flex;
margin-bottom: 16px;
border-bottom: 1px solid var(--background-modifier-border);
`
}
});
const localImageTab = tabContainer.createDiv({
cls: "pixel-banner-tab active",
text: "\u{1F4BE} Local Image",
attr: {
style: `
padding: 8px 16px;
cursor: pointer;
border-bottom: 2px solid transparent;
margin-right: 8px;
transition: all 0.2s ease;
`
}
});
const webTab = tabContainer.createDiv({
cls: "pixel-banner-tab",
text: "\u{1F310} WEB",
attr: {
style: `
padding: 8px 16px;
cursor: pointer;
border-bottom: 2px solid transparent;
margin-right: 8px;
transition: all 0.2s ease;
`
}
});
this.collectionsTab = tabContainer.createDiv({
cls: "pixel-banner-tab",
text: "\u{1F4D1} Collections",
attr: {
style: `
padding: 8px 16px;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.2s ease;
display: none; /* Initially hidden */
`
}
});
const localImageContent = contentEl.createDiv({
cls: "pixel-banner-tab-content local-image-content",
attr: {
style: `
display: block;
`
}
});
const webContent = contentEl.createDiv({
cls: "pixel-banner-tab-content web-content",
attr: {
style: `
display: none;
`
}
});
this.collectionsContent = contentEl.createDiv({
cls: "pixel-banner-tab-content collections-content",
attr: {
style: `
display: none;
`
}
});
const tabStyles = document.createElement("style");
tabStyles.textContent = `
.pixel-banner-tab.active {
border-bottom: 2px solid var(--interactive-accent) !important;
font-weight: bold;
}
`;
document.head.appendChild(tabStyles);
this.tabStyles = tabStyles;
const switchTab = (targetTab) => {
localImageTab.classList.remove("active");
webTab.classList.remove("active");
if (this.collectionsTab) {
this.collectionsTab.classList.remove("active");
}
targetTab.classList.add("active");
if (targetTab === localImageTab) {
localImageContent.style.display = "block";
webContent.style.display = "none";
this.collectionsContent.style.display = "none";
} else if (targetTab === webTab) {
localImageContent.style.display = "none";
webContent.style.display = "block";
this.collectionsContent.style.display = "none";
} else {
localImageContent.style.display = "none";
webContent.style.display = "none";
this.collectionsContent.style.display = "block";
}
};
localImageTab.addEventListener("click", () => switchTab(localImageTab));
webTab.addEventListener("click", () => switchTab(webTab));
this.collectionsTab.addEventListener("click", () => switchTab(this.collectionsTab));
this.checkServerStatus();
const searchContainer = localImageContent.createDiv({ cls: "pixel-banner-search-container" });
searchContainer.style.display = "flex";
searchContainer.style.gap = "8px";
searchContainer.style.alignItems = "center";
searchContainer.style.marginBottom = "1em";
this.initializeCollectionsTab(this.collectionsContent);
const searchRow = searchContainer.createDiv({ cls: "search-row" });
const searchInput = searchRow.createEl("input", {
type: "text",
placeholder: "Search images...",
value: this.defaultPath
});
searchInput.style.flex = "1";
const clearButton = searchRow.createEl("button", {
text: "Clear"
});
const controlsRow = searchContainer.createDiv({
cls: "controls-row",
attr: {
style: `
display: flex !important;
gap: 8px;
align-items: center;
`
}
});
const uploadButton = controlsRow.createEl("button", {
text: "\u{1F4E4} Upload"
});
uploadButton.addEventListener("click", () => {
fileInput.click();
});
const shorPathToggleContainer = controlsRow.createDiv({
cls: "pixel-banner-path-toggle",
attr: {
style: `
display: flex !important;
align-items: center;
gap: 8px;
cursor: pointer;
`
}
});
const shorPathToggleLabel = shorPathToggleContainer.createSpan({
text: "Use short path",
attr: {
style: `
font-size: 12px;
color: var(--text-muted);
`
}
});
const shorPathToggle = new import_obsidian18.Setting(shorPathToggleContainer).addToggle((cb) => {
cb.setValue(this.plugin.settings.useShortPath).onChange(async (value) => {
this.plugin.settings.useShortPath = value;
await this.plugin.saveSettings();
});
});
shorPathToggle.settingEl.style.border = "none";
shorPathToggle.settingEl.style.padding = "0";
shorPathToggle.settingEl.style.margin = "0";
shorPathToggle.infoEl.remove();
const fileInput = searchContainer.createEl("input", {
type: "file",
attr: {
accept: "image/*",
style: "display: none;"
}
});
fileInput.addEventListener("change", async (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = async () => {
const arrayBuffer = reader.result;
const defaultFolder = this.plugin.settings.defaultSelectIconPath || "";
const folderPath = await new Promise((resolve) => {
new IconFolderSelectionModal(this.app, defaultFolder, (result) => {
resolve(result);
}).open();
});
if (!folderPath) {
new import_obsidian18.Notice("No folder selected");
return;
}
if (!await this.app.vault.adapter.exists(folderPath)) {
await this.app.vault.createFolder(folderPath);
}
const suggestedName = file.name;
const fileName = await new Promise((resolve) => {
new SaveImageModal(this.app, suggestedName, (result) => {
resolve(result);
}).open();
});
if (!fileName) {
new import_obsidian18.Notice("No file name provided");
return;
}
try {
const fullPath = `${folderPath}/${fileName}`.replace(/\/+/g, "/");
const newFile = await this.app.vault.createBinary(fullPath, arrayBuffer);
this.onChoose(newFile);
this.close();
} catch (error) {
new import_obsidian18.Notice(`Failed to save image: ${error.message}`);
}
};
reader.readAsArrayBuffer(file);
}
});
clearButton.addEventListener("click", () => {
searchInput.value = "";
this.searchQuery = "";
this.updateImageGrid();
});
searchInput.addEventListener("input", this.debounce(() => {
this.searchQuery = searchInput && searchInput.value && typeof searchInput.value === "string" ? searchInput.value.toLowerCase() : "";
this.updateImageGrid();
}, 500));
const webUrlInputContainer = webContent.createDiv({
cls: "web-url-input-container",
attr: {
style: `
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
margin-bottom: 16px;
`
}
});
const urlInput = webUrlInputContainer.createEl("input", {
type: "text",
placeholder: "Enter image URL...",
cls: "web-url-input",
attr: {
style: `
flex: 1;
padding: 8px;
border-radius: 4px;
border: 1px solid var(--background-modifier-border);
`
}
});
const useUrlButton = webUrlInputContainer.createEl("button", {
text: "Use URL",
cls: "use-url-button",
attr: {
style: `
background-color: var(--interactive-accent);
color: var(--text-on-accent);
`
}
});
useUrlButton.addEventListener("click", () => {
const url = urlInput.value.trim();
if (validateUrl(url)) {
this.onChoose({
path: url,
name: url.split("/").pop() || "image",
extension: url.split(".").pop() || "jpg",
isWebUrl: true
});
this.close();
} else {
new import_obsidian18.Notice("Please enter a valid image URL");
}
});
const validateUrl = (url) => {
try {
new URL(url);
const imageExtensions = ["jpg", "jpeg", "png", "gif", "svg", "webp", "avif", "bmp"];
const extension = url && url.split && url.split(".").pop() ? url.split(".").pop().toLowerCase() : "";
return imageExtensions.includes(extension);
} catch (e) {
return false;
}
};
webContent.createEl("div", {
text: "Enter a direct URL to an image file (jpg, png, gif, etc.).",
attr: {
style: `
font-size: 12px;
color: var(--text-muted);
margin-bottom: 16px;
`
}
});
const previewContainer = webContent.createDiv({
cls: "web-image-preview-container",
attr: {
style: `
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 16px;
padding: 16px;
border: 1px dashed var(--background-modifier-border);
border-radius: 4px;
`
}
});
const previewImage = previewContainer.createEl("img", {
cls: "web-image-preview",
attr: {
style: `
max-width: 100%;
max-height: 200px;
object-fit: contain;
`
}
});
const previewCaption = previewContainer.createEl("div", {
cls: "web-image-preview-caption",
attr: {
style: `
margin-top: 8px;
font-size: 12px;
color: var(--text-muted);
`
}
});
urlInput.addEventListener("input", this.debounce(() => {
const url = urlInput.value.trim();
if (validateUrl(url)) {
previewImage.src = url;
previewCaption.textContent = url.split("/").pop();
previewContainer.style.display = "flex";
previewImage.onerror = () => {
previewContainer.style.display = "none";
new import_obsidian18.Notice("Failed to load image preview");
};
} else {
previewContainer.style.display = "none";
}
}, 500));
this.gridContainer = localImageContent.createDiv({ cls: "pixel-banner-image-grid" });
this.paginationContainer = localImageContent.createDiv({
cls: "pixel-banner-pagination",
attr: {
style: `
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
flex-wrap: wrap;
margin-top: 15px;
`
}
});
this.updateImageGrid();
const modalEl = this.modalEl;
modalEl.style.position = "absolute";
modalEl.style.left = `${modalEl.getBoundingClientRect().left}px`;
modalEl.style.top = `${modalEl.getBoundingClientRect().top}px`;
}
async checkServerStatus() {
try {
const pingUrl = `${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.PING}`;
const response = await fetch(pingUrl);
const data = await response.json();
if (response.ok && data.response === "pong") {
this.collectionsTab.style.display = "block";
} else {
console.log("Server response did not match expected format", data);
}
} catch (error) {
console.error(`Failed to connect to server: ${error.message}`);
}
}
async fetchIconCategories() {
try {
const url = `${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.BANNER_ICON_CATEGORIES}?key=${PIXEL_BANNER_PLUS.BANNER_ICON_KEY}`;
const headers = {
"Accept": "application/json"
};
const response = await fetch(url, {
headers
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (!data.success) {
throw new Error(data.error || "Failed to fetch icon categories");
}
this.iconCategories = data.categories || [];
if (this.iconCategories.length === 0) {
throw new Error("No icon categories found");
}
return this.iconCategories;
} catch (error) {
console.error("Error fetching icon categories:", error);
throw error;
}
}
async fetchIconsByCategory() {
this.showCollectionsLoading();
try {
let url = `${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.BANNER_ICONS}?page=${this.iconsCurrentPage}&limit=${this.iconsPerPage}&key=${PIXEL_BANNER_PLUS.BANNER_ICON_KEY}`;
if (this.selectedIconCategory && this.selectedIconCategory !== "all") {
url += `&category_id=${this.selectedIconCategory}`;
}
const headers = {
"Accept": "application/json"
};
const response = await fetch(url, {
headers
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (!data.success) {
throw new Error(data.error || "Failed to fetch icons");
}
this.iconsCurrentPage = data.currentPage || this.iconsCurrentPage;
this.renderIcons(data.bannerIcons, data.totalPages, data.totalCount);
} catch (error) {
console.error("Error fetching icons by category:", error);
this.showCollectionsError(error.message);
} finally {
this.hideCollectionsLoading();
}
}
initializeCollectionsTab(contentEl) {
const loadingContainer = contentEl.createDiv({
cls: "pixel-banner-loading-container",
attr: {
style: `
display: flex;
justify-content: center;
align-items: center;
height: 200px;
`
}
});
const loadingSpinner = loadingContainer.createDiv({
cls: "pixel-banner-loading-spinner",
attr: {
style: `
width: 40px;
height: 40px;
border: 4px solid var(--background-modifier-border);
border-top: 4px solid var(--text-accent);
border-radius: 50%;
animation: pixel-banner-spin 1s linear infinite;
`
}
});
const spinnerStyle = document.createElement("style");
spinnerStyle.textContent = `
@keyframes pixel-banner-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`;
document.head.appendChild(spinnerStyle);
this.spinnerStyle = spinnerStyle;
this.fetchIconCategories().then(() => {
loadingContainer.remove();
const categoriesContainer = contentEl.createDiv({
cls: "pixel-banner-categories-container",
attr: {
style: `
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
`
}
});
categoriesContainer.createSpan({
text: "Icon Categories:",
attr: {
style: `
font-size: 12px;
color: var(--text-muted);
`
}
});
this.categoriesDropdown = categoriesContainer.createEl("select", {
cls: "pixel-banner-categories-dropdown",
attr: {
style: `
border-radius: 4px;
border: 1px solid var(--background-modifier-border);
flex-grow: 1;
`
}
});
const allIconsOption = this.categoriesDropdown.createEl("option", {
text: "\u2B50 ALL ICONS",
value: "all"
});
allIconsOption.selected = true;
this.iconCategories.forEach((category, index) => {
this.categoriesDropdown.createEl("option", {
text: category.category,
value: category.id
});
});
this.categoriesDropdown.addEventListener("change", async () => {
this.selectedIconCategory = this.categoriesDropdown.value;
this.selectedIconCategoryIndex = this.categoriesDropdown.selectedIndex;
this.iconsCurrentPage = 1;
this.iconSearchInput.value = "";
this.isIconsSearchMode = false;
await this.fetchIconsByCategory();
});
const nextCategoryButton = categoriesContainer.createEl("button", {
text: "\u25B6\uFE0F Next Category",
cls: "pixel-banner-next-category-button",
attr: {
style: `
padding: 8px 10px;
border-radius: 4px;
background-color: var(--interactive-accent);
color: var(--text-on-accent);
cursor: pointer;
transition: background-color 0.2s ease;
`
}
});
nextCategoryButton.addEventListener("click", async () => {
const optionsCount = this.categoriesDropdown.options.length;
let nextIndex = this.selectedIconCategoryIndex + 1;
if (nextIndex >= optionsCount) {
nextIndex = 0;
}
this.categoriesDropdown.selectedIndex = nextIndex;
this.selectedIconCategory = this.categoriesDropdown.value;
this.selectedIconCategoryIndex = nextIndex;
this.iconSearchInput.value = "";
this.isIconsSearchMode = false;
this.iconsCurrentPage = 1;
await this.fetchIconsByCategory();
});
const searchContainer = contentEl.createDiv({
cls: "pixel-banner-search-container",
attr: {
style: `
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
`
}
});
this.iconSearchInput = searchContainer.createEl("input", {
type: "text",
placeholder: "Search icons...",
cls: "pixel-banner-search-input",
attr: {
style: `
padding: 8px;
border-radius: 4px;
border: 1px solid var(--background-modifier-border);
flex-grow: 1;
`
}
});
const searchButton = searchContainer.createEl("button", {
text: "Search",
cls: "pixel-banner-search-button",
attr: {
style: `
padding: 8px 16px;
border-radius: 4px;
background-color: var(--interactive-accent);
color: var(--text-on-accent);
`
}
});
searchButton.addEventListener("click", () => {
this.searchIcons();
});
this.iconSearchInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
this.searchIcons();
}
});
const clearSearchButton = searchContainer.createEl("button", {
text: "Clear",
cls: "pixel-banner-clear-search-button"
});
clearSearchButton.addEventListener("click", () => {
this.iconSearchInput.value = "";
this.isIconsSearchMode = false;
if (this.selectedIconCategory) {
this.fetchIconsByCategory();
}
});
this.collectionsGridContainer = contentEl.createDiv({
cls: "pixel-banner-collections-grid",
attr: {
style: `
position: relative;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 12px;
margin-top: 12px;
max-height: 400px;
overflow-y: auto;
padding: 8px;
min-height: 200px;
`
}
});
this.collectionsPaginationContainer = contentEl.createDiv({
cls: "pixel-banner-collections-pagination",
attr: {
style: `
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 12px;
padding-top: 8px;
border-top: 1px solid var(--background-modifier-border);
`
}
});
this.selectedIconCategory = "all";
this.selectedIconCategoryIndex = 0;
this.fetchIconsByCategory();
}).catch((err) => {
loadingContainer.remove();
contentEl.createEl("div", {
cls: "pixel-banner-error",
text: `\u{1F62D} Failed to load icon categories. Please try again later.`,
attr: {
style: `
color: var(--text-error);
padding: 16px;
border: 1px solid var(--background-modifier-error);
border-radius: 4px;
background-color: var(--background-modifier-error-rgb);
margin-bottom: 16px;
`
}
});
});
}
async onChooseIcon(icon) {
try {
const url = `${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.BANNER_ICONS_ID.replace(":id", icon.id)}?key=${PIXEL_BANNER_PLUS.BANNER_ICON_KEY}`;
const response = await fetch(url, {
headers: {
"x-user-email": this.plugin.settings.pixelBannerPlusEmail,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json"
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (!data.success) {
throw new Error(data.error || "Failed to fetch icon details");
}
const iconData = data.bannerIcon;
let extension = "png";
if (iconData.base64Image) {
const mimeTypeMatch = iconData.base64Image.match(/data:image\/([\w\+\-\.]+);base64/);
if (mimeTypeMatch && mimeTypeMatch[1]) {
extension = mimeTypeMatch[1];
if (extension === "jpeg") extension = "jpg";
if (extension === "svg+xml") extension = "svg";
}
} else if (iconData.file_name) {
const parts = iconData.file_name.split(".");
if (parts.length > 1) {
extension = parts && parts.length > 0 && parts[parts.length - 1] ? parts[parts.length - 1].toLowerCase() : "";
}
}
if (iconData.base64Image) {
const defaultFolder = this.plugin.settings.defaultSelectIconPath || "";
const folderPath = await new Promise((resolve) => {
new IconFolderSelectionModal(this.app, defaultFolder, (result) => {
resolve(result);
}).open();
});
if (!folderPath) {
new import_obsidian18.Notice("No folder selected");
return;
}
if (!await this.app.vault.adapter.exists(folderPath)) {
await this.app.vault.createFolder(folderPath);
}
const suggestedName = `${iconData.description || "icon"}`;
const userFileName = await new Promise((resolve) => {
new SaveImageModal(this.app, suggestedName, (result) => {
resolve(result);
}).open();
});
if (!userFileName) {
new import_obsidian18.Notice("No file name provided");
return;
}
let baseName = userFileName;
if (baseName.includes(".")) {
baseName = baseName.substring(0, baseName.lastIndexOf("."));
}
const finalFileName = `${baseName}.${extension}`;
const base64Parts = iconData.base64Image.split(",");
const base64Data = base64Parts[1];
const binaryString = window.atob(base64Data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
try {
const fullPath = `${folderPath}/${finalFileName}`.replace(/\/+/g, "/");
const newFile = await this.app.vault.createBinary(fullPath, bytes.buffer);
this.onChoose(newFile);
this.close();
} catch (error) {
new import_obsidian18.Notice(`Failed to save image: ${error.message}`);
}
}
} catch (error) {
console.error("Error selecting icon:", error);
new import_obsidian18.Notice(`Failed to select icon: ${error.message}`);
}
}
showCollectionsLoading() {
if (this.collectionsGridContainer) {
const loadingContainer = this.collectionsGridContainer.createDiv({
cls: "pixel-banner-loading-container",
attr: {
style: `
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--background-secondary);
opacity: 0.7;
`
}
});
loadingContainer.createDiv({
cls: "pixel-banner-loading-spinner",
attr: {
style: `
width: 40px;
height: 40px;
border: 4px solid var(--background-modifier-border);
border-top: 4px solid var(--text-accent);
border-radius: 50%;
animation: pixel-banner-spin 1s linear infinite;
`
}
});
}
}
hideCollectionsLoading() {
}
showCollectionsError(message) {
if (this.collectionsGridContainer) {
this.collectionsGridContainer.empty();
this.collectionsGridContainer.createEl("div", {
cls: "pixel-banner-error",
text: `Error: ${message}`,
attr: {
style: `
color: var(--text-error);
padding: 16px;
border: 1px solid var(--background-modifier-error);
border-radius: 4px;
background-color: var(--background-modifier-error-rgb);
margin-bottom: 16px;
grid-column: 1 / -1;
`
}
});
}
}
renderIcons(icons, totalPages, totalCount) {
this.collectionsGridContainer.empty();
this.collectionsPaginationContainer.empty();
this.iconsTotalPages = totalPages || 1;
if (!icons || icons.length === 0) {
this.collectionsGridContainer.createEl("div", {
cls: "pixel-banner-no-icons",
text: this.isIconsSearchMode ? "No icons found matching your search." : "No icons found in this category.",
attr: {
style: `
width: 100%;
padding: 32px;
text-align: center;
color: var(--text-muted);
background-color: var(--background-secondary);
border-radius: 4px;
grid-column: 1 / -1;
`
}
});
return;
}
icons.forEach((icon) => {
const iconContainer = this.collectionsGridContainer.createDiv({
cls: "pixel-banner-icon-item",
attr: {
style: `
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
overflow: hidden;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
`
}
});
iconContainer.addEventListener("mouseenter", () => {
iconContainer.style.transform = "translateY(-2px)";
iconContainer.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.1)";
});
iconContainer.addEventListener("mouseleave", () => {
iconContainer.style.transform = "none";
iconContainer.style.boxShadow = "none";
});
const imageContainer = iconContainer.createDiv({
attr: {
style: `
height: 120px;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--background-secondary);
padding: 8px;
`
}
});
const image = imageContainer.createEl("img", {
attr: {
src: icon.base64Image,
alt: icon.description,
style: `
max-width: 100%;
max-height: 100%;
object-fit: contain;
`
}
});
const infoContainer = iconContainer.createDiv({
attr: {
style: `
padding: 8px;
background-color: var(--background-primary);
`
}
});
infoContainer.createEl("div", {
text: icon.description,
attr: {
style: `
font-size: 12px;
font-weight: 500;
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`
}
});
infoContainer.createEl("div", {
text: icon.category,
attr: {
style: `
font-size: 10px;
color: var(--text-muted);
`
}
});
iconContainer.addEventListener("click", () => {
this.onChooseIcon(icon);
});
});
if (totalPages > 1) {
const paginationInfo = this.collectionsPaginationContainer.createEl("div", {
text: `Page ${this.iconsCurrentPage} of ${totalPages} (${totalCount} icons)`,
attr: {
style: `
font-size: 12px;
color: var(--text-muted);
`
}
});
const paginationControls = this.collectionsPaginationContainer.createDiv({
attr: {
style: `
display: flex;
gap: 4px;
`
}
});
const firstPageButton = paginationControls.createEl("button", {
text: "\xAB",
cls: "pixel-banner-pagination-button"
});
if (this.iconsCurrentPage === 1) {
firstPageButton.disabled = true;
}
firstPageButton.addEventListener("click", () => {
if (this.iconsCurrentPage !== 1) {
this.iconsCurrentPage = 1;
this._isPaginating = true;
this.isIconsSearchMode ? this.searchIcons() : this.fetchIconsByCategory();
}
});
const prevPageButton = paginationControls.createEl("button", {
text: "\u2039",
cls: "pixel-banner-pagination-button"
});
if (this.iconsCurrentPage === 1) {
prevPageButton.disabled = true;
}
prevPageButton.addEventListener("click", () => {
if (this.iconsCurrentPage > 1) {
this.iconsCurrentPage--;
this._isPaginating = true;
this.isIconsSearchMode ? this.searchIcons() : this.fetchIconsByCategory();
}
});
const pageNumber = paginationControls.createEl("span", {
text: this.iconsCurrentPage.toString(),
attr: {
style: `
padding: 4px 8px;
`
}
});
const nextPageButton = paginationControls.createEl("button", {
text: "\u203A",
cls: "pixel-banner-pagination-button"
});
if (this.iconsCurrentPage === totalPages) {
nextPageButton.disabled = true;
}
nextPageButton.addEventListener("click", () => {
if (this.iconsCurrentPage < totalPages) {
this.iconsCurrentPage++;
this._isPaginating = true;
this.isIconsSearchMode ? this.searchIcons() : this.fetchIconsByCategory();
}
});
const lastPageButton = paginationControls.createEl("button", {
text: "\u203A\u203A",
cls: "pixel-banner-pagination-button"
});
if (this.iconsCurrentPage === totalPages) {
lastPageButton.disabled = true;
}
lastPageButton.addEventListener("click", () => {
if (this.iconsCurrentPage !== totalPages) {
this.iconsCurrentPage = totalPages;
this._isPaginating = true;
this.isIconsSearchMode ? this.searchIcons() : this.fetchIconsByCategory();
}
});
}
}
async searchIcons() {
const searchTerm = this.iconSearchInput.value.trim();
if (!searchTerm) return;
this.isIconsSearchMode = true;
if (!this._isPaginating) {
this.iconsCurrentPage = 1;
}
this.showCollectionsLoading();
try {
const url = `${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.BANNER_ICONS_SEARCH}?key=${PIXEL_BANNER_PLUS.BANNER_ICON_KEY}`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
searchTerm,
page: this.iconsCurrentPage,
limit: this.iconsPerPage
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (!data.success) {
throw new Error(data.error || "Failed to search icons");
}
this.iconsCurrentPage = data.currentPage || this.iconsCurrentPage;
this.renderIcons(data.bannerIcons, data.totalPages, data.totalCount);
} catch (error) {
console.error("Error searching icons:", error);
this.showCollectionsError(error.message);
} finally {
this.hideCollectionsLoading();
this._isPaginating = false;
}
}
updateImageGrid() {
this.gridContainer.empty();
this.paginationContainer.empty();
let filteredFiles = this.imageFiles.filter((file) => {
const filePath = file && file.path ? file.path.toLowerCase() : "";
const fileName = file && file.name ? file.name.toLowerCase() : "";
return filePath.includes(this.searchQuery) || fileName.includes(this.searchQuery);
});
filteredFiles = this.sortFiles(filteredFiles);
const totalImages = filteredFiles.length;
const totalPages = Math.ceil(totalImages / this.imagesPerPage);
const startIndex = (this.currentPage - 1) * this.imagesPerPage;
const endIndex = Math.min(startIndex + this.imagesPerPage, totalImages);
const currentFiles = filteredFiles.slice(startIndex, endIndex);
if (currentFiles.length === 0) {
const noImagesMessage = this.gridContainer.createEl("div", {
cls: "pixel-banner-no-images",
text: filteredFiles.length === 0 ? "\u{1F50D} No images found matching your search." : "No images on this page.",
attr: {
style: `
width: 100%;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2em;
color: var(--text-muted);
border: 1px dashed var(--background-modifier-border);
border-radius: 8px;
background-color: var(--background-secondary);
grid-column: 1 / -1;
text-align: center;
padding: 20px;
`
}
});
}
currentFiles.forEach((file) => {
const imageContainer = this.gridContainer.createDiv({ cls: "pixel-banner-image-container" });
const thumbnailContainer = imageContainer.createDiv({
attr: {
style: `
display: flex;
justify-content: center;
align-items: center;
height: 150px;
`
}
});
if (file && file.extension && file.extension.toLowerCase() === "svg") {
this.app.vault.readBinary(file).then((arrayBuffer) => {
const blob = new Blob([arrayBuffer], { type: "image/svg+xml" });
const url = URL.createObjectURL(blob);
const img = thumbnailContainer.createEl("img", {
cls: "pixel-banner-image-thumbnail",
attr: { src: url }
});
const cleanup = () => URL.revokeObjectURL(url);
img.addEventListener("load", cleanup);
img.addEventListener("error", cleanup);
}).catch(() => {
thumbnailContainer.createEl("div", {
cls: "pixel-banner-image-error",
text: "Error loading SVG"
});
});
} else {
this.app.vault.readBinary(file).then((arrayBuffer) => {
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
const img = thumbnailContainer.createEl("img", {
cls: "pixel-banner-image-thumbnail",
attr: { src: url }
});
const cleanup = () => URL.revokeObjectURL(url);
img.addEventListener("load", cleanup);
img.addEventListener("error", cleanup);
}).catch(() => {
thumbnailContainer.createEl("div", {
cls: "pixel-banner-image-error",
text: "Error loading image"
});
});
}
const deleteButton = imageContainer.createDiv({ cls: "pixel-banner-image-delete" });
deleteButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
deleteButton.addEventListener("click", (e) => {
e.stopPropagation();
this.deleteImage(file);
});
const infoContainer = imageContainer.createDiv({ cls: "pixel-banner-image-info" });
const displayPath = this.plugin.settings.useShortPath ? this.getShortPath(file.path) : file.path;
infoContainer.createDiv({ text: displayPath, cls: "pixel-banner-image-path" });
imageContainer.addEventListener("click", () => {
if (!file || !file.path) {
console.error("\u{1F527} ERROR: file or file.path is undefined during selection");
return;
}
this.onChoose(file.path);
this.close();
});
});
if (totalPages > 1) {
const paginationInfo = this.paginationContainer.createDiv({
cls: "pixel-banner-pagination-info",
text: `Showing ${startIndex + 1}-${endIndex} of ${totalImages} images | Page ${this.currentPage} of ${totalPages}`
});
const paginationControls = this.paginationContainer.createDiv({
cls: "pixel-banner-controls",
attr: {
style: "display: flex; gap: 5px; align-items: center;"
}
});
const firstPageButton = paginationControls.createEl("button", {
cls: `pixel-banner-pagination-button ${this.currentPage === 1 ? "disabled" : ""}`,
text: "\xAB\xAB"
});
firstPageButton.addEventListener("click", () => {
if (this.currentPage !== 1) {
this.currentPage = 1;
this.updateImageGrid();
}
});
const prevPageButton = paginationControls.createEl("button", {
cls: `pixel-banner-pagination-button ${this.currentPage === 1 ? "disabled" : ""}`,
text: "\u2039"
});
prevPageButton.addEventListener("click", () => {
if (this.currentPage > 1) {
this.currentPage--;
this.updateImageGrid();
}
});
const nextPageButton = paginationControls.createEl("button", {
cls: `pixel-banner-pagination-button ${this.currentPage === totalPages ? "disabled" : ""}`,
text: "\u203A"
});
nextPageButton.addEventListener("click", () => {
if (this.currentPage < totalPages) {
this.currentPage++;
this.updateImageGrid();
}
});
const lastPageButton = paginationControls.createEl("button", {
cls: `pixel-banner-pagination-button ${this.currentPage === totalPages ? "disabled" : ""}`,
text: "\u203A\u203A"
});
lastPageButton.addEventListener("click", () => {
if (this.currentPage !== totalPages) {
this.currentPage = totalPages;
this.updateImageGrid();
}
});
}
}
sortFiles(files) {
return files.sort((a, b) => {
if (this.sortOrder === "name-asc") {
return a.name.localeCompare(b.name);
} else if (this.sortOrder === "name-desc") {
return b.name.localeCompare(a.name);
} else if (this.sortOrder === "date-asc") {
return a.stat.mtime - b.stat.mtime;
} else if (this.sortOrder === "date-desc") {
return b.stat.mtime - a.stat.mtime;
}
return 0;
});
}
getShortPath(path) {
const parts = path.split("/");
const fileName = parts.pop();
if (parts.length > 1) {
return `${parts[0]}/.../${fileName}`;
}
return path;
}
onClose() {
if (this.style) {
this.style.remove();
}
if (this.tabStyles) {
this.tabStyles.remove();
}
if (this.spinnerStyle) {
this.spinnerStyle.remove();
}
if (this.plugin.settings.openTargetingModalAfterSelectingBannerOrIcon && !this.targetingModalOpened) {
const activeFile = this.app.workspace.getActiveFile();
if (activeFile) {
const { TargetPositionModal: TargetPositionModal2 } = (init_targetPositionModal(), __toCommonJS(targetPositionModal_exports));
new TargetPositionModal2(this.app, this.plugin).open();
}
}
const { contentEl } = this;
contentEl.empty();
}
};
}
});
// node_modules/emojilib/dist/emoji-en-US.json
var emoji_en_US_default;
var init_emoji_en_US = __esm({
"node_modules/emojilib/dist/emoji-en-US.json"() {
emoji_en_US_default = {
"\u{1F600}": [
"grinning_face",
"face",
"smile",
"happy",
"joy",
":D",
"grin",
"smiley"
],
"\u{1F603}": [
"grinning_face_with_big_eyes",
"face",
"happy",
"joy",
"haha",
":D",
":)",
"smile",
"funny",
"mouth",
"open",
"smiley",
"smiling"
],
"\u{1F604}": [
"grinning_face_with_smiling_eyes",
"face",
"happy",
"joy",
"funny",
"haha",
"laugh",
"like",
":D",
":)",
"smile",
"eye",
"grin",
"mouth",
"open",
"pleased",
"smiley"
],
"\u{1F601}": [
"beaming_face_with_smiling_eyes",
"face",
"happy",
"smile",
"joy",
"kawaii",
"eye",
"grin",
"grinning"
],
"\u{1F606}": [
"grinning_squinting_face",
"happy",
"joy",
"lol",
"satisfied",
"haha",
"face",
"glad",
"XD",
"laugh",
"big",
"closed",
"eyes",
"grin",
"laughing",
"mouth",
"open",
"smile",
"smiling",
"tightly"
],
"\u{1F605}": [
"grinning_face_with_sweat",
"face",
"hot",
"happy",
"laugh",
"sweat",
"smile",
"relief",
"cold",
"exercise",
"mouth",
"open",
"smiling"
],
"\u{1F923}": [
"rolling_on_the_floor_laughing",
"face",
"rolling",
"floor",
"laughing",
"lol",
"haha",
"rofl",
"laugh",
"rotfl"
],
"\u{1F602}": [
"face_with_tears_of_joy",
"face",
"cry",
"tears",
"weep",
"happy",
"happytears",
"haha",
"crying",
"laugh",
"laughing",
"lol",
"tear"
],
"\u{1F642}": [
"slightly_smiling_face",
"face",
"smile",
"fine",
"happy",
"this"
],
"\u{1F643}": [
"upside_down_face",
"face",
"flipped",
"silly",
"smile",
"sarcasm"
],
"\u{1F609}": [
"winking_face",
"face",
"happy",
"mischievous",
"secret",
";)",
"smile",
"eye",
"flirt",
"wink",
"winky"
],
"\u{1F60A}": [
"smiling_face_with_smiling_eyes",
"face",
"smile",
"happy",
"flushed",
"crush",
"embarrassed",
"shy",
"joy",
"^^",
"blush",
"eye",
"proud",
"smiley"
],
"\u{1F607}": [
"smiling_face_with_halo",
"face",
"angel",
"heaven",
"halo",
"innocent",
"fairy",
"fantasy",
"smile",
"tale"
],
"\u{1F970}": [
"smiling_face_with_hearts",
"face",
"love",
"like",
"affection",
"valentines",
"infatuation",
"crush",
"hearts",
"adore",
"eyes",
"three"
],
"\u{1F60D}": [
"smiling_face_with_heart_eyes",
"face",
"love",
"like",
"affection",
"valentines",
"infatuation",
"crush",
"heart",
"eye",
"shaped",
"smile"
],
"\u{1F929}": [
"star_struck",
"face",
"smile",
"starry",
"eyes",
"grinning",
"excited",
"eyed",
"wow"
],
"\u{1F618}": [
"face_blowing_a_kiss",
"face",
"love",
"like",
"affection",
"valentines",
"infatuation",
"kiss",
"blow",
"flirt",
"heart",
"kissing",
"throwing"
],
"\u{1F617}": [
"kissing_face",
"love",
"like",
"face",
"3",
"valentines",
"infatuation",
"kiss",
"duck",
"kissy",
"whistling"
],
"\u263A\uFE0F": [
"smiling_face",
"face",
"blush",
"massage",
"happiness",
"happy",
"outlined",
"pleased",
"relaxed",
"smile",
"smiley",
"white"
],
"\u{1F61A}": [
"kissing_face_with_closed_eyes",
"face",
"love",
"like",
"affection",
"valentines",
"infatuation",
"kiss",
"eye",
"kissy"
],
"\u{1F619}": [
"kissing_face_with_smiling_eyes",
"face",
"affection",
"valentines",
"infatuation",
"kiss",
"eye",
"kissy",
"smile",
"whistle",
"whistling"
],
"\u{1F60B}": [
"face_savoring_food",
"happy",
"joy",
"tongue",
"smile",
"face",
"silly",
"yummy",
"nom",
"delicious",
"savouring",
"goofy",
"hungry",
"lick",
"licking",
"lips",
"smiling",
"um",
"yum"
],
"\u{1F61B}": [
"face_with_tongue",
"face",
"prank",
"childish",
"playful",
"mischievous",
"smile",
"tongue",
"cheeky",
"out",
"stuck"
],
"\u{1F61C}": [
"winking_face_with_tongue",
"face",
"prank",
"childish",
"playful",
"mischievous",
"smile",
"wink",
"tongue",
"crazy",
"eye",
"joke",
"out",
"silly",
"stuck"
],
"\u{1F92A}": [
"zany_face",
"face",
"goofy",
"crazy",
"excited",
"eye",
"eyes",
"grinning",
"large",
"one",
"small",
"wacky",
"wild"
],
"\u{1F61D}": [
"squinting_face_with_tongue",
"face",
"prank",
"playful",
"mischievous",
"smile",
"tongue",
"closed",
"eye",
"eyes",
"horrible",
"out",
"stuck",
"taste",
"tightly"
],
"\u{1F911}": [
"money_mouth_face",
"face",
"rich",
"dollar",
"money",
"eyes",
"sign"
],
"\u{1F917}": [
"hugging_face",
"face",
"smile",
"hug",
"hands",
"hugs",
"open",
"smiling"
],
"\u{1F92D}": [
"face_with_hand_over_mouth",
"face",
"whoops",
"shock",
"surprise",
"blushing",
"covering",
"eyes",
"quiet",
"smiling"
],
"\u{1F92B}": [
"shushing_face",
"face",
"quiet",
"shhh",
"closed",
"covering",
"finger",
"hush",
"lips",
"shh",
"shush",
"silence"
],
"\u{1F914}": [
"thinking_face",
"face",
"hmmm",
"think",
"consider",
"chin",
"shade",
"thinker",
"throwing",
"thumb"
],
"\u{1F910}": [
"zipper_mouth_face",
"face",
"sealed",
"zipper",
"secret",
"hush",
"lips",
"silence",
"zip"
],
"\u{1F928}": [
"face_with_raised_eyebrow",
"face",
"distrust",
"scepticism",
"disapproval",
"disbelief",
"surprise",
"suspicious",
"colbert",
"mild",
"one",
"rock",
"skeptic"
],
"\u{1F610}": [
"neutral_face",
"indifference",
"meh",
":|",
"neutral",
"deadpan",
"faced",
"mouth",
"straight"
],
"\u{1F611}": [
"expressionless_face",
"face",
"indifferent",
"-_-",
"meh",
"deadpan",
"inexpressive",
"mouth",
"straight",
"unexpressive"
],
"\u{1F636}": [
"face_without_mouth",
"face",
"blank",
"mouthless",
"mute",
"no",
"quiet",
"silence",
"silent"
],
"\u{1F60F}": [
"smirking_face",
"face",
"smile",
"mean",
"prank",
"smug",
"sarcasm",
"flirting",
"sexual",
"smirk",
"suggestive"
],
"\u{1F612}": [
"unamused_face",
"indifference",
"bored",
"straight face",
"serious",
"sarcasm",
"unimpressed",
"skeptical",
"dubious",
"ugh",
"side_eye",
"dissatisfied",
"meh",
"unhappy"
],
"\u{1F644}": [
"face_with_rolling_eyes",
"face",
"eyeroll",
"frustrated",
"eye",
"roll"
],
"\u{1F62C}": [
"grimacing_face",
"face",
"grimace",
"teeth",
"awkward",
"eek",
"nervous"
],
"\u{1F925}": [
"lying_face",
"face",
"lie",
"pinocchio",
"liar",
"long",
"nose"
],
"\u{1F60C}": [
"relieved_face",
"face",
"relaxed",
"phew",
"massage",
"happiness",
"content",
"pleased",
"whew"
],
"\u{1F614}": [
"pensive_face",
"face",
"sad",
"depressed",
"upset",
"dejected",
"sadface",
"sorrowful"
],
"\u{1F62A}": [
"sleepy_face",
"face",
"tired",
"rest",
"nap",
"bubble",
"side",
"sleep",
"snot",
"tear"
],
"\u{1F924}": [
"drooling_face",
"face",
"drool"
],
"\u{1F634}": [
"sleeping_face",
"face",
"tired",
"sleepy",
"night",
"zzz",
"sleep",
"snoring"
],
"\u{1F637}": [
"face_with_medical_mask",
"face",
"sick",
"ill",
"disease",
"covid",
"cold",
"coronavirus",
"doctor",
"medicine",
"surgical"
],
"\u{1F912}": [
"face_with_thermometer",
"sick",
"temperature",
"thermometer",
"cold",
"fever",
"covid",
"ill"
],
"\u{1F915}": [
"face_with_head_bandage",
"injured",
"clumsy",
"bandage",
"hurt",
"bandaged",
"injury"
],
"\u{1F922}": [
"nauseated_face",
"face",
"vomit",
"gross",
"green",
"sick",
"throw up",
"ill",
"barf",
"disgust",
"disgusted",
"green\xA0face"
],
"\u{1F92E}": [
"face_vomiting",
"face",
"sick",
"barf",
"ill",
"mouth",
"open",
"puke",
"spew",
"throwing",
"up",
"vomit"
],
"\u{1F927}": [
"sneezing_face",
"face",
"gesundheit",
"sneeze",
"sick",
"allergy",
"achoo"
],
"\u{1F975}": [
"hot_face",
"face",
"feverish",
"heat",
"red",
"sweating",
"overheated",
"stroke"
],
"\u{1F976}": [
"cold_face",
"face",
"blue",
"freezing",
"frozen",
"frostbite",
"icicles",
"ice"
],
"\u{1F974}": [
"woozy_face",
"face",
"dizzy",
"intoxicated",
"tipsy",
"wavy",
"drunk",
"eyes",
"groggy",
"mouth",
"uneven"
],
"\u{1F635}": [
"dizzy_face",
"spent",
"unconscious",
"xox",
"dizzy",
"cross",
"crossed",
"dead",
"eyes",
"knocked",
"out",
"spiral\xA0eyes"
],
"\u{1F92F}": [
"exploding_head",
"face",
"shocked",
"mind",
"blown",
"blowing",
"explosion",
"mad"
],
"\u{1F920}": [
"cowboy_hat_face",
"face",
"cowgirl",
"hat"
],
"\u{1F973}": [
"partying_face",
"face",
"celebration",
"woohoo",
"birthday",
"hat",
"horn",
"party"
],
"\u{1F60E}": [
"smiling_face_with_sunglasses",
"face",
"cool",
"smile",
"summer",
"beach",
"sunglass",
"best",
"bright",
"eye",
"eyewear",
"friends",
"glasses",
"mutual",
"snapchat",
"sun",
"weather"
],
"\u{1F913}": [
"nerd_face",
"face",
"nerdy",
"geek",
"dork",
"glasses",
"smiling"
],
"\u{1F9D0}": [
"face_with_monocle",
"face",
"stuffy",
"wealthy",
"rich",
"exploration",
"inspection"
],
"\u{1F615}": [
"confused_face",
"face",
"indifference",
"huh",
"weird",
"hmmm",
":/",
"meh",
"nonplussed",
"puzzled",
"s"
],
"\u{1F61F}": [
"worried_face",
"face",
"concern",
"nervous",
":(",
"sad",
"sadface"
],
"\u{1F641}": [
"slightly_frowning_face",
"face",
"frowning",
"disappointed",
"sad",
"upset",
"frown",
"unhappy"
],
"\u2639\uFE0F": [
"frowning_face",
"face",
"sad",
"upset",
"frown",
"megafrown",
"unhappy",
"white"
],
"\u{1F62E}": [
"face_with_open_mouth",
"face",
"surprise",
"impressed",
"wow",
"whoa",
":O",
"surprised",
"sympathy"
],
"\u{1F62F}": [
"hushed_face",
"face",
"woo",
"shh",
"silence",
"speechless",
"stunned",
"surprise",
"surprised"
],
"\u{1F632}": [
"astonished_face",
"face",
"xox",
"surprised",
"poisoned",
"amazed",
"drunk\xA0face",
"gasp",
"gasping",
"shocked",
"totally"
],
"\u{1F633}": [
"flushed_face",
"face",
"blush",
"shy",
"flattered",
"blushing",
"dazed",
"embarrassed",
"eyes",
"open",
"shame",
"wide"
],
"\u{1F97A}": [
"pleading_face",
"face",
"begging",
"mercy",
"cry",
"tears",
"sad",
"grievance",
"eyes",
"glossy",
"puppy",
"simp"
],
"\u{1F626}": [
"frowning_face_with_open_mouth",
"face",
"aw",
"what",
"frown",
"yawning"
],
"\u{1F627}": [
"anguished_face",
"face",
"stunned",
"nervous",
"pained"
],
"\u{1F628}": [
"fearful_face",
"face",
"scared",
"terrified",
"nervous",
"fear",
"oops",
"shocked",
"surprised"
],
"\u{1F630}": [
"anxious_face_with_sweat",
"face",
"nervous",
"sweat",
"blue",
"cold",
"concerned\xA0face",
"mouth",
"open",
"rushed"
],
"\u{1F625}": [
"sad_but_relieved_face",
"face",
"phew",
"sweat",
"nervous",
"disappointed",
"eyebrow",
"whew"
],
"\u{1F622}": [
"crying_face",
"face",
"tears",
"sad",
"depressed",
"upset",
":'(",
"cry",
"tear"
],
"\u{1F62D}": [
"loudly_crying_face",
"sobbing",
"face",
"cry",
"tears",
"sad",
"upset",
"depressed",
"bawling",
"sob",
"tear"
],
"\u{1F631}": [
"face_screaming_in_fear",
"face",
"munch",
"scared",
"omg",
"alone",
"fearful",
"home",
"horror",
"scream",
"shocked"
],
"\u{1F616}": [
"confounded_face",
"face",
"confused",
"sick",
"unwell",
"oops",
":S",
"mouth",
"quivering",
"scrunched"
],
"\u{1F623}": [
"persevering_face",
"face",
"sick",
"no",
"upset",
"oops",
"eyes",
"helpless",
"persevere",
"scrunched",
"struggling"
],
"\u{1F61E}": [
"disappointed_face",
"face",
"sad",
"upset",
"depressed",
":(",
"sadface"
],
"\u{1F613}": [
"downcast_face_with_sweat",
"face",
"hot",
"sad",
"tired",
"exercise",
"cold",
"hard",
"work"
],
"\u{1F629}": [
"weary_face",
"face",
"tired",
"sleepy",
"sad",
"frustrated",
"upset",
"distraught",
"wailing"
],
"\u{1F62B}": [
"tired_face",
"sick",
"whine",
"upset",
"frustrated",
"distraught",
"exhausted",
"fed",
"up"
],
"\u{1F971}": [
"yawning_face",
"tired",
"sleepy",
"bored",
"yawn"
],
"\u{1F624}": [
"face_with_steam_from_nose",
"face",
"gas",
"phew",
"proud",
"pride",
"triumph",
"airing",
"frustrated",
"grievances",
"look",
"mad",
"smug",
"steaming",
"won"
],
"\u{1F621}": [
"pouting_face",
"angry",
"mad",
"hate",
"despise",
"enraged",
"grumpy",
"pout",
"rage",
"red"
],
"\u{1F620}": [
"angry_face",
"mad",
"face",
"annoyed",
"frustrated",
"anger",
"grumpy"
],
"\u{1F92C}": [
"face_with_symbols_on_mouth",
"face",
"swearing",
"cursing",
"cussing",
"profanity",
"expletive",
"covering",
"foul",
"grawlix",
"over",
"serious"
],
"\u{1F608}": [
"smiling_face_with_horns",
"devil",
"horns",
"evil",
"fairy",
"fantasy",
"happy",
"imp",
"purple",
"red\xA0devil",
"smile",
"tale"
],
"\u{1F47F}": [
"angry_face_with_horns",
"devil",
"angry",
"horns",
"demon",
"evil",
"fairy",
"fantasy",
"goblin",
"imp",
"purple",
"sad",
"tale"
],
"\u{1F480}": [
"skull",
"dead",
"skeleton",
"creepy",
"death",
"dead",
"body",
"danger",
"face",
"fairy",
"grey",
"halloween",
"monster",
"poison",
"tale"
],
"\u2620\uFE0F": [
"skull_and_crossbones",
"poison",
"danger",
"deadly",
"scary",
"death",
"pirate",
"evil",
"body",
"face",
"halloween",
"monster"
],
"\u{1F4A9}": [
"pile_of_poo",
"hankey",
"shitface",
"fail",
"turd",
"shit",
"comic",
"crap",
"dirt",
"dog",
"dung",
"face",
"monster",
"poop",
"smiling",
"bad",
"needs_improvement"
],
"\u{1F921}": [
"clown_face",
"face",
"mock"
],
"\u{1F479}": [
"ogre",
"monster",
"red",
"mask",
"halloween",
"scary",
"creepy",
"devil",
"demon",
"japanese_ogre",
"creature",
"face",
"fairy",
"fantasy",
"oni",
"tale",
"shrek"
],
"\u{1F47A}": [
"goblin",
"red",
"evil",
"mask",
"monster",
"scary",
"creepy",
"japanese_goblin",
"creature",
"face",
"fairy",
"fantasy",
"long",
"nose",
"tale",
"tengu"
],
"\u{1F47B}": [
"ghost",
"halloween",
"spooky",
"scary",
"creature",
"disappear",
"face",
"fairy",
"fantasy",
"ghoul",
"monster",
"tale"
],
"\u{1F47D}": [
"alien",
"UFO",
"paul",
"weird",
"outer_space",
"creature",
"et",
"extraterrestrial",
"face",
"fairy",
"fantasy",
"monster",
"tale",
"external"
],
"\u{1F47E}": [
"alien_monster",
"game",
"arcade",
"play",
"creature",
"extraterrestrial",
"face",
"fairy",
"fantasy",
"invader",
"retro",
"space",
"tale",
"ufo",
"video"
],
"\u{1F916}": [
"robot",
"computer",
"machine",
"bot",
"face",
"monster"
],
"\u{1F63A}": [
"grinning_cat",
"animal",
"cats",
"happy",
"smile",
"face",
"mouth",
"open",
"smiley",
"smiling"
],
"\u{1F638}": [
"grinning_cat_with_smiling_eyes",
"animal",
"cats",
"smile",
"eye",
"face",
"grin",
"happy"
],
"\u{1F639}": [
"cat_with_tears_of_joy",
"animal",
"cats",
"haha",
"happy",
"tears",
"face",
"laughing",
"tear"
],
"\u{1F63B}": [
"smiling_cat_with_heart_eyes",
"animal",
"love",
"like",
"affection",
"cats",
"valentines",
"heart",
"eye",
"face",
"loving\xA0cat",
"shaped",
"smile"
],
"\u{1F63C}": [
"cat_with_wry_smile",
"animal",
"cats",
"smirk",
"face",
"ironic",
"smirking"
],
"\u{1F63D}": [
"kissing_cat",
"animal",
"cats",
"kiss",
"closed",
"eye",
"eyes",
"face"
],
"\u{1F640}": [
"weary_cat",
"animal",
"cats",
"munch",
"scared",
"scream",
"face",
"fear",
"horror",
"oh",
"screaming",
"surprised"
],
"\u{1F63F}": [
"crying_cat",
"animal",
"tears",
"weep",
"sad",
"cats",
"upset",
"cry",
"face",
"sad\xA0cat",
"tear"
],
"\u{1F63E}": [
"pouting_cat",
"animal",
"cats",
"face",
"grumpy"
],
"\u{1F648}": [
"see_no_evil_monkey",
"monkey",
"animal",
"nature",
"haha",
"blind",
"covering",
"eyes",
"face",
"forbidden",
"gesture",
"ignore",
"mizaru",
"not",
"prohibited"
],
"\u{1F649}": [
"hear_no_evil_monkey",
"animal",
"monkey",
"nature",
"covering",
"deaf",
"ears",
"face",
"forbidden",
"gesture",
"kikazaru",
"not",
"prohibited"
],
"\u{1F64A}": [
"speak_no_evil_monkey",
"monkey",
"animal",
"nature",
"omg",
"covering",
"face",
"forbidden",
"gesture",
"hush",
"iwazaru",
"mouth",
"mute",
"not",
"no\xA0speaking",
"prohibited",
"ignore"
],
"\u{1F48B}": [
"kiss_mark",
"face",
"lips",
"love",
"like",
"affection",
"valentines",
"heart",
"kissing",
"lipstick",
"romance"
],
"\u{1F48C}": [
"love_letter",
"email",
"like",
"affection",
"envelope",
"valentines",
"heart",
"mail",
"note",
"romance"
],
"\u{1F498}": [
"heart_with_arrow",
"love",
"like",
"heart",
"affection",
"valentines",
"cupid",
"lovestruck",
"romance"
],
"\u{1F49D}": [
"heart_with_ribbon",
"love",
"valentines",
"box",
"chocolate",
"chocolates",
"gift",
"valentine"
],
"\u{1F496}": [
"sparkling_heart",
"love",
"like",
"affection",
"valentines",
"excited",
"sparkle",
"sparkly",
"stars\xA0heart"
],
"\u{1F497}": [
"growing_heart",
"like",
"love",
"affection",
"valentines",
"pink",
"excited",
"heartpulse",
"multiple",
"nervous",
"pulse",
"triple"
],
"\u{1F493}": [
"beating_heart",
"love",
"like",
"affection",
"valentines",
"pink",
"heart",
"alarm",
"heartbeat",
"pulsating",
"wifi"
],
"\u{1F49E}": [
"revolving_hearts",
"love",
"like",
"affection",
"valentines",
"heart",
"two"
],
"\u{1F495}": [
"two_hearts",
"love",
"like",
"affection",
"valentines",
"heart",
"pink",
"small"
],
"\u{1F49F}": [
"heart_decoration",
"purple-square",
"love",
"like"
],
"\u2763\uFE0F": [
"heart_exclamation",
"decoration",
"love",
"above",
"an",
"as",
"dot",
"heavy",
"mark",
"ornament",
"punctuation",
"red"
],
"\u{1F494}": [
"broken_heart",
"sad",
"sorry",
"break",
"heart",
"heartbreak",
"breaking",
"brokenhearted"
],
"\u2764\uFE0F": [
"red_heart",
"love",
"like",
"valentines",
"black",
"heavy"
],
"\u{1F9E1}": [
"orange_heart",
"love",
"like",
"affection",
"valentines"
],
"\u{1F49B}": [
"yellow_heart",
"love",
"like",
"affection",
"valentines",
"bf",
"gold",
"snapchat"
],
"\u{1F49A}": [
"green_heart",
"love",
"like",
"affection",
"valentines",
"nct"
],
"\u{1F499}": [
"blue_heart",
"love",
"like",
"affection",
"valentines",
"brand",
"neutral"
],
"\u{1F49C}": [
"purple_heart",
"love",
"like",
"affection",
"valentines",
"bts",
"emoji"
],
"\u{1F90E}": [
"brown_heart",
"coffee"
],
"\u{1F5A4}": [
"black_heart",
"evil",
"dark",
"wicked"
],
"\u{1F90D}": [
"white_heart",
"pure"
],
"\u{1F4AF}": [
"hundred_points",
"score",
"perfect",
"numbers",
"century",
"exam",
"quiz",
"test",
"pass",
"hundred",
"100",
"full",
"keep",
"symbol"
],
"\u{1F4A2}": [
"anger_symbol",
"angry",
"mad",
"comic",
"pop",
"sign",
"vein"
],
"\u{1F4A5}": [
"collision",
"bomb",
"explode",
"explosion",
"blown",
"bang",
"boom",
"comic",
"impact",
"red",
"spark",
"symbol",
"break"
],
"\u{1F4AB}": [
"dizzy",
"star",
"sparkle",
"shoot",
"magic",
"circle",
"comic",
"symbol",
"animations",
"transitions"
],
"\u{1F4A6}": [
"sweat_droplets",
"water",
"drip",
"oops",
"comic",
"drops",
"plewds",
"splashing",
"symbol",
"workout"
],
"\u{1F4A8}": [
"dashing_away",
"wind",
"air",
"fast",
"shoo",
"fart",
"smoke",
"puff",
"blow",
"comic",
"dash",
"gust",
"running",
"steam",
"symbol",
"vaping"
],
"\u{1F573}\uFE0F": [
"hole",
"embarrassing"
],
"\u{1F4A3}": [
"bomb",
"boom",
"explode",
"explosion",
"terrorism",
"comic"
],
"\u{1F4AC}": [
"speech_balloon",
"bubble",
"words",
"message",
"talk",
"chatting",
"chat",
"comic",
"comment",
"dialog",
"text",
"literals"
],
"\u{1F441}\uFE0F\u200D\u{1F5E8}\uFE0F": [
"eye_in_speech_bubble",
"info",
"am",
"i",
"witness"
],
"\u{1F5E8}\uFE0F": [
"left_speech_bubble",
"words",
"message",
"talk",
"chatting",
"dialog"
],
"\u{1F5EF}\uFE0F": [
"right_anger_bubble",
"caption",
"speech",
"thinking",
"mad",
"angry",
"balloon",
"zag",
"zig"
],
"\u{1F4AD}": [
"thought_balloon",
"bubble",
"cloud",
"speech",
"thinking",
"dream",
"comic"
],
"\u{1F4A4}": [
"zzz",
"sleepy",
"tired",
"dream",
"bedtime",
"boring",
"comic",
"sign",
"sleep",
"sleeping",
"symbol"
],
"\u{1F44B}": [
"waving_hand",
"wave",
"hands",
"gesture",
"goodbye",
"solong",
"farewell",
"hello",
"hi",
"palm",
"body",
"sign"
],
"\u{1F91A}": [
"raised_back_of_hand",
"fingers",
"raised",
"backhand",
"body"
],
"\u{1F590}\uFE0F": [
"hand_with_fingers_splayed",
"hand",
"fingers",
"palm",
"body",
"finger",
"five",
"raised"
],
"\u270B": [
"raised_hand",
"fingers",
"stop",
"highfive",
"palm",
"ban",
"body",
"five",
"high"
],
"\u{1F596}": [
"vulcan_salute",
"hand",
"fingers",
"spock",
"star trek",
"between",
"body",
"finger",
"middle",
"part",
"prosper",
"raised",
"ring",
"split"
],
"\u{1F44C}": [
"ok_hand",
"fingers",
"limbs",
"perfect",
"ok",
"okay",
"body",
"sign"
],
"\u{1F90F}": [
"pinching_hand",
"tiny",
"small",
"size",
"amount",
"body",
"little"
],
"\u270C\uFE0F": [
"victory_hand",
"fingers",
"ohyeah",
"hand",
"peace",
"victory",
"two",
"air",
"body",
"quotes",
"sign",
"v"
],
"\u{1F91E}": [
"crossed_fingers",
"good",
"lucky",
"body",
"cross",
"finger",
"hand",
"hopeful",
"index",
"luck",
"middle"
],
"\u{1F91F}": [
"love_you_gesture",
"hand",
"fingers",
"gesture",
"body",
"i",
"ily",
"sign"
],
"\u{1F918}": [
"sign_of_the_horns",
"hand",
"fingers",
"evil_eye",
"sign_of_horns",
"rock_on",
"body",
"devil",
"finger",
"heavy",
"metal"
],
"\u{1F919}": [
"call_me_hand",
"hands",
"gesture",
"shaka",
"body",
"phone",
"sign"
],
"\u{1F448}": [
"backhand_index_pointing_left",
"direction",
"fingers",
"hand",
"left",
"body",
"finger",
"point",
"white"
],
"\u{1F449}": [
"backhand_index_pointing_right",
"fingers",
"hand",
"direction",
"right",
"body",
"finger",
"point",
"white"
],
"\u{1F446}": [
"backhand_index_pointing_up",
"fingers",
"hand",
"direction",
"up",
"body",
"finger",
"middle",
"point",
"white"
],
"\u{1F595}": [
"middle_finger",
"hand",
"fingers",
"rude",
"middle",
"flipping",
"bird",
"body",
"dito",
"extended",
"fu",
"medio",
"middle\xA0finger",
"reversed"
],
"\u{1F447}": [
"backhand_index_pointing_down",
"fingers",
"hand",
"direction",
"down",
"body",
"finger",
"point",
"white"
],
"\u261D\uFE0F": [
"index_pointing_up",
"hand",
"fingers",
"direction",
"up",
"body",
"finger",
"point",
"secret",
"white"
],
"\u{1F44D}": [
"thumbs_up",
"thumbsup",
"yes",
"awesome",
"good",
"agree",
"accept",
"cool",
"hand",
"like",
"+1",
"approve",
"body",
"ok",
"sign",
"thumb"
],
"\u{1F44E}": [
"thumbs_down",
"thumbsdown",
"no",
"dislike",
"hand",
"-1",
"bad",
"body",
"bury",
"disapprove",
"sign",
"thumb"
],
"\u270A": [
"raised_fist",
"fingers",
"hand",
"grasp",
"body",
"clenched",
"power",
"pump",
"punch"
],
"\u{1F44A}": [
"oncoming_fist",
"angry",
"violence",
"fist",
"hit",
"attack",
"hand",
"body",
"bro",
"brofist",
"bump",
"clenched",
"closed",
"facepunch",
"fisted",
"punch",
"sign"
],
"\u{1F91B}": [
"left_facing_fist",
"hand",
"fistbump",
"body",
"bump",
"leftwards"
],
"\u{1F91C}": [
"right_facing_fist",
"hand",
"fistbump",
"body",
"bump",
"rightwards",
"right\xA0fist"
],
"\u{1F44F}": [
"clapping_hands",
"hands",
"praise",
"applause",
"congrats",
"yay",
"body",
"clap",
"golf",
"hand",
"round",
"sign"
],
"\u{1F64C}": [
"raising_hands",
"gesture",
"hooray",
"yea",
"celebration",
"hands",
"air",
"arms",
"banzai",
"body",
"both",
"festivus",
"hallelujah",
"hand",
"miracle",
"person",
"praise",
"raised",
"two"
],
"\u{1F450}": [
"open_hands",
"fingers",
"butterfly",
"hands",
"open",
"body",
"hand",
"hug",
"jazz",
"sign"
],
"\u{1F932}": [
"palms_up_together",
"hands",
"gesture",
"cupped",
"prayer",
"body",
"dua",
"facing"
],
"\u{1F91D}": [
"handshake",
"agreement",
"shake",
"deal",
"hand",
"hands",
"meeting",
"shaking"
],
"\u{1F64F}": [
"folded_hands",
"please",
"hope",
"wish",
"namaste",
"highfive",
"pray",
"thank you",
"thanks",
"appreciate",
"ask",
"body",
"bow",
"five",
"gesture",
"hand",
"high",
"person",
"prayer",
"pressed",
"together"
],
"\u270D\uFE0F": [
"writing_hand",
"lower_left_ballpoint_pen",
"stationery",
"write",
"compose",
"body"
],
"\u{1F485}": [
"nail_polish",
"nail_care",
"beauty",
"manicure",
"finger",
"fashion",
"nail",
"slay",
"body",
"cosmetics",
"fingers",
"nonchalant"
],
"\u{1F933}": [
"selfie",
"camera",
"phone",
"arm",
"hand"
],
"\u{1F4AA}": [
"flexed_biceps",
"arm",
"flex",
"hand",
"summer",
"strong",
"biceps",
"bicep",
"body",
"comic",
"feats",
"flexing",
"muscle",
"muscles",
"strength",
"workout"
],
"\u{1F9BE}": [
"mechanical_arm",
"accessibility",
"body",
"prosthetic"
],
"\u{1F9BF}": [
"mechanical_leg",
"accessibility",
"body",
"prosthetic"
],
"\u{1F9B5}": [
"leg",
"kick",
"limb",
"body"
],
"\u{1F9B6}": [
"foot",
"kick",
"stomp",
"body"
],
"\u{1F442}": [
"ear",
"face",
"hear",
"sound",
"listen",
"body",
"ears",
"hearing",
"listening",
"nose"
],
"\u{1F9BB}": [
"ear_with_hearing_aid",
"accessibility",
"body",
"hard"
],
"\u{1F443}": [
"nose",
"smell",
"sniff",
"body",
"smelling",
"sniffing",
"stinky"
],
"\u{1F9E0}": [
"brain",
"smart",
"intelligent",
"body",
"organ"
],
"\u{1F9B7}": [
"tooth",
"teeth",
"dentist",
"body"
],
"\u{1F9B4}": [
"bone",
"skeleton",
"body"
],
"\u{1F440}": [
"eyes",
"look",
"watch",
"stalk",
"peek",
"see",
"body",
"eye",
"eyeballs",
"face",
"shifty",
"wide"
],
"\u{1F441}\uFE0F": [
"eye",
"face",
"look",
"see",
"watch",
"stare",
"body",
"single"
],
"\u{1F445}": [
"tongue",
"mouth",
"playful",
"body",
"out",
"taste"
],
"\u{1F444}": [
"mouth",
"kiss",
"body",
"kissing",
"lips"
],
"\u{1F476}": [
"baby",
"child",
"boy",
"girl",
"toddler",
"newborn",
"young"
],
"\u{1F9D2}": [
"child",
"gender-neutral",
"young",
"boy",
"gender",
"girl",
"inclusive",
"neutral",
"person",
"unspecified"
],
"\u{1F466}": [
"boy",
"man",
"male",
"guy",
"teenager",
"child",
"young"
],
"\u{1F467}": [
"girl",
"female",
"woman",
"teenager",
"child",
"maiden",
"virgin",
"virgo",
"young",
"zodiac"
],
"\u{1F9D1}": [
"person",
"gender-neutral",
"adult",
"female",
"gender",
"inclusive",
"male",
"man",
"men",
"neutral",
"unspecified",
"woman",
"women"
],
"\u{1F471}": [
"person_blond_hair",
"hairstyle",
"blonde",
"haired",
"man"
],
"\u{1F468}": [
"man",
"mustache",
"father",
"dad",
"guy",
"classy",
"sir",
"moustache",
"adult",
"male",
"men"
],
"\u{1F9D4}": [
"man_beard",
"person",
"bewhiskered",
"bearded"
],
"\u{1F468}\u200D\u{1F9B0}": [
"man_red_hair",
"hairstyle",
"adult",
"ginger",
"haired",
"male",
"men",
"redhead"
],
"\u{1F468}\u200D\u{1F9B1}": [
"man_curly_hair",
"hairstyle",
"adult",
"haired",
"male",
"men"
],
"\u{1F468}\u200D\u{1F9B3}": [
"man_white_hair",
"old",
"elder",
"adult",
"haired",
"male",
"men"
],
"\u{1F468}\u200D\u{1F9B2}": [
"man_bald",
"hairless",
"adult",
"hair",
"male",
"men",
"no"
],
"\u{1F469}": [
"woman",
"female",
"girls",
"lady",
"adult",
"women",
"yellow"
],
"\u{1F469}\u200D\u{1F9B0}": [
"woman_red_hair",
"hairstyle",
"adult",
"female",
"ginger",
"haired",
"redhead",
"women"
],
"\u{1F9D1}\u200D\u{1F9B0}": [
"person_red_hair",
"hairstyle",
"adult",
"gender",
"haired",
"unspecified"
],
"\u{1F469}\u200D\u{1F9B1}": [
"woman_curly_hair",
"hairstyle",
"adult",
"female",
"haired",
"women"
],
"\u{1F9D1}\u200D\u{1F9B1}": [
"person_curly_hair",
"hairstyle",
"adult",
"gender",
"haired",
"unspecified"
],
"\u{1F469}\u200D\u{1F9B3}": [
"woman_white_hair",
"old",
"elder",
"adult",
"female",
"haired",
"women"
],
"\u{1F9D1}\u200D\u{1F9B3}": [
"person_white_hair",
"elder",
"old",
"adult",
"gender",
"haired",
"unspecified"
],
"\u{1F469}\u200D\u{1F9B2}": [
"woman_bald",
"hairless",
"adult",
"female",
"hair",
"no",
"women"
],
"\u{1F9D1}\u200D\u{1F9B2}": [
"person_bald",
"hairless",
"adult",
"gender",
"hair",
"no",
"unspecified"
],
"\u{1F471}\u200D\u2640\uFE0F": [
"woman_blond_hair",
"woman",
"female",
"girl",
"blonde",
"person",
"haired",
"women"
],
"\u{1F471}\u200D\u2642\uFE0F": [
"man_blond_hair",
"man",
"male",
"boy",
"blonde",
"guy",
"person",
"haired",
"men"
],
"\u{1F9D3}": [
"older_person",
"human",
"elder",
"senior",
"gender-neutral",
"adult",
"female",
"gender",
"male",
"man",
"men",
"neutral",
"old",
"unspecified",
"woman",
"women"
],
"\u{1F474}": [
"old_man",
"human",
"male",
"men",
"old",
"elder",
"senior",
"adult",
"elderly",
"grandpa",
"older"
],
"\u{1F475}": [
"old_woman",
"human",
"female",
"women",
"lady",
"old",
"elder",
"senior",
"adult",
"elderly",
"grandma",
"nanna",
"older"
],
"\u{1F64D}": [
"person_frowning",
"worried",
"frown",
"gesture",
"sad",
"woman"
],
"\u{1F64D}\u200D\u2642\uFE0F": [
"man_frowning",
"male",
"boy",
"man",
"sad",
"depressed",
"discouraged",
"unhappy",
"frown",
"gesture",
"men"
],
"\u{1F64D}\u200D\u2640\uFE0F": [
"woman_frowning",
"female",
"girl",
"woman",
"sad",
"depressed",
"discouraged",
"unhappy",
"frown",
"gesture",
"women"
],
"\u{1F64E}": [
"person_pouting",
"upset",
"blank",
"face",
"fed",
"gesture",
"look",
"up"
],
"\u{1F64E}\u200D\u2642\uFE0F": [
"man_pouting",
"male",
"boy",
"man",
"gesture",
"men"
],
"\u{1F64E}\u200D\u2640\uFE0F": [
"woman_pouting",
"female",
"girl",
"woman",
"gesture",
"women"
],
"\u{1F645}": [
"person_gesturing_no",
"decline",
"arms",
"deal",
"denied",
"face",
"forbidden",
"gesture",
"good",
"halt",
"hand",
"not",
"ok",
"prohibited",
"stop",
"x"
],
"\u{1F645}\u200D\u2642\uFE0F": [
"man_gesturing_no",
"male",
"boy",
"man",
"nope",
"denied",
"forbidden",
"gesture",
"good",
"halt",
"hand",
"men",
"ng",
"not",
"ok",
"prohibited",
"stop"
],
"\u{1F645}\u200D\u2640\uFE0F": [
"woman_gesturing_no",
"female",
"girl",
"woman",
"nope",
"denied",
"forbidden",
"gesture",
"good",
"halt",
"hand",
"ng",
"not",
"ok",
"prohibited",
"stop",
"women"
],
"\u{1F646}": [
"person_gesturing_ok",
"agree",
"ballerina",
"face",
"gesture",
"hand",
"hands",
"head"
],
"\u{1F646}\u200D\u2642\uFE0F": [
"man_gesturing_ok",
"men",
"boy",
"male",
"blue",
"human",
"man",
"gesture",
"hand"
],
"\u{1F646}\u200D\u2640\uFE0F": [
"woman_gesturing_ok",
"women",
"girl",
"female",
"pink",
"human",
"woman",
"gesture",
"hand"
],
"\u{1F481}": [
"person_tipping_hand",
"information",
"attendant",
"bellhop",
"concierge",
"desk",
"female",
"flick",
"girl",
"hair",
"help",
"sassy",
"woman",
"women"
],
"\u{1F481}\u200D\u2642\uFE0F": [
"man_tipping_hand",
"male",
"boy",
"man",
"human",
"information",
"desk",
"help",
"men",
"sassy"
],
"\u{1F481}\u200D\u2640\uFE0F": [
"woman_tipping_hand",
"female",
"girl",
"woman",
"human",
"information",
"desk",
"help",
"sassy",
"women"
],
"\u{1F64B}": [
"person_raising_hand",
"question",
"answering",
"gesture",
"happy",
"one",
"raised",
"up"
],
"\u{1F64B}\u200D\u2642\uFE0F": [
"man_raising_hand",
"male",
"boy",
"man",
"gesture",
"happy",
"men",
"one",
"raised"
],
"\u{1F64B}\u200D\u2640\uFE0F": [
"woman_raising_hand",
"female",
"girl",
"woman",
"gesture",
"happy",
"one",
"raised",
"women"
],
"\u{1F9CF}": [
"deaf_person",
"accessibility",
"ear",
"hear"
],
"\u{1F9CF}\u200D\u2642\uFE0F": [
"deaf_man",
"accessibility",
"male",
"men"
],
"\u{1F9CF}\u200D\u2640\uFE0F": [
"deaf_woman",
"accessibility",
"female",
"women"
],
"\u{1F647}": [
"person_bowing",
"respectiful",
"apology",
"bow",
"boy",
"cute",
"deeply",
"dogeza",
"gesture",
"man",
"massage",
"respect",
"sorry",
"thanks"
],
"\u{1F647}\u200D\u2642\uFE0F": [
"man_bowing",
"man",
"male",
"boy",
"apology",
"bow",
"deeply",
"favor",
"gesture",
"men",
"respect",
"sorry",
"thanks"
],
"\u{1F647}\u200D\u2640\uFE0F": [
"woman_bowing",
"woman",
"female",
"girl",
"apology",
"bow",
"deeply",
"favor",
"gesture",
"respect",
"sorry",
"thanks",
"women"
],
"\u{1F926}": [
"person_facepalming",
"disappointed",
"disbelief",
"exasperation",
"face",
"facepalm",
"head",
"hitting",
"palm",
"picard",
"smh"
],
"\u{1F926}\u200D\u2642\uFE0F": [
"man_facepalming",
"man",
"male",
"boy",
"disbelief",
"exasperation",
"face",
"facepalm",
"men",
"palm"
],
"\u{1F926}\u200D\u2640\uFE0F": [
"woman_facepalming",
"woman",
"female",
"girl",
"disbelief",
"exasperation",
"face",
"facepalm",
"palm",
"women"
],
"\u{1F937}": [
"person_shrugging",
"regardless",
"doubt",
"ignorance",
"indifference",
"shrug",
"shruggie",
"\xAF\\"
],
"\u{1F937}\u200D\u2642\uFE0F": [
"man_shrugging",
"man",
"male",
"boy",
"confused",
"indifferent",
"doubt",
"ignorance",
"indifference",
"men",
"shrug"
],
"\u{1F937}\u200D\u2640\uFE0F": [
"woman_shrugging",
"woman",
"female",
"girl",
"confused",
"indifferent",
"doubt",
"ignorance",
"indifference",
"shrug",
"women"
],
"\u{1F9D1}\u200D\u2695\uFE0F": [
"health_worker",
"hospital",
"dentist",
"doctor",
"healthcare",
"md",
"nurse",
"physician",
"professional",
"therapist"
],
"\u{1F468}\u200D\u2695\uFE0F": [
"man_health_worker",
"doctor",
"nurse",
"therapist",
"healthcare",
"man",
"human",
"dentist",
"male",
"md",
"men",
"physician",
"professional"
],
"\u{1F469}\u200D\u2695\uFE0F": [
"woman_health_worker",
"doctor",
"nurse",
"therapist",
"healthcare",
"woman",
"human",
"dentist",
"female",
"md",
"physician",
"professional",
"women"
],
"\u{1F9D1}\u200D\u{1F393}": [
"student",
"learn",
"education",
"graduate",
"pupil",
"school"
],
"\u{1F468}\u200D\u{1F393}": [
"man_student",
"graduate",
"man",
"human",
"education",
"graduation",
"male",
"men",
"pupil",
"school"
],
"\u{1F469}\u200D\u{1F393}": [
"woman_student",
"graduate",
"woman",
"human",
"education",
"female",
"graduation",
"pupil",
"school",
"women"
],
"\u{1F9D1}\u200D\u{1F3EB}": [
"teacher",
"professor",
"education",
"educator",
"instructor"
],
"\u{1F468}\u200D\u{1F3EB}": [
"man_teacher",
"instructor",
"professor",
"man",
"human",
"education",
"educator",
"male",
"men",
"school"
],
"\u{1F469}\u200D\u{1F3EB}": [
"woman_teacher",
"instructor",
"professor",
"woman",
"human",
"education",
"educator",
"female",
"school",
"women"
],
"\u{1F9D1}\u200D\u2696\uFE0F": [
"judge",
"law",
"court",
"justice",
"scales"
],
"\u{1F468}\u200D\u2696\uFE0F": [
"man_judge",
"justice",
"court",
"man",
"human",
"law",
"male",
"men",
"scales"
],
"\u{1F469}\u200D\u2696\uFE0F": [
"woman_judge",
"justice",
"court",
"woman",
"human",
"female",
"law",
"scales",
"women"
],
"\u{1F9D1}\u200D\u{1F33E}": [
"farmer",
"crops",
"farm",
"farming",
"gardener",
"rancher",
"worker"
],
"\u{1F468}\u200D\u{1F33E}": [
"man_farmer",
"rancher",
"gardener",
"man",
"human",
"farm",
"farming",
"male",
"men",
"worker"
],
"\u{1F469}\u200D\u{1F33E}": [
"woman_farmer",
"rancher",
"gardener",
"woman",
"human",
"farm",
"farming",
"female",
"women",
"worker"
],
"\u{1F9D1}\u200D\u{1F373}": [
"cook",
"food",
"kitchen",
"culinary",
"chef",
"cooking",
"service"
],
"\u{1F468}\u200D\u{1F373}": [
"man_cook",
"chef",
"man",
"human",
"cooking",
"food",
"male",
"men",
"service"
],
"\u{1F469}\u200D\u{1F373}": [
"woman_cook",
"chef",
"woman",
"human",
"cooking",
"female",
"food",
"service",
"women"
],
"\u{1F9D1}\u200D\u{1F527}": [
"mechanic",
"worker",
"technician",
"electrician",
"person",
"plumber",
"repair",
"tradesperson"
],
"\u{1F468}\u200D\u{1F527}": [
"man_mechanic",
"plumber",
"man",
"human",
"wrench",
"electrician",
"male",
"men",
"person",
"repair",
"tradesperson"
],
"\u{1F469}\u200D\u{1F527}": [
"woman_mechanic",
"plumber",
"woman",
"human",
"wrench",
"electrician",
"female",
"person",
"repair",
"tradesperson",
"women"
],
"\u{1F9D1}\u200D\u{1F3ED}": [
"factory_worker",
"labor",
"assembly",
"industrial",
"welder"
],
"\u{1F468}\u200D\u{1F3ED}": [
"man_factory_worker",
"assembly",
"industrial",
"man",
"human",
"male",
"men",
"welder"
],
"\u{1F469}\u200D\u{1F3ED}": [
"woman_factory_worker",
"assembly",
"industrial",
"woman",
"human",
"female",
"welder",
"women"
],
"\u{1F9D1}\u200D\u{1F4BC}": [
"office_worker",
"business",
"accountant",
"adviser",
"analyst",
"architect",
"banker",
"clerk",
"manager"
],
"\u{1F468}\u200D\u{1F4BC}": [
"man_office_worker",
"business",
"manager",
"man",
"human",
"accountant",
"adviser",
"analyst",
"architect",
"banker",
"businessman",
"ceo",
"clerk",
"male",
"men"
],
"\u{1F469}\u200D\u{1F4BC}": [
"woman_office_worker",
"business",
"manager",
"woman",
"human",
"accountant",
"adviser",
"analyst",
"architect",
"banker",
"businesswoman",
"ceo",
"clerk",
"female",
"women"
],
"\u{1F9D1}\u200D\u{1F52C}": [
"scientist",
"chemistry",
"biologist",
"chemist",
"engineer",
"lab",
"mathematician",
"physicist",
"technician"
],
"\u{1F468}\u200D\u{1F52C}": [
"man_scientist",
"biologist",
"chemist",
"engineer",
"physicist",
"man",
"human",
"lab",
"male",
"mathematician",
"men",
"research",
"technician"
],
"\u{1F469}\u200D\u{1F52C}": [
"woman_scientist",
"biologist",
"chemist",
"engineer",
"physicist",
"woman",
"human",
"female",
"lab",
"mathematician",
"research",
"technician",
"women"
],
"\u{1F9D1}\u200D\u{1F4BB}": [
"technologist",
"computer",
"coder",
"engineer",
"laptop",
"software",
"technology",
"developer"
],
"\u{1F468}\u200D\u{1F4BB}": [
"man_technologist",
"coder",
"developer",
"engineer",
"programmer",
"software",
"man",
"human",
"laptop",
"computer",
"blogger",
"male",
"men",
"technology"
],
"\u{1F469}\u200D\u{1F4BB}": [
"woman_technologist",
"coder",
"developer",
"engineer",
"programmer",
"software",
"woman",
"human",
"laptop",
"computer",
"blogger",
"female",
"technology",
"women"
],
"\u{1F9D1}\u200D\u{1F3A4}": [
"singer",
"song",
"artist",
"performer",
"actor",
"entertainer",
"music",
"musician",
"rock",
"rocker",
"rockstar",
"star"
],
"\u{1F468}\u200D\u{1F3A4}": [
"man_singer",
"rockstar",
"entertainer",
"man",
"human",
"actor",
"aladdin",
"bowie",
"male",
"men",
"music",
"musician",
"rock",
"rocker",
"sane",
"star"
],
"\u{1F469}\u200D\u{1F3A4}": [
"woman_singer",
"rockstar",
"entertainer",
"woman",
"human",
"actor",
"female",
"music",
"musician",
"rock",
"rocker",
"star",
"women"
],
"\u{1F9D1}\u200D\u{1F3A8}": [
"artist",
"painting",
"draw",
"creativity",
"art",
"paint",
"painter",
"palette"
],
"\u{1F468}\u200D\u{1F3A8}": [
"man_artist",
"painter",
"man",
"human",
"art",
"male",
"men",
"paint",
"palette"
],
"\u{1F469}\u200D\u{1F3A8}": [
"woman_artist",
"painter",
"woman",
"human",
"art",
"female",
"paint",
"palette",
"women"
],
"\u{1F9D1}\u200D\u2708\uFE0F": [
"pilot",
"fly",
"plane",
"airplane",
"aviation",
"aviator"
],
"\u{1F468}\u200D\u2708\uFE0F": [
"man_pilot",
"aviator",
"plane",
"man",
"human",
"airplane",
"aviation",
"male",
"men"
],
"\u{1F469}\u200D\u2708\uFE0F": [
"woman_pilot",
"aviator",
"plane",
"woman",
"human",
"airplane",
"aviation",
"female",
"women"
],
"\u{1F9D1}\u200D\u{1F680}": [
"astronaut",
"outerspace",
"moon",
"planets",
"rocket",
"space",
"stars"
],
"\u{1F468}\u200D\u{1F680}": [
"man_astronaut",
"space",
"rocket",
"man",
"human",
"cosmonaut",
"male",
"men",
"moon",
"planets",
"stars"
],
"\u{1F469}\u200D\u{1F680}": [
"woman_astronaut",
"space",
"rocket",
"woman",
"human",
"cosmonaut",
"female",
"moon",
"planets",
"stars",
"women"
],
"\u{1F9D1}\u200D\u{1F692}": [
"firefighter",
"fire",
"firetruck"
],
"\u{1F468}\u200D\u{1F692}": [
"man_firefighter",
"fireman",
"man",
"human",
"fire",
"firetruck",
"male",
"men"
],
"\u{1F469}\u200D\u{1F692}": [
"woman_firefighter",
"fireman",
"woman",
"human",
"female",
"fire",
"firetruck",
"women"
],
"\u{1F46E}": [
"police_officer",
"cop",
"law",
"policeman",
"policewoman"
],
"\u{1F46E}\u200D\u2642\uFE0F": [
"man_police_officer",
"man",
"police",
"law",
"legal",
"enforcement",
"arrest",
"911",
"cop",
"male",
"men",
"policeman"
],
"\u{1F46E}\u200D\u2640\uFE0F": [
"woman_police_officer",
"woman",
"police",
"law",
"legal",
"enforcement",
"arrest",
"911",
"female",
"cop",
"policewoman",
"women"
],
"\u{1F575}\uFE0F": [
"detective",
"human",
"spy",
"eye",
"or",
"private",
"sleuth"
],
"\u{1F575}\uFE0F\u200D\u2642\uFE0F": [
"man_detective",
"crime",
"male",
"men",
"sleuth",
"spy"
],
"\u{1F575}\uFE0F\u200D\u2640\uFE0F": [
"woman_detective",
"human",
"spy",
"detective",
"female",
"woman",
"sleuth",
"women"
],
"\u{1F482}": [
"guard",
"protect",
"british",
"guardsman"
],
"\u{1F482}\u200D\u2642\uFE0F": [
"man_guard",
"uk",
"gb",
"british",
"male",
"guy",
"royal",
"guardsman",
"men"
],
"\u{1F482}\u200D\u2640\uFE0F": [
"woman_guard",
"uk",
"gb",
"british",
"female",
"royal",
"woman",
"guardsman",
"guardswoman",
"women"
],
"\u{1F477}": [
"construction_worker",
"labor",
"build",
"builder",
"face",
"hard",
"hat",
"helmet",
"safety",
"add_ci",
"update_ci"
],
"\u{1F477}\u200D\u2642\uFE0F": [
"man_construction_worker",
"male",
"human",
"wip",
"guy",
"build",
"construction",
"worker",
"labor",
"helmet",
"men"
],
"\u{1F477}\u200D\u2640\uFE0F": [
"woman_construction_worker",
"female",
"human",
"wip",
"build",
"construction",
"worker",
"labor",
"woman",
"helmet",
"women"
],
"\u{1F934}": [
"prince",
"boy",
"man",
"male",
"crown",
"royal",
"king",
"fairy",
"fantasy",
"men",
"tale"
],
"\u{1F478}": [
"princess",
"girl",
"woman",
"female",
"blond",
"crown",
"royal",
"queen",
"blonde",
"fairy",
"fantasy",
"tale",
"tiara",
"women"
],
"\u{1F473}": [
"person_wearing_turban",
"headdress",
"arab",
"man",
"muslim",
"sikh"
],
"\u{1F473}\u200D\u2642\uFE0F": [
"man_wearing_turban",
"male",
"indian",
"hinduism",
"arabs",
"men"
],
"\u{1F473}\u200D\u2640\uFE0F": [
"woman_wearing_turban",
"female",
"indian",
"hinduism",
"arabs",
"woman",
"women"
],
"\u{1F472}": [
"man_with_skullcap",
"male",
"boy",
"chinese",
"asian",
"cap",
"gua",
"hat",
"mao",
"person",
"pi"
],
"\u{1F9D5}": [
"woman_with_headscarf",
"female",
"hijab",
"mantilla",
"tichel"
],
"\u{1F935}": [
"man_in_tuxedo",
"couple",
"marriage",
"wedding",
"groom",
"male",
"men",
"person",
"suit"
],
"\u{1F470}": [
"bride_with_veil",
"couple",
"marriage",
"wedding",
"woman",
"bride",
"person"
],
"\u{1F930}": [
"pregnant_woman",
"baby",
"female",
"pregnancy",
"pregnant\xA0lady",
"women"
],
"\u{1F931}": [
"breast_feeding",
"nursing",
"baby",
"breastfeeding",
"child",
"female",
"infant",
"milk",
"mother",
"woman",
"women"
],
"\u{1F47C}": [
"baby_angel",
"heaven",
"wings",
"halo",
"cherub",
"cupid",
"face",
"fairy",
"fantasy",
"putto",
"tale"
],
"\u{1F385}": [
"santa_claus",
"festival",
"man",
"male",
"xmas",
"father christmas",
"activity",
"celebration",
"men",
"nicholas",
"saint",
"sinterklaas"
],
"\u{1F936}": [
"mrs_claus",
"woman",
"female",
"xmas",
"mother christmas",
"activity",
"celebration",
"mrs.",
"santa",
"women"
],
"\u{1F9B8}": [
"superhero",
"marvel",
"fantasy",
"good",
"hero",
"heroine",
"superpower",
"superpowers"
],
"\u{1F9B8}\u200D\u2642\uFE0F": [
"man_superhero",
"man",
"male",
"good",
"hero",
"superpowers",
"fantasy",
"men",
"superpower"
],
"\u{1F9B8}\u200D\u2640\uFE0F": [
"woman_superhero",
"woman",
"female",
"good",
"heroine",
"superpowers",
"fantasy",
"hero",
"superpower",
"women"
],
"\u{1F9B9}": [
"supervillain",
"marvel",
"bad",
"criminal",
"evil",
"fantasy",
"superpower",
"superpowers",
"villain"
],
"\u{1F9B9}\u200D\u2642\uFE0F": [
"man_supervillain",
"man",
"male",
"evil",
"bad",
"criminal",
"hero",
"superpowers",
"fantasy",
"men",
"superpower",
"villain"
],
"\u{1F9B9}\u200D\u2640\uFE0F": [
"woman_supervillain",
"woman",
"female",
"evil",
"bad",
"criminal",
"heroine",
"superpowers",
"fantasy",
"superpower",
"villain",
"women"
],
"\u{1F9D9}": [
"mage",
"magic",
"fantasy",
"sorcerer",
"sorceress",
"witch",
"wizard"
],
"\u{1F9D9}\u200D\u2642\uFE0F": [
"man_mage",
"man",
"male",
"mage",
"sorcerer",
"fantasy",
"men",
"wizard"
],
"\u{1F9D9}\u200D\u2640\uFE0F": [
"woman_mage",
"woman",
"female",
"mage",
"witch",
"fantasy",
"sorceress",
"wizard",
"women"
],
"\u{1F9DA}": [
"fairy",
"wings",
"magical",
"fantasy",
"oberon",
"puck",
"titania"
],
"\u{1F9DA}\u200D\u2642\uFE0F": [
"man_fairy",
"man",
"male",
"fantasy",
"men",
"oberon",
"puck"
],
"\u{1F9DA}\u200D\u2640\uFE0F": [
"woman_fairy",
"woman",
"female",
"fantasy",
"titania",
"wings",
"women"
],
"\u{1F9DB}": [
"vampire",
"blood",
"twilight",
"dracula",
"fantasy",
"undead"
],
"\u{1F9DB}\u200D\u2642\uFE0F": [
"man_vampire",
"man",
"male",
"dracula",
"fantasy",
"men",
"undead"
],
"\u{1F9DB}\u200D\u2640\uFE0F": [
"woman_vampire",
"woman",
"female",
"fantasy",
"undead",
"unded",
"women"
],
"\u{1F9DC}": [
"merperson",
"sea",
"fantasy",
"merboy",
"mergirl",
"mermaid",
"merman",
"merwoman"
],
"\u{1F9DC}\u200D\u2642\uFE0F": [
"merman",
"man",
"male",
"triton",
"fantasy",
"men",
"mermaid"
],
"\u{1F9DC}\u200D\u2640\uFE0F": [
"mermaid",
"woman",
"female",
"merwoman",
"ariel",
"fantasy",
"women"
],
"\u{1F9DD}": [
"elf",
"magical",
"ears",
"fantasy",
"legolas",
"pointed"
],
"\u{1F9DD}\u200D\u2642\uFE0F": [
"man_elf",
"man",
"male",
"ears",
"fantasy",
"magical",
"men",
"pointed"
],
"\u{1F9DD}\u200D\u2640\uFE0F": [
"woman_elf",
"woman",
"female",
"ears",
"fantasy",
"magical",
"pointed",
"women"
],
"\u{1F9DE}": [
"genie",
"magical",
"wishes",
"djinn",
"djinni",
"fantasy",
"jinni"
],
"\u{1F9DE}\u200D\u2642\uFE0F": [
"man_genie",
"man",
"male",
"djinn",
"fantasy",
"men"
],
"\u{1F9DE}\u200D\u2640\uFE0F": [
"woman_genie",
"woman",
"female",
"djinn",
"fantasy",
"women"
],
"\u{1F9DF}": [
"zombie",
"dead",
"fantasy",
"undead",
"walking"
],
"\u{1F9DF}\u200D\u2642\uFE0F": [
"man_zombie",
"man",
"male",
"dracula",
"undead",
"walking dead",
"fantasy",
"men"
],
"\u{1F9DF}\u200D\u2640\uFE0F": [
"woman_zombie",
"woman",
"female",
"undead",
"walking dead",
"fantasy",
"women"
],
"\u{1F486}": [
"person_getting_massage",
"relax",
"face",
"head",
"massaging",
"salon",
"spa"
],
"\u{1F486}\u200D\u2642\uFE0F": [
"man_getting_massage",
"male",
"boy",
"man",
"head",
"face",
"men",
"salon",
"spa"
],
"\u{1F486}\u200D\u2640\uFE0F": [
"woman_getting_massage",
"female",
"girl",
"woman",
"head",
"face",
"salon",
"spa",
"women"
],
"\u{1F487}": [
"person_getting_haircut",
"hairstyle",
"barber",
"beauty",
"cutting",
"hair",
"hairdresser",
"parlor"
],
"\u{1F487}\u200D\u2642\uFE0F": [
"man_getting_haircut",
"male",
"boy",
"man",
"barber",
"beauty",
"men",
"parlor"
],
"\u{1F487}\u200D\u2640\uFE0F": [
"woman_getting_haircut",
"female",
"girl",
"woman",
"barber",
"beauty",
"parlor",
"women"
],
"\u{1F6B6}": [
"person_walking",
"move",
"hike",
"pedestrian",
"walk",
"walker"
],
"\u{1F6B6}\u200D\u2642\uFE0F": [
"man_walking",
"human",
"feet",
"steps",
"hike",
"male",
"men",
"pedestrian",
"walk"
],
"\u{1F6B6}\u200D\u2640\uFE0F": [
"woman_walking",
"human",
"feet",
"steps",
"woman",
"female",
"hike",
"pedestrian",
"walk",
"women"
],
"\u{1F9CD}": [
"person_standing",
"still",
"stand"
],
"\u{1F9CD}\u200D\u2642\uFE0F": [
"man_standing",
"still",
"male",
"men",
"stand"
],
"\u{1F9CD}\u200D\u2640\uFE0F": [
"woman_standing",
"still",
"female",
"stand",
"women"
],
"\u{1F9CE}": [
"person_kneeling",
"pray",
"respectful",
"kneel"
],
"\u{1F9CE}\u200D\u2642\uFE0F": [
"man_kneeling",
"pray",
"respectful",
"kneel",
"male",
"men"
],
"\u{1F9CE}\u200D\u2640\uFE0F": [
"woman_kneeling",
"respectful",
"pray",
"female",
"kneel",
"women"
],
"\u{1F9D1}\u200D\u{1F9AF}": [
"person_with_probing_cane",
"blind",
"accessibility",
"white"
],
"\u{1F468}\u200D\u{1F9AF}": [
"man_with_probing_cane",
"blind",
"accessibility",
"male",
"men",
"white"
],
"\u{1F469}\u200D\u{1F9AF}": [
"woman_with_probing_cane",
"blind",
"accessibility",
"female",
"white",
"women"
],
"\u{1F9D1}\u200D\u{1F9BC}": [
"person_in_motorized_wheelchair",
"disability",
"accessibility"
],
"\u{1F468}\u200D\u{1F9BC}": [
"man_in_motorized_wheelchair",
"disability",
"accessibility",
"male",
"men"
],
"\u{1F469}\u200D\u{1F9BC}": [
"woman_in_motorized_wheelchair",
"disability",
"accessibility",
"female",
"women"
],
"\u{1F9D1}\u200D\u{1F9BD}": [
"person_in_manual_wheelchair",
"disability",
"accessibility"
],
"\u{1F468}\u200D\u{1F9BD}": [
"man_in_manual_wheelchair",
"disability",
"accessibility",
"male",
"men"
],
"\u{1F469}\u200D\u{1F9BD}": [
"woman_in_manual_wheelchair",
"disability",
"accessibility",
"female",
"women"
],
"\u{1F3C3}": [
"person_running",
"move",
"exercise",
"jogging",
"marathon",
"run",
"runner",
"workout"
],
"\u{1F3C3}\u200D\u2642\uFE0F": [
"man_running",
"man",
"walking",
"exercise",
"race",
"running",
"male",
"marathon",
"men",
"racing",
"runner",
"workout"
],
"\u{1F3C3}\u200D\u2640\uFE0F": [
"woman_running",
"woman",
"walking",
"exercise",
"race",
"running",
"female",
"boy",
"marathon",
"racing",
"runner",
"women",
"workout"
],
"\u{1F483}": [
"woman_dancing",
"female",
"girl",
"woman",
"fun",
"dance",
"dancer",
"dress",
"red",
"salsa",
"women"
],
"\u{1F57A}": [
"man_dancing",
"male",
"boy",
"fun",
"dancer",
"dance",
"disco",
"men"
],
"\u{1F574}\uFE0F": [
"man_in_suit_levitating",
"suit",
"business",
"levitate",
"hover",
"jump",
"boy",
"hovering",
"jabsco",
"male",
"men",
"person",
"rude",
"walt"
],
"\u{1F46F}": [
"people_with_bunny_ears",
"perform",
"costume",
"dancer",
"dancing",
"ear",
"partying",
"wearing",
"women"
],
"\u{1F46F}\u200D\u2642\uFE0F": [
"men_with_bunny_ears",
"male",
"bunny",
"men",
"boys",
"dancer",
"dancing",
"ear",
"man",
"partying",
"wearing"
],
"\u{1F46F}\u200D\u2640\uFE0F": [
"women_with_bunny_ears",
"female",
"bunny",
"women",
"girls",
"dancer",
"dancing",
"ear",
"partying",
"people",
"wearing"
],
"\u{1F9D6}": [
"person_in_steamy_room",
"relax",
"spa",
"hamam",
"sauna",
"steam",
"steambath"
],
"\u{1F9D6}\u200D\u2642\uFE0F": [
"man_in_steamy_room",
"male",
"man",
"spa",
"steamroom",
"sauna",
"hamam",
"men",
"steam",
"steambath"
],
"\u{1F9D6}\u200D\u2640\uFE0F": [
"woman_in_steamy_room",
"female",
"woman",
"spa",
"steamroom",
"sauna",
"hamam",
"steam",
"steambath",
"women"
],
"\u{1F9D7}": [
"person_climbing",
"sport",
"bouldering",
"climber",
"rock"
],
"\u{1F9D7}\u200D\u2642\uFE0F": [
"man_climbing",
"sports",
"hobby",
"man",
"male",
"rock",
"bouldering",
"climber",
"men"
],
"\u{1F9D7}\u200D\u2640\uFE0F": [
"woman_climbing",
"sports",
"hobby",
"woman",
"female",
"rock",
"bouldering",
"climber",
"women"
],
"\u{1F93A}": [
"person_fencing",
"sports",
"fencing",
"sword",
"fencer"
],
"\u{1F3C7}": [
"horse_racing",
"animal",
"betting",
"competition",
"gambling",
"luck",
"jockey",
"race",
"racehorse"
],
"\u26F7\uFE0F": [
"skier",
"sports",
"winter",
"snow",
"ski"
],
"\u{1F3C2}": [
"snowboarder",
"sports",
"winter",
"ski",
"snow",
"snowboard",
"snowboarding"
],
"\u{1F3CC}\uFE0F": [
"person_golfing",
"sports",
"business",
"ball",
"club",
"golf",
"golfer"
],
"\u{1F3CC}\uFE0F\u200D\u2642\uFE0F": [
"man_golfing",
"sport",
"ball",
"golf",
"golfer",
"male",
"men"
],
"\u{1F3CC}\uFE0F\u200D\u2640\uFE0F": [
"woman_golfing",
"sports",
"business",
"woman",
"female",
"ball",
"golf",
"golfer",
"women"
],
"\u{1F3C4}": [
"person_surfing",
"sport",
"sea",
"surf",
"surfer"
],
"\u{1F3C4}\u200D\u2642\uFE0F": [
"man_surfing",
"sports",
"ocean",
"sea",
"summer",
"beach",
"male",
"men",
"surfer"
],
"\u{1F3C4}\u200D\u2640\uFE0F": [
"woman_surfing",
"sports",
"ocean",
"sea",
"summer",
"beach",
"woman",
"female",
"surfer",
"women"
],
"\u{1F6A3}": [
"person_rowing_boat",
"sport",
"move",
"paddles",
"rowboat",
"vehicle"
],
"\u{1F6A3}\u200D\u2642\uFE0F": [
"man_rowing_boat",
"sports",
"hobby",
"water",
"ship",
"male",
"men",
"rowboat",
"vehicle"
],
"\u{1F6A3}\u200D\u2640\uFE0F": [
"woman_rowing_boat",
"sports",
"hobby",
"water",
"ship",
"woman",
"female",
"rowboat",
"vehicle",
"women"
],
"\u{1F3CA}": [
"person_swimming",
"sport",
"pool",
"swim",
"swimmer"
],
"\u{1F3CA}\u200D\u2642\uFE0F": [
"man_swimming",
"sports",
"exercise",
"human",
"athlete",
"water",
"summer",
"male",
"men",
"swim",
"swimmer"
],
"\u{1F3CA}\u200D\u2640\uFE0F": [
"woman_swimming",
"sports",
"exercise",
"human",
"athlete",
"water",
"summer",
"woman",
"female",
"swim",
"swimmer",
"women"
],
"\u26F9\uFE0F": [
"person_bouncing_ball",
"sports",
"human",
"basketball",
"player"
],
"\u26F9\uFE0F\u200D\u2642\uFE0F": [
"man_bouncing_ball",
"sport",
"basketball",
"male",
"men",
"player"
],
"\u26F9\uFE0F\u200D\u2640\uFE0F": [
"woman_bouncing_ball",
"sports",
"human",
"woman",
"female",
"basketball",
"player",
"women"
],
"\u{1F3CB}\uFE0F": [
"person_lifting_weights",
"sports",
"training",
"exercise",
"bodybuilder",
"gym",
"lifter",
"weight",
"weightlifter",
"workout"
],
"\u{1F3CB}\uFE0F\u200D\u2642\uFE0F": [
"man_lifting_weights",
"sport",
"gym",
"lifter",
"male",
"men",
"weight",
"weightlifter",
"workout"
],
"\u{1F3CB}\uFE0F\u200D\u2640\uFE0F": [
"woman_lifting_weights",
"sports",
"training",
"exercise",
"woman",
"female",
"gym",
"lifter",
"weight",
"weightlifter",
"women",
"workout"
],
"\u{1F6B4}": [
"person_biking",
"bicycle",
"bike",
"cyclist",
"sport",
"move",
"bicyclist"
],
"\u{1F6B4}\u200D\u2642\uFE0F": [
"man_biking",
"bicycle",
"bike",
"cyclist",
"sports",
"exercise",
"hipster",
"bicyclist",
"male",
"men"
],
"\u{1F6B4}\u200D\u2640\uFE0F": [
"woman_biking",
"bicycle",
"bike",
"cyclist",
"sports",
"exercise",
"hipster",
"woman",
"female",
"bicyclist",
"women"
],
"\u{1F6B5}": [
"person_mountain_biking",
"bicycle",
"bike",
"cyclist",
"sport",
"move",
"bicyclist",
"biker"
],
"\u{1F6B5}\u200D\u2642\uFE0F": [
"man_mountain_biking",
"bicycle",
"bike",
"cyclist",
"transportation",
"sports",
"human",
"race",
"bicyclist",
"biker",
"male",
"men"
],
"\u{1F6B5}\u200D\u2640\uFE0F": [
"woman_mountain_biking",
"bicycle",
"bike",
"cyclist",
"transportation",
"sports",
"human",
"race",
"woman",
"female",
"bicyclist",
"biker",
"women"
],
"\u{1F938}": [
"person_cartwheeling",
"sport",
"gymnastic",
"cartwheel",
"doing",
"gymnast",
"gymnastics"
],
"\u{1F938}\u200D\u2642\uFE0F": [
"man_cartwheeling",
"gymnastics",
"cartwheel",
"doing",
"male",
"men"
],
"\u{1F938}\u200D\u2640\uFE0F": [
"woman_cartwheeling",
"gymnastics",
"cartwheel",
"doing",
"female",
"women"
],
"\u{1F93C}": [
"people_wrestling",
"sport",
"wrestle",
"wrestler",
"wrestlers"
],
"\u{1F93C}\u200D\u2642\uFE0F": [
"men_wrestling",
"sports",
"wrestlers",
"male",
"man",
"wrestle",
"wrestler"
],
"\u{1F93C}\u200D\u2640\uFE0F": [
"women_wrestling",
"sports",
"wrestlers",
"female",
"woman",
"wrestle",
"wrestler"
],
"\u{1F93D}": [
"person_playing_water_polo",
"sport"
],
"\u{1F93D}\u200D\u2642\uFE0F": [
"man_playing_water_polo",
"sports",
"pool",
"male",
"men"
],
"\u{1F93D}\u200D\u2640\uFE0F": [
"woman_playing_water_polo",
"sports",
"pool",
"female",
"women"
],
"\u{1F93E}": [
"person_playing_handball",
"sport",
"ball"
],
"\u{1F93E}\u200D\u2642\uFE0F": [
"man_playing_handball",
"sports",
"ball",
"male",
"men"
],
"\u{1F93E}\u200D\u2640\uFE0F": [
"woman_playing_handball",
"sports",
"ball",
"female",
"women"
],
"\u{1F939}": [
"person_juggling",
"performance",
"balance",
"juggle",
"juggler",
"multitask",
"skill"
],
"\u{1F939}\u200D\u2642\uFE0F": [
"man_juggling",
"juggle",
"balance",
"skill",
"multitask",
"juggler",
"male",
"men"
],
"\u{1F939}\u200D\u2640\uFE0F": [
"woman_juggling",
"juggle",
"balance",
"skill",
"multitask",
"female",
"juggler",
"women"
],
"\u{1F9D8}": [
"person_in_lotus_position",
"meditate",
"meditation",
"serenity",
"yoga"
],
"\u{1F9D8}\u200D\u2642\uFE0F": [
"man_in_lotus_position",
"man",
"male",
"meditation",
"yoga",
"serenity",
"zen",
"mindfulness",
"men"
],
"\u{1F9D8}\u200D\u2640\uFE0F": [
"woman_in_lotus_position",
"woman",
"female",
"meditation",
"yoga",
"serenity",
"zen",
"mindfulness",
"women"
],
"\u{1F6C0}": [
"person_taking_bath",
"clean",
"shower",
"bathroom",
"bathing",
"bathtub",
"hot"
],
"\u{1F6CC}": [
"person_in_bed",
"bed",
"rest",
"accommodation",
"hotel",
"sleep",
"sleeping"
],
"\u{1F9D1}\u200D\u{1F91D}\u200D\u{1F9D1}": [
"people_holding_hands",
"friendship",
"couple",
"date",
"gender",
"hand",
"hold",
"inclusive",
"neutral",
"nonconforming"
],
"\u{1F46D}": [
"women_holding_hands",
"pair",
"friendship",
"couple",
"love",
"like",
"female",
"people",
"human",
"date",
"hand",
"hold",
"lesbian",
"lgbt",
"pride",
"two",
"woman"
],
"\u{1F46B}": [
"woman_and_man_holding_hands",
"pair",
"people",
"human",
"love",
"date",
"dating",
"like",
"affection",
"valentines",
"marriage",
"couple",
"female",
"hand",
"heterosexual",
"hold",
"male",
"men",
"straight",
"women"
],
"\u{1F46C}": [
"men_holding_hands",
"pair",
"couple",
"love",
"like",
"bromance",
"friendship",
"people",
"human",
"date",
"gay",
"hand",
"hold",
"lgbt",
"male",
"man",
"pride",
"two"
],
"\u{1F48F}": [
"kiss",
"pair",
"valentines",
"love",
"like",
"dating",
"marriage",
"couple",
"couplekiss",
"female",
"gender",
"heart",
"kissing",
"male",
"man",
"men",
"neutral",
"romance",
"woman",
"women"
],
"\u{1F469}\u200D\u2764\uFE0F\u200D\u{1F48B}\u200D\u{1F468}": [
"kiss_woman_man",
"love",
"couple",
"couplekiss",
"female",
"heart",
"kissing",
"male",
"men",
"romance",
"women"
],
"\u{1F468}\u200D\u2764\uFE0F\u200D\u{1F48B}\u200D\u{1F468}": [
"kiss_man_man",
"pair",
"valentines",
"love",
"like",
"dating",
"marriage",
"couple",
"couplekiss",
"gay",
"heart",
"kissing",
"lgbt",
"male",
"men",
"pride",
"romance",
"two"
],
"\u{1F469}\u200D\u2764\uFE0F\u200D\u{1F48B}\u200D\u{1F469}": [
"kiss_woman_woman",
"pair",
"valentines",
"love",
"like",
"dating",
"marriage",
"couple",
"couplekiss",
"female",
"heart",
"kissing",
"lesbian",
"lgbt",
"pride",
"romance",
"two",
"women"
],
"\u{1F491}": [
"couple_with_heart",
"pair",
"love",
"like",
"affection",
"human",
"dating",
"valentines",
"marriage",
"female",
"gender",
"loving",
"male",
"man",
"men",
"neutral",
"romance",
"woman",
"women"
],
"\u{1F469}\u200D\u2764\uFE0F\u200D\u{1F468}": [
"couple_with_heart_woman_man",
"love",
"female",
"male",
"men",
"romance",
"women"
],
"\u{1F468}\u200D\u2764\uFE0F\u200D\u{1F468}": [
"couple_with_heart_man_man",
"pair",
"love",
"like",
"affection",
"human",
"dating",
"valentines",
"marriage",
"gay",
"lgbt",
"male",
"men",
"pride",
"romance",
"two"
],
"\u{1F469}\u200D\u2764\uFE0F\u200D\u{1F469}": [
"couple_with_heart_woman_woman",
"pair",
"love",
"like",
"affection",
"human",
"dating",
"valentines",
"marriage",
"female",
"lesbian",
"lgbt",
"pride",
"romance",
"two",
"women"
],
"\u{1F46A}": [
"family",
"home",
"parents",
"child",
"mom",
"dad",
"father",
"mother",
"people",
"human",
"boy",
"female",
"male",
"man",
"men",
"woman",
"women"
],
"\u{1F468}\u200D\u{1F469}\u200D\u{1F466}": [
"family_man_woman_boy",
"love",
"father",
"mother",
"son"
],
"\u{1F468}\u200D\u{1F469}\u200D\u{1F467}": [
"family_man_woman_girl",
"home",
"parents",
"people",
"human",
"child",
"daughter",
"father",
"female",
"male",
"men",
"mother",
"women"
],
"\u{1F468}\u200D\u{1F469}\u200D\u{1F467}\u200D\u{1F466}": [
"family_man_woman_girl_boy",
"home",
"parents",
"people",
"human",
"children",
"child",
"daughter",
"father",
"female",
"male",
"men",
"mother",
"son",
"women"
],
"\u{1F468}\u200D\u{1F469}\u200D\u{1F466}\u200D\u{1F466}": [
"family_man_woman_boy_boy",
"home",
"parents",
"people",
"human",
"children",
"child",
"father",
"female",
"male",
"men",
"mother",
"sons",
"two",
"women"
],
"\u{1F468}\u200D\u{1F469}\u200D\u{1F467}\u200D\u{1F467}": [
"family_man_woman_girl_girl",
"home",
"parents",
"people",
"human",
"children",
"child",
"daughters",
"father",
"female",
"male",
"men",
"mother",
"two",
"women"
],
"\u{1F468}\u200D\u{1F468}\u200D\u{1F466}": [
"family_man_man_boy",
"home",
"parents",
"people",
"human",
"children",
"child",
"father",
"fathers",
"gay",
"lgbt",
"male",
"men",
"pride",
"son",
"two"
],
"\u{1F468}\u200D\u{1F468}\u200D\u{1F467}": [
"family_man_man_girl",
"home",
"parents",
"people",
"human",
"children",
"child",
"daughter",
"father",
"fathers",
"gay",
"lgbt",
"male",
"men",
"pride",
"two"
],
"\u{1F468}\u200D\u{1F468}\u200D\u{1F467}\u200D\u{1F466}": [
"family_man_man_girl_boy",
"home",
"parents",
"people",
"human",
"children",
"child",
"daughter",
"father",
"fathers",
"gay",
"lgbt",
"male",
"men",
"pride",
"son",
"two"
],
"\u{1F468}\u200D\u{1F468}\u200D\u{1F466}\u200D\u{1F466}": [
"family_man_man_boy_boy",
"home",
"parents",
"people",
"human",
"children",
"child",
"father",
"fathers",
"gay",
"lgbt",
"male",
"men",
"pride",
"sons",
"two"
],
"\u{1F468}\u200D\u{1F468}\u200D\u{1F467}\u200D\u{1F467}": [
"family_man_man_girl_girl",
"home",
"parents",
"people",
"human",
"children",
"child",
"daughters",
"father",
"fathers",
"gay",
"lgbt",
"male",
"men",
"pride",
"two"
],
"\u{1F469}\u200D\u{1F469}\u200D\u{1F466}": [
"family_woman_woman_boy",
"home",
"parents",
"people",
"human",
"children",
"child",
"female",
"lesbian",
"lgbt",
"mother",
"mothers",
"pride",
"son",
"two",
"women"
],
"\u{1F469}\u200D\u{1F469}\u200D\u{1F467}": [
"family_woman_woman_girl",
"home",
"parents",
"people",
"human",
"children",
"child",
"daughter",
"female",
"lesbian",
"lgbt",
"mother",
"mothers",
"pride",
"two",
"women"
],
"\u{1F469}\u200D\u{1F469}\u200D\u{1F467}\u200D\u{1F466}": [
"family_woman_woman_girl_boy",
"home",
"parents",
"people",
"human",
"children",
"child",
"daughter",
"female",
"lesbian",
"lgbt",
"mother",
"mothers",
"pride",
"son",
"two",
"women"
],
"\u{1F469}\u200D\u{1F469}\u200D\u{1F466}\u200D\u{1F466}": [
"family_woman_woman_boy_boy",
"home",
"parents",
"people",
"human",
"children",
"child",
"female",
"lesbian",
"lgbt",
"mother",
"mothers",
"pride",
"sons",
"two",
"women"
],
"\u{1F469}\u200D\u{1F469}\u200D\u{1F467}\u200D\u{1F467}": [
"family_woman_woman_girl_girl",
"home",
"parents",
"people",
"human",
"children",
"child",
"daughters",
"female",
"lesbian",
"lgbt",
"mother",
"mothers",
"pride",
"two",
"women"
],
"\u{1F468}\u200D\u{1F466}": [
"family_man_boy",
"home",
"parent",
"people",
"human",
"child",
"father",
"male",
"men",
"son"
],
"\u{1F468}\u200D\u{1F466}\u200D\u{1F466}": [
"family_man_boy_boy",
"home",
"parent",
"people",
"human",
"children",
"child",
"father",
"male",
"men",
"sons",
"two"
],
"\u{1F468}\u200D\u{1F467}": [
"family_man_girl",
"home",
"parent",
"people",
"human",
"child",
"daughter",
"father",
"female",
"male"
],
"\u{1F468}\u200D\u{1F467}\u200D\u{1F466}": [
"family_man_girl_boy",
"home",
"parent",
"people",
"human",
"children",
"child",
"daughter",
"father",
"male",
"men",
"son"
],
"\u{1F468}\u200D\u{1F467}\u200D\u{1F467}": [
"family_man_girl_girl",
"home",
"parent",
"people",
"human",
"children",
"child",
"daughters",
"father",
"female",
"male",
"two"
],
"\u{1F469}\u200D\u{1F466}": [
"family_woman_boy",
"home",
"parent",
"people",
"human",
"child",
"female",
"mother",
"son",
"women"
],
"\u{1F469}\u200D\u{1F466}\u200D\u{1F466}": [
"family_woman_boy_boy",
"home",
"parent",
"people",
"human",
"children",
"child",
"female",
"mother",
"sons",
"two",
"women"
],
"\u{1F469}\u200D\u{1F467}": [
"family_woman_girl",
"home",
"parent",
"people",
"human",
"child",
"daughter",
"female",
"mother",
"women"
],
"\u{1F469}\u200D\u{1F467}\u200D\u{1F466}": [
"family_woman_girl_boy",
"home",
"parent",
"people",
"human",
"children",
"child",
"daughter",
"female",
"male",
"mother",
"son"
],
"\u{1F469}\u200D\u{1F467}\u200D\u{1F467}": [
"family_woman_girl_girl",
"home",
"parent",
"people",
"human",
"children",
"child",
"daughters",
"female",
"mother",
"two",
"women"
],
"\u{1F5E3}\uFE0F": [
"speaking_head",
"user",
"person",
"human",
"sing",
"say",
"talk",
"face",
"mansplaining",
"shout",
"shouting",
"silhouette",
"speak"
],
"\u{1F464}": [
"bust_in_silhouette",
"user",
"person",
"human",
"shadow"
],
"\u{1F465}": [
"busts_in_silhouette",
"user",
"person",
"human",
"group",
"team",
"bust",
"people",
"shadows",
"silhouettes",
"two",
"users",
"contributors"
],
"\u{1F463}": [
"footprints",
"feet",
"tracking",
"walking",
"beach",
"body",
"clothing",
"footprint",
"footsteps",
"print",
"tracks"
],
"\u{1F435}": [
"monkey_face",
"animal",
"nature",
"circus",
"head"
],
"\u{1F412}": [
"monkey",
"animal",
"nature",
"banana",
"circus",
"cheeky"
],
"\u{1F98D}": [
"gorilla",
"animal",
"nature",
"circus"
],
"\u{1F9A7}": [
"orangutan",
"animal",
"ape"
],
"\u{1F436}": [
"dog_face",
"animal",
"friend",
"nature",
"woof",
"puppy",
"pet",
"faithful"
],
"\u{1F415}": [
"dog",
"animal",
"nature",
"friend",
"doge",
"pet",
"faithful",
"dog2",
"doggo"
],
"\u{1F9AE}": [
"guide_dog",
"animal",
"blind",
"accessibility",
"eye",
"seeing"
],
"\u{1F415}\u200D\u{1F9BA}": [
"service_dog",
"blind",
"animal",
"accessibility",
"assistance"
],
"\u{1F429}": [
"poodle",
"dog",
"animal",
"101",
"nature",
"pet",
"miniature",
"standard",
"toy"
],
"\u{1F43A}": [
"wolf",
"animal",
"nature",
"wild",
"face"
],
"\u{1F98A}": [
"fox",
"animal",
"nature",
"face"
],
"\u{1F99D}": [
"raccoon",
"animal",
"nature",
"curious",
"face",
"sly"
],
"\u{1F431}": [
"cat_face",
"animal",
"meow",
"nature",
"pet",
"kitten",
"kitty"
],
"\u{1F408}": [
"cat",
"animal",
"meow",
"pet",
"cats",
"cat2",
"domestic",
"feline",
"housecat"
],
"\u{1F981}": [
"lion",
"animal",
"nature",
"face",
"leo",
"zodiac"
],
"\u{1F42F}": [
"tiger_face",
"animal",
"cat",
"danger",
"wild",
"nature",
"roar",
"cute"
],
"\u{1F405}": [
"tiger",
"animal",
"nature",
"roar",
"bengal",
"tiger2"
],
"\u{1F406}": [
"leopard",
"animal",
"nature",
"african",
"jaguar"
],
"\u{1F434}": [
"horse_face",
"animal",
"brown",
"nature",
"head"
],
"\u{1F40E}": [
"horse",
"animal",
"gamble",
"luck",
"equestrian",
"galloping",
"racehorse",
"racing",
"speed"
],
"\u{1F984}": [
"unicorn",
"animal",
"nature",
"mystical",
"face"
],
"\u{1F993}": [
"zebra",
"animal",
"nature",
"stripes",
"safari",
"face",
"stripe"
],
"\u{1F98C}": [
"deer",
"animal",
"nature",
"horns",
"venison",
"buck",
"reindeer",
"stag"
],
"\u{1F42E}": [
"cow_face",
"beef",
"ox",
"animal",
"nature",
"moo",
"milk",
"happy"
],
"\u{1F402}": [
"ox",
"animal",
"cow",
"beef",
"bull",
"bullock",
"oxen",
"steer",
"taurus",
"zodiac"
],
"\u{1F403}": [
"water_buffalo",
"animal",
"nature",
"ox",
"cow",
"domestic"
],
"\u{1F404}": [
"cow",
"beef",
"ox",
"animal",
"nature",
"moo",
"milk",
"cow2",
"dairy"
],
"\u{1F437}": [
"pig_face",
"animal",
"oink",
"nature",
"head"
],
"\u{1F416}": [
"pig",
"animal",
"nature",
"hog",
"pig2",
"sow"
],
"\u{1F417}": [
"boar",
"animal",
"nature",
"pig",
"warthog",
"wild"
],
"\u{1F43D}": [
"pig_nose",
"animal",
"oink",
"face",
"snout"
],
"\u{1F40F}": [
"ram",
"animal",
"sheep",
"nature",
"aries",
"male",
"zodiac"
],
"\u{1F411}": [
"ewe",
"animal",
"nature",
"wool",
"shipit",
"female",
"lamb",
"sheep"
],
"\u{1F410}": [
"goat",
"animal",
"nature",
"capricorn",
"zodiac"
],
"\u{1F42A}": [
"camel",
"animal",
"hot",
"desert",
"hump",
"arabian",
"bump",
"dromedary",
"one"
],
"\u{1F42B}": [
"two_hump_camel",
"animal",
"nature",
"hot",
"desert",
"hump",
"asian",
"bactrian",
"bump"
],
"\u{1F999}": [
"llama",
"animal",
"nature",
"alpaca",
"guanaco",
"vicu\xF1a",
"wool"
],
"\u{1F992}": [
"giraffe",
"animal",
"nature",
"spots",
"safari",
"face"
],
"\u{1F418}": [
"elephant",
"animal",
"nature",
"nose",
"th",
"circus"
],
"\u{1F98F}": [
"rhinoceros",
"animal",
"nature",
"horn",
"rhino"
],
"\u{1F99B}": [
"hippopotamus",
"animal",
"nature",
"hippo"
],
"\u{1F42D}": [
"mouse_face",
"animal",
"nature",
"cheese_wedge",
"rodent"
],
"\u{1F401}": [
"mouse",
"animal",
"nature",
"rodent",
"dormouse",
"mice",
"mouse2"
],
"\u{1F400}": [
"rat",
"animal",
"mouse",
"rodent"
],
"\u{1F439}": [
"hamster",
"animal",
"nature",
"face",
"pet"
],
"\u{1F430}": [
"rabbit_face",
"animal",
"nature",
"pet",
"spring",
"magic",
"bunny",
"easter"
],
"\u{1F407}": [
"rabbit",
"animal",
"nature",
"pet",
"magic",
"spring",
"bunny",
"rabbit2"
],
"\u{1F43F}\uFE0F": [
"chipmunk",
"animal",
"nature",
"rodent",
"squirrel"
],
"\u{1F994}": [
"hedgehog",
"animal",
"nature",
"spiny",
"face"
],
"\u{1F987}": [
"bat",
"animal",
"nature",
"blind",
"vampire",
"batman"
],
"\u{1F43B}": [
"bear",
"animal",
"nature",
"wild",
"face",
"teddy"
],
"\u{1F428}": [
"koala",
"animal",
"nature",
"bear",
"face",
"marsupial"
],
"\u{1F43C}": [
"panda",
"animal",
"nature",
"face"
],
"\u{1F9A5}": [
"sloth",
"animal",
"lazy",
"slow"
],
"\u{1F9A6}": [
"otter",
"animal",
"fishing",
"playful"
],
"\u{1F9A8}": [
"skunk",
"animal",
"smelly",
"stink"
],
"\u{1F998}": [
"kangaroo",
"animal",
"nature",
"australia",
"joey",
"hop",
"marsupial",
"jump",
"roo"
],
"\u{1F9A1}": [
"badger",
"animal",
"nature",
"honey",
"pester"
],
"\u{1F43E}": [
"paw_prints",
"animal",
"tracking",
"footprints",
"dog",
"cat",
"pet",
"feet",
"kitten",
"print",
"puppy"
],
"\u{1F983}": [
"turkey",
"animal",
"bird",
"thanksgiving",
"wild"
],
"\u{1F414}": [
"chicken",
"animal",
"cluck",
"nature",
"bird",
"hen"
],
"\u{1F413}": [
"rooster",
"animal",
"nature",
"chicken",
"bird",
"cock",
"cockerel"
],
"\u{1F423}": [
"hatching_chick",
"animal",
"chicken",
"egg",
"born",
"baby",
"bird"
],
"\u{1F424}": [
"baby_chick",
"animal",
"chicken",
"bird",
"yellow"
],
"\u{1F425}": [
"front_facing_baby_chick",
"animal",
"chicken",
"baby",
"bird",
"hatched",
"standing"
],
"\u{1F426}": [
"bird",
"animal",
"nature",
"fly",
"tweet",
"spring"
],
"\u{1F427}": [
"penguin",
"animal",
"nature",
"bird"
],
"\u{1F54A}\uFE0F": [
"dove",
"animal",
"bird",
"fly",
"peace"
],
"\u{1F985}": [
"eagle",
"animal",
"nature",
"bird",
"bald"
],
"\u{1F986}": [
"duck",
"animal",
"nature",
"bird",
"mallard"
],
"\u{1F9A2}": [
"swan",
"animal",
"nature",
"bird",
"cygnet",
"duckling",
"ugly"
],
"\u{1F989}": [
"owl",
"animal",
"nature",
"bird",
"hoot",
"wise"
],
"\u{1F9A9}": [
"flamingo",
"animal",
"flamboyant",
"tropical"
],
"\u{1F99A}": [
"peacock",
"animal",
"nature",
"peahen",
"bird",
"ostentatious",
"proud"
],
"\u{1F99C}": [
"parrot",
"animal",
"nature",
"bird",
"pirate",
"talk"
],
"\u{1F438}": [
"frog",
"animal",
"nature",
"croak",
"toad",
"face"
],
"\u{1F40A}": [
"crocodile",
"animal",
"nature",
"reptile",
"lizard",
"alligator",
"croc"
],
"\u{1F422}": [
"turtle",
"animal",
"slow",
"nature",
"tortoise",
"terrapin"
],
"\u{1F98E}": [
"lizard",
"animal",
"nature",
"reptile",
"gecko"
],
"\u{1F40D}": [
"snake",
"animal",
"evil",
"nature",
"hiss",
"python",
"bearer",
"ophiuchus",
"serpent",
"zodiac"
],
"\u{1F432}": [
"dragon_face",
"animal",
"myth",
"nature",
"chinese",
"green",
"fairy",
"head",
"tale"
],
"\u{1F409}": [
"dragon",
"animal",
"myth",
"nature",
"chinese",
"green",
"fairy",
"tale"
],
"\u{1F995}": [
"sauropod",
"animal",
"nature",
"dinosaur",
"brachiosaurus",
"brontosaurus",
"diplodocus",
"extinct"
],
"\u{1F996}": [
"t_rex",
"animal",
"nature",
"dinosaur",
"tyrannosaurus",
"extinct",
"trex"
],
"\u{1F433}": [
"spouting_whale",
"animal",
"nature",
"sea",
"ocean",
"cute",
"face"
],
"\u{1F40B}": [
"whale",
"animal",
"nature",
"sea",
"ocean",
"whale2"
],
"\u{1F42C}": [
"dolphin",
"animal",
"nature",
"fish",
"sea",
"ocean",
"flipper",
"fins",
"beach"
],
"\u{1F41F}": [
"fish",
"animal",
"food",
"nature",
"freshwater",
"pisces",
"zodiac"
],
"\u{1F420}": [
"tropical_fish",
"animal",
"swim",
"ocean",
"beach",
"nemo",
"blue",
"yellow"
],
"\u{1F421}": [
"blowfish",
"animal",
"nature",
"food",
"sea",
"ocean",
"fish",
"fugu",
"pufferfish"
],
"\u{1F988}": [
"shark",
"animal",
"nature",
"fish",
"sea",
"ocean",
"jaws",
"fins",
"beach",
"great",
"white"
],
"\u{1F419}": [
"octopus",
"animal",
"creature",
"ocean",
"sea",
"nature",
"beach"
],
"\u{1F41A}": [
"spiral_shell",
"nature",
"sea",
"beach",
"seashell"
],
"\u{1F40C}": [
"snail",
"slow",
"animal",
"shell",
"garden",
"slug"
],
"\u{1F98B}": [
"butterfly",
"animal",
"insect",
"nature",
"caterpillar",
"pretty"
],
"\u{1F41B}": [
"bug",
"animal",
"insect",
"nature",
"worm",
"caterpillar"
],
"\u{1F41C}": [
"ant",
"animal",
"insect",
"nature",
"bug"
],
"\u{1F41D}": [
"honeybee",
"animal",
"insect",
"nature",
"bug",
"spring",
"honey",
"bee",
"bumblebee"
],
"\u{1F41E}": [
"lady_beetle",
"animal",
"insect",
"nature",
"ladybug",
"bug",
"ladybird"
],
"\u{1F997}": [
"cricket",
"animal",
"chirp",
"grasshopper",
"insect",
"orthoptera"
],
"\u{1F577}\uFE0F": [
"spider",
"animal",
"arachnid",
"insect"
],
"\u{1F578}\uFE0F": [
"spider_web",
"animal",
"insect",
"arachnid",
"silk",
"cobweb",
"spiderweb"
],
"\u{1F982}": [
"scorpion",
"animal",
"arachnid",
"scorpio",
"scorpius",
"zodiac"
],
"\u{1F99F}": [
"mosquito",
"animal",
"nature",
"insect",
"malaria",
"disease",
"fever",
"pest",
"virus"
],
"\u{1F9A0}": [
"microbe",
"amoeba",
"bacteria",
"germs",
"virus",
"covid",
"cell",
"coronavirus",
"germ",
"microorganism"
],
"\u{1F490}": [
"bouquet",
"flowers",
"nature",
"spring",
"flower",
"plant",
"romance"
],
"\u{1F338}": [
"cherry_blossom",
"nature",
"plant",
"spring",
"flower",
"pink",
"sakura"
],
"\u{1F4AE}": [
"white_flower",
"japanese",
"spring",
"blossom",
"cherry",
"doily",
"done",
"paper",
"stamp",
"well"
],
"\u{1F3F5}\uFE0F": [
"rosette",
"flower",
"decoration",
"military",
"plant"
],
"\u{1F339}": [
"rose",
"flowers",
"valentines",
"love",
"spring",
"flower",
"plant",
"red"
],
"\u{1F940}": [
"wilted_flower",
"plant",
"nature",
"flower",
"rose",
"dead",
"drooping"
],
"\u{1F33A}": [
"hibiscus",
"plant",
"vegetable",
"flowers",
"beach",
"flower"
],
"\u{1F33B}": [
"sunflower",
"nature",
"plant",
"fall",
"flower",
"sun",
"yellow"
],
"\u{1F33C}": [
"blossom",
"nature",
"flowers",
"yellow",
"blossoming\xA0flower",
"daisy",
"flower",
"plant"
],
"\u{1F337}": [
"tulip",
"flowers",
"plant",
"nature",
"summer",
"spring",
"flower"
],
"\u{1F331}": [
"seedling",
"plant",
"nature",
"grass",
"lawn",
"spring",
"sprout",
"sprouting",
"young",
"seed"
],
"\u{1F332}": [
"evergreen_tree",
"plant",
"nature",
"fir",
"pine",
"wood"
],
"\u{1F333}": [
"deciduous_tree",
"plant",
"nature",
"rounded",
"shedding",
"wood"
],
"\u{1F334}": [
"palm_tree",
"plant",
"vegetable",
"nature",
"summer",
"beach",
"mojito",
"tropical",
"coconut"
],
"\u{1F335}": [
"cactus",
"vegetable",
"plant",
"nature",
"desert"
],
"\u{1F33E}": [
"sheaf_of_rice",
"nature",
"plant",
"crop",
"ear",
"farming",
"grain",
"wheat"
],
"\u{1F33F}": [
"herb",
"vegetable",
"plant",
"medicine",
"weed",
"grass",
"lawn",
"crop",
"leaf"
],
"\u2618\uFE0F": [
"shamrock",
"vegetable",
"plant",
"nature",
"irish",
"clover",
"trefoil"
],
"\u{1F340}": [
"four_leaf_clover",
"vegetable",
"plant",
"nature",
"lucky",
"irish",
"ireland",
"luck"
],
"\u{1F341}": [
"maple_leaf",
"nature",
"plant",
"vegetable",
"ca",
"fall",
"canada",
"canadian",
"falling"
],
"\u{1F342}": [
"fallen_leaf",
"nature",
"plant",
"vegetable",
"leaves",
"autumn",
"brown",
"fall",
"falling"
],
"\u{1F343}": [
"leaf_fluttering_in_wind",
"nature",
"plant",
"tree",
"vegetable",
"grass",
"lawn",
"spring",
"blow",
"flutter",
"green",
"leaves"
],
"\u{1F347}": [
"grapes",
"fruit",
"food",
"wine",
"grape",
"plant"
],
"\u{1F348}": [
"melon",
"fruit",
"nature",
"food",
"cantaloupe",
"honeydew",
"muskmelon",
"plant"
],
"\u{1F349}": [
"watermelon",
"fruit",
"food",
"picnic",
"summer",
"plant"
],
"\u{1F34A}": [
"tangerine",
"food",
"fruit",
"nature",
"orange",
"mandarin",
"plant"
],
"\u{1F34B}": [
"lemon",
"fruit",
"nature",
"citrus",
"lemonade",
"plant"
],
"\u{1F34C}": [
"banana",
"fruit",
"food",
"monkey",
"plant",
"plantain"
],
"\u{1F34D}": [
"pineapple",
"fruit",
"nature",
"food",
"plant"
],
"\u{1F96D}": [
"mango",
"fruit",
"food",
"tropical"
],
"\u{1F34E}": [
"red_apple",
"fruit",
"mac",
"school",
"delicious",
"plant"
],
"\u{1F34F}": [
"green_apple",
"fruit",
"nature",
"delicious",
"golden",
"granny",
"plant",
"smith"
],
"\u{1F350}": [
"pear",
"fruit",
"nature",
"food",
"plant"
],
"\u{1F351}": [
"peach",
"fruit",
"nature",
"food",
"bottom",
"butt",
"plant"
],
"\u{1F352}": [
"cherries",
"food",
"fruit",
"berries",
"cherry",
"plant",
"red",
"wild"
],
"\u{1F353}": [
"strawberry",
"fruit",
"food",
"nature",
"berry",
"plant"
],
"\u{1F95D}": [
"kiwi_fruit",
"fruit",
"food",
"chinese",
"gooseberry",
"kiwifruit"
],
"\u{1F345}": [
"tomato",
"fruit",
"vegetable",
"nature",
"food",
"plant"
],
"\u{1F965}": [
"coconut",
"fruit",
"nature",
"food",
"palm",
"cocoanut",
"colada",
"pi\xF1a"
],
"\u{1F951}": [
"avocado",
"fruit",
"food"
],
"\u{1F346}": [
"eggplant",
"vegetable",
"nature",
"food",
"aubergine",
"phallic",
"plant",
"purple"
],
"\u{1F954}": [
"potato",
"food",
"tuber",
"vegatable",
"starch",
"baked",
"idaho",
"vegetable"
],
"\u{1F955}": [
"carrot",
"vegetable",
"food",
"orange"
],
"\u{1F33D}": [
"ear_of_corn",
"food",
"vegetable",
"plant",
"cob",
"maize",
"maze"
],
"\u{1F336}\uFE0F": [
"hot_pepper",
"food",
"spicy",
"chilli",
"chili",
"plant"
],
"\u{1F952}": [
"cucumber",
"fruit",
"food",
"pickle",
"gherkin",
"vegetable"
],
"\u{1F96C}": [
"leafy_green",
"food",
"vegetable",
"plant",
"bok choy",
"cabbage",
"kale",
"lettuce",
"chinese",
"cos",
"greens",
"romaine"
],
"\u{1F966}": [
"broccoli",
"fruit",
"food",
"vegetable",
"cabbage",
"wild"
],
"\u{1F9C4}": [
"garlic",
"food",
"spice",
"cook",
"flavoring",
"plant",
"vegetable"
],
"\u{1F9C5}": [
"onion",
"cook",
"food",
"spice",
"flavoring",
"plant",
"vegetable"
],
"\u{1F344}": [
"mushroom",
"plant",
"vegetable",
"fungus",
"shroom",
"toadstool"
],
"\u{1F95C}": [
"peanuts",
"food",
"nut",
"nuts",
"peanut",
"vegetable"
],
"\u{1F330}": [
"chestnut",
"food",
"squirrel",
"acorn",
"nut",
"plant"
],
"\u{1F35E}": [
"bread",
"food",
"wheat",
"breakfast",
"toast",
"loaf"
],
"\u{1F950}": [
"croissant",
"food",
"bread",
"french",
"breakfast",
"crescent",
"roll"
],
"\u{1F956}": [
"baguette_bread",
"food",
"bread",
"french",
"france",
"bakery"
],
"\u{1F968}": [
"pretzel",
"food",
"bread",
"twisted",
"germany",
"bakery",
"soft",
"twist"
],
"\u{1F96F}": [
"bagel",
"food",
"bread",
"bakery",
"schmear",
"jewish_bakery",
"breakfast",
"cheese",
"cream"
],
"\u{1F95E}": [
"pancakes",
"food",
"breakfast",
"flapjacks",
"hotcakes",
"brunch",
"cr\xEApe",
"cr\xEApes",
"hotcake",
"pancake"
],
"\u{1F9C7}": [
"waffle",
"food",
"breakfast",
"brunch",
"indecisive",
"iron"
],
"\u{1F9C0}": [
"cheese_wedge",
"food",
"chadder",
"swiss"
],
"\u{1F356}": [
"meat_on_bone",
"good",
"food",
"drumstick",
"barbecue",
"bbq",
"manga"
],
"\u{1F357}": [
"poultry_leg",
"food",
"meat",
"drumstick",
"bird",
"chicken",
"turkey",
"bone"
],
"\u{1F969}": [
"cut_of_meat",
"food",
"cow",
"meat",
"cut",
"chop",
"lambchop",
"porkchop",
"steak"
],
"\u{1F953}": [
"bacon",
"food",
"breakfast",
"pork",
"pig",
"meat",
"brunch",
"rashers"
],
"\u{1F354}": [
"hamburger",
"meat",
"fast food",
"beef",
"cheeseburger",
"mcdonalds",
"burger king"
],
"\u{1F35F}": [
"french_fries",
"chips",
"snack",
"fast food",
"potato",
"mcdonald's"
],
"\u{1F355}": [
"pizza",
"food",
"party",
"italy",
"cheese",
"pepperoni",
"slice"
],
"\u{1F32D}": [
"hot_dog",
"food",
"frankfurter",
"america",
"hotdog",
"redhot",
"sausage",
"wiener"
],
"\u{1F96A}": [
"sandwich",
"food",
"lunch",
"bread",
"toast",
"bakery",
"cheese",
"deli",
"meat",
"vegetables"
],
"\u{1F32E}": [
"taco",
"food",
"mexican"
],
"\u{1F32F}": [
"burrito",
"food",
"mexican",
"wrap"
],
"\u{1F959}": [
"stuffed_flatbread",
"food",
"flatbread",
"stuffed",
"gyro",
"mediterranean",
"doner",
"falafel",
"kebab",
"pita",
"sandwich",
"shawarma"
],
"\u{1F9C6}": [
"falafel",
"food",
"mediterranean",
"chickpea",
"falfel",
"meatball"
],
"\u{1F95A}": [
"egg",
"food",
"chicken",
"breakfast",
"easter_egg"
],
"\u{1F373}": [
"cooking",
"food",
"breakfast",
"kitchen",
"egg",
"skillet",
"fried",
"frying",
"pan"
],
"\u{1F958}": [
"shallow_pan_of_food",
"food",
"cooking",
"casserole",
"paella",
"skillet",
"curry"
],
"\u{1F372}": [
"pot_of_food",
"food",
"meat",
"soup",
"hot pot",
"bowl",
"stew"
],
"\u{1F963}": [
"bowl_with_spoon",
"food",
"breakfast",
"cereal",
"oatmeal",
"porridge",
"congee",
"tableware"
],
"\u{1F957}": [
"green_salad",
"food",
"healthy",
"lettuce",
"vegetable"
],
"\u{1F37F}": [
"popcorn",
"food",
"movie theater",
"films",
"snack",
"drama",
"corn",
"popping"
],
"\u{1F9C8}": [
"butter",
"food",
"cook",
"dairy"
],
"\u{1F9C2}": [
"salt",
"condiment",
"shaker"
],
"\u{1F96B}": [
"canned_food",
"food",
"soup",
"tomatoes",
"can",
"preserve",
"tin",
"tinned"
],
"\u{1F371}": [
"bento_box",
"food",
"japanese",
"box",
"lunch",
"assets"
],
"\u{1F358}": [
"rice_cracker",
"food",
"japanese",
"snack",
"senbei"
],
"\u{1F359}": [
"rice_ball",
"food",
"japanese",
"onigiri",
"omusubi"
],
"\u{1F35A}": [
"cooked_rice",
"food",
"asian",
"boiled",
"bowl",
"steamed"
],
"\u{1F35B}": [
"curry_rice",
"food",
"spicy",
"hot",
"indian"
],
"\u{1F35C}": [
"steaming_bowl",
"food",
"japanese",
"noodle",
"chopsticks",
"ramen",
"noodles",
"soup"
],
"\u{1F35D}": [
"spaghetti",
"food",
"italian",
"pasta",
"noodle"
],
"\u{1F360}": [
"roasted_sweet_potato",
"food",
"nature",
"plant",
"goguma",
"yam"
],
"\u{1F362}": [
"oden",
"skewer",
"food",
"japanese",
"kebab",
"seafood",
"stick"
],
"\u{1F363}": [
"sushi",
"food",
"fish",
"japanese",
"rice",
"sashimi",
"seafood"
],
"\u{1F364}": [
"fried_shrimp",
"food",
"animal",
"appetizer",
"summer",
"prawn",
"tempura"
],
"\u{1F365}": [
"fish_cake_with_swirl",
"food",
"japan",
"sea",
"beach",
"narutomaki",
"pink",
"swirl",
"kamaboko",
"surimi",
"ramen",
"design",
"fishcake",
"pastry"
],
"\u{1F96E}": [
"moon_cake",
"food",
"autumn",
"dessert",
"festival",
"mooncake",
"yu\xE8b\u01D0ng"
],
"\u{1F361}": [
"dango",
"food",
"dessert",
"sweet",
"japanese",
"barbecue",
"meat",
"balls",
"green",
"pink",
"skewer",
"stick",
"white"
],
"\u{1F95F}": [
"dumpling",
"food",
"empanada",
"pierogi",
"potsticker",
"gyoza",
"gy\u014Dza",
"jiaozi"
],
"\u{1F960}": [
"fortune_cookie",
"food",
"prophecy",
"dessert"
],
"\u{1F961}": [
"takeout_box",
"food",
"leftovers",
"chinese",
"container",
"out",
"oyster",
"pail",
"take"
],
"\u{1F980}": [
"crab",
"animal",
"crustacean",
"cancer",
"zodiac"
],
"\u{1F99E}": [
"lobster",
"animal",
"nature",
"bisque",
"claws",
"seafood"
],
"\u{1F990}": [
"shrimp",
"animal",
"ocean",
"nature",
"seafood",
"food",
"prawn",
"shellfish",
"small"
],
"\u{1F991}": [
"squid",
"animal",
"nature",
"ocean",
"sea",
"food",
"molusc"
],
"\u{1F9AA}": [
"oyster",
"food",
"diving",
"pearl"
],
"\u{1F366}": [
"soft_ice_cream",
"food",
"hot",
"dessert",
"summer",
"icecream",
"mr.",
"serve",
"sweet",
"whippy"
],
"\u{1F367}": [
"shaved_ice",
"hot",
"dessert",
"summer",
"cone",
"snow",
"sweet"
],
"\u{1F368}": [
"ice_cream",
"food",
"hot",
"dessert",
"bowl",
"sweet"
],
"\u{1F369}": [
"doughnut",
"food",
"dessert",
"snack",
"sweet",
"donut",
"breakfast"
],
"\u{1F36A}": [
"cookie",
"food",
"snack",
"oreo",
"chocolate",
"sweet",
"dessert",
"biscuit",
"chip"
],
"\u{1F382}": [
"birthday_cake",
"food",
"dessert",
"cake",
"candles",
"celebration",
"party",
"pastry",
"sweet"
],
"\u{1F370}": [
"shortcake",
"food",
"dessert",
"cake",
"pastry",
"piece",
"slice",
"strawberry",
"sweet"
],
"\u{1F9C1}": [
"cupcake",
"food",
"dessert",
"bakery",
"sweet",
"cake",
"fairy",
"pastry"
],
"\u{1F967}": [
"pie",
"food",
"dessert",
"pastry",
"filling",
"sweet"
],
"\u{1F36B}": [
"chocolate_bar",
"food",
"snack",
"dessert",
"sweet",
"candy"
],
"\u{1F36C}": [
"candy",
"snack",
"dessert",
"sweet",
"lolly"
],
"\u{1F36D}": [
"lollipop",
"food",
"snack",
"candy",
"sweet",
"dessert",
"lollypop",
"sucker"
],
"\u{1F36E}": [
"custard",
"dessert",
"food",
"pudding",
"flan",
"caramel",
"creme",
"sweet"
],
"\u{1F36F}": [
"honey_pot",
"bees",
"sweet",
"kitchen",
"honeypot"
],
"\u{1F37C}": [
"baby_bottle",
"food",
"container",
"milk",
"drink",
"feeding"
],
"\u{1F95B}": [
"glass_of_milk",
"beverage",
"drink",
"cow"
],
"\u2615": [
"hot_beverage",
"beverage",
"caffeine",
"latte",
"espresso",
"coffee",
"mug",
"cafe",
"chocolate",
"drink",
"steaming",
"tea"
],
"\u{1F375}": [
"teacup_without_handle",
"drink",
"bowl",
"breakfast",
"green",
"british",
"beverage",
"cup",
"matcha",
"tea"
],
"\u{1F376}": [
"sake",
"wine",
"drink",
"drunk",
"beverage",
"japanese",
"alcohol",
"booze",
"bar",
"bottle",
"cup",
"rice"
],
"\u{1F37E}": [
"bottle_with_popping_cork",
"drink",
"wine",
"bottle",
"celebration",
"bar",
"bubbly",
"champagne",
"party",
"sparkling"
],
"\u{1F377}": [
"wine_glass",
"drink",
"beverage",
"drunk",
"alcohol",
"booze",
"bar",
"red"
],
"\u{1F378}": [
"cocktail_glass",
"drink",
"drunk",
"alcohol",
"beverage",
"booze",
"mojito",
"bar",
"martini"
],
"\u{1F379}": [
"tropical_drink",
"beverage",
"cocktail",
"summer",
"beach",
"alcohol",
"booze",
"mojito",
"bar",
"fruit",
"punch",
"tiki",
"vacation"
],
"\u{1F37A}": [
"beer_mug",
"relax",
"beverage",
"drink",
"drunk",
"party",
"pub",
"summer",
"alcohol",
"booze",
"bar",
"stein"
],
"\u{1F37B}": [
"clinking_beer_mugs",
"relax",
"beverage",
"drink",
"drunk",
"party",
"pub",
"summer",
"alcohol",
"booze",
"bar",
"beers",
"cheers",
"clink",
"drinks",
"mug"
],
"\u{1F942}": [
"clinking_glasses",
"beverage",
"drink",
"party",
"alcohol",
"celebrate",
"cheers",
"wine",
"champagne",
"toast",
"celebration",
"clink",
"glass"
],
"\u{1F943}": [
"tumbler_glass",
"drink",
"beverage",
"drunk",
"alcohol",
"liquor",
"booze",
"bourbon",
"scotch",
"whisky",
"glass",
"shot",
"rum",
"whiskey"
],
"\u{1F964}": [
"cup_with_straw",
"drink",
"soda",
"go",
"juice",
"malt",
"milkshake",
"pop",
"smoothie",
"soft",
"tableware",
"water"
],
"\u{1F9C3}": [
"beverage_box",
"drink",
"juice",
"straw",
"sweet"
],
"\u{1F9C9}": [
"mate",
"drink",
"tea",
"beverage",
"bombilla",
"chimarr\xE3o",
"cimarr\xF3n",
"mat\xE9",
"yerba"
],
"\u{1F9CA}": [
"ice",
"water",
"cold",
"cube",
"iceberg"
],
"\u{1F962}": [
"chopsticks",
"food",
"hashi",
"jeotgarak",
"kuaizi"
],
"\u{1F37D}\uFE0F": [
"fork_and_knife_with_plate",
"food",
"eat",
"meal",
"lunch",
"dinner",
"restaurant",
"cooking",
"cutlery",
"dining",
"tableware"
],
"\u{1F374}": [
"fork_and_knife",
"cutlery",
"kitchen",
"cooking",
"silverware",
"tableware"
],
"\u{1F944}": [
"spoon",
"cutlery",
"kitchen",
"tableware"
],
"\u{1F52A}": [
"kitchen_knife",
"knife",
"blade",
"cutlery",
"kitchen",
"weapon",
"butchers",
"chop",
"cooking",
"cut",
"hocho",
"tool"
],
"\u{1F3FA}": [
"amphora",
"vase",
"jar",
"aquarius",
"cooking",
"drink",
"jug",
"tool",
"zodiac"
],
"\u{1F30D}": [
"globe_showing_europe_africa",
"globe",
"world",
"earth",
"international",
"planet"
],
"\u{1F30E}": [
"globe_showing_americas",
"globe",
"world",
"USA",
"earth",
"international",
"planet"
],
"\u{1F30F}": [
"globe_showing_asia_australia",
"globe",
"world",
"east",
"earth",
"international",
"planet"
],
"\u{1F310}": [
"globe_with_meridians",
"earth",
"international",
"world",
"internet",
"interweb",
"i18n",
"global",
"web",
"wide",
"www",
"internationalization",
"localization"
],
"\u{1F5FA}\uFE0F": [
"world_map",
"location",
"direction",
"travel"
],
"\u{1F5FE}": [
"map_of_japan",
"nation",
"country",
"japanese",
"asia",
"silhouette"
],
"\u{1F9ED}": [
"compass",
"magnetic",
"navigation",
"orienteering"
],
"\u{1F3D4}\uFE0F": [
"snow_capped_mountain",
"photo",
"nature",
"environment",
"winter",
"cold"
],
"\u26F0\uFE0F": [
"mountain",
"photo",
"nature",
"environment"
],
"\u{1F30B}": [
"volcano",
"photo",
"nature",
"disaster",
"eruption",
"mountain",
"weather"
],
"\u{1F5FB}": [
"mount_fuji",
"photo",
"mountain",
"nature",
"japanese",
"capped",
"san",
"snow"
],
"\u{1F3D5}\uFE0F": [
"camping",
"photo",
"outdoors",
"tent",
"campsite"
],
"\u{1F3D6}\uFE0F": [
"beach_with_umbrella",
"weather",
"summer",
"sunny",
"sand",
"mojito"
],
"\u{1F3DC}\uFE0F": [
"desert",
"photo",
"warm",
"saharah"
],
"\u{1F3DD}\uFE0F": [
"desert_island",
"photo",
"tropical",
"mojito"
],
"\u{1F3DE}\uFE0F": [
"national_park",
"photo",
"environment",
"nature"
],
"\u{1F3DF}\uFE0F": [
"stadium",
"photo",
"place",
"sports",
"concert",
"venue",
"grandstand",
"sport"
],
"\u{1F3DB}\uFE0F": [
"classical_building",
"art",
"culture",
"history"
],
"\u{1F3D7}\uFE0F": [
"building_construction",
"wip",
"working",
"progress",
"crane",
"architectural"
],
"\u{1F9F1}": [
"brick",
"bricks",
"clay",
"construction",
"mortar",
"wall",
"infrastructure"
],
"\u{1F3D8}\uFE0F": [
"houses",
"buildings",
"photo",
"building",
"group",
"house"
],
"\u{1F3DA}\uFE0F": [
"derelict_house",
"abandon",
"evict",
"broken",
"building",
"abandoned",
"haunted",
"old"
],
"\u{1F3E0}": [
"house",
"building",
"home"
],
"\u{1F3E1}": [
"house_with_garden",
"home",
"plant",
"nature",
"building",
"tree"
],
"\u{1F3E2}": [
"office_building",
"building",
"bureau",
"work",
"city",
"high",
"rise"
],
"\u{1F3E3}": [
"japanese_post_office",
"building",
"envelope",
"communication",
"japan",
"mark",
"postal"
],
"\u{1F3E4}": [
"post_office",
"building",
"email",
"european"
],
"\u{1F3E5}": [
"hospital",
"building",
"health",
"surgery",
"doctor",
"cross",
"emergency",
"medical",
"medicine",
"red",
"room"
],
"\u{1F3E6}": [
"bank",
"building",
"money",
"sales",
"cash",
"business",
"enterprise",
"bakkureru",
"bk",
"branch"
],
"\u{1F3E8}": [
"hotel",
"building",
"accomodation",
"checkin",
"accommodation",
"h"
],
"\u{1F3E9}": [
"love_hotel",
"like",
"affection",
"dating",
"building",
"heart",
"hospital"
],
"\u{1F3EA}": [
"convenience_store",
"building",
"shopping",
"groceries",
"corner",
"e",
"eleven\xAE",
"hour",
"kwik",
"mart",
"shop"
],
"\u{1F3EB}": [
"school",
"building",
"student",
"education",
"learn",
"teach",
"clock",
"elementary",
"high",
"middle",
"tower"
],
"\u{1F3EC}": [
"department_store",
"building",
"shopping",
"mall",
"center",
"shops"
],
"\u{1F3ED}": [
"factory",
"building",
"industry",
"pollution",
"smoke",
"industrial",
"smog"
],
"\u{1F3EF}": [
"japanese_castle",
"photo",
"building",
"fortress"
],
"\u{1F3F0}": [
"castle",
"building",
"royalty",
"history",
"european",
"turrets"
],
"\u{1F492}": [
"wedding",
"love",
"like",
"affection",
"couple",
"marriage",
"bride",
"groom",
"activity",
"chapel",
"church",
"heart",
"romance"
],
"\u{1F5FC}": [
"tokyo_tower",
"photo",
"japanese",
"eiffel",
"red"
],
"\u{1F5FD}": [
"statue_of_liberty",
"american",
"newyork",
"new",
"york"
],
"\u26EA": [
"church",
"building",
"religion",
"christ",
"christian",
"cross"
],
"\u{1F54C}": [
"mosque",
"islam",
"worship",
"minaret",
"domed",
"muslim",
"religion",
"roof"
],
"\u{1F6D5}": [
"hindu_temple",
"religion"
],
"\u{1F54D}": [
"synagogue",
"judaism",
"worship",
"temple",
"jewish",
"jew",
"religion",
"synagog"
],
"\u26E9\uFE0F": [
"shinto_shrine",
"temple",
"japan",
"kyoto",
"kami",
"michi",
"no",
"religion"
],
"\u{1F54B}": [
"kaaba",
"mecca",
"mosque",
"islam",
"muslim",
"religion"
],
"\u26F2": [
"fountain",
"photo",
"summer",
"water",
"fresh",
"feature",
"park"
],
"\u26FA": [
"tent",
"photo",
"camping",
"outdoors"
],
"\u{1F301}": [
"foggy",
"photo",
"mountain",
"bridge",
"city",
"fog",
"fog\xA0bridge",
"karl",
"under",
"weather"
],
"\u{1F303}": [
"night_with_stars",
"evening",
"city",
"downtown",
"star",
"starry",
"weather"
],
"\u{1F3D9}\uFE0F": [
"cityscape",
"photo",
"night life",
"urban",
"building",
"city",
"skyline"
],
"\u{1F304}": [
"sunrise_over_mountains",
"view",
"vacation",
"photo",
"morning",
"mountain",
"sun",
"weather"
],
"\u{1F305}": [
"sunrise",
"morning",
"view",
"vacation",
"photo",
"sun",
"sunset",
"weather"
],
"\u{1F306}": [
"cityscape_at_dusk",
"photo",
"evening",
"sky",
"buildings",
"building",
"city",
"landscape",
"orange",
"sun",
"sunset",
"weather"
],
"\u{1F307}": [
"sunset",
"photo",
"good morning",
"dawn",
"building",
"buildings",
"city",
"dusk",
"over",
"sun",
"sunrise",
"weather"
],
"\u{1F309}": [
"bridge_at_night",
"photo",
"sanfrancisco",
"gate",
"golden",
"weather"
],
"\u2668\uFE0F": [
"hot_springs",
"bath",
"warm",
"relax",
"hotsprings",
"onsen",
"steam",
"steaming"
],
"\u{1F3A0}": [
"carousel_horse",
"photo",
"carnival",
"activity",
"entertainment",
"fairground",
"go",
"merry",
"round"
],
"\u{1F3A1}": [
"ferris_wheel",
"photo",
"carnival",
"londoneye",
"activity",
"amusement",
"big",
"entertainment",
"fairground",
"observation",
"park"
],
"\u{1F3A2}": [
"roller_coaster",
"carnival",
"playground",
"photo",
"fun",
"activity",
"amusement",
"entertainment",
"park",
"rollercoaster",
"theme"
],
"\u{1F488}": [
"barber_pole",
"hair",
"salon",
"style",
"barber's",
"haircut",
"hairdresser",
"shop",
"stripes"
],
"\u{1F3AA}": [
"circus_tent",
"festival",
"carnival",
"party",
"activity",
"big",
"entertainment",
"top"
],
"\u{1F682}": [
"locomotive",
"transportation",
"vehicle",
"train",
"engine",
"railway",
"steam"
],
"\u{1F683}": [
"railway_car",
"transportation",
"vehicle",
"carriage",
"electric",
"railcar",
"railroad",
"train",
"tram",
"trolleybus",
"wagon"
],
"\u{1F684}": [
"high_speed_train",
"transportation",
"vehicle",
"bullettrain",
"railway",
"shinkansen",
"side"
],
"\u{1F685}": [
"bullet_train",
"transportation",
"vehicle",
"speed",
"fast",
"public",
"travel",
"bullettrain",
"front",
"high",
"nose",
"railway",
"shinkansen"
],
"\u{1F686}": [
"train",
"transportation",
"vehicle",
"diesel",
"electric",
"passenger",
"railway",
"regular",
"train2"
],
"\u{1F687}": [
"metro",
"transportation",
"blue-square",
"mrt",
"underground",
"tube",
"subway",
"train",
"vehicle"
],
"\u{1F688}": [
"light_rail",
"transportation",
"vehicle",
"railway"
],
"\u{1F689}": [
"station",
"transportation",
"vehicle",
"public",
"platform",
"railway",
"train"
],
"\u{1F68A}": [
"tram",
"transportation",
"vehicle",
"trolleybus"
],
"\u{1F69D}": [
"monorail",
"transportation",
"vehicle"
],
"\u{1F69E}": [
"mountain_railway",
"transportation",
"vehicle",
"car",
"funicular",
"train"
],
"\u{1F68B}": [
"tram_car",
"transportation",
"vehicle",
"carriage",
"public",
"travel",
"train",
"trolleybus"
],
"\u{1F68C}": [
"bus",
"car",
"vehicle",
"transportation",
"school"
],
"\u{1F68D}": [
"oncoming_bus",
"vehicle",
"transportation",
"front"
],
"\u{1F68E}": [
"trolleybus",
"bart",
"transportation",
"vehicle",
"bus",
"electric\xA0bus",
"tram",
"trolley"
],
"\u{1F690}": [
"minibus",
"vehicle",
"car",
"transportation",
"bus",
"minivan",
"mover",
"people"
],
"\u{1F691}": [
"ambulance",
"health",
"911",
"hospital",
"vehicle"
],
"\u{1F692}": [
"fire_engine",
"transportation",
"cars",
"vehicle",
"department",
"truck"
],
"\u{1F693}": [
"police_car",
"vehicle",
"cars",
"transportation",
"law",
"legal",
"enforcement",
"cop",
"patrol",
"side"
],
"\u{1F694}": [
"oncoming_police_car",
"vehicle",
"law",
"legal",
"enforcement",
"911",
"front\xA0of",
"\u{1F693}\xA0cop"
],
"\u{1F695}": [
"taxi",
"uber",
"vehicle",
"cars",
"transportation",
"new",
"side",
"taxicab",
"york"
],
"\u{1F696}": [
"oncoming_taxi",
"vehicle",
"cars",
"uber",
"front",
"taxicab"
],
"\u{1F697}": [
"automobile",
"red",
"transportation",
"vehicle",
"car",
"side"
],
"\u{1F698}": [
"oncoming_automobile",
"car",
"vehicle",
"transportation",
"front"
],
"\u{1F699}": [
"sport_utility_vehicle",
"transportation",
"vehicle",
"blue",
"campervan",
"car",
"motorhome",
"recreational",
"rv"
],
"\u{1F69A}": [
"delivery_truck",
"cars",
"transportation",
"vehicle",
"resources"
],
"\u{1F69B}": [
"articulated_lorry",
"vehicle",
"cars",
"transportation",
"express",
"green",
"semi",
"truck"
],
"\u{1F69C}": [
"tractor",
"vehicle",
"car",
"farming",
"agriculture",
"farm"
],
"\u{1F3CE}\uFE0F": [
"racing_car",
"sports",
"race",
"fast",
"formula",
"f1",
"one"
],
"\u{1F3CD}\uFE0F": [
"motorcycle",
"race",
"sports",
"fast",
"motorbike",
"racing"
],
"\u{1F6F5}": [
"motor_scooter",
"vehicle",
"vespa",
"sasha",
"bike",
"cycle"
],
"\u{1F9BD}": [
"manual_wheelchair",
"accessibility"
],
"\u{1F9BC}": [
"motorized_wheelchair",
"accessibility"
],
"\u{1F6FA}": [
"auto_rickshaw",
"move",
"transportation",
"tuk"
],
"\u{1F6B2}": [
"bicycle",
"bike",
"sports",
"exercise",
"hipster",
"push",
"vehicle"
],
"\u{1F6F4}": [
"kick_scooter",
"vehicle",
"kick",
"razor"
],
"\u{1F6F9}": [
"skateboard",
"board",
"skate"
],
"\u{1F68F}": [
"bus_stop",
"transportation",
"wait",
"busstop"
],
"\u{1F6E3}\uFE0F": [
"motorway",
"road",
"cupertino",
"interstate",
"highway"
],
"\u{1F6E4}\uFE0F": [
"railway_track",
"train",
"transportation"
],
"\u{1F6E2}\uFE0F": [
"oil_drum",
"barrell"
],
"\u26FD": [
"fuel_pump",
"gas station",
"petroleum",
"diesel",
"fuelpump",
"petrol"
],
"\u{1F6A8}": [
"police_car_light",
"police",
"ambulance",
"911",
"emergency",
"alert",
"error",
"pinged",
"law",
"legal",
"beacon",
"cars",
"car\u2019s",
"emergency\xA0light",
"flashing",
"revolving",
"rotating",
"siren",
"vehicle",
"warning"
],
"\u{1F6A5}": [
"horizontal_traffic_light",
"transportation",
"signal"
],
"\u{1F6A6}": [
"vertical_traffic_light",
"transportation",
"driving",
"semaphore",
"signal"
],
"\u{1F6D1}": [
"stop_sign",
"stop",
"octagonal"
],
"\u{1F6A7}": [
"construction",
"wip",
"progress",
"caution",
"warning",
"barrier",
"black",
"roadwork",
"sign",
"striped",
"yellow",
"work_in_progress"
],
"\u2693": [
"anchor",
"ship",
"ferry",
"sea",
"boat",
"admiralty",
"fisherman",
"pattern",
"tool"
],
"\u26F5": [
"sailboat",
"ship",
"summer",
"transportation",
"water",
"sailing",
"boat",
"dinghy",
"resort",
"sea",
"vehicle",
"yacht"
],
"\u{1F6F6}": [
"canoe",
"boat",
"paddle",
"water",
"ship"
],
"\u{1F6A4}": [
"speedboat",
"ship",
"transportation",
"vehicle",
"summer",
"boat",
"motorboat",
"powerboat"
],
"\u{1F6F3}\uFE0F": [
"passenger_ship",
"yacht",
"cruise",
"ferry",
"vehicle"
],
"\u26F4\uFE0F": [
"ferry",
"boat",
"ship",
"yacht",
"passenger"
],
"\u{1F6E5}\uFE0F": [
"motor_boat",
"ship",
"motorboat",
"vehicle"
],
"\u{1F6A2}": [
"ship",
"transportation",
"titanic",
"deploy",
"boat",
"cruise",
"passenger",
"vehicle"
],
"\u2708\uFE0F": [
"airplane",
"vehicle",
"transportation",
"flight",
"fly",
"aeroplane",
"plane"
],
"\u{1F6E9}\uFE0F": [
"small_airplane",
"flight",
"transportation",
"fly",
"vehicle",
"aeroplane",
"plane"
],
"\u{1F6EB}": [
"airplane_departure",
"airport",
"flight",
"landing",
"aeroplane",
"departures",
"off",
"plane",
"taking",
"vehicle"
],
"\u{1F6EC}": [
"airplane_arrival",
"airport",
"flight",
"boarding",
"aeroplane",
"arrivals",
"arriving",
"landing",
"plane",
"vehicle"
],
"\u{1FA82}": [
"parachute",
"fly",
"glide",
"hang",
"parasail",
"skydive"
],
"\u{1F4BA}": [
"seat",
"sit",
"airplane",
"transport",
"bus",
"flight",
"fly",
"aeroplane",
"chair",
"train"
],
"\u{1F681}": [
"helicopter",
"transportation",
"vehicle",
"fly"
],
"\u{1F69F}": [
"suspension_railway",
"vehicle",
"transportation"
],
"\u{1F6A0}": [
"mountain_cableway",
"transportation",
"vehicle",
"ski",
"cable",
"gondola"
],
"\u{1F6A1}": [
"aerial_tramway",
"transportation",
"vehicle",
"ski",
"cable",
"car",
"gondola",
"ropeway"
],
"\u{1F6F0}\uFE0F": [
"satellite",
"communication",
"gps",
"orbit",
"spaceflight",
"NASA",
"ISS",
"artificial",
"space",
"vehicle"
],
"\u{1F680}": [
"rocket",
"launch",
"ship",
"staffmode",
"NASA",
"outer space",
"outer_space",
"fly",
"shuttle",
"vehicle",
"deploy"
],
"\u{1F6F8}": [
"flying_saucer",
"transportation",
"vehicle",
"ufo",
"alien",
"extraterrestrial",
"fantasy",
"space"
],
"\u{1F6CE}\uFE0F": [
"bellhop_bell",
"service",
"hotel"
],
"\u{1F9F3}": [
"luggage",
"packing",
"travel",
"suitcase"
],
"\u231B": [
"hourglass_done",
"time",
"clock",
"oldschool",
"limit",
"exam",
"quiz",
"test",
"sand",
"timer"
],
"\u23F3": [
"hourglass_not_done",
"oldschool",
"time",
"countdown",
"flowing",
"sand",
"timer"
],
"\u231A": [
"watch",
"time",
"accessories",
"apple",
"clock",
"timepiece",
"wrist",
"wristwatch"
],
"\u23F0": [
"alarm_clock",
"time",
"wake",
"morning"
],
"\u23F1\uFE0F": [
"stopwatch",
"time",
"deadline",
"clock"
],
"\u23F2\uFE0F": [
"timer_clock",
"alarm"
],
"\u{1F570}\uFE0F": [
"mantelpiece_clock",
"time"
],
"\u{1F55B}": [
"twelve_o_clock",
"12",
"00:00",
"0000",
"12:00",
"1200",
"time",
"noon",
"midnight",
"midday",
"late",
"early",
"schedule",
"clock12",
"face",
"oclock",
"o\u2019clock"
],
"\u{1F567}": [
"twelve_thirty",
"00:30",
"0030",
"12:30",
"1230",
"time",
"late",
"early",
"schedule",
"clock",
"clock1230",
"face"
],
"\u{1F550}": [
"one_o_clock",
"1",
"1:00",
"100",
"13:00",
"1300",
"time",
"late",
"early",
"schedule",
"clock1",
"face",
"oclock",
"o\u2019clock"
],
"\u{1F55C}": [
"one_thirty",
"1:30",
"130",
"13:30",
"1330",
"time",
"late",
"early",
"schedule",
"clock",
"clock130",
"face"
],
"\u{1F551}": [
"two_o_clock",
"2",
"2:00",
"200",
"14:00",
"1400",
"time",
"late",
"early",
"schedule",
"clock2",
"face",
"oclock",
"o\u2019clock"
],
"\u{1F55D}": [
"two_thirty",
"2:30",
"230",
"14:30",
"1430",
"time",
"late",
"early",
"schedule",
"clock",
"clock230",
"face"
],
"\u{1F552}": [
"three_o_clock",
"3",
"3:00",
"300",
"15:00",
"1500",
"time",
"late",
"early",
"schedule",
"clock3",
"face",
"oclock",
"o\u2019clock"
],
"\u{1F55E}": [
"three_thirty",
"3:30",
"330",
"15:30",
"1530",
"time",
"late",
"early",
"schedule",
"clock",
"clock330",
"face"
],
"\u{1F553}": [
"four_o_clock",
"4",
"4:00",
"400",
"16:00",
"1600",
"time",
"late",
"early",
"schedule",
"clock4",
"face",
"oclock",
"o\u2019clock"
],
"\u{1F55F}": [
"four_thirty",
"4:30",
"430",
"16:30",
"1630",
"time",
"late",
"early",
"schedule",
"clock",
"clock430",
"face"
],
"\u{1F554}": [
"five_o_clock",
"5",
"5:00",
"500",
"17:00",
"1700",
"time",
"late",
"early",
"schedule",
"clock5",
"face",
"oclock",
"o\u2019clock"
],
"\u{1F560}": [
"five_thirty",
"5:30",
"530",
"17:30",
"1730",
"time",
"late",
"early",
"schedule",
"clock",
"clock530",
"face"
],
"\u{1F555}": [
"six_o_clock",
"6",
"6:00",
"600",
"18:00",
"1800",
"time",
"late",
"early",
"schedule",
"dawn",
"dusk",
"clock6",
"face",
"oclock",
"o\u2019clock"
],
"\u{1F561}": [
"six_thirty",
"6:30",
"630",
"18:30",
"1830",
"time",
"late",
"early",
"schedule",
"clock",
"clock630",
"face"
],
"\u{1F556}": [
"seven_o_clock",
"7",
"7:00",
"700",
"19:00",
"1900",
"time",
"late",
"early",
"schedule",
"clock7",
"face",
"oclock",
"o\u2019clock"
],
"\u{1F562}": [
"seven_thirty",
"7:30",
"730",
"19:30",
"1930",
"time",
"late",
"early",
"schedule",
"clock",
"clock730",
"face"
],
"\u{1F557}": [
"eight_o_clock",
"8",
"8:00",
"800",
"20:00",
"2000",
"time",
"late",
"early",
"schedule",
"clock8",
"face",
"oclock",
"o\u2019clock"
],
"\u{1F563}": [
"eight_thirty",
"8:30",
"830",
"20:30",
"2030",
"time",
"late",
"early",
"schedule",
"clock",
"clock830",
"face"
],
"\u{1F558}": [
"nine_o_clock",
"9",
"9:00",
"900",
"21:00",
"2100",
"time",
"late",
"early",
"schedule",
"clock9",
"face",
"oclock",
"o\u2019clock"
],
"\u{1F564}": [
"nine_thirty",
"9:30",
"930",
"21:30",
"2130",
"time",
"late",
"early",
"schedule",
"clock",
"clock930",
"face"
],
"\u{1F559}": [
"ten_o_clock",
"10",
"10:00",
"1000",
"22:00",
"2200",
"time",
"late",
"early",
"schedule",
"clock10",
"face",
"oclock",
"o\u2019clock"
],
"\u{1F565}": [
"ten_thirty",
"10:30",
"1030",
"22:30",
"2230",
"time",
"late",
"early",
"schedule",
"clock",
"clock1030",
"face"
],
"\u{1F55A}": [
"eleven_o_clock",
"11",
"11:00",
"1100",
"23:00",
"2300",
"time",
"late",
"early",
"schedule",
"clock11",
"face",
"oclock",
"o\u2019clock"
],
"\u{1F566}": [
"eleven_thirty",
"11:30",
"1130",
"23:30",
"2330",
"time",
"late",
"early",
"schedule",
"clock",
"clock1130",
"face"
],
"\u{1F311}": [
"new_moon",
"nature",
"twilight",
"planet",
"space",
"night",
"evening",
"sleep",
"dark",
"eclipse",
"shadow\xA0moon",
"solar",
"symbol",
"weather"
],
"\u{1F312}": [
"waxing_crescent_moon",
"nature",
"twilight",
"planet",
"space",
"night",
"evening",
"sleep",
"symbol",
"weather"
],
"\u{1F313}": [
"first_quarter_moon",
"nature",
"twilight",
"planet",
"space",
"night",
"evening",
"sleep",
"symbol",
"weather"
],
"\u{1F314}": [
"waxing_gibbous_moon",
"nature",
"night",
"sky",
"gray",
"twilight",
"planet",
"space",
"evening",
"sleep",
"symbol",
"weather"
],
"\u{1F315}": [
"full_moon",
"nature",
"yellow",
"twilight",
"planet",
"space",
"night",
"evening",
"sleep",
"symbol",
"weather"
],
"\u{1F316}": [
"waning_gibbous_moon",
"nature",
"twilight",
"planet",
"space",
"night",
"evening",
"sleep",
"waxing_gibbous_moon",
"symbol",
"weather"
],
"\u{1F317}": [
"last_quarter_moon",
"nature",
"twilight",
"planet",
"space",
"night",
"evening",
"sleep",
"symbol",
"weather"
],
"\u{1F318}": [
"waning_crescent_moon",
"nature",
"twilight",
"planet",
"space",
"night",
"evening",
"sleep",
"symbol",
"weather"
],
"\u{1F319}": [
"crescent_moon",
"night",
"sleep",
"sky",
"evening",
"magic",
"space",
"weather"
],
"\u{1F31A}": [
"new_moon_face",
"nature",
"twilight",
"planet",
"space",
"night",
"evening",
"sleep",
"creepy",
"dark",
"molester",
"weather"
],
"\u{1F31B}": [
"first_quarter_moon_face",
"nature",
"twilight",
"planet",
"space",
"night",
"evening",
"sleep",
"weather"
],
"\u{1F31C}": [
"last_quarter_moon_face",
"nature",
"twilight",
"planet",
"space",
"night",
"evening",
"sleep",
"weather"
],
"\u{1F321}\uFE0F": [
"thermometer",
"weather",
"temperature",
"hot",
"cold"
],
"\u2600\uFE0F": [
"sun",
"weather",
"nature",
"brightness",
"summer",
"beach",
"spring",
"black",
"bright",
"rays",
"space",
"sunny",
"sunshine"
],
"\u{1F31D}": [
"full_moon_face",
"nature",
"twilight",
"planet",
"space",
"night",
"evening",
"sleep",
"bright",
"moonface",
"smiley",
"smiling",
"weather"
],
"\u{1F31E}": [
"sun_with_face",
"nature",
"morning",
"sky",
"bright",
"smiley",
"smiling",
"space",
"summer",
"sunface",
"weather"
],
"\u{1FA90}": [
"ringed_planet",
"outerspace",
"planets",
"saturn",
"saturnine",
"space"
],
"\u2B50": [
"star",
"night",
"yellow",
"gold",
"medium",
"white"
],
"\u{1F31F}": [
"glowing_star",
"night",
"sparkle",
"awesome",
"good",
"magic",
"glittery",
"glow",
"shining",
"star2"
],
"\u{1F320}": [
"shooting_star",
"night",
"photo",
"activity",
"falling",
"meteoroid",
"space",
"stars",
"upon",
"when",
"wish",
"you"
],
"\u{1F30C}": [
"milky_way",
"photo",
"space",
"stars",
"galaxy",
"night",
"sky",
"universe",
"weather"
],
"\u2601\uFE0F": [
"cloud",
"weather",
"sky",
"cloudy",
"overcast"
],
"\u26C5": [
"sun_behind_cloud",
"weather",
"nature",
"cloudy",
"morning",
"fall",
"spring",
"partly",
"sunny"
],
"\u26C8\uFE0F": [
"cloud_with_lightning_and_rain",
"weather",
"lightning",
"thunder"
],
"\u{1F324}\uFE0F": [
"sun_behind_small_cloud",
"weather",
"white"
],
"\u{1F325}\uFE0F": [
"sun_behind_large_cloud",
"weather",
"white"
],
"\u{1F326}\uFE0F": [
"sun_behind_rain_cloud",
"weather",
"white"
],
"\u{1F327}\uFE0F": [
"cloud_with_rain",
"weather"
],
"\u{1F328}\uFE0F": [
"cloud_with_snow",
"weather",
"cold"
],
"\u{1F329}\uFE0F": [
"cloud_with_lightning",
"weather",
"thunder"
],
"\u{1F32A}\uFE0F": [
"tornado",
"weather",
"cyclone",
"twister",
"cloud",
"whirlwind"
],
"\u{1F32B}\uFE0F": [
"fog",
"weather",
"cloud"
],
"\u{1F32C}\uFE0F": [
"wind_face",
"gust",
"air",
"blow",
"blowing",
"cloud",
"mother",
"nature",
"weather"
],
"\u{1F300}": [
"cyclone",
"weather",
"swirl",
"blue",
"cloud",
"vortex",
"spiral",
"whirlpool",
"spin",
"tornado",
"hurricane",
"typhoon",
"dizzy",
"twister"
],
"\u{1F308}": [
"rainbow",
"nature",
"happy",
"unicorn_face",
"photo",
"sky",
"spring",
"gay",
"lgbt",
"pride",
"primary",
"rain",
"weather"
],
"\u{1F302}": [
"closed_umbrella",
"weather",
"rain",
"drizzle",
"clothing",
"collapsed\xA0umbrella",
"pink"
],
"\u2602\uFE0F": [
"umbrella",
"weather",
"spring",
"clothing",
"open",
"rain"
],
"\u2614": [
"umbrella_with_rain_drops",
"rainy",
"weather",
"spring",
"clothing",
"drop",
"raining"
],
"\u26F1\uFE0F": [
"umbrella_on_ground",
"weather",
"summer",
"beach",
"parasol",
"rain",
"sun"
],
"\u26A1": [
"high_voltage",
"thunder",
"weather",
"lightning bolt",
"fast",
"zap",
"danger",
"electric",
"electricity",
"sign",
"thunderbolt",
"speed"
],
"\u2744\uFE0F": [
"snowflake",
"winter",
"season",
"cold",
"weather",
"christmas",
"xmas",
"snow",
"snowing"
],
"\u2603\uFE0F": [
"snowman",
"winter",
"season",
"cold",
"weather",
"christmas",
"xmas",
"frozen",
"snow",
"snowflakes",
"snowing"
],
"\u26C4": [
"snowman_without_snow",
"winter",
"season",
"cold",
"weather",
"christmas",
"xmas",
"frozen",
"without_snow",
"frosty",
"olaf"
],
"\u2604\uFE0F": [
"comet",
"space"
],
"\u{1F525}": [
"fire",
"hot",
"cook",
"flame",
"burn",
"lit",
"snapstreak",
"tool",
"remove"
],
"\u{1F4A7}": [
"droplet",
"water",
"drip",
"faucet",
"spring",
"cold",
"comic",
"drop",
"sweat",
"weather"
],
"\u{1F30A}": [
"water_wave",
"sea",
"water",
"wave",
"nature",
"tsunami",
"disaster",
"beach",
"ocean",
"waves",
"weather"
],
"\u{1F383}": [
"jack_o_lantern",
"halloween",
"light",
"pumpkin",
"creepy",
"fall",
"activity",
"celebration",
"entertainment",
"gourd"
],
"\u{1F384}": [
"christmas_tree",
"festival",
"vacation",
"december",
"xmas",
"celebration",
"activity",
"entertainment",
"xmas\xA0tree"
],
"\u{1F386}": [
"fireworks",
"photo",
"festival",
"carnival",
"congratulations",
"activity",
"celebration",
"entertainment",
"explosion"
],
"\u{1F387}": [
"sparkler",
"stars",
"night",
"shine",
"activity",
"celebration",
"entertainment",
"firework",
"fireworks",
"hanabi",
"senko",
"sparkle"
],
"\u{1F9E8}": [
"firecracker",
"dynamite",
"boom",
"explode",
"explosion",
"explosive",
"fireworks"
],
"\u2728": [
"sparkles",
"stars",
"shine",
"shiny",
"cool",
"awesome",
"good",
"magic",
"entertainment",
"glitter",
"sparkle",
"star"
],
"\u{1F388}": [
"balloon",
"party",
"celebration",
"birthday",
"circus",
"activity",
"entertainment",
"red"
],
"\u{1F389}": [
"party_popper",
"party",
"congratulations",
"birthday",
"magic",
"circus",
"celebration",
"tada",
"activity",
"entertainment",
"hat",
"hooray"
],
"\u{1F38A}": [
"confetti_ball",
"festival",
"party",
"birthday",
"circus",
"activity",
"celebration",
"entertainment"
],
"\u{1F38B}": [
"tanabata_tree",
"plant",
"nature",
"branch",
"summer",
"bamboo",
"wish",
"star_festival",
"tanzaku",
"activity",
"banner",
"celebration",
"entertainment",
"japanese"
],
"\u{1F38D}": [
"pine_decoration",
"japanese",
"plant",
"nature",
"vegetable",
"panda",
"new_years",
"bamboo",
"activity",
"celebration",
"kadomatsu",
"year"
],
"\u{1F38E}": [
"japanese_dolls",
"japanese",
"toy",
"kimono",
"activity",
"celebration",
"doll",
"entertainment",
"festival",
"hinamatsuri",
"imperial"
],
"\u{1F38F}": [
"carp_streamer",
"fish",
"japanese",
"koinobori",
"carp",
"banner",
"activity",
"celebration",
"entertainment",
"flag",
"flags",
"socks",
"wind"
],
"\u{1F390}": [
"wind_chime",
"nature",
"ding",
"spring",
"bell",
"activity",
"celebration",
"entertainment",
"furin",
"jellyfish"
],
"\u{1F391}": [
"moon_viewing_ceremony",
"photo",
"japan",
"asia",
"tsukimi",
"activity",
"autumn",
"celebration",
"dumplings",
"entertainment",
"festival",
"grass",
"harvest",
"mid",
"rice",
"scene"
],
"\u{1F9E7}": [
"red_envelope",
"gift",
"ang",
"good",
"h\xF3ngb\u0101o",
"lai",
"luck",
"money",
"packet",
"pao",
"see"
],
"\u{1F380}": [
"ribbon",
"decoration",
"pink",
"girl",
"bowtie",
"bow",
"celebration"
],
"\u{1F381}": [
"wrapped_gift",
"present",
"birthday",
"christmas",
"xmas",
"box",
"celebration",
"entertainment"
],
"\u{1F397}\uFE0F": [
"reminder_ribbon",
"sports",
"cause",
"support",
"awareness",
"celebration"
],
"\u{1F39F}\uFE0F": [
"admission_tickets",
"sports",
"concert",
"entrance",
"entertainment",
"ticket"
],
"\u{1F3AB}": [
"ticket",
"event",
"concert",
"pass",
"activity",
"admission",
"entertainment",
"stub",
"tour",
"world"
],
"\u{1F396}\uFE0F": [
"military_medal",
"award",
"winning",
"army",
"celebration",
"decoration",
"medallion"
],
"\u{1F3C6}": [
"trophy",
"win",
"award",
"contest",
"place",
"ftw",
"ceremony",
"championship",
"prize",
"winner",
"winners"
],
"\u{1F3C5}": [
"sports_medal",
"award",
"winning",
"gold",
"winner"
],
"\u{1F947}": [
"1st_place_medal",
"award",
"winning",
"first",
"gold"
],
"\u{1F948}": [
"2nd_place_medal",
"award",
"second",
"silver"
],
"\u{1F949}": [
"3rd_place_medal",
"award",
"third",
"bronze"
],
"\u26BD": [
"soccer_ball",
"sports",
"football"
],
"\u26BE": [
"baseball",
"sports",
"balls",
"ball",
"softball"
],
"\u{1F94E}": [
"softball",
"sports",
"balls",
"ball",
"game",
"glove",
"sport",
"underarm"
],
"\u{1F3C0}": [
"basketball",
"sports",
"balls",
"NBA",
"ball",
"hoop",
"orange"
],
"\u{1F3D0}": [
"volleyball",
"sports",
"balls",
"ball",
"game"
],
"\u{1F3C8}": [
"american_football",
"sports",
"balls",
"NFL",
"ball",
"gridiron",
"superbowl"
],
"\u{1F3C9}": [
"rugby_football",
"sports",
"team",
"ball",
"league",
"union"
],
"\u{1F3BE}": [
"tennis",
"sports",
"balls",
"green",
"ball",
"racket",
"racquet"
],
"\u{1F94F}": [
"flying_disc",
"sports",
"frisbee",
"ultimate",
"game",
"golf",
"sport"
],
"\u{1F3B3}": [
"bowling",
"sports",
"fun",
"play",
"ball",
"game",
"pin",
"pins",
"skittles",
"ten"
],
"\u{1F3CF}": [
"cricket_game",
"sports",
"ball",
"bat",
"field"
],
"\u{1F3D1}": [
"field_hockey",
"sports",
"ball",
"game",
"stick"
],
"\u{1F3D2}": [
"ice_hockey",
"sports",
"game",
"puck",
"stick"
],
"\u{1F94D}": [
"lacrosse",
"sports",
"ball",
"stick",
"game",
"goal",
"sport"
],
"\u{1F3D3}": [
"ping_pong",
"sports",
"pingpong",
"ball",
"bat",
"game",
"paddle",
"table",
"tennis"
],
"\u{1F3F8}": [
"badminton",
"sports",
"birdie",
"game",
"racquet",
"shuttlecock"
],
"\u{1F94A}": [
"boxing_glove",
"sports",
"fighting"
],
"\u{1F94B}": [
"martial_arts_uniform",
"judo",
"karate",
"taekwondo"
],
"\u{1F945}": [
"goal_net",
"sports",
"catch"
],
"\u26F3": [
"flag_in_hole",
"sports",
"business",
"flag",
"hole",
"summer",
"golf"
],
"\u26F8\uFE0F": [
"ice_skate",
"sports",
"skating"
],
"\u{1F3A3}": [
"fishing_pole",
"food",
"hobby",
"summer",
"entertainment",
"fish",
"rod"
],
"\u{1F93F}": [
"diving_mask",
"sport",
"ocean",
"scuba",
"snorkeling"
],
"\u{1F3BD}": [
"running_shirt",
"play",
"pageant",
"athletics",
"marathon",
"sash",
"singlet"
],
"\u{1F3BF}": [
"skis",
"sports",
"winter",
"cold",
"snow",
"boot",
"ski",
"skiing"
],
"\u{1F6F7}": [
"sled",
"sleigh",
"luge",
"toboggan",
"sledge"
],
"\u{1F94C}": [
"curling_stone",
"sports",
"game",
"rock"
],
"\u{1F3AF}": [
"direct_hit",
"game",
"play",
"bar",
"target",
"bullseye",
"activity",
"archery",
"bull",
"dart",
"darts",
"entertainment",
"eye"
],
"\u{1FA80}": [
"yo_yo",
"toy",
"fluctuate",
"yoyo"
],
"\u{1FA81}": [
"kite",
"wind",
"fly",
"soar",
"toy"
],
"\u{1F3B1}": [
"pool_8_ball",
"pool",
"hobby",
"game",
"luck",
"magic",
"8ball",
"billiard",
"billiards",
"cue",
"eight",
"snooker"
],
"\u{1F52E}": [
"crystal_ball",
"disco",
"party",
"magic",
"circus",
"fortune_teller",
"clairvoyant",
"fairy",
"fantasy",
"psychic",
"purple",
"tale",
"tool"
],
"\u{1F9FF}": [
"nazar_amulet",
"bead",
"charm",
"boncu\u011Fu",
"evil",
"eye",
"talisman"
],
"\u{1F3AE}": [
"video_game",
"play",
"console",
"PS4",
"controller",
"entertainment",
"gamepad",
"playstation",
"u",
"wii",
"xbox"
],
"\u{1F579}\uFE0F": [
"joystick",
"game",
"play",
"entertainment",
"video"
],
"\u{1F3B0}": [
"slot_machine",
"bet",
"gamble",
"vegas",
"fruit machine",
"luck",
"casino",
"activity",
"gambling",
"game",
"poker"
],
"\u{1F3B2}": [
"game_die",
"dice",
"random",
"tabletop",
"play",
"luck",
"entertainment",
"gambling"
],
"\u{1F9E9}": [
"puzzle_piece",
"interlocking",
"puzzle",
"piece",
"clue",
"jigsaw"
],
"\u{1F9F8}": [
"teddy_bear",
"plush",
"stuffed",
"plaything",
"toy"
],
"\u2660\uFE0F": [
"spade_suit",
"poker",
"cards",
"suits",
"magic",
"black",
"card",
"game",
"spades"
],
"\u2665\uFE0F": [
"heart_suit",
"poker",
"cards",
"magic",
"suits",
"black",
"card",
"game",
"hearts"
],
"\u2666\uFE0F": [
"diamond_suit",
"poker",
"cards",
"magic",
"suits",
"black",
"card",
"diamonds",
"game"
],
"\u2663\uFE0F": [
"club_suit",
"poker",
"cards",
"magic",
"suits",
"black",
"card",
"clubs",
"game"
],
"\u265F\uFE0F": [
"chess_pawn",
"expendable",
"black",
"dupe",
"game",
"piece"
],
"\u{1F0CF}": [
"joker",
"poker",
"cards",
"game",
"play",
"magic",
"black",
"card",
"entertainment",
"playing",
"wildcard"
],
"\u{1F004}": [
"mahjong_red_dragon",
"game",
"play",
"chinese",
"kanji",
"tile"
],
"\u{1F3B4}": [
"flower_playing_cards",
"game",
"sunset",
"red",
"activity",
"card",
"deck",
"entertainment",
"hanafuda",
"hwatu",
"japanese",
"of\xA0cards"
],
"\u{1F3AD}": [
"performing_arts",
"acting",
"theater",
"drama",
"activity",
"art",
"comedy",
"entertainment",
"greek",
"logo",
"mask",
"masks",
"theatre",
"theatre\xA0masks",
"tragedy"
],
"\u{1F5BC}\uFE0F": [
"framed_picture",
"photography",
"art",
"frame",
"museum",
"painting"
],
"\u{1F3A8}": [
"artist_palette",
"design",
"paint",
"draw",
"colors",
"activity",
"art",
"entertainment",
"museum",
"painting",
"improve"
],
"\u{1F9F5}": [
"thread",
"needle",
"sewing",
"spool",
"string",
"crafts"
],
"\u{1F9F6}": [
"yarn",
"ball",
"crochet",
"knit",
"crafts"
],
"\u{1F453}": [
"glasses",
"fashion",
"accessories",
"eyesight",
"nerdy",
"dork",
"geek",
"clothing",
"eye",
"eyeglasses",
"eyewear"
],
"\u{1F576}\uFE0F": [
"sunglasses",
"face",
"cool",
"accessories",
"dark",
"eye",
"eyewear",
"glasses"
],
"\u{1F97D}": [
"goggles",
"eyes",
"protection",
"safety",
"clothing",
"eye",
"swimming",
"welding"
],
"\u{1F97C}": [
"lab_coat",
"doctor",
"experiment",
"scientist",
"chemist",
"clothing"
],
"\u{1F9BA}": [
"safety_vest",
"protection",
"emergency"
],
"\u{1F454}": [
"necktie",
"shirt",
"suitup",
"formal",
"fashion",
"cloth",
"business",
"clothing",
"tie"
],
"\u{1F455}": [
"t_shirt",
"fashion",
"cloth",
"casual",
"shirt",
"tee",
"clothing",
"polo",
"tshirt"
],
"\u{1F456}": [
"jeans",
"fashion",
"shopping",
"clothing",
"denim",
"pants",
"trousers"
],
"\u{1F9E3}": [
"scarf",
"neck",
"winter",
"clothes",
"clothing"
],
"\u{1F9E4}": [
"gloves",
"hands",
"winter",
"clothes",
"clothing",
"hand"
],
"\u{1F9E5}": [
"coat",
"jacket",
"clothing"
],
"\u{1F9E6}": [
"socks",
"stockings",
"clothes",
"clothing",
"pair",
"stocking"
],
"\u{1F457}": [
"dress",
"clothes",
"fashion",
"shopping",
"clothing",
"gown",
"skirt"
],
"\u{1F458}": [
"kimono",
"dress",
"fashion",
"women",
"female",
"japanese",
"clothing",
"dressing",
"gown"
],
"\u{1F97B}": [
"sari",
"dress",
"clothing",
"saree",
"shari"
],
"\u{1FA71}": [
"one_piece_swimsuit",
"fashion",
"bathing",
"clothing",
"suit",
"swim"
],
"\u{1FA72}": [
"briefs",
"clothing",
"bathing",
"brief",
"suit",
"swim",
"swimsuit",
"underwear"
],
"\u{1FA73}": [
"shorts",
"clothing",
"bathing",
"pants",
"suit",
"swim",
"swimsuit",
"underwear"
],
"\u{1F459}": [
"bikini",
"swimming",
"female",
"woman",
"girl",
"fashion",
"beach",
"summer",
"bathers",
"clothing",
"swim",
"swimsuit"
],
"\u{1F45A}": [
"woman_s_clothes",
"fashion",
"shopping_bags",
"female",
"blouse",
"clothing",
"pink",
"shirt",
"womans",
"woman\u2019s"
],
"\u{1F45B}": [
"purse",
"fashion",
"accessories",
"money",
"sales",
"shopping",
"clothing",
"coin",
"wallet"
],
"\u{1F45C}": [
"handbag",
"fashion",
"accessory",
"accessories",
"shopping",
"bag",
"clothing",
"purse",
"women\u2019s"
],
"\u{1F45D}": [
"clutch_bag",
"bag",
"accessories",
"shopping",
"clothing",
"pouch",
"small"
],
"\u{1F6CD}\uFE0F": [
"shopping_bags",
"mall",
"buy",
"purchase",
"bag",
"hotel"
],
"\u{1F392}": [
"backpack",
"student",
"education",
"bag",
"activity",
"rucksack",
"satchel",
"school"
],
"\u{1F45E}": [
"man_s_shoe",
"fashion",
"male",
"brown",
"clothing",
"dress",
"mans",
"man\u2019s"
],
"\u{1F45F}": [
"running_shoe",
"shoes",
"sports",
"sneakers",
"athletic",
"clothing",
"runner",
"sneaker",
"sport",
"tennis",
"trainer"
],
"\u{1F97E}": [
"hiking_boot",
"backpacking",
"camping",
"hiking",
"clothing"
],
"\u{1F97F}": [
"flat_shoe",
"ballet",
"slip-on",
"slipper",
"clothing",
"woman\u2019s"
],
"\u{1F460}": [
"high_heeled_shoe",
"fashion",
"shoes",
"female",
"pumps",
"stiletto",
"clothing",
"heel",
"heels",
"woman"
],
"\u{1F461}": [
"woman_s_sandal",
"shoes",
"fashion",
"flip flops",
"clothing",
"heeled",
"sandals",
"shoe",
"womans",
"woman\u2019s"
],
"\u{1FA70}": [
"ballet_shoes",
"dance",
"clothing",
"pointe",
"shoe"
],
"\u{1F462}": [
"woman_s_boot",
"shoes",
"fashion",
"boots",
"clothing",
"cowgirl",
"heeled",
"high",
"knee",
"shoe",
"womans",
"woman\u2019s"
],
"\u{1F451}": [
"crown",
"king",
"kod",
"leader",
"royalty",
"lord",
"clothing",
"queen",
"royal"
],
"\u{1F452}": [
"woman_s_hat",
"fashion",
"accessories",
"female",
"lady",
"spring",
"bow",
"clothing",
"ladies",
"womans",
"woman\u2019s"
],
"\u{1F3A9}": [
"top_hat",
"magic",
"gentleman",
"classy",
"circus",
"activity",
"clothing",
"entertainment",
"formal",
"groom",
"tophat",
"wear"
],
"\u{1F393}": [
"graduation_cap",
"school",
"college",
"degree",
"university",
"graduation",
"cap",
"hat",
"legal",
"learn",
"education",
"academic",
"activity",
"board",
"celebration",
"clothing",
"graduate",
"mortar",
"square"
],
"\u{1F9E2}": [
"billed_cap",
"cap",
"baseball",
"clothing",
"hat"
],
"\u26D1\uFE0F": [
"rescue_worker_s_helmet",
"construction",
"build",
"aid",
"cross",
"face",
"hat",
"white",
"worker\u2019s"
],
"\u{1F4FF}": [
"prayer_beads",
"dhikr",
"religious",
"clothing",
"necklace",
"religion",
"rosary"
],
"\u{1F484}": [
"lipstick",
"female",
"girl",
"fashion",
"woman",
"cosmetics",
"gloss",
"lip",
"makeup",
"style"
],
"\u{1F48D}": [
"ring",
"wedding",
"propose",
"marriage",
"valentines",
"diamond",
"fashion",
"jewelry",
"gem",
"engagement",
"engaged",
"romance"
],
"\u{1F48E}": [
"gem_stone",
"blue",
"ruby",
"diamond",
"jewelry",
"gemstone",
"jewel",
"romance"
],
"\u{1F507}": [
"muted_speaker",
"sound",
"volume",
"silence",
"quiet",
"cancellation",
"mute",
"off",
"silent",
"stroke"
],
"\u{1F508}": [
"speaker_low_volume",
"sound",
"volume",
"silence",
"broadcast",
"soft"
],
"\u{1F509}": [
"speaker_medium_volume",
"volume",
"speaker",
"broadcast",
"low",
"one",
"reduce",
"sound",
"wave"
],
"\u{1F50A}": [
"speaker_high_volume",
"volume",
"noise",
"noisy",
"speaker",
"broadcast",
"entertainment",
"increase",
"loud",
"sound",
"three",
"waves"
],
"\u{1F4E2}": [
"loudspeaker",
"volume",
"sound",
"address",
"announcement",
"bullhorn",
"communication",
"loud",
"megaphone",
"pa",
"public",
"system"
],
"\u{1F4E3}": [
"megaphone",
"sound",
"speaker",
"volume",
"bullhorn",
"cheering",
"communication",
"mega"
],
"\u{1F4EF}": [
"postal_horn",
"instrument",
"music",
"bugle",
"communication",
"entertainment",
"french",
"post"
],
"\u{1F514}": [
"bell",
"sound",
"notification",
"christmas",
"xmas",
"chime",
"liberty",
"ringer",
"wedding"
],
"\u{1F515}": [
"bell_with_slash",
"sound",
"volume",
"mute",
"quiet",
"silent",
"cancellation",
"disabled",
"forbidden",
"muted",
"no",
"not",
"notifications",
"off",
"prohibited",
"ringer",
"stroke"
],
"\u{1F3BC}": [
"musical_score",
"treble",
"clef",
"compose",
"activity",
"entertainment",
"music",
"sheet"
],
"\u{1F3B5}": [
"musical_note",
"score",
"tone",
"sound",
"activity",
"beamed",
"eighth",
"entertainment",
"music",
"notes",
"pair",
"quavers"
],
"\u{1F3B6}": [
"musical_notes",
"music",
"score",
"activity",
"entertainment",
"multiple",
"note",
"singing"
],
"\u{1F399}\uFE0F": [
"studio_microphone",
"sing",
"recording",
"artist",
"talkshow",
"mic",
"music",
"podcast"
],
"\u{1F39A}\uFE0F": [
"level_slider",
"scale",
"music"
],
"\u{1F39B}\uFE0F": [
"control_knobs",
"dial",
"music"
],
"\u{1F3A4}": [
"microphone",
"sound",
"music",
"PA",
"sing",
"talkshow",
"activity",
"entertainment",
"karaoke",
"mic",
"singing"
],
"\u{1F3A7}": [
"headphone",
"music",
"score",
"gadgets",
"activity",
"earbud",
"earphone",
"earphones",
"entertainment",
"headphones",
"ipod"
],
"\u{1F4FB}": [
"radio",
"communication",
"music",
"podcast",
"program",
"digital",
"entertainment",
"video",
"wireless"
],
"\u{1F3B7}": [
"saxophone",
"music",
"instrument",
"jazz",
"blues",
"activity",
"entertainment",
"sax"
],
"\u{1F3B8}": [
"guitar",
"music",
"instrument",
"acoustic\xA0guitar",
"activity",
"bass",
"electric",
"entertainment",
"rock"
],
"\u{1F3B9}": [
"musical_keyboard",
"piano",
"instrument",
"compose",
"activity",
"entertainment",
"music"
],
"\u{1F3BA}": [
"trumpet",
"music",
"brass",
"activity",
"entertainment",
"horn",
"instrument",
"jazz"
],
"\u{1F3BB}": [
"violin",
"music",
"instrument",
"orchestra",
"symphony",
"activity",
"entertainment",
"quartet",
"smallest",
"string",
"world\u2019s"
],
"\u{1FA95}": [
"banjo",
"music",
"instructment",
"activity",
"entertainment",
"instrument",
"stringed"
],
"\u{1F941}": [
"drum",
"music",
"instrument",
"drumsticks",
"snare"
],
"\u{1F4F1}": [
"mobile_phone",
"technology",
"apple",
"gadgets",
"dial",
"cell",
"communication",
"iphone",
"smartphone",
"telephone",
"responsive_design"
],
"\u{1F4F2}": [
"mobile_phone_with_arrow",
"iphone",
"incoming",
"call",
"calling",
"cell",
"communication",
"left",
"pointing",
"receive",
"rightwards",
"telephone"
],
"\u260E\uFE0F": [
"telephone",
"technology",
"communication",
"dial",
"black",
"phone",
"rotary"
],
"\u{1F4DE}": [
"telephone_receiver",
"technology",
"communication",
"dial",
"call",
"handset",
"phone"
],
"\u{1F4DF}": [
"pager",
"bbcall",
"oldschool",
"90s",
"beeper",
"bleeper",
"communication"
],
"\u{1F4E0}": [
"fax_machine",
"communication",
"technology",
"facsimile"
],
"\u{1F50B}": [
"battery",
"power",
"energy",
"sustain",
"aa",
"phone"
],
"\u{1F50C}": [
"electric_plug",
"charger",
"power",
"ac",
"adaptor",
"cable",
"electricity"
],
"\u{1F4BB}": [
"laptop",
"technology",
"screen",
"display",
"monitor",
"computer",
"desktop",
"notebook",
"pc",
"personal"
],
"\u{1F5A5}\uFE0F": [
"desktop_computer",
"technology",
"computing",
"screen",
"imac"
],
"\u{1F5A8}\uFE0F": [
"printer",
"paper",
"ink",
"computer"
],
"\u2328\uFE0F": [
"keyboard",
"technology",
"computer",
"type",
"input",
"text"
],
"\u{1F5B1}\uFE0F": [
"computer_mouse",
"click",
"button",
"three"
],
"\u{1F5B2}\uFE0F": [
"trackball",
"technology",
"trackpad",
"computer"
],
"\u{1F4BD}": [
"computer_disk",
"technology",
"record",
"data",
"disk",
"90s",
"entertainment",
"minidisc",
"minidisk",
"optical"
],
"\u{1F4BE}": [
"floppy_disk",
"oldschool",
"technology",
"save",
"90s",
"80s",
"computer"
],
"\u{1F4BF}": [
"optical_disk",
"technology",
"dvd",
"disk",
"disc",
"90s",
"cd",
"compact",
"computer",
"rom"
],
"\u{1F4C0}": [
"dvd",
"cd",
"disk",
"disc",
"computer",
"entertainment",
"optical",
"rom",
"video"
],
"\u{1F9EE}": [
"abacus",
"calculation",
"count",
"counting",
"frame",
"math"
],
"\u{1F3A5}": [
"movie_camera",
"film",
"record",
"activity",
"cinema",
"entertainment",
"hollywood",
"video"
],
"\u{1F39E}\uFE0F": [
"film_frames",
"movie",
"cinema",
"entertainment",
"strip"
],
"\u{1F4FD}\uFE0F": [
"film_projector",
"video",
"tape",
"record",
"movie",
"cinema",
"entertainment"
],
"\u{1F3AC}": [
"clapper_board",
"movie",
"film",
"record",
"activity",
"clapboard",
"director",
"entertainment",
"slate"
],
"\u{1F4FA}": [
"television",
"technology",
"program",
"oldschool",
"show",
"entertainment",
"tv",
"video"
],
"\u{1F4F7}": [
"camera",
"gadgets",
"photography",
"digital",
"entertainment",
"photo",
"video"
],
"\u{1F4F8}": [
"camera_with_flash",
"photography",
"gadgets",
"photo",
"video",
"snapshots"
],
"\u{1F4F9}": [
"video_camera",
"film",
"record",
"camcorder",
"entertainment"
],
"\u{1F4FC}": [
"videocassette",
"record",
"video",
"oldschool",
"90s",
"80s",
"entertainment",
"tape",
"vcr",
"vhs"
],
"\u{1F50D}": [
"magnifying_glass_tilted_left",
"search",
"zoom",
"find",
"detective",
"icon",
"mag",
"magnifier",
"pointing",
"tool"
],
"\u{1F50E}": [
"magnifying_glass_tilted_right",
"search",
"zoom",
"find",
"detective",
"icon",
"mag",
"magnifier",
"pointing",
"tool",
"seo"
],
"\u{1F56F}\uFE0F": [
"candle",
"fire",
"wax",
"light"
],
"\u{1F4A1}": [
"light_bulb",
"light",
"electricity",
"idea",
"comic",
"electric"
],
"\u{1F526}": [
"flashlight",
"dark",
"camping",
"sight",
"night",
"electric",
"light",
"tool",
"torch"
],
"\u{1F3EE}": [
"red_paper_lantern",
"light",
"paper",
"halloween",
"spooky",
"asian",
"bar",
"izakaya",
"japanese"
],
"\u{1FA94}": [
"diya_lamp",
"lighting",
"oil"
],
"\u{1F4D4}": [
"notebook_with_decorative_cover",
"classroom",
"notes",
"record",
"paper",
"study",
"book",
"decorated"
],
"\u{1F4D5}": [
"closed_book",
"read",
"library",
"knowledge",
"textbook",
"learn",
"red"
],
"\u{1F4D6}": [
"open_book",
"book",
"read",
"library",
"knowledge",
"literature",
"learn",
"study",
"novel"
],
"\u{1F4D7}": [
"green_book",
"read",
"library",
"knowledge",
"study",
"textbook"
],
"\u{1F4D8}": [
"blue_book",
"read",
"library",
"knowledge",
"learn",
"study",
"textbook"
],
"\u{1F4D9}": [
"orange_book",
"read",
"library",
"knowledge",
"textbook",
"study"
],
"\u{1F4DA}": [
"books",
"literature",
"library",
"study",
"book",
"pile",
"stack"
],
"\u{1F4D3}": [
"notebook",
"stationery",
"record",
"notes",
"paper",
"study",
"black",
"book",
"composition",
"white"
],
"\u{1F4D2}": [
"ledger",
"notes",
"paper",
"binder",
"book",
"bound",
"notebook",
"spiral",
"yellow"
],
"\u{1F4C3}": [
"page_with_curl",
"documents",
"office",
"paper",
"curled",
"curly\xA0page",
"document",
"license"
],
"\u{1F4DC}": [
"scroll",
"documents",
"ancient",
"history",
"paper",
"degree",
"document",
"parchment"
],
"\u{1F4C4}": [
"page_facing_up",
"documents",
"office",
"paper",
"information",
"document",
"printed"
],
"\u{1F4F0}": [
"newspaper",
"press",
"headline",
"communication",
"news",
"paper"
],
"\u{1F5DE}\uFE0F": [
"rolled_up_newspaper",
"press",
"headline",
"delivery",
"news",
"paper",
"roll"
],
"\u{1F4D1}": [
"bookmark_tabs",
"favorite",
"save",
"order",
"tidy",
"mark",
"marker"
],
"\u{1F516}": [
"bookmark",
"favorite",
"label",
"save",
"mark",
"price",
"tag"
],
"\u{1F3F7}\uFE0F": [
"label",
"sale",
"tag"
],
"\u{1F4B0}": [
"money_bag",
"dollar",
"payment",
"coins",
"sale",
"cream",
"moneybag",
"moneybags",
"rich"
],
"\u{1F4B4}": [
"yen_banknote",
"money",
"sales",
"japanese",
"dollar",
"currency",
"bank",
"banknotes",
"bill",
"note",
"sign"
],
"\u{1F4B5}": [
"dollar_banknote",
"money",
"sales",
"bill",
"currency",
"american",
"bank",
"banknotes",
"note",
"sign"
],
"\u{1F4B6}": [
"euro_banknote",
"money",
"sales",
"dollar",
"currency",
"bank",
"banknotes",
"bill",
"note",
"sign"
],
"\u{1F4B7}": [
"pound_banknote",
"british",
"sterling",
"money",
"sales",
"bills",
"uk",
"england",
"currency",
"bank",
"banknotes",
"bill",
"note",
"quid",
"sign",
"twenty"
],
"\u{1F4B8}": [
"money_with_wings",
"dollar",
"bills",
"payment",
"sale",
"bank",
"banknote",
"bill",
"fly",
"flying",
"losing",
"note"
],
"\u{1F4B3}": [
"credit_card",
"money",
"sales",
"dollar",
"bill",
"payment",
"shopping",
"amex",
"bank",
"club",
"diners",
"mastercard",
"subscription",
"visa"
],
"\u{1F9FE}": [
"receipt",
"accounting",
"expenses",
"bookkeeping",
"evidence",
"proof"
],
"\u{1F4B9}": [
"chart_increasing_with_yen",
"green-square",
"graph",
"presentation",
"stats",
"bank",
"currency",
"exchange",
"growth",
"market",
"money",
"rate",
"rise",
"sign",
"trend",
"upward",
"upwards"
],
"\u{1F4B1}": [
"currency_exchange",
"money",
"sales",
"dollar",
"travel",
"bank"
],
"\u{1F4B2}": [
"heavy_dollar_sign",
"money",
"sales",
"payment",
"currency",
"buck"
],
"\u2709\uFE0F": [
"envelope",
"letter",
"postal",
"inbox",
"communication",
"email",
"\u2709\xA0letter"
],
"\u{1F4E7}": [
"e_mail",
"communication",
"inbox",
"email",
"letter",
"symbol"
],
"\u{1F4E8}": [
"incoming_envelope",
"email",
"inbox",
"communication",
"fast",
"letter",
"lines",
"mail",
"receive"
],
"\u{1F4E9}": [
"envelope_with_arrow",
"email",
"communication",
"above",
"down",
"downwards",
"insert",
"letter",
"mail",
"outgoing",
"sent"
],
"\u{1F4E4}": [
"outbox_tray",
"inbox",
"email",
"box",
"communication",
"letter",
"mail",
"sent"
],
"\u{1F4E5}": [
"inbox_tray",
"email",
"documents",
"box",
"communication",
"letter",
"mail",
"receive"
],
"\u{1F4E6}": [
"package",
"mail",
"gift",
"cardboard",
"box",
"moving",
"communication",
"parcel",
"shipping",
"container"
],
"\u{1F4EB}": [
"closed_mailbox_with_raised_flag",
"email",
"inbox",
"communication",
"mail",
"postbox"
],
"\u{1F4EA}": [
"closed_mailbox_with_lowered_flag",
"email",
"communication",
"inbox",
"mail",
"postbox"
],
"\u{1F4EC}": [
"open_mailbox_with_raised_flag",
"email",
"inbox",
"communication",
"mail",
"postbox"
],
"\u{1F4ED}": [
"open_mailbox_with_lowered_flag",
"email",
"inbox",
"communication",
"mail",
"no",
"postbox"
],
"\u{1F4EE}": [
"postbox",
"email",
"letter",
"envelope",
"communication",
"mail",
"mailbox"
],
"\u{1F5F3}\uFE0F": [
"ballot_box_with_ballot",
"election",
"vote",
"voting"
],
"\u270F\uFE0F": [
"pencil",
"stationery",
"write",
"paper",
"writing",
"school",
"study",
"lead",
"pencil2",
"typos"
],
"\u2712\uFE0F": [
"black_nib",
"pen",
"stationery",
"writing",
"write",
"fountain",
"\u2712\xA0fountain"
],
"\u{1F58B}\uFE0F": [
"fountain_pen",
"stationery",
"writing",
"write",
"communication",
"left",
"lower"
],
"\u{1F58A}\uFE0F": [
"pen",
"stationery",
"writing",
"write",
"ballpoint",
"communication",
"left",
"lower"
],
"\u{1F58C}\uFE0F": [
"paintbrush",
"drawing",
"creativity",
"art",
"brush",
"communication",
"left",
"lower",
"painting"
],
"\u{1F58D}\uFE0F": [
"crayon",
"drawing",
"creativity",
"communication",
"left",
"lower"
],
"\u{1F4DD}": [
"memo",
"write",
"documents",
"stationery",
"pencil",
"paper",
"writing",
"legal",
"exam",
"quiz",
"test",
"study",
"compose",
"communication",
"document",
"memorandum",
"note",
"documentation"
],
"\u{1F4BC}": [
"briefcase",
"business",
"documents",
"work",
"law",
"legal",
"job",
"career",
"suitcase"
],
"\u{1F4C1}": [
"file_folder",
"documents",
"business",
"office",
"closed",
"directory",
"manilla"
],
"\u{1F4C2}": [
"open_file_folder",
"documents",
"load"
],
"\u{1F5C2}\uFE0F": [
"card_index_dividers",
"organizing",
"business",
"stationery"
],
"\u{1F4C5}": [
"calendar",
"schedule",
"date",
"day",
"emoji",
"july",
"world"
],
"\u{1F4C6}": [
"tear_off_calendar",
"schedule",
"date",
"planning",
"day",
"desk"
],
"\u{1F5D2}\uFE0F": [
"spiral_notepad",
"memo",
"stationery",
"note",
"pad"
],
"\u{1F5D3}\uFE0F": [
"spiral_calendar",
"date",
"schedule",
"planning",
"pad"
],
"\u{1F4C7}": [
"card_index",
"business",
"stationery",
"rolodex",
"system"
],
"\u{1F4C8}": [
"chart_increasing",
"graph",
"presentation",
"stats",
"recovery",
"business",
"economics",
"money",
"sales",
"good",
"success",
"growth",
"metrics",
"pointing",
"positive\xA0chart",
"trend",
"up",
"upward",
"upwards",
"analytics"
],
"\u{1F4C9}": [
"chart_decreasing",
"graph",
"presentation",
"stats",
"recession",
"business",
"economics",
"money",
"sales",
"bad",
"failure",
"down",
"downwards",
"down\xA0pointing",
"metrics",
"negative\xA0chart",
"trend"
],
"\u{1F4CA}": [
"bar_chart",
"graph",
"presentation",
"stats",
"metrics"
],
"\u{1F4CB}": [
"clipboard",
"stationery",
"documents"
],
"\u{1F4CC}": [
"pushpin",
"stationery",
"mark",
"here",
"location",
"pin",
"tack",
"thumb"
],
"\u{1F4CD}": [
"round_pushpin",
"stationery",
"location",
"map",
"here",
"dropped",
"pin",
"red"
],
"\u{1F4CE}": [
"paperclip",
"documents",
"stationery",
"clippy"
],
"\u{1F587}\uFE0F": [
"linked_paperclips",
"documents",
"stationery",
"communication",
"link",
"paperclip"
],
"\u{1F4CF}": [
"straight_ruler",
"stationery",
"calculate",
"length",
"math",
"school",
"drawing",
"architect",
"sketch",
"edge"
],
"\u{1F4D0}": [
"triangular_ruler",
"stationery",
"math",
"architect",
"sketch",
"set",
"triangle"
],
"\u2702\uFE0F": [
"scissors",
"stationery",
"cut",
"black",
"cutting",
"tool"
],
"\u{1F5C3}\uFE0F": [
"card_file_box",
"business",
"stationery",
"database"
],
"\u{1F5C4}\uFE0F": [
"file_cabinet",
"filing",
"organizing"
],
"\u{1F5D1}\uFE0F": [
"wastebasket",
"bin",
"trash",
"rubbish",
"garbage",
"toss",
"basket",
"can",
"litter",
"wastepaper"
],
"\u{1F512}": [
"locked",
"security",
"password",
"padlock",
"closed",
"lock",
"private",
"privacy"
],
"\u{1F513}": [
"unlocked",
"privacy",
"security",
"lock",
"open",
"padlock",
"unlock"
],
"\u{1F50F}": [
"locked_with_pen",
"security",
"secret",
"fountain",
"ink",
"lock",
"lock\xA0with",
"nib",
"privacy"
],
"\u{1F510}": [
"locked_with_key",
"security",
"privacy",
"closed",
"lock",
"secure",
"secret"
],
"\u{1F511}": [
"key",
"lock",
"door",
"password",
"gold"
],
"\u{1F5DD}\uFE0F": [
"old_key",
"lock",
"door",
"password",
"clue"
],
"\u{1F528}": [
"hammer",
"tools",
"build",
"create",
"claw",
"handyman",
"tool"
],
"\u{1FA93}": [
"axe",
"tool",
"chop",
"cut",
"hatchet",
"split",
"wood"
],
"\u26CF\uFE0F": [
"pick",
"tools",
"dig",
"mining",
"pickaxe",
"tool"
],
"\u2692\uFE0F": [
"hammer_and_pick",
"tools",
"build",
"create",
"tool"
],
"\u{1F6E0}\uFE0F": [
"hammer_and_wrench",
"tools",
"build",
"create",
"spanner",
"tool"
],
"\u{1F5E1}\uFE0F": [
"dagger",
"weapon",
"knife"
],
"\u2694\uFE0F": [
"crossed_swords",
"weapon"
],
"\u{1F52B}": [
"pistol",
"violence",
"weapon",
"revolver",
"gun",
"handgun",
"shoot",
"squirt",
"tool",
"water"
],
"\u{1F3F9}": [
"bow_and_arrow",
"sports",
"archer",
"archery",
"sagittarius",
"tool",
"zodiac"
],
"\u{1F6E1}\uFE0F": [
"shield",
"protection",
"security",
"weapon"
],
"\u{1F527}": [
"wrench",
"tools",
"diy",
"ikea",
"fix",
"maintainer",
"spanner",
"tool"
],
"\u{1F529}": [
"nut_and_bolt",
"handy",
"tools",
"fix",
"screw",
"tool"
],
"\u2699\uFE0F": [
"gear",
"cog",
"cogwheel",
"tool"
],
"\u{1F5DC}\uFE0F": [
"clamp",
"tool",
"compress",
"compression",
"table",
"vice",
"winzip"
],
"\u2696\uFE0F": [
"balance_scale",
"law",
"fairness",
"weight",
"justice",
"libra",
"scales",
"tool",
"zodiac"
],
"\u{1F9AF}": [
"probing_cane",
"accessibility",
"blind",
"white"
],
"\u{1F517}": [
"link",
"rings",
"url",
"chain",
"hyperlink",
"linked",
"symbol"
],
"\u26D3\uFE0F": [
"chains",
"lock",
"arrest",
"chain"
],
"\u{1F9F0}": [
"toolbox",
"tools",
"diy",
"fix",
"maintainer",
"mechanic",
"chest",
"tool"
],
"\u{1F9F2}": [
"magnet",
"attraction",
"magnetic",
"horseshoe"
],
"\u2697\uFE0F": [
"alembic",
"distilling",
"science",
"experiment",
"chemistry",
"tool"
],
"\u{1F9EA}": [
"test_tube",
"chemistry",
"experiment",
"lab",
"science",
"chemist",
"test"
],
"\u{1F9EB}": [
"petri_dish",
"bacteria",
"biology",
"culture",
"lab",
"biologist"
],
"\u{1F9EC}": [
"dna",
"biologist",
"genetics",
"life",
"double",
"evolution",
"gene",
"helix"
],
"\u{1F52C}": [
"microscope",
"laboratory",
"experiment",
"zoomin",
"science",
"study",
"investigate",
"magnify",
"tool"
],
"\u{1F52D}": [
"telescope",
"stars",
"space",
"zoom",
"science",
"astronomy",
"stargazing",
"tool"
],
"\u{1F4E1}": [
"satellite_antenna",
"communication",
"future",
"radio",
"space",
"dish",
"signal"
],
"\u{1F489}": [
"syringe",
"health",
"hospital",
"drugs",
"blood",
"medicine",
"needle",
"doctor",
"nurse",
"shot",
"sick",
"tool",
"vaccination",
"vaccine"
],
"\u{1FA78}": [
"drop_of_blood",
"period",
"hurt",
"harm",
"wound",
"bleed",
"doctor",
"donation",
"injury",
"medicine",
"menstruation"
],
"\u{1F48A}": [
"pill",
"health",
"medicine",
"doctor",
"pharmacy",
"drug",
"capsule",
"drugs",
"sick",
"tablet"
],
"\u{1FA79}": [
"adhesive_bandage",
"heal",
"aid",
"band",
"doctor",
"medicine",
"plaster"
],
"\u{1FA7A}": [
"stethoscope",
"health",
"doctor",
"heart",
"medicine",
"healthcheck"
],
"\u{1F6AA}": [
"door",
"house",
"entry",
"exit",
"doorway",
"front"
],
"\u{1F6CF}\uFE0F": [
"bed",
"sleep",
"rest",
"bedroom",
"hotel"
],
"\u{1F6CB}\uFE0F": [
"couch_and_lamp",
"read",
"chill",
"hotel",
"lounge",
"settee",
"sofa"
],
"\u{1FA91}": [
"chair",
"sit",
"furniture",
"seat"
],
"\u{1F6BD}": [
"toilet",
"restroom",
"wc",
"washroom",
"bathroom",
"potty",
"loo"
],
"\u{1F6BF}": [
"shower",
"clean",
"water",
"bathroom",
"bath",
"head"
],
"\u{1F6C1}": [
"bathtub",
"clean",
"shower",
"bathroom",
"bath",
"bubble"
],
"\u{1FA92}": [
"razor",
"cut",
"sharp",
"shave"
],
"\u{1F9F4}": [
"lotion_bottle",
"moisturizer",
"sunscreen",
"shampoo"
],
"\u{1F9F7}": [
"safety_pin",
"diaper",
"punk",
"rock"
],
"\u{1F9F9}": [
"broom",
"cleaning",
"sweeping",
"witch",
"brush",
"sweep"
],
"\u{1F9FA}": [
"basket",
"laundry",
"farming",
"picnic"
],
"\u{1F9FB}": [
"roll_of_paper",
"roll",
"toilet",
"towels"
],
"\u{1F9FC}": [
"soap",
"bar",
"bathing",
"cleaning",
"lather",
"soapdish"
],
"\u{1F9FD}": [
"sponge",
"absorbing",
"cleaning",
"porous"
],
"\u{1F9EF}": [
"fire_extinguisher",
"quench",
"extinguish"
],
"\u{1F6D2}": [
"shopping_cart",
"trolley"
],
"\u{1F6AC}": [
"cigarette",
"kills",
"tobacco",
"joint",
"smoke",
"activity",
"smoking",
"symbol"
],
"\u26B0\uFE0F": [
"coffin",
"vampire",
"dead",
"die",
"death",
"rip",
"graveyard",
"cemetery",
"casket",
"funeral",
"box",
"remove"
],
"\u26B1\uFE0F": [
"funeral_urn",
"dead",
"die",
"death",
"rip",
"ashes",
"vase"
],
"\u{1F5FF}": [
"moai",
"rock",
"easter island",
"carving",
"face",
"human",
"moyai",
"statue",
"stone"
],
"\u{1F3E7}": [
"atm_sign",
"money",
"sales",
"cash",
"blue-square",
"payment",
"bank",
"automated",
"machine",
"teller"
],
"\u{1F6AE}": [
"litter_in_bin_sign",
"blue-square",
"sign",
"human",
"info",
"its",
"litterbox",
"person",
"place",
"put",
"symbol",
"trash"
],
"\u{1F6B0}": [
"potable_water",
"blue-square",
"liquid",
"restroom",
"cleaning",
"faucet",
"drink",
"drinking",
"symbol",
"tap",
"thirst",
"thirsty"
],
"\u267F": [
"wheelchair_symbol",
"blue-square",
"disabled",
"accessibility",
"access",
"accessible",
"bathroom"
],
"\u{1F6B9}": [
"men_s_room",
"toilet",
"restroom",
"wc",
"blue-square",
"gender",
"male",
"lavatory",
"man",
"mens",
"men\u2019s",
"symbol"
],
"\u{1F6BA}": [
"women_s_room",
"purple-square",
"woman",
"female",
"toilet",
"loo",
"restroom",
"gender",
"lavatory",
"symbol",
"wc",
"womens",
"womens\xA0toilet",
"women\u2019s"
],
"\u{1F6BB}": [
"restroom",
"blue-square",
"toilet",
"refresh",
"wc",
"gender",
"bathroom",
"lavatory",
"sign"
],
"\u{1F6BC}": [
"baby_symbol",
"orange-square",
"child",
"change",
"changing",
"nursery",
"station"
],
"\u{1F6BE}": [
"water_closet",
"toilet",
"restroom",
"blue-square",
"lavatory",
"wc"
],
"\u{1F6C2}": [
"passport_control",
"custom",
"blue-square",
"border",
"permissions",
"authorization",
"roles"
],
"\u{1F6C3}": [
"customs",
"passport",
"border",
"blue-square"
],
"\u{1F6C4}": [
"baggage_claim",
"blue-square",
"airport",
"transport"
],
"\u{1F6C5}": [
"left_luggage",
"blue-square",
"travel",
"baggage",
"bag\xA0with",
"key",
"locked",
"locker",
"suitcase"
],
"\u26A0\uFE0F": [
"warning",
"exclamation",
"wip",
"alert",
"error",
"problem",
"issue",
"sign",
"symbol"
],
"\u{1F6B8}": [
"children_crossing",
"school",
"warning",
"danger",
"sign",
"driving",
"yellow-diamond",
"child",
"kids",
"pedestrian",
"traffic",
"experience",
"usability"
],
"\u26D4": [
"no_entry",
"limit",
"security",
"privacy",
"bad",
"denied",
"stop",
"circle",
"forbidden",
"not",
"prohibited",
"traffic"
],
"\u{1F6AB}": [
"prohibited",
"forbid",
"stop",
"limit",
"denied",
"disallow",
"circle",
"backslash",
"banned",
"block",
"crossed",
"entry",
"forbidden",
"no",
"not",
"red",
"restricted",
"sign"
],
"\u{1F6B3}": [
"no_bicycles",
"no_bikes",
"bicycle",
"bike",
"cyclist",
"prohibited",
"circle",
"forbidden",
"not",
"sign",
"vehicle"
],
"\u{1F6AD}": [
"no_smoking",
"cigarette",
"blue-square",
"smell",
"smoke",
"forbidden",
"not",
"prohibited",
"sign",
"symbol"
],
"\u{1F6AF}": [
"no_littering",
"trash",
"bin",
"garbage",
"circle",
"do",
"forbidden",
"litter",
"not",
"prohibited",
"symbol"
],
"\u{1F6B1}": [
"non_potable_water",
"drink",
"faucet",
"tap",
"circle",
"drinking",
"forbidden",
"no",
"not",
"prohibited",
"symbol"
],
"\u{1F6B7}": [
"no_pedestrians",
"rules",
"crossing",
"walking",
"circle",
"forbidden",
"not",
"pedestrian",
"people",
"prohibited"
],
"\u{1F4F5}": [
"no_mobile_phones",
"iphone",
"mute",
"circle",
"cell",
"communication",
"forbidden",
"not",
"phone",
"prohibited",
"smartphones",
"telephone"
],
"\u{1F51E}": [
"no_one_under_eighteen",
"18",
"drink",
"pub",
"night",
"minor",
"circle",
"age",
"forbidden",
"not",
"nsfw",
"prohibited",
"restriction",
"symbol",
"underage"
],
"\u2622\uFE0F": [
"radioactive",
"nuclear",
"danger",
"international",
"radiation",
"sign",
"symbol"
],
"\u2623\uFE0F": [
"biohazard",
"danger",
"sign"
],
"\u2B06\uFE0F": [
"up_arrow",
"blue-square",
"continue",
"top",
"direction",
"black",
"cardinal",
"north",
"pointing",
"upwards",
"upgrade"
],
"\u2197\uFE0F": [
"up_right_arrow",
"blue-square",
"point",
"direction",
"diagonal",
"northeast",
"east",
"intercardinal",
"north",
"upper"
],
"\u27A1\uFE0F": [
"right_arrow",
"blue-square",
"next",
"black",
"cardinal",
"direction",
"east",
"pointing",
"rightwards",
"right\xA0arrow"
],
"\u2198\uFE0F": [
"down_right_arrow",
"blue-square",
"direction",
"diagonal",
"southeast",
"east",
"intercardinal",
"lower",
"right\xA0arrow",
"south"
],
"\u2B07\uFE0F": [
"down_arrow",
"blue-square",
"direction",
"bottom",
"black",
"cardinal",
"downwards",
"down\xA0arrow",
"pointing",
"south",
"downgrade"
],
"\u2199\uFE0F": [
"down_left_arrow",
"blue-square",
"direction",
"diagonal",
"southwest",
"intercardinal",
"left\xA0arrow",
"lower",
"south",
"west"
],
"\u2B05\uFE0F": [
"left_arrow",
"blue-square",
"previous",
"back",
"black",
"cardinal",
"direction",
"leftwards",
"left\xA0arrow",
"pointing",
"west"
],
"\u2196\uFE0F": [
"up_left_arrow",
"blue-square",
"point",
"direction",
"diagonal",
"northwest",
"intercardinal",
"left\xA0arrow",
"north",
"upper",
"west"
],
"\u2195\uFE0F": [
"up_down_arrow",
"blue-square",
"direction",
"way",
"vertical",
"arrows",
"intercardinal",
"northwest"
],
"\u2194\uFE0F": [
"left_right_arrow",
"shape",
"direction",
"horizontal",
"sideways",
"arrows",
"horizontal\xA0arrows"
],
"\u21A9\uFE0F": [
"right_arrow_curving_left",
"back",
"return",
"blue-square",
"undo",
"enter",
"curved",
"email",
"hook",
"leftwards",
"reply"
],
"\u21AA\uFE0F": [
"left_arrow_curving_right",
"blue-square",
"return",
"rotate",
"direction",
"email",
"forward",
"hook",
"rightwards",
"right\xA0curved"
],
"\u2934\uFE0F": [
"right_arrow_curving_up",
"blue-square",
"direction",
"top",
"heading",
"pointing",
"rightwards",
"then",
"upwards"
],
"\u2935\uFE0F": [
"right_arrow_curving_down",
"blue-square",
"direction",
"bottom",
"curved",
"downwards",
"heading",
"pointing",
"rightwards",
"then"
],
"\u{1F503}": [
"clockwise_vertical_arrows",
"sync",
"cycle",
"round",
"repeat",
"arrow",
"circle",
"downwards",
"open",
"reload",
"upwards"
],
"\u{1F504}": [
"counterclockwise_arrows_button",
"blue-square",
"sync",
"cycle",
"anticlockwise",
"arrow",
"circle",
"downwards",
"open",
"refresh",
"rotate",
"switch",
"upwards",
"withershins"
],
"\u{1F519}": [
"back_arrow",
"arrow",
"words",
"return",
"above",
"leftwards"
],
"\u{1F51A}": [
"end_arrow",
"words",
"arrow",
"above",
"leftwards"
],
"\u{1F51B}": [
"on_arrow",
"arrow",
"words",
"above",
"exclamation",
"left",
"mark",
"on!",
"right"
],
"\u{1F51C}": [
"soon_arrow",
"arrow",
"words",
"above",
"rightwards"
],
"\u{1F51D}": [
"top_arrow",
"words",
"blue-square",
"above",
"up",
"upwards"
],
"\u{1F6D0}": [
"place_of_worship",
"religion",
"church",
"temple",
"prayer",
"building",
"religious"
],
"\u269B\uFE0F": [
"atom_symbol",
"science",
"physics",
"chemistry",
"atheist"
],
"\u{1F549}\uFE0F": [
"om",
"hinduism",
"buddhism",
"sikhism",
"jainism",
"aumkara",
"hindu",
"omkara",
"pranava",
"religion",
"symbol"
],
"\u2721\uFE0F": [
"star_of_david",
"judaism",
"jew",
"jewish",
"magen",
"religion"
],
"\u2638\uFE0F": [
"wheel_of_dharma",
"hinduism",
"buddhism",
"sikhism",
"jainism",
"buddhist",
"helm",
"religion"
],
"\u262F\uFE0F": [
"yin_yang",
"balance",
"religion",
"tao",
"taoist"
],
"\u271D\uFE0F": [
"latin_cross",
"christianity",
"christian",
"religion"
],
"\u2626\uFE0F": [
"orthodox_cross",
"suppedaneum",
"religion",
"christian"
],
"\u262A\uFE0F": [
"star_and_crescent",
"islam",
"muslim",
"religion"
],
"\u262E\uFE0F": [
"peace_symbol",
"hippie",
"sign"
],
"\u{1F54E}": [
"menorah",
"hanukkah",
"candles",
"jewish",
"branches",
"candelabrum",
"candlestick",
"chanukiah",
"nine",
"religion"
],
"\u{1F52F}": [
"dotted_six_pointed_star",
"purple-square",
"religion",
"jewish",
"hexagram",
"dot",
"fortune",
"middle"
],
"\u2648": [
"aries",
"sign",
"purple-square",
"zodiac",
"astrology",
"ram"
],
"\u2649": [
"taurus",
"purple-square",
"sign",
"zodiac",
"astrology",
"bull",
"ox"
],
"\u264A": [
"gemini",
"sign",
"zodiac",
"purple-square",
"astrology",
"twins"
],
"\u264B": [
"cancer",
"sign",
"zodiac",
"purple-square",
"astrology",
"crab"
],
"\u264C": [
"leo",
"sign",
"purple-square",
"zodiac",
"astrology",
"lion"
],
"\u264D": [
"virgo",
"sign",
"zodiac",
"purple-square",
"astrology",
"maiden",
"virgin"
],
"\u264E": [
"libra",
"sign",
"purple-square",
"zodiac",
"astrology",
"balance",
"justice",
"scales"
],
"\u264F": [
"scorpio",
"sign",
"zodiac",
"purple-square",
"astrology",
"scorpion",
"scorpius"
],
"\u2650": [
"sagittarius",
"sign",
"zodiac",
"purple-square",
"astrology",
"archer"
],
"\u2651": [
"capricorn",
"sign",
"zodiac",
"purple-square",
"astrology",
"goat"
],
"\u2652": [
"aquarius",
"sign",
"purple-square",
"zodiac",
"astrology",
"bearer",
"water"
],
"\u2653": [
"pisces",
"purple-square",
"sign",
"zodiac",
"astrology",
"fish"
],
"\u26CE": [
"ophiuchus",
"sign",
"purple-square",
"constellation",
"astrology",
"bearer",
"serpent",
"snake",
"zodiac"
],
"\u{1F500}": [
"shuffle_tracks_button",
"blue-square",
"shuffle",
"music",
"random",
"arrow",
"arrows",
"crossed",
"rightwards",
"symbol",
"twisted",
"merge"
],
"\u{1F501}": [
"repeat_button",
"loop",
"record",
"arrow",
"arrows",
"circle",
"clockwise",
"leftwards",
"open",
"retweet",
"rightwards",
"symbol"
],
"\u{1F502}": [
"repeat_single_button",
"blue-square",
"loop",
"arrow",
"arrows",
"circle",
"circled",
"clockwise",
"leftwards",
"number",
"once",
"one",
"open",
"overlay",
"rightwards",
"symbol",
"track"
],
"\u25B6\uFE0F": [
"play_button",
"blue-square",
"right",
"direction",
"play",
"arrow",
"black",
"forward",
"pointing",
"right\xA0triangle",
"triangle"
],
"\u23E9": [
"fast_forward_button",
"blue-square",
"play",
"speed",
"continue",
"arrow",
"black",
"double",
"pointing",
"right",
"symbol",
"triangle"
],
"\u23ED\uFE0F": [
"next_track_button",
"forward",
"next",
"blue-square",
"arrow",
"bar",
"black",
"double",
"pointing",
"right",
"scene",
"skip",
"symbol",
"triangle",
"vertical"
],
"\u23EF\uFE0F": [
"play_or_pause_button",
"blue-square",
"play",
"pause",
"arrow",
"bar",
"black",
"double",
"play/pause",
"pointing",
"right",
"symbol",
"triangle",
"vertical"
],
"\u25C0\uFE0F": [
"reverse_button",
"blue-square",
"left",
"direction",
"arrow",
"backward",
"black",
"pointing",
"triangle"
],
"\u23EA": [
"fast_reverse_button",
"play",
"blue-square",
"arrow",
"black",
"double",
"left",
"pointing",
"rewind",
"symbol",
"triangle",
"revert"
],
"\u23EE\uFE0F": [
"last_track_button",
"backward",
"arrow",
"bar",
"black",
"double",
"left",
"pointing",
"previous",
"scene",
"skip",
"symbol",
"triangle",
"vertical"
],
"\u{1F53C}": [
"upwards_button",
"blue-square",
"triangle",
"direction",
"point",
"forward",
"top",
"arrow",
"pointing",
"red",
"small",
"up"
],
"\u23EB": [
"fast_up_button",
"blue-square",
"direction",
"top",
"arrow",
"black",
"double",
"pointing",
"triangle"
],
"\u{1F53D}": [
"downwards_button",
"blue-square",
"direction",
"bottom",
"arrow",
"down",
"pointing",
"red",
"small",
"triangle"
],
"\u23EC": [
"fast_down_button",
"blue-square",
"direction",
"bottom",
"arrow",
"black",
"double",
"pointing",
"triangle"
],
"\u23F8\uFE0F": [
"pause_button",
"pause",
"blue-square",
"bar",
"double",
"symbol",
"vertical"
],
"\u23F9\uFE0F": [
"stop_button",
"blue-square",
"black",
"for",
"square",
"symbol"
],
"\u23FA\uFE0F": [
"record_button",
"blue-square",
"black",
"circle",
"for",
"symbol"
],
"\u23CF\uFE0F": [
"eject_button",
"blue-square",
"symbol"
],
"\u{1F3A6}": [
"cinema",
"blue-square",
"record",
"film",
"movie",
"curtain",
"stage",
"theater",
"activity",
"camera",
"entertainment",
"movies",
"screen",
"symbol"
],
"\u{1F505}": [
"dim_button",
"sun",
"afternoon",
"warm",
"summer",
"brightness",
"decrease",
"low",
"symbol"
],
"\u{1F506}": [
"bright_button",
"sun",
"light",
"brightness",
"high",
"increase",
"symbol"
],
"\u{1F4F6}": [
"antenna_bars",
"blue-square",
"reception",
"phone",
"internet",
"connection",
"wifi",
"bluetooth",
"bars",
"bar",
"cell",
"cellular",
"communication",
"mobile",
"signal",
"stairs",
"strength",
"telephone"
],
"\u{1F4F3}": [
"vibration_mode",
"orange-square",
"phone",
"cell",
"communication",
"heart",
"mobile",
"silent",
"telephone"
],
"\u{1F4F4}": [
"mobile_phone_off",
"mute",
"orange-square",
"silence",
"quiet",
"cell",
"communication",
"telephone"
],
"\u2640\uFE0F": [
"female_sign",
"woman",
"women",
"lady",
"girl",
"symbol",
"venus"
],
"\u2642\uFE0F": [
"male_sign",
"man",
"boy",
"men",
"mars",
"symbol"
],
"\u2695\uFE0F": [
"medical_symbol",
"health",
"hospital",
"aesculapius",
"asclepius",
"asklepios",
"care",
"doctor",
"medicine",
"rod",
"snake",
"staff"
],
"\u267E\uFE0F": [
"infinity",
"forever",
"paper",
"permanent",
"sign",
"unbounded",
"universal"
],
"\u267B\uFE0F": [
"recycling_symbol",
"arrow",
"environment",
"garbage",
"trash",
"black",
"green",
"logo",
"recycle",
"universal",
"reuse"
],
"\u269C\uFE0F": [
"fleur_de_lis",
"decorative",
"scout",
"new",
"orleans",
"saints",
"scouts"
],
"\u{1F531}": [
"trident_emblem",
"weapon",
"spear",
"anchor",
"pitchfork",
"ship",
"tool"
],
"\u{1F4DB}": [
"name_badge",
"fire",
"forbid",
"tag",
"tofu"
],
"\u{1F530}": [
"japanese_symbol_for_beginner",
"badge",
"shield",
"chevron",
"green",
"leaf",
"mark",
"shoshinsha",
"tool",
"yellow"
],
"\u2B55": [
"hollow_red_circle",
"circle",
"round",
"correct",
"heavy",
"large",
"mark",
"o"
],
"\u2705": [
"check_mark_button",
"green-square",
"ok",
"agree",
"vote",
"election",
"answer",
"tick",
"green",
"heavy",
"symbol",
"white",
"pass_tests"
],
"\u2611\uFE0F": [
"check_box_with_check",
"ok",
"agree",
"confirm",
"black-square",
"vote",
"election",
"yes",
"tick",
"ballot",
"checkbox",
"mark"
],
"\u2714\uFE0F": [
"check_mark",
"ok",
"nike",
"answer",
"yes",
"tick",
"heavy"
],
"\u2716\uFE0F": [
"multiplication_sign",
"math",
"calculation",
"cancel",
"heavy",
"multiply",
"symbol",
"x"
],
"\u274C": [
"cross_mark",
"no",
"delete",
"remove",
"cancel",
"red",
"multiplication",
"multiply",
"x"
],
"\u274E": [
"cross_mark_button",
"x",
"green-square",
"no",
"deny",
"negative",
"square",
"squared"
],
"\u2795": [
"plus_sign",
"math",
"calculation",
"addition",
"more",
"increase",
"heavy",
"symbol",
"add"
],
"\u2796": [
"minus_sign",
"math",
"calculation",
"subtract",
"less",
"heavy",
"symbol",
"remove"
],
"\u2797": [
"division_sign",
"divide",
"math",
"calculation",
"heavy",
"symbol"
],
"\u27B0": [
"curly_loop",
"scribble",
"draw",
"shape",
"squiggle",
"curl",
"curling"
],
"\u27BF": [
"double_curly_loop",
"tape",
"cassette",
"curl",
"curling",
"voicemail"
],
"\u303D\uFE0F": [
"part_alternation_mark",
"graph",
"presentation",
"stats",
"business",
"economics",
"bad",
"m",
"mcdonald\u2019s"
],
"\u2733\uFE0F": [
"eight_spoked_asterisk",
"star",
"sparkle",
"green-square"
],
"\u2734\uFE0F": [
"eight_pointed_star",
"orange-square",
"shape",
"polygon",
"black",
"orange"
],
"\u2747\uFE0F": [
"sparkle",
"stars",
"green-square",
"awesome",
"good",
"fireworks"
],
"\u203C\uFE0F": [
"double_exclamation_mark",
"exclamation",
"surprise",
"bangbang",
"punctuation",
"red"
],
"\u2049\uFE0F": [
"exclamation_question_mark",
"wat",
"punctuation",
"surprise",
"interrobang",
"red"
],
"\u2753": [
"question_mark",
"doubt",
"confused",
"black",
"ornament",
"punctuation",
"red"
],
"\u2754": [
"white_question_mark",
"doubts",
"gray",
"huh",
"confused",
"grey",
"ornament",
"outlined",
"punctuation"
],
"\u2755": [
"white_exclamation_mark",
"surprise",
"punctuation",
"gray",
"wow",
"warning",
"grey",
"ornament",
"outlined"
],
"\u2757": [
"exclamation_mark",
"heavy_exclamation_mark",
"danger",
"surprise",
"punctuation",
"wow",
"warning",
"bang",
"red",
"symbol"
],
"\u3030\uFE0F": [
"wavy_dash",
"draw",
"line",
"moustache",
"mustache",
"squiggle",
"scribble",
"punctuation",
"wave"
],
"\xA9\uFE0F": [
"copyright",
"ip",
"license",
"circle",
"law",
"legal",
"c",
"sign"
],
"\xAE\uFE0F": [
"registered",
"alphabet",
"circle",
"r",
"sign"
],
"\u2122\uFE0F": [
"trade_mark",
"trademark",
"brand",
"law",
"legal",
"sign",
"tm"
],
"#\uFE0F\u20E3": [
"keycap_",
"symbol",
"blue-square",
"twitter",
"hash",
"hashtag",
"key",
"number",
"octothorpe",
"pound",
"sign"
],
"*\uFE0F\u20E3": [
"keycap_",
"star",
"keycap",
"asterisk"
],
"0\uFE0F\u20E3": [
"keycap_0",
"0",
"numbers",
"blue-square",
"null",
"zero",
"digit"
],
"1\uFE0F\u20E3": [
"keycap_1",
"blue-square",
"numbers",
"1",
"one",
"digit"
],
"2\uFE0F\u20E3": [
"keycap_2",
"numbers",
"2",
"prime",
"blue-square",
"two",
"digit"
],
"3\uFE0F\u20E3": [
"keycap_3",
"3",
"numbers",
"prime",
"blue-square",
"three",
"digit"
],
"4\uFE0F\u20E3": [
"keycap_4",
"4",
"numbers",
"blue-square",
"four",
"digit"
],
"5\uFE0F\u20E3": [
"keycap_5",
"5",
"numbers",
"blue-square",
"prime",
"five",
"digit"
],
"6\uFE0F\u20E3": [
"keycap_6",
"6",
"numbers",
"blue-square",
"six",
"digit"
],
"7\uFE0F\u20E3": [
"keycap_7",
"7",
"numbers",
"blue-square",
"prime",
"seven",
"digit"
],
"8\uFE0F\u20E3": [
"keycap_8",
"8",
"blue-square",
"numbers",
"eight",
"digit"
],
"9\uFE0F\u20E3": [
"keycap_9",
"blue-square",
"numbers",
"9",
"nine",
"digit"
],
"\u{1F51F}": [
"keycap_10",
"numbers",
"10",
"blue-square",
"ten",
"number"
],
"\u{1F520}": [
"input_latin_uppercase",
"alphabet",
"words",
"letters",
"uppercase",
"blue-square",
"abcd",
"capital",
"for",
"symbol"
],
"\u{1F521}": [
"input_latin_lowercase",
"blue-square",
"letters",
"lowercase",
"alphabet",
"abcd",
"for",
"small",
"symbol"
],
"\u{1F522}": [
"input_numbers",
"numbers",
"blue-square",
"1234",
"1",
"2",
"3",
"4",
"for",
"numeric",
"symbol"
],
"\u{1F523}": [
"input_symbols",
"blue-square",
"music",
"note",
"ampersand",
"percent",
"glyphs",
"characters",
"for",
"symbol",
"symbol\xA0input"
],
"\u{1F524}": [
"input_latin_letters",
"blue-square",
"alphabet",
"abc",
"for",
"symbol"
],
"\u{1F170}\uFE0F": [
"a_button",
"red-square",
"alphabet",
"letter",
"blood",
"capital",
"latin",
"negative",
"squared",
"type"
],
"\u{1F18E}": [
"ab_button",
"red-square",
"alphabet",
"blood",
"negative",
"squared",
"type"
],
"\u{1F171}\uFE0F": [
"b_button",
"red-square",
"alphabet",
"letter",
"blood",
"capital",
"latin",
"negative",
"squared",
"type"
],
"\u{1F191}": [
"cl_button",
"alphabet",
"words",
"red-square",
"clear",
"sign",
"squared"
],
"\u{1F192}": [
"cool_button",
"words",
"blue-square",
"sign",
"square",
"squared"
],
"\u{1F193}": [
"free_button",
"blue-square",
"words",
"sign",
"squared"
],
"\u2139\uFE0F": [
"information",
"blue-square",
"alphabet",
"letter",
"i",
"info",
"lowercase",
"source",
"tourist"
],
"\u{1F194}": [
"id_button",
"purple-square",
"words",
"identification",
"identity",
"sign",
"squared"
],
"\u24C2\uFE0F": [
"circled_m",
"alphabet",
"blue-circle",
"letter",
"capital",
"circle",
"latin",
"metro"
],
"\u{1F195}": [
"new_button",
"blue-square",
"words",
"start",
"fresh",
"sign",
"squared"
],
"\u{1F196}": [
"ng_button",
"blue-square",
"words",
"shape",
"icon",
"blooper",
"good",
"no",
"sign",
"squared"
],
"\u{1F17E}\uFE0F": [
"o_button",
"alphabet",
"red-square",
"letter",
"blood",
"capital",
"latin",
"negative",
"o2",
"squared",
"type"
],
"\u{1F197}": [
"ok_button",
"good",
"agree",
"yes",
"blue-square",
"okay",
"sign",
"square",
"squared"
],
"\u{1F17F}\uFE0F": [
"p_button",
"cars",
"blue-square",
"alphabet",
"letter",
"capital",
"latin",
"negative",
"parking",
"sign",
"squared"
],
"\u{1F198}": [
"sos_button",
"help",
"red-square",
"words",
"emergency",
"911",
"distress",
"sign",
"signal",
"squared"
],
"\u{1F199}": [
"up_button",
"blue-square",
"above",
"high",
"exclamation",
"level",
"mark",
"sign",
"squared",
"up!"
],
"\u{1F19A}": [
"vs_button",
"words",
"orange-square",
"squared",
"versus"
],
"\u{1F201}": [
"japanese_here_button",
"blue-square",
"here",
"katakana",
"japanese",
"destination",
"koko",
"meaning",
"sign",
"squared",
"word",
"\u201Chere\u201D"
],
"\u{1F202}\uFE0F": [
"japanese_service_charge_button",
"japanese",
"blue-square",
"katakana",
"charge\u201D",
"meaning",
"or",
"sa",
"sign",
"squared",
"\u201Cservice",
"\u201Cservice\u201D"
],
"\u{1F237}\uFE0F": [
"japanese_monthly_amount_button",
"chinese",
"month",
"moon",
"japanese",
"orange-square",
"kanji",
"amount\u201D",
"cjk",
"ideograph",
"meaning",
"radical",
"sign",
"squared",
"u6708",
"unified",
"\u201Cmonthly"
],
"\u{1F236}": [
"japanese_not_free_of_charge_button",
"orange-square",
"chinese",
"have",
"kanji",
"charge\u201D",
"cjk",
"exist",
"ideograph",
"meaning",
"own",
"sign",
"squared",
"u6709",
"unified",
"\u201Cnot"
],
"\u{1F22F}": [
"japanese_reserved_button",
"chinese",
"point",
"green-square",
"kanji",
"cjk",
"finger",
"ideograph",
"meaning",
"sign",
"squared",
"u6307",
"unified",
"\u201Creserved\u201D"
],
"\u{1F250}": [
"japanese_bargain_button",
"chinese",
"kanji",
"obtain",
"get",
"circle",
"acquire",
"advantage",
"circled",
"ideograph",
"meaning",
"sign",
"\u201Cbargain\u201D"
],
"\u{1F239}": [
"japanese_discount_button",
"cut",
"divide",
"chinese",
"kanji",
"pink-square",
"bargain",
"cjk",
"ideograph",
"meaning",
"sale",
"sign",
"squared",
"u5272",
"unified",
"\u201Cdiscount\u201D"
],
"\u{1F21A}": [
"japanese_free_of_charge_button",
"nothing",
"chinese",
"kanji",
"japanese",
"orange-square",
"charge\u201D",
"cjk",
"ideograph",
"lacking",
"meaning",
"negation",
"sign",
"squared",
"u7121",
"unified",
"\u201Cfree"
],
"\u{1F232}": [
"japanese_prohibited_button",
"kanji",
"japanese",
"chinese",
"forbidden",
"limit",
"restricted",
"red-square",
"cjk",
"forbid",
"ideograph",
"meaning",
"prohibit",
"sign",
"squared",
"u7981",
"unified",
"\u201Cprohibited\u201D"
],
"\u{1F251}": [
"japanese_acceptable_button",
"ok",
"good",
"chinese",
"kanji",
"agree",
"yes",
"orange-circle",
"accept",
"circled",
"ideograph",
"meaning",
"sign",
"\u201Cacceptable\u201D"
],
"\u{1F238}": [
"japanese_application_button",
"chinese",
"japanese",
"kanji",
"orange-square",
"apply",
"cjk",
"form",
"ideograph",
"meaning",
"monkey",
"request",
"sign",
"squared",
"u7533",
"unified",
"\u201Capplication\u201D"
],
"\u{1F234}": [
"japanese_passing_grade_button",
"japanese",
"chinese",
"join",
"kanji",
"red-square",
"agreement",
"cjk",
"grade\u201D",
"ideograph",
"meaning",
"sign",
"squared",
"together",
"u5408",
"unified",
"\u201Cpassing"
],
"\u{1F233}": [
"japanese_vacancy_button",
"kanji",
"japanese",
"chinese",
"empty",
"sky",
"blue-square",
"7a7a",
"available",
"cjk",
"ideograph",
"meaning",
"sign",
"squared",
"u7a7a",
"unified",
"\u201Cvacancy\u201D"
],
"\u3297\uFE0F": [
"japanese_congratulations_button",
"chinese",
"kanji",
"japanese",
"red-circle",
"circled",
"congratulate",
"congratulation",
"ideograph",
"meaning",
"sign",
"\u201Ccongratulations\u201D"
],
"\u3299\uFE0F": [
"japanese_secret_button",
"privacy",
"chinese",
"sshh",
"kanji",
"red-circle",
"circled",
"ideograph",
"meaning",
"sign",
"\u201Csecret\u201D"
],
"\u{1F23A}": [
"japanese_open_for_business_button",
"japanese",
"opening hours",
"orange-square",
"55b6",
"business\u201D",
"chinese",
"cjk",
"ideograph",
"meaning",
"operating",
"sign",
"squared",
"u55b6",
"unified",
"work",
"\u201Copen"
],
"\u{1F235}": [
"japanese_no_vacancy_button",
"full",
"chinese",
"japanese",
"red-square",
"kanji",
"6e80",
"cjk",
"fullness",
"ideograph",
"meaning",
"sign",
"squared",
"u6e80",
"unified",
"vacancy\u201D",
"\u201Cfull;",
"\u201Cno"
],
"\u{1F534}": [
"red_circle",
"shape",
"error",
"danger",
"geometric",
"large"
],
"\u{1F7E0}": [
"orange_circle",
"round",
"geometric",
"large"
],
"\u{1F7E1}": [
"yellow_circle",
"round",
"geometric",
"large"
],
"\u{1F7E2}": [
"green_circle",
"round",
"geometric",
"large"
],
"\u{1F535}": [
"blue_circle",
"shape",
"icon",
"button",
"geometric",
"large"
],
"\u{1F7E3}": [
"purple_circle",
"round",
"geometric",
"large"
],
"\u{1F7E4}": [
"brown_circle",
"round",
"geometric",
"large"
],
"\u26AB": [
"black_circle",
"shape",
"button",
"round",
"geometric",
"medium"
],
"\u26AA": [
"white_circle",
"shape",
"round",
"geometric",
"medium"
],
"\u{1F7E5}": [
"red_square",
"card",
"geometric",
"large"
],
"\u{1F7E7}": [
"orange_square",
"geometric",
"large"
],
"\u{1F7E8}": [
"yellow_square",
"card",
"geometric",
"large"
],
"\u{1F7E9}": [
"green_square",
"geometric",
"large"
],
"\u{1F7E6}": [
"blue_square",
"geometric",
"large"
],
"\u{1F7EA}": [
"purple_square",
"geometric",
"large"
],
"\u{1F7EB}": [
"brown_square",
"geometric",
"large"
],
"\u2B1B": [
"black_large_square",
"shape",
"icon",
"button",
"geometric"
],
"\u2B1C": [
"white_large_square",
"shape",
"icon",
"stone",
"button",
"geometric"
],
"\u25FC\uFE0F": [
"black_medium_square",
"shape",
"button",
"icon",
"geometric"
],
"\u25FB\uFE0F": [
"white_medium_square",
"shape",
"stone",
"icon",
"geometric"
],
"\u25FE": [
"black_medium_small_square",
"icon",
"shape",
"button",
"geometric"
],
"\u25FD": [
"white_medium_small_square",
"shape",
"stone",
"icon",
"button",
"geometric"
],
"\u25AA\uFE0F": [
"black_small_square",
"shape",
"icon",
"geometric"
],
"\u25AB\uFE0F": [
"white_small_square",
"shape",
"icon",
"geometric"
],
"\u{1F536}": [
"large_orange_diamond",
"shape",
"jewel",
"gem",
"geometric"
],
"\u{1F537}": [
"large_blue_diamond",
"shape",
"jewel",
"gem",
"geometric"
],
"\u{1F538}": [
"small_orange_diamond",
"shape",
"jewel",
"gem",
"geometric"
],
"\u{1F539}": [
"small_blue_diamond",
"shape",
"jewel",
"gem",
"geometric"
],
"\u{1F53A}": [
"red_triangle_pointed_up",
"shape",
"direction",
"up",
"top",
"geometric",
"pointing",
"small"
],
"\u{1F53B}": [
"red_triangle_pointed_down",
"shape",
"direction",
"bottom",
"geometric",
"pointing",
"small"
],
"\u{1F4A0}": [
"diamond_with_a_dot",
"jewel",
"blue",
"gem",
"crystal",
"fancy",
"comic",
"cuteness",
"flower",
"geometric",
"inside",
"kawaii",
"shape"
],
"\u{1F518}": [
"radio_button",
"input",
"old",
"music",
"circle",
"geometric"
],
"\u{1F533}": [
"white_square_button",
"shape",
"input",
"geometric",
"outlined"
],
"\u{1F532}": [
"black_square_button",
"shape",
"input",
"frame",
"geometric"
],
"\u{1F3C1}": [
"chequered_flag",
"contest",
"finishline",
"race",
"gokart",
"checkered",
"finish",
"girl",
"grid",
"milestone",
"racing"
],
"\u{1F6A9}": [
"triangular_flag",
"mark",
"milestone",
"place",
"pole",
"post",
"red",
"flag"
],
"\u{1F38C}": [
"crossed_flags",
"japanese",
"nation",
"country",
"border",
"activity",
"celebration",
"cross",
"flag",
"two"
],
"\u{1F3F4}": [
"black_flag",
"pirate",
"waving"
],
"\u{1F3F3}\uFE0F": [
"white_flag",
"losing",
"loser",
"lost",
"surrender",
"give up",
"fail",
"waving"
],
"\u{1F3F3}\uFE0F\u200D\u{1F308}": [
"rainbow_flag",
"flag",
"rainbow",
"pride",
"gay",
"lgbt",
"queer",
"homosexual",
"lesbian",
"bisexual"
],
"\u{1F3F4}\u200D\u2620\uFE0F": [
"pirate_flag",
"skull",
"crossbones",
"flag",
"banner",
"jolly",
"plunder",
"roger",
"treasure"
],
"\u{1F1E6}\u{1F1E8}": [
"flag_ascension_island"
],
"\u{1F1E6}\u{1F1E9}": [
"flag_andorra",
"ad",
"flag",
"nation",
"country",
"banner",
"andorra",
"andorran"
],
"\u{1F1E6}\u{1F1EA}": [
"flag_united_arab_emirates",
"united",
"arab",
"emirates",
"flag",
"nation",
"country",
"banner",
"united_arab_emirates",
"emirati",
"uae"
],
"\u{1F1E6}\u{1F1EB}": [
"flag_afghanistan",
"af",
"flag",
"nation",
"country",
"banner",
"afghanistan",
"afghan"
],
"\u{1F1E6}\u{1F1EC}": [
"flag_antigua_barbuda",
"antigua",
"barbuda",
"flag",
"nation",
"country",
"banner",
"antigua_barbuda"
],
"\u{1F1E6}\u{1F1EE}": [
"flag_anguilla",
"ai",
"flag",
"nation",
"country",
"banner",
"anguilla",
"anguillan"
],
"\u{1F1E6}\u{1F1F1}": [
"flag_albania",
"al",
"flag",
"nation",
"country",
"banner",
"albania",
"albanian"
],
"\u{1F1E6}\u{1F1F2}": [
"flag_armenia",
"am",
"flag",
"nation",
"country",
"banner",
"armenia",
"armenian"
],
"\u{1F1E6}\u{1F1F4}": [
"flag_angola",
"ao",
"flag",
"nation",
"country",
"banner",
"angola",
"angolan"
],
"\u{1F1E6}\u{1F1F6}": [
"flag_antarctica",
"aq",
"flag",
"nation",
"country",
"banner",
"antarctica",
"antarctic"
],
"\u{1F1E6}\u{1F1F7}": [
"flag_argentina",
"ar",
"flag",
"nation",
"country",
"banner",
"argentina",
"argentinian"
],
"\u{1F1E6}\u{1F1F8}": [
"flag_american_samoa",
"american",
"ws",
"flag",
"nation",
"country",
"banner",
"american_samoa",
"samoan"
],
"\u{1F1E6}\u{1F1F9}": [
"flag_austria",
"at",
"flag",
"nation",
"country",
"banner",
"austria",
"austrian"
],
"\u{1F1E6}\u{1F1FA}": [
"flag_australia",
"au",
"flag",
"nation",
"country",
"banner",
"australia",
"aussie",
"australian",
"heard",
"mcdonald"
],
"\u{1F1E6}\u{1F1FC}": [
"flag_aruba",
"aw",
"flag",
"nation",
"country",
"banner",
"aruba",
"aruban"
],
"\u{1F1E6}\u{1F1FD}": [
"flag_aland_islands",
"\xC5land",
"islands",
"flag",
"nation",
"country",
"banner",
"aland_islands"
],
"\u{1F1E6}\u{1F1FF}": [
"flag_azerbaijan",
"az",
"flag",
"nation",
"country",
"banner",
"azerbaijan",
"azerbaijani"
],
"\u{1F1E7}\u{1F1E6}": [
"flag_bosnia_herzegovina",
"bosnia",
"herzegovina",
"flag",
"nation",
"country",
"banner",
"bosnia_herzegovina"
],
"\u{1F1E7}\u{1F1E7}": [
"flag_barbados",
"bb",
"flag",
"nation",
"country",
"banner",
"barbados",
"bajan",
"barbadian"
],
"\u{1F1E7}\u{1F1E9}": [
"flag_bangladesh",
"bd",
"flag",
"nation",
"country",
"banner",
"bangladesh",
"bangladeshi"
],
"\u{1F1E7}\u{1F1EA}": [
"flag_belgium",
"be",
"flag",
"nation",
"country",
"banner",
"belgium",
"belgian"
],
"\u{1F1E7}\u{1F1EB}": [
"flag_burkina_faso",
"burkina",
"faso",
"flag",
"nation",
"country",
"banner",
"burkina_faso",
"burkinabe"
],
"\u{1F1E7}\u{1F1EC}": [
"flag_bulgaria",
"bg",
"flag",
"nation",
"country",
"banner",
"bulgaria",
"bulgarian"
],
"\u{1F1E7}\u{1F1ED}": [
"flag_bahrain",
"bh",
"flag",
"nation",
"country",
"banner",
"bahrain",
"bahrainian",
"bahrani"
],
"\u{1F1E7}\u{1F1EE}": [
"flag_burundi",
"bi",
"flag",
"nation",
"country",
"banner",
"burundi",
"burundian"
],
"\u{1F1E7}\u{1F1EF}": [
"flag_benin",
"bj",
"flag",
"nation",
"country",
"banner",
"benin",
"beninese"
],
"\u{1F1E7}\u{1F1F1}": [
"flag_st_barthelemy",
"saint",
"barth\xE9lemy",
"flag",
"nation",
"country",
"banner",
"st_barthelemy",
"st."
],
"\u{1F1E7}\u{1F1F2}": [
"flag_bermuda",
"bm",
"flag",
"nation",
"country",
"banner",
"bermuda",
"bermudan\xA0flag"
],
"\u{1F1E7}\u{1F1F3}": [
"flag_brunei",
"bn",
"darussalam",
"flag",
"nation",
"country",
"banner",
"brunei",
"bruneian"
],
"\u{1F1E7}\u{1F1F4}": [
"flag_bolivia",
"bo",
"flag",
"nation",
"country",
"banner",
"bolivia",
"bolivian"
],
"\u{1F1E7}\u{1F1F6}": [
"flag_caribbean_netherlands",
"bonaire",
"flag",
"nation",
"country",
"banner",
"caribbean_netherlands",
"eustatius",
"saba",
"sint"
],
"\u{1F1E7}\u{1F1F7}": [
"flag_brazil",
"br",
"flag",
"nation",
"country",
"banner",
"brazil",
"brasil",
"brazilian",
"for"
],
"\u{1F1E7}\u{1F1F8}": [
"flag_bahamas",
"bs",
"flag",
"nation",
"country",
"banner",
"bahamas",
"bahamian"
],
"\u{1F1E7}\u{1F1F9}": [
"flag_bhutan",
"bt",
"flag",
"nation",
"country",
"banner",
"bhutan",
"bhutanese"
],
"\u{1F1E7}\u{1F1FB}": [
"flag_bouvet_island",
"norway"
],
"\u{1F1E7}\u{1F1FC}": [
"flag_botswana",
"bw",
"flag",
"nation",
"country",
"banner",
"botswana",
"batswana"
],
"\u{1F1E7}\u{1F1FE}": [
"flag_belarus",
"by",
"flag",
"nation",
"country",
"banner",
"belarus",
"belarusian"
],
"\u{1F1E7}\u{1F1FF}": [
"flag_belize",
"bz",
"flag",
"nation",
"country",
"banner",
"belize",
"belizean"
],
"\u{1F1E8}\u{1F1E6}": [
"flag_canada",
"ca",
"flag",
"nation",
"country",
"banner",
"canada",
"canadian"
],
"\u{1F1E8}\u{1F1E8}": [
"flag_cocos_islands",
"cocos",
"keeling",
"islands",
"flag",
"nation",
"country",
"banner",
"cocos_islands",
"island"
],
"\u{1F1E8}\u{1F1E9}": [
"flag_congo_kinshasa",
"congo",
"democratic",
"republic",
"flag",
"nation",
"country",
"banner",
"congo_kinshasa",
"drc"
],
"\u{1F1E8}\u{1F1EB}": [
"flag_central_african_republic",
"central",
"african",
"republic",
"flag",
"nation",
"country",
"banner",
"central_african_republic"
],
"\u{1F1E8}\u{1F1EC}": [
"flag_congo_brazzaville",
"congo",
"flag",
"nation",
"country",
"banner",
"congo_brazzaville",
"republic"
],
"\u{1F1E8}\u{1F1ED}": [
"flag_switzerland",
"ch",
"flag",
"nation",
"country",
"banner",
"switzerland",
"cross",
"red",
"swiss"
],
"\u{1F1E8}\u{1F1EE}": [
"flag_cote_d_ivoire",
"ivory",
"coast",
"flag",
"nation",
"country",
"banner",
"cote_d_ivoire",
"c\xF4te",
"divoire",
"d\u2019ivoire"
],
"\u{1F1E8}\u{1F1F0}": [
"flag_cook_islands",
"cook",
"islands",
"flag",
"nation",
"country",
"banner",
"cook_islands",
"island",
"islander"
],
"\u{1F1E8}\u{1F1F1}": [
"flag_chile",
"flag",
"nation",
"country",
"banner",
"chile",
"chilean"
],
"\u{1F1E8}\u{1F1F2}": [
"flag_cameroon",
"cm",
"flag",
"nation",
"country",
"banner",
"cameroon",
"cameroonian"
],
"\u{1F1E8}\u{1F1F3}": [
"flag_china",
"china",
"chinese",
"prc",
"flag",
"country",
"nation",
"banner",
"cn",
"indicator",
"letters",
"regional",
"symbol"
],
"\u{1F1E8}\u{1F1F4}": [
"flag_colombia",
"co",
"flag",
"nation",
"country",
"banner",
"colombia",
"colombian"
],
"\u{1F1E8}\u{1F1F5}": [
"flag_clipperton_island"
],
"\u{1F1E8}\u{1F1F7}": [
"flag_costa_rica",
"costa",
"rica",
"flag",
"nation",
"country",
"banner",
"costa_rica",
"rican"
],
"\u{1F1E8}\u{1F1FA}": [
"flag_cuba",
"cu",
"flag",
"nation",
"country",
"banner",
"cuba",
"cuban"
],
"\u{1F1E8}\u{1F1FB}": [
"flag_cape_verde",
"cabo",
"verde",
"flag",
"nation",
"country",
"banner",
"cape_verde",
"verdian"
],
"\u{1F1E8}\u{1F1FC}": [
"flag_curacao",
"cura\xE7ao",
"flag",
"nation",
"country",
"banner",
"curacao",
"antilles",
"cura\xE7aoan"
],
"\u{1F1E8}\u{1F1FD}": [
"flag_christmas_island",
"christmas",
"island",
"flag",
"nation",
"country",
"banner",
"christmas_island"
],
"\u{1F1E8}\u{1F1FE}": [
"flag_cyprus",
"cy",
"flag",
"nation",
"country",
"banner",
"cyprus",
"cypriot"
],
"\u{1F1E8}\u{1F1FF}": [
"flag_czechia",
"cz",
"flag",
"nation",
"country",
"banner",
"czechia",
"czech",
"republic"
],
"\u{1F1E9}\u{1F1EA}": [
"flag_germany",
"german",
"nation",
"flag",
"country",
"banner",
"germany",
"de",
"deutsch",
"indicator",
"letters",
"regional",
"symbol"
],
"\u{1F1E9}\u{1F1EC}": [
"flag_diego_garcia"
],
"\u{1F1E9}\u{1F1EF}": [
"flag_djibouti",
"dj",
"flag",
"nation",
"country",
"banner",
"djibouti",
"djiboutian"
],
"\u{1F1E9}\u{1F1F0}": [
"flag_denmark",
"dk",
"flag",
"nation",
"country",
"banner",
"denmark",
"danish"
],
"\u{1F1E9}\u{1F1F2}": [
"flag_dominica",
"dm",
"flag",
"nation",
"country",
"banner",
"dominica"
],
"\u{1F1E9}\u{1F1F4}": [
"flag_dominican_republic",
"dominican",
"republic",
"flag",
"nation",
"country",
"banner",
"dominican_republic",
"dom",
"rep"
],
"\u{1F1E9}\u{1F1FF}": [
"flag_algeria",
"dz",
"flag",
"nation",
"country",
"banner",
"algeria",
"algerian"
],
"\u{1F1EA}\u{1F1E6}": [
"flag_ceuta_melilla"
],
"\u{1F1EA}\u{1F1E8}": [
"flag_ecuador",
"ec",
"flag",
"nation",
"country",
"banner",
"ecuador",
"ecuadorian"
],
"\u{1F1EA}\u{1F1EA}": [
"flag_estonia",
"ee",
"flag",
"nation",
"country",
"banner",
"estonia",
"estonian"
],
"\u{1F1EA}\u{1F1EC}": [
"flag_egypt",
"eg",
"flag",
"nation",
"country",
"banner",
"egypt",
"egyptian"
],
"\u{1F1EA}\u{1F1ED}": [
"flag_western_sahara",
"western",
"sahara",
"flag",
"nation",
"country",
"banner",
"western_sahara",
"saharan",
"west"
],
"\u{1F1EA}\u{1F1F7}": [
"flag_eritrea",
"er",
"flag",
"nation",
"country",
"banner",
"eritrea",
"eritrean"
],
"\u{1F1EA}\u{1F1F8}": [
"flag_spain",
"spain",
"flag",
"nation",
"country",
"banner",
"ceuta",
"es",
"indicator",
"letters",
"melilla",
"regional",
"spanish",
"symbol"
],
"\u{1F1EA}\u{1F1F9}": [
"flag_ethiopia",
"et",
"flag",
"nation",
"country",
"banner",
"ethiopia",
"ethiopian"
],
"\u{1F1EA}\u{1F1FA}": [
"flag_european_union",
"european",
"union",
"flag",
"banner",
"eu"
],
"\u{1F1EB}\u{1F1EE}": [
"flag_finland",
"fi",
"flag",
"nation",
"country",
"banner",
"finland",
"finnish"
],
"\u{1F1EB}\u{1F1EF}": [
"flag_fiji",
"fj",
"flag",
"nation",
"country",
"banner",
"fiji",
"fijian"
],
"\u{1F1EB}\u{1F1F0}": [
"flag_falkland_islands",
"falkland",
"islands",
"malvinas",
"flag",
"nation",
"country",
"banner",
"falkland_islands",
"falklander",
"falklands",
"island",
"islas"
],
"\u{1F1EB}\u{1F1F2}": [
"flag_micronesia",
"micronesia",
"federated",
"states",
"flag",
"nation",
"country",
"banner",
"micronesian"
],
"\u{1F1EB}\u{1F1F4}": [
"flag_faroe_islands",
"faroe",
"islands",
"flag",
"nation",
"country",
"banner",
"faroe_islands",
"island",
"islander"
],
"\u{1F1EB}\u{1F1F7}": [
"flag_france",
"banner",
"flag",
"nation",
"france",
"french",
"country",
"clipperton",
"fr",
"indicator",
"island",
"letters",
"martin",
"regional",
"saint",
"st.",
"symbol"
],
"\u{1F1EC}\u{1F1E6}": [
"flag_gabon",
"ga",
"flag",
"nation",
"country",
"banner",
"gabon",
"gabonese"
],
"\u{1F1EC}\u{1F1E7}": [
"flag_united_kingdom",
"united",
"kingdom",
"great",
"britain",
"northern",
"ireland",
"flag",
"nation",
"country",
"banner",
"british",
"UK",
"english",
"england",
"union jack",
"united_kingdom",
"cornwall",
"gb",
"scotland",
"wales"
],
"\u{1F1EC}\u{1F1E9}": [
"flag_grenada",
"gd",
"flag",
"nation",
"country",
"banner",
"grenada",
"grenadian"
],
"\u{1F1EC}\u{1F1EA}": [
"flag_georgia",
"ge",
"flag",
"nation",
"country",
"banner",
"georgia",
"georgian"
],
"\u{1F1EC}\u{1F1EB}": [
"flag_french_guiana",
"french",
"guiana",
"flag",
"nation",
"country",
"banner",
"french_guiana",
"guinean"
],
"\u{1F1EC}\u{1F1EC}": [
"flag_guernsey",
"gg",
"flag",
"nation",
"country",
"banner",
"guernsey"
],
"\u{1F1EC}\u{1F1ED}": [
"flag_ghana",
"gh",
"flag",
"nation",
"country",
"banner",
"ghana",
"ghanaian"
],
"\u{1F1EC}\u{1F1EE}": [
"flag_gibraltar",
"gi",
"flag",
"nation",
"country",
"banner",
"gibraltar",
"gibraltarian"
],
"\u{1F1EC}\u{1F1F1}": [
"flag_greenland",
"gl",
"flag",
"nation",
"country",
"banner",
"greenland",
"greenlandic"
],
"\u{1F1EC}\u{1F1F2}": [
"flag_gambia",
"gm",
"flag",
"nation",
"country",
"banner",
"gambia",
"gambian\xA0flag"
],
"\u{1F1EC}\u{1F1F3}": [
"flag_guinea",
"gn",
"flag",
"nation",
"country",
"banner",
"guinea",
"guinean"
],
"\u{1F1EC}\u{1F1F5}": [
"flag_guadeloupe",
"gp",
"flag",
"nation",
"country",
"banner",
"guadeloupe",
"guadeloupean"
],
"\u{1F1EC}\u{1F1F6}": [
"flag_equatorial_guinea",
"equatorial",
"gn",
"flag",
"nation",
"country",
"banner",
"equatorial_guinea",
"equatoguinean",
"guinean"
],
"\u{1F1EC}\u{1F1F7}": [
"flag_greece",
"gr",
"flag",
"nation",
"country",
"banner",
"greece",
"greek"
],
"\u{1F1EC}\u{1F1F8}": [
"flag_south_georgia_south_sandwich_islands",
"south",
"georgia",
"sandwich",
"islands",
"flag",
"nation",
"country",
"banner",
"south_georgia_south_sandwich_islands",
"island"
],
"\u{1F1EC}\u{1F1F9}": [
"flag_guatemala",
"gt",
"flag",
"nation",
"country",
"banner",
"guatemala",
"guatemalan"
],
"\u{1F1EC}\u{1F1FA}": [
"flag_guam",
"gu",
"flag",
"nation",
"country",
"banner",
"guam",
"chamorro",
"guamanian"
],
"\u{1F1EC}\u{1F1FC}": [
"flag_guinea_bissau",
"gw",
"bissau",
"flag",
"nation",
"country",
"banner",
"guinea_bissau"
],
"\u{1F1EC}\u{1F1FE}": [
"flag_guyana",
"gy",
"flag",
"nation",
"country",
"banner",
"guyana",
"guyanese"
],
"\u{1F1ED}\u{1F1F0}": [
"flag_hong_kong_sar_china",
"hong",
"kong",
"flag",
"nation",
"country",
"banner",
"hong_kong_sar_china"
],
"\u{1F1ED}\u{1F1F2}": [
"flag_heard_mcdonald_islands"
],
"\u{1F1ED}\u{1F1F3}": [
"flag_honduras",
"hn",
"flag",
"nation",
"country",
"banner",
"honduras",
"honduran"
],
"\u{1F1ED}\u{1F1F7}": [
"flag_croatia",
"hr",
"flag",
"nation",
"country",
"banner",
"croatia",
"croatian"
],
"\u{1F1ED}\u{1F1F9}": [
"flag_haiti",
"ht",
"flag",
"nation",
"country",
"banner",
"haiti",
"haitian"
],
"\u{1F1ED}\u{1F1FA}": [
"flag_hungary",
"hu",
"flag",
"nation",
"country",
"banner",
"hungary",
"hungarian"
],
"\u{1F1EE}\u{1F1E8}": [
"flag_canary_islands",
"canary",
"islands",
"flag",
"nation",
"country",
"banner",
"canary_islands",
"island"
],
"\u{1F1EE}\u{1F1E9}": [
"flag_indonesia",
"flag",
"nation",
"country",
"banner",
"indonesia",
"indonesian"
],
"\u{1F1EE}\u{1F1EA}": [
"flag_ireland",
"ie",
"flag",
"nation",
"country",
"banner",
"ireland",
"irish\xA0flag"
],
"\u{1F1EE}\u{1F1F1}": [
"flag_israel",
"il",
"flag",
"nation",
"country",
"banner",
"israel",
"israeli"
],
"\u{1F1EE}\u{1F1F2}": [
"flag_isle_of_man",
"isle",
"man",
"flag",
"nation",
"country",
"banner",
"isle_of_man",
"manx"
],
"\u{1F1EE}\u{1F1F3}": [
"flag_india",
"in",
"flag",
"nation",
"country",
"banner",
"india",
"indian"
],
"\u{1F1EE}\u{1F1F4}": [
"flag_british_indian_ocean_territory",
"british",
"indian",
"ocean",
"territory",
"flag",
"nation",
"country",
"banner",
"british_indian_ocean_territory",
"chagos",
"diego",
"garcia",
"island"
],
"\u{1F1EE}\u{1F1F6}": [
"flag_iraq",
"iq",
"flag",
"nation",
"country",
"banner",
"iraq",
"iraqi"
],
"\u{1F1EE}\u{1F1F7}": [
"flag_iran",
"iran",
"islamic",
"republic",
"flag",
"nation",
"country",
"banner",
"iranian\xA0flag"
],
"\u{1F1EE}\u{1F1F8}": [
"flag_iceland",
"is",
"flag",
"nation",
"country",
"banner",
"iceland",
"icelandic"
],
"\u{1F1EE}\u{1F1F9}": [
"flag_italy",
"italy",
"flag",
"nation",
"country",
"banner",
"indicator",
"italian",
"letters",
"regional",
"symbol"
],
"\u{1F1EF}\u{1F1EA}": [
"flag_jersey",
"je",
"flag",
"nation",
"country",
"banner",
"jersey"
],
"\u{1F1EF}\u{1F1F2}": [
"flag_jamaica",
"jm",
"flag",
"nation",
"country",
"banner",
"jamaica",
"jamaican\xA0flag"
],
"\u{1F1EF}\u{1F1F4}": [
"flag_jordan",
"jo",
"flag",
"nation",
"country",
"banner",
"jordan",
"jordanian"
],
"\u{1F1EF}\u{1F1F5}": [
"flag_japan",
"japanese",
"nation",
"flag",
"country",
"banner",
"japan",
"jp",
"ja",
"indicator",
"letters",
"regional",
"symbol"
],
"\u{1F1F0}\u{1F1EA}": [
"flag_kenya",
"ke",
"flag",
"nation",
"country",
"banner",
"kenya",
"kenyan"
],
"\u{1F1F0}\u{1F1EC}": [
"flag_kyrgyzstan",
"kg",
"flag",
"nation",
"country",
"banner",
"kyrgyzstan",
"kyrgyzstani"
],
"\u{1F1F0}\u{1F1ED}": [
"flag_cambodia",
"kh",
"flag",
"nation",
"country",
"banner",
"cambodia",
"cambodian"
],
"\u{1F1F0}\u{1F1EE}": [
"flag_kiribati",
"ki",
"flag",
"nation",
"country",
"banner",
"kiribati",
"i"
],
"\u{1F1F0}\u{1F1F2}": [
"flag_comoros",
"km",
"flag",
"nation",
"country",
"banner",
"comoros",
"comoran"
],
"\u{1F1F0}\u{1F1F3}": [
"flag_st_kitts_nevis",
"saint",
"kitts",
"nevis",
"flag",
"nation",
"country",
"banner",
"st_kitts_nevis",
"st."
],
"\u{1F1F0}\u{1F1F5}": [
"flag_north_korea",
"north",
"korea",
"nation",
"flag",
"country",
"banner",
"north_korea",
"korean"
],
"\u{1F1F0}\u{1F1F7}": [
"flag_south_korea",
"south",
"korea",
"nation",
"flag",
"country",
"banner",
"south_korea",
"indicator",
"korean",
"kr",
"letters",
"regional",
"symbol"
],
"\u{1F1F0}\u{1F1FC}": [
"flag_kuwait",
"kw",
"flag",
"nation",
"country",
"banner",
"kuwait",
"kuwaiti"
],
"\u{1F1F0}\u{1F1FE}": [
"flag_cayman_islands",
"cayman",
"islands",
"flag",
"nation",
"country",
"banner",
"cayman_islands",
"caymanian",
"island"
],
"\u{1F1F0}\u{1F1FF}": [
"flag_kazakhstan",
"kz",
"flag",
"nation",
"country",
"banner",
"kazakhstan",
"kazakh",
"kazakhstani"
],
"\u{1F1F1}\u{1F1E6}": [
"flag_laos",
"lao",
"democratic",
"republic",
"flag",
"nation",
"country",
"banner",
"laos",
"laotian"
],
"\u{1F1F1}\u{1F1E7}": [
"flag_lebanon",
"lb",
"flag",
"nation",
"country",
"banner",
"lebanon",
"lebanese"
],
"\u{1F1F1}\u{1F1E8}": [
"flag_st_lucia",
"saint",
"lucia",
"flag",
"nation",
"country",
"banner",
"st_lucia",
"st."
],
"\u{1F1F1}\u{1F1EE}": [
"flag_liechtenstein",
"li",
"flag",
"nation",
"country",
"banner",
"liechtenstein",
"liechtensteiner"
],
"\u{1F1F1}\u{1F1F0}": [
"flag_sri_lanka",
"sri",
"lanka",
"flag",
"nation",
"country",
"banner",
"sri_lanka",
"lankan"
],
"\u{1F1F1}\u{1F1F7}": [
"flag_liberia",
"lr",
"flag",
"nation",
"country",
"banner",
"liberia",
"liberian"
],
"\u{1F1F1}\u{1F1F8}": [
"flag_lesotho",
"ls",
"flag",
"nation",
"country",
"banner",
"lesotho",
"basotho"
],
"\u{1F1F1}\u{1F1F9}": [
"flag_lithuania",
"lt",
"flag",
"nation",
"country",
"banner",
"lithuania",
"lithuanian"
],
"\u{1F1F1}\u{1F1FA}": [
"flag_luxembourg",
"lu",
"flag",
"nation",
"country",
"banner",
"luxembourg",
"luxembourger"
],
"\u{1F1F1}\u{1F1FB}": [
"flag_latvia",
"lv",
"flag",
"nation",
"country",
"banner",
"latvia",
"latvian"
],
"\u{1F1F1}\u{1F1FE}": [
"flag_libya",
"ly",
"flag",
"nation",
"country",
"banner",
"libya",
"libyan"
],
"\u{1F1F2}\u{1F1E6}": [
"flag_morocco",
"ma",
"flag",
"nation",
"country",
"banner",
"morocco",
"moroccan"
],
"\u{1F1F2}\u{1F1E8}": [
"flag_monaco",
"mc",
"flag",
"nation",
"country",
"banner",
"monaco",
"mon\xE9gasque"
],
"\u{1F1F2}\u{1F1E9}": [
"flag_moldova",
"moldova",
"republic",
"flag",
"nation",
"country",
"banner",
"moldovan"
],
"\u{1F1F2}\u{1F1EA}": [
"flag_montenegro",
"me",
"flag",
"nation",
"country",
"banner",
"montenegro",
"montenegrin"
],
"\u{1F1F2}\u{1F1EB}": [
"flag_st_martin",
"st."
],
"\u{1F1F2}\u{1F1EC}": [
"flag_madagascar",
"mg",
"flag",
"nation",
"country",
"banner",
"madagascar",
"madagascan"
],
"\u{1F1F2}\u{1F1ED}": [
"flag_marshall_islands",
"marshall",
"islands",
"flag",
"nation",
"country",
"banner",
"marshall_islands",
"island",
"marshallese"
],
"\u{1F1F2}\u{1F1F0}": [
"flag_north_macedonia",
"macedonia",
"flag",
"nation",
"country",
"banner",
"north_macedonia",
"macedonian"
],
"\u{1F1F2}\u{1F1F1}": [
"flag_mali",
"ml",
"flag",
"nation",
"country",
"banner",
"mali",
"malian"
],
"\u{1F1F2}\u{1F1F2}": [
"flag_myanmar",
"mm",
"flag",
"nation",
"country",
"banner",
"myanmar",
"burma",
"burmese",
"for",
"myanmarese\xA0flag"
],
"\u{1F1F2}\u{1F1F3}": [
"flag_mongolia",
"mn",
"flag",
"nation",
"country",
"banner",
"mongolia",
"mongolian"
],
"\u{1F1F2}\u{1F1F4}": [
"flag_macao_sar_china",
"macao",
"flag",
"nation",
"country",
"banner",
"macao_sar_china",
"macanese\xA0flag",
"macau"
],
"\u{1F1F2}\u{1F1F5}": [
"flag_northern_mariana_islands",
"northern",
"mariana",
"islands",
"flag",
"nation",
"country",
"banner",
"northern_mariana_islands",
"island",
"micronesian",
"north"
],
"\u{1F1F2}\u{1F1F6}": [
"flag_martinique",
"mq",
"flag",
"nation",
"country",
"banner",
"martinique",
"martiniquais\xA0flag",
"of\xA0martinique",
"snake"
],
"\u{1F1F2}\u{1F1F7}": [
"flag_mauritania",
"mr",
"flag",
"nation",
"country",
"banner",
"mauritania",
"mauritanian"
],
"\u{1F1F2}\u{1F1F8}": [
"flag_montserrat",
"ms",
"flag",
"nation",
"country",
"banner",
"montserrat",
"montserratian"
],
"\u{1F1F2}\u{1F1F9}": [
"flag_malta",
"mt",
"flag",
"nation",
"country",
"banner",
"malta",
"maltese"
],
"\u{1F1F2}\u{1F1FA}": [
"flag_mauritius",
"mu",
"flag",
"nation",
"country",
"banner",
"mauritius",
"mauritian"
],
"\u{1F1F2}\u{1F1FB}": [
"flag_maldives",
"mv",
"flag",
"nation",
"country",
"banner",
"maldives",
"maldivian"
],
"\u{1F1F2}\u{1F1FC}": [
"flag_malawi",
"mw",
"flag",
"nation",
"country",
"banner",
"malawi",
"malawian\xA0flag"
],
"\u{1F1F2}\u{1F1FD}": [
"flag_mexico",
"mx",
"flag",
"nation",
"country",
"banner",
"mexico",
"mexican"
],
"\u{1F1F2}\u{1F1FE}": [
"flag_malaysia",
"my",
"flag",
"nation",
"country",
"banner",
"malaysia",
"malaysian"
],
"\u{1F1F2}\u{1F1FF}": [
"flag_mozambique",
"mz",
"flag",
"nation",
"country",
"banner",
"mozambique",
"mozambican"
],
"\u{1F1F3}\u{1F1E6}": [
"flag_namibia",
"na",
"flag",
"nation",
"country",
"banner",
"namibia",
"namibian"
],
"\u{1F1F3}\u{1F1E8}": [
"flag_new_caledonia",
"new",
"caledonia",
"flag",
"nation",
"country",
"banner",
"new_caledonia",
"caledonian"
],
"\u{1F1F3}\u{1F1EA}": [
"flag_niger",
"ne",
"flag",
"nation",
"country",
"banner",
"niger",
"nigerien\xA0flag"
],
"\u{1F1F3}\u{1F1EB}": [
"flag_norfolk_island",
"norfolk",
"island",
"flag",
"nation",
"country",
"banner",
"norfolk_island"
],
"\u{1F1F3}\u{1F1EC}": [
"flag_nigeria",
"flag",
"nation",
"country",
"banner",
"nigeria",
"nigerian"
],
"\u{1F1F3}\u{1F1EE}": [
"flag_nicaragua",
"ni",
"flag",
"nation",
"country",
"banner",
"nicaragua",
"nicaraguan"
],
"\u{1F1F3}\u{1F1F1}": [
"flag_netherlands",
"nl",
"flag",
"nation",
"country",
"banner",
"netherlands",
"dutch"
],
"\u{1F1F3}\u{1F1F4}": [
"flag_norway",
"no",
"flag",
"nation",
"country",
"banner",
"norway",
"bouvet",
"jan",
"mayen",
"norwegian",
"svalbard"
],
"\u{1F1F3}\u{1F1F5}": [
"flag_nepal",
"np",
"flag",
"nation",
"country",
"banner",
"nepal",
"nepalese"
],
"\u{1F1F3}\u{1F1F7}": [
"flag_nauru",
"nr",
"flag",
"nation",
"country",
"banner",
"nauru",
"nauruan"
],
"\u{1F1F3}\u{1F1FA}": [
"flag_niue",
"nu",
"flag",
"nation",
"country",
"banner",
"niue",
"niuean"
],
"\u{1F1F3}\u{1F1FF}": [
"flag_new_zealand",
"new",
"zealand",
"flag",
"nation",
"country",
"banner",
"new_zealand",
"kiwi"
],
"\u{1F1F4}\u{1F1F2}": [
"flag_oman",
"om_symbol",
"flag",
"nation",
"country",
"banner",
"oman",
"omani"
],
"\u{1F1F5}\u{1F1E6}": [
"flag_panama",
"pa",
"flag",
"nation",
"country",
"banner",
"panama",
"panamanian"
],
"\u{1F1F5}\u{1F1EA}": [
"flag_peru",
"pe",
"flag",
"nation",
"country",
"banner",
"peru",
"peruvian"
],
"\u{1F1F5}\u{1F1EB}": [
"flag_french_polynesia",
"french",
"polynesia",
"flag",
"nation",
"country",
"banner",
"french_polynesia",
"polynesian"
],
"\u{1F1F5}\u{1F1EC}": [
"flag_papua_new_guinea",
"papua",
"new",
"guinea",
"flag",
"nation",
"country",
"banner",
"papua_new_guinea",
"guinean",
"png"
],
"\u{1F1F5}\u{1F1ED}": [
"flag_philippines",
"ph",
"flag",
"nation",
"country",
"banner",
"philippines"
],
"\u{1F1F5}\u{1F1F0}": [
"flag_pakistan",
"pk",
"flag",
"nation",
"country",
"banner",
"pakistan",
"pakistani"
],
"\u{1F1F5}\u{1F1F1}": [
"flag_poland",
"pl",
"flag",
"nation",
"country",
"banner",
"poland",
"polish"
],
"\u{1F1F5}\u{1F1F2}": [
"flag_st_pierre_miquelon",
"saint",
"pierre",
"miquelon",
"flag",
"nation",
"country",
"banner",
"st_pierre_miquelon",
"st."
],
"\u{1F1F5}\u{1F1F3}": [
"flag_pitcairn_islands",
"pitcairn",
"flag",
"nation",
"country",
"banner",
"pitcairn_islands",
"island"
],
"\u{1F1F5}\u{1F1F7}": [
"flag_puerto_rico",
"puerto",
"rico",
"flag",
"nation",
"country",
"banner",
"puerto_rico",
"rican"
],
"\u{1F1F5}\u{1F1F8}": [
"flag_palestinian_territories",
"palestine",
"palestinian",
"territories",
"flag",
"nation",
"country",
"banner",
"palestinian_territories"
],
"\u{1F1F5}\u{1F1F9}": [
"flag_portugal",
"pt",
"flag",
"nation",
"country",
"banner",
"portugal",
"portugese"
],
"\u{1F1F5}\u{1F1FC}": [
"flag_palau",
"pw",
"flag",
"nation",
"country",
"banner",
"palau",
"palauan"
],
"\u{1F1F5}\u{1F1FE}": [
"flag_paraguay",
"py",
"flag",
"nation",
"country",
"banner",
"paraguay",
"paraguayan"
],
"\u{1F1F6}\u{1F1E6}": [
"flag_qatar",
"qa",
"flag",
"nation",
"country",
"banner",
"qatar",
"qatari"
],
"\u{1F1F7}\u{1F1EA}": [
"flag_reunion",
"r\xE9union",
"flag",
"nation",
"country",
"banner",
"reunion",
"r\xE9unionnais"
],
"\u{1F1F7}\u{1F1F4}": [
"flag_romania",
"ro",
"flag",
"nation",
"country",
"banner",
"romania",
"romanian"
],
"\u{1F1E8}\u{1F1F6}": [
"flag_sark",
"cq",
"flag",
"banner"
],
"\u{1F1F7}\u{1F1F8}": [
"flag_serbia",
"rs",
"flag",
"nation",
"country",
"banner",
"serbia",
"serbian\xA0flag"
],
"\u{1F1F7}\u{1F1FA}": [
"flag_russia",
"russian",
"federation",
"flag",
"nation",
"country",
"banner",
"russia",
"indicator",
"letters",
"regional",
"ru",
"symbol"
],
"\u{1F1F7}\u{1F1FC}": [
"flag_rwanda",
"rw",
"flag",
"nation",
"country",
"banner",
"rwanda",
"rwandan"
],
"\u{1F1F8}\u{1F1E6}": [
"flag_saudi_arabia",
"flag",
"nation",
"country",
"banner",
"saudi_arabia",
"arabian\xA0flag"
],
"\u{1F1F8}\u{1F1E7}": [
"flag_solomon_islands",
"solomon",
"islands",
"flag",
"nation",
"country",
"banner",
"solomon_islands",
"island",
"islander\xA0flag"
],
"\u{1F1F8}\u{1F1E8}": [
"flag_seychelles",
"sc",
"flag",
"nation",
"country",
"banner",
"seychelles",
"seychellois\xA0flag"
],
"\u{1F1F8}\u{1F1E9}": [
"flag_sudan",
"sd",
"flag",
"nation",
"country",
"banner",
"sudan",
"sudanese"
],
"\u{1F1F8}\u{1F1EA}": [
"flag_sweden",
"se",
"flag",
"nation",
"country",
"banner",
"sweden",
"swedish"
],
"\u{1F1F8}\u{1F1EC}": [
"flag_singapore",
"sg",
"flag",
"nation",
"country",
"banner",
"singapore",
"singaporean"
],
"\u{1F1F8}\u{1F1ED}": [
"flag_st_helena",
"saint",
"helena",
"ascension",
"tristan",
"cunha",
"flag",
"nation",
"country",
"banner",
"st_helena",
"st."
],
"\u{1F1F8}\u{1F1EE}": [
"flag_slovenia",
"si",
"flag",
"nation",
"country",
"banner",
"slovenia",
"slovenian"
],
"\u{1F1F8}\u{1F1EF}": [
"flag_svalbard_jan_mayen"
],
"\u{1F1F8}\u{1F1F0}": [
"flag_slovakia",
"sk",
"flag",
"nation",
"country",
"banner",
"slovakia",
"slovakian",
"slovak\xA0flag"
],
"\u{1F1F8}\u{1F1F1}": [
"flag_sierra_leone",
"sierra",
"leone",
"flag",
"nation",
"country",
"banner",
"sierra_leone",
"leonean"
],
"\u{1F1F8}\u{1F1F2}": [
"flag_san_marino",
"san",
"marino",
"flag",
"nation",
"country",
"banner",
"san_marino",
"sammarinese"
],
"\u{1F1F8}\u{1F1F3}": [
"flag_senegal",
"sn",
"flag",
"nation",
"country",
"banner",
"senegal",
"sengalese"
],
"\u{1F1F8}\u{1F1F4}": [
"flag_somalia",
"so",
"flag",
"nation",
"country",
"banner",
"somalia",
"somalian\xA0flag"
],
"\u{1F1F8}\u{1F1F7}": [
"flag_suriname",
"sr",
"flag",
"nation",
"country",
"banner",
"suriname",
"surinamer"
],
"\u{1F1F8}\u{1F1F8}": [
"flag_south_sudan",
"south",
"sd",
"flag",
"nation",
"country",
"banner",
"south_sudan",
"sudanese\xA0flag"
],
"\u{1F1F8}\u{1F1F9}": [
"flag_sao_tome_principe",
"sao",
"tome",
"principe",
"flag",
"nation",
"country",
"banner",
"sao_tome_principe",
"pr\xEDncipe",
"s\xE3o",
"tom\xE9"
],
"\u{1F1F8}\u{1F1FB}": [
"flag_el_salvador",
"el",
"salvador",
"flag",
"nation",
"country",
"banner",
"el_salvador",
"salvadoran"
],
"\u{1F1F8}\u{1F1FD}": [
"flag_sint_maarten",
"sint",
"maarten",
"dutch",
"flag",
"nation",
"country",
"banner",
"sint_maarten"
],
"\u{1F1F8}\u{1F1FE}": [
"flag_syria",
"syrian",
"arab",
"republic",
"flag",
"nation",
"country",
"banner",
"syria"
],
"\u{1F1F8}\u{1F1FF}": [
"flag_eswatini",
"sz",
"flag",
"nation",
"country",
"banner",
"eswatini",
"swaziland"
],
"\u{1F1F9}\u{1F1E6}": [
"flag_tristan_da_cunha"
],
"\u{1F1F9}\u{1F1E8}": [
"flag_turks_caicos_islands",
"turks",
"caicos",
"islands",
"flag",
"nation",
"country",
"banner",
"turks_caicos_islands",
"island"
],
"\u{1F1F9}\u{1F1E9}": [
"flag_chad",
"td",
"flag",
"nation",
"country",
"banner",
"chad",
"chadian"
],
"\u{1F1F9}\u{1F1EB}": [
"flag_french_southern_territories",
"french",
"southern",
"territories",
"flag",
"nation",
"country",
"banner",
"french_southern_territories",
"antarctic",
"lands"
],
"\u{1F1F9}\u{1F1EC}": [
"flag_togo",
"tg",
"flag",
"nation",
"country",
"banner",
"togo",
"togolese"
],
"\u{1F1F9}\u{1F1ED}": [
"flag_thailand",
"th",
"flag",
"nation",
"country",
"banner",
"thailand",
"thai"
],
"\u{1F1F9}\u{1F1EF}": [
"flag_tajikistan",
"tj",
"flag",
"nation",
"country",
"banner",
"tajikistan",
"tajik"
],
"\u{1F1F9}\u{1F1F0}": [
"flag_tokelau",
"tk",
"flag",
"nation",
"country",
"banner",
"tokelau",
"tokelauan"
],
"\u{1F1F9}\u{1F1F1}": [
"flag_timor_leste",
"timor",
"leste",
"flag",
"nation",
"country",
"banner",
"timor_leste",
"east",
"leste\xA0flag",
"timorese"
],
"\u{1F1F9}\u{1F1F2}": [
"flag_turkmenistan",
"flag",
"nation",
"country",
"banner",
"turkmenistan",
"turkmen"
],
"\u{1F1F9}\u{1F1F3}": [
"flag_tunisia",
"tn",
"flag",
"nation",
"country",
"banner",
"tunisia",
"tunisian"
],
"\u{1F1F9}\u{1F1F4}": [
"flag_tonga",
"to",
"flag",
"nation",
"country",
"banner",
"tonga",
"tongan\xA0flag"
],
"\u{1F1F9}\u{1F1F7}": [
"flag_turkey",
"turkey",
"flag",
"nation",
"country",
"banner",
"tr",
"turkish\xA0flag",
"t\xFCrkiye"
],
"\u{1F1F9}\u{1F1F9}": [
"flag_trinidad_tobago",
"trinidad",
"tobago",
"flag",
"nation",
"country",
"banner",
"trinidad_tobago"
],
"\u{1F1F9}\u{1F1FB}": [
"flag_tuvalu",
"flag",
"nation",
"country",
"banner",
"tuvalu",
"tuvaluan"
],
"\u{1F1F9}\u{1F1FC}": [
"flag_taiwan",
"tw",
"flag",
"nation",
"country",
"banner",
"taiwan",
"china",
"taiwanese"
],
"\u{1F1F9}\u{1F1FF}": [
"flag_tanzania",
"tanzania",
"united",
"republic",
"flag",
"nation",
"country",
"banner",
"tanzanian"
],
"\u{1F1FA}\u{1F1E6}": [
"flag_ukraine",
"ua",
"flag",
"nation",
"country",
"banner",
"ukraine",
"ukrainian"
],
"\u{1F1FA}\u{1F1EC}": [
"flag_uganda",
"ug",
"flag",
"nation",
"country",
"banner",
"uganda",
"ugandan\xA0flag"
],
"\u{1F1FA}\u{1F1F2}": [
"flag_u_s_outlying_islands",
"u.s.",
"us"
],
"\u{1F1FA}\u{1F1F3}": [
"flag_united_nations",
"un",
"flag",
"banner"
],
"\u{1F1FA}\u{1F1F8}": [
"flag_united_states",
"united",
"states",
"america",
"flag",
"nation",
"country",
"banner",
"united_states",
"american",
"indicator",
"islands",
"letters",
"outlying",
"regional",
"symbol",
"us",
"usa"
],
"\u{1F1FA}\u{1F1FE}": [
"flag_uruguay",
"uy",
"flag",
"nation",
"country",
"banner",
"uruguay",
"uruguayan"
],
"\u{1F1FA}\u{1F1FF}": [
"flag_uzbekistan",
"uz",
"flag",
"nation",
"country",
"banner",
"uzbekistan",
"uzbek",
"uzbekistani"
],
"\u{1F1FB}\u{1F1E6}": [
"flag_vatican_city",
"vatican",
"city",
"flag",
"nation",
"country",
"banner",
"vatican_city",
"vanticanien"
],
"\u{1F1FB}\u{1F1E8}": [
"flag_st_vincent_grenadines",
"saint",
"vincent",
"grenadines",
"flag",
"nation",
"country",
"banner",
"st_vincent_grenadines",
"st."
],
"\u{1F1FB}\u{1F1EA}": [
"flag_venezuela",
"ve",
"bolivarian",
"republic",
"flag",
"nation",
"country",
"banner",
"venezuela",
"venezuelan"
],
"\u{1F1FB}\u{1F1EC}": [
"flag_british_virgin_islands",
"british",
"virgin",
"islands",
"bvi",
"flag",
"nation",
"country",
"banner",
"british_virgin_islands",
"island",
"islander"
],
"\u{1F1FB}\u{1F1EE}": [
"flag_u_s_virgin_islands",
"virgin",
"islands",
"us",
"flag",
"nation",
"country",
"banner",
"u_s_virgin_islands",
"america",
"island",
"islander",
"states",
"u.s.",
"united",
"usa"
],
"\u{1F1FB}\u{1F1F3}": [
"flag_vietnam",
"viet",
"nam",
"flag",
"nation",
"country",
"banner",
"vietnam",
"vietnamese"
],
"\u{1F1FB}\u{1F1FA}": [
"flag_vanuatu",
"vu",
"flag",
"nation",
"country",
"banner",
"vanuatu",
"ni",
"vanuatu\xA0flag"
],
"\u{1F1FC}\u{1F1EB}": [
"flag_wallis_futuna",
"wallis",
"futuna",
"flag",
"nation",
"country",
"banner",
"wallis_futuna"
],
"\u{1F1FC}\u{1F1F8}": [
"flag_samoa",
"ws",
"flag",
"nation",
"country",
"banner",
"samoa",
"samoan\xA0flag"
],
"\u{1F1FD}\u{1F1F0}": [
"flag_kosovo",
"xk",
"flag",
"nation",
"country",
"banner",
"kosovo",
"kosovar"
],
"\u{1F1FE}\u{1F1EA}": [
"flag_yemen",
"ye",
"flag",
"nation",
"country",
"banner",
"yemen",
"yemeni\xA0flag"
],
"\u{1F1FE}\u{1F1F9}": [
"flag_mayotte",
"yt",
"flag",
"nation",
"country",
"banner",
"mayotte"
],
"\u{1F1FF}\u{1F1E6}": [
"flag_south_africa",
"south",
"africa",
"flag",
"nation",
"country",
"banner",
"south_africa",
"african\xA0flag"
],
"\u{1F1FF}\u{1F1F2}": [
"flag_zambia",
"zm",
"flag",
"nation",
"country",
"banner",
"zambia",
"zambian"
],
"\u{1F1FF}\u{1F1FC}": [
"flag_zimbabwe",
"zw",
"flag",
"nation",
"country",
"banner",
"zimbabwe",
"zim",
"zimbabwean\xA0flag"
],
"\u{1F3F4}\u{E0067}\u{E0062}\u{E0065}\u{E006E}\u{E0067}\u{E007F}": [
"flag_england",
"flag",
"english",
"cross",
"george's",
"st"
],
"\u{1F3F4}\u{E0067}\u{E0062}\u{E0073}\u{E0063}\u{E0074}\u{E007F}": [
"flag_scotland",
"flag",
"scottish",
"andrew's",
"cross",
"saltire",
"st"
],
"\u{1F3F4}\u{E0067}\u{E0062}\u{E0077}\u{E006C}\u{E0073}\u{E007F}": [
"flag_wales",
"flag",
"welsh",
"baner",
"cymru",
"ddraig",
"dragon",
"goch",
"red",
"y"
],
"\u{1F972}": [
"smiling face with tear",
"sad",
"cry",
"pretend",
"grateful",
"happy",
"proud",
"relieved",
"smile",
"touched"
],
"\u{1F978}": [
"disguised face",
"pretent",
"brows",
"glasses",
"moustache",
"disguise",
"incognito",
"nose"
],
"\u{1F90C}": [
"pinched fingers",
"size",
"tiny",
"small",
"che",
"finger",
"gesture",
"hand",
"interrogation",
"ma",
"purse",
"sarcastic",
"vuoi"
],
"\u{1FAC0}": [
"anatomical heart",
"health",
"heartbeat",
"cardiology",
"organ",
"pulse"
],
"\u{1FAC1}": [
"lungs",
"breathe",
"breath",
"exhalation",
"inhalation",
"organ",
"respiration"
],
"\u{1F977}": [
"ninja",
"ninjutsu",
"skills",
"japanese",
"fighter",
"hidden",
"stealth"
],
"\u{1F935}\u200D\u2642\uFE0F": [
"man in tuxedo",
"formal",
"fashion",
"groom",
"male",
"men",
"person",
"suit",
"wedding"
],
"\u{1F935}\u200D\u2640\uFE0F": [
"woman in tuxedo",
"formal",
"fashion",
"female",
"wedding",
"women"
],
"\u{1F470}\u200D\u2642\uFE0F": [
"man with veil",
"wedding",
"marriage",
"bride",
"male",
"men"
],
"\u{1F470}\u200D\u2640\uFE0F": [
"woman with veil",
"wedding",
"marriage",
"bride",
"female",
"women"
],
"\u{1F469}\u200D\u{1F37C}": [
"woman feeding baby",
"birth",
"food",
"bottle",
"child",
"female",
"infant",
"milk",
"nursing",
"women"
],
"\u{1F468}\u200D\u{1F37C}": [
"man feeding baby",
"birth",
"food",
"bottle",
"child",
"infant",
"male",
"men",
"milk",
"nursing"
],
"\u{1F9D1}\u200D\u{1F37C}": [
"person feeding baby",
"birth",
"food",
"bottle",
"child",
"infant",
"milk",
"nursing"
],
"\u{1F9D1}\u200D\u{1F384}": [
"mx claus",
"christmas",
"activity",
"celebration",
"mx.",
"santa"
],
"\u{1FAC2}": [
"people hugging",
"care",
"goodbye",
"hello",
"hug",
"thanks"
],
"\u{1F408}\u200D\u2B1B": [
"black cat",
"superstition",
"luck",
"halloween",
"pet",
"unlucky"
],
"\u{1F9AC}": [
"bison",
"ox",
"buffalo",
"herd",
"wisent"
],
"\u{1F9A3}": [
"mammoth",
"elephant",
"tusks",
"extinct",
"extinction",
"large",
"tusk",
"woolly"
],
"\u{1F9AB}": [
"beaver",
"animal",
"rodent",
"dam"
],
"\u{1F43B}\u200D\u2744\uFE0F": [
"polar bear",
"animal",
"arctic",
"face",
"white"
],
"\u{1F9A4}": [
"dodo",
"animal",
"bird",
"extinct",
"extinction",
"large",
"mauritius",
"obsolete"
],
"\u{1FAB6}": [
"feather",
"bird",
"fly",
"flight",
"light",
"plumage"
],
"\u{1F9AD}": [
"seal",
"animal",
"creature",
"sea",
"lion"
],
"\u{1FAB2}": [
"beetle",
"insect",
"bug"
],
"\u{1FAB3}": [
"cockroach",
"insect",
"pests",
"pest",
"roach"
],
"\u{1FAB0}": [
"fly",
"insect",
"disease",
"maggot",
"pest",
"rotting"
],
"\u{1FAB1}": [
"worm",
"animal",
"annelid",
"earthworm",
"parasite"
],
"\u{1FAB4}": [
"potted plant",
"greenery",
"house",
"boring",
"grow",
"houseplant",
"nurturing",
"useless"
],
"\u{1FAD0}": [
"blueberries",
"fruit",
"berry",
"bilberry",
"blue",
"blueberry"
],
"\u{1FAD2}": [
"olive",
"fruit",
"food",
"olives"
],
"\u{1FAD1}": [
"bell pepper",
"fruit",
"plant",
"capsicum",
"vegetable"
],
"\u{1FAD3}": [
"flatbread",
"flour",
"food",
"bakery",
"arepa",
"bread",
"flat",
"lavash",
"naan",
"pita"
],
"\u{1FAD4}": [
"tamale",
"food",
"masa",
"mexican",
"tamal",
"wrapped"
],
"\u{1FAD5}": [
"fondue",
"cheese",
"pot",
"food",
"chocolate",
"melted",
"swiss"
],
"\u{1FAD6}": [
"teapot",
"drink",
"hot",
"kettle",
"pot",
"tea"
],
"\u{1F9CB}": [
"bubble tea",
"taiwan",
"boba",
"milk tea",
"straw",
"momi",
"pearl",
"tapioca"
],
"\u{1FAA8}": [
"rock",
"stone",
"boulder",
"construction",
"heavy",
"solid"
],
"\u{1FAB5}": [
"wood",
"nature",
"timber",
"trunk",
"construction",
"log",
"lumber"
],
"\u{1F6D6}": [
"hut",
"house",
"structure",
"roundhouse",
"yurt"
],
"\u{1F6FB}": [
"pickup truck",
"car",
"transportation",
"vehicle"
],
"\u{1F6FC}": [
"roller skate",
"footwear",
"sports",
"derby",
"inline"
],
"\u{1FA84}": [
"magic wand",
"supernature",
"power",
"witch",
"wizard"
],
"\u{1FA85}": [
"pinata",
"mexico",
"candy",
"celebration",
"party",
"pi\xF1ata"
],
"\u{1FA86}": [
"nesting dolls",
"matryoshka",
"toy",
"doll",
"russia",
"russian"
],
"\u{1FAA1}": [
"sewing needle",
"stitches",
"embroidery",
"sutures",
"tailoring"
],
"\u{1FAA2}": [
"knot",
"rope",
"scout",
"tangled",
"tie",
"twine",
"twist"
],
"\u{1FA74}": [
"thong sandal",
"footwear",
"summer",
"beach",
"flip",
"flops",
"jandals",
"sandals",
"thongs",
"z\u014Dri"
],
"\u{1FA96}": [
"military helmet",
"army",
"protection",
"soldier",
"warrior"
],
"\u{1FA97}": [
"accordion",
"music",
"accordian",
"box",
"concertina",
"squeeze"
],
"\u{1FA98}": [
"long drum",
"music",
"beat",
"conga",
"djembe",
"rhythm"
],
"\u{1FA99}": [
"coin",
"money",
"currency",
"gold",
"metal",
"silver",
"treasure"
],
"\u{1FA83}": [
"boomerang",
"weapon",
"australia",
"rebound",
"repercussion"
],
"\u{1FA9A}": [
"carpentry saw",
"cut",
"chop",
"carpenter",
"hand",
"lumber",
"tool"
],
"\u{1FA9B}": [
"screwdriver",
"tools",
"screw",
"tool"
],
"\u{1FA9D}": [
"hook",
"tools",
"catch",
"crook",
"curve",
"ensnare",
"fishing",
"point",
"selling",
"tool"
],
"\u{1FA9C}": [
"ladder",
"tools",
"climb",
"rung",
"step",
"tool"
],
"\u{1F6D7}": [
"elevator",
"lift",
"accessibility",
"hoist"
],
"\u{1FA9E}": [
"mirror",
"reflection",
"reflector",
"speculum"
],
"\u{1FA9F}": [
"window",
"scenery",
"air",
"frame",
"fresh",
"glass",
"opening",
"transparent",
"view"
],
"\u{1FAA0}": [
"plunger",
"toilet",
"cup",
"force",
"plumber",
"suction"
],
"\u{1FAA4}": [
"mouse trap",
"cheese",
"bait",
"mousetrap",
"rodent",
"snare"
],
"\u{1FAA3}": [
"bucket",
"water",
"container",
"cask",
"pail",
"vat"
],
"\u{1FAA5}": [
"toothbrush",
"hygiene",
"dental",
"bathroom",
"brush",
"clean",
"teeth"
],
"\u{1FAA6}": [
"headstone",
"death",
"rip",
"grave",
"cemetery",
"graveyard",
"halloween",
"tombstone"
],
"\u{1FAA7}": [
"placard",
"announcement",
"demonstration",
"lawn",
"picket",
"post",
"protest",
"sign"
],
"\u26A7\uFE0F": [
"transgender symbol",
"transgender",
"lgbtq",
"female",
"lgbt",
"male",
"pride",
"sign",
"stroke"
],
"\u{1F3F3}\uFE0F\u200D\u26A7\uFE0F": [
"transgender flag",
"transgender",
"flag",
"pride",
"lgbtq",
"blue",
"lgbt",
"light",
"pink",
"trans",
"white"
],
"\u{1F636}\u200D\u{1F32B}\uFE0F": [
"face in clouds",
"shower",
"steam",
"dream",
"absentminded",
"brain",
"fog",
"forgetful",
"haze",
"head",
"impractical",
"unrealistic"
],
"\u{1F62E}\u200D\u{1F4A8}": [
"face exhaling",
"relieve",
"relief",
"tired",
"sigh",
"exhale",
"gasp",
"groan",
"whisper",
"whistle"
],
"\u{1F635}\u200D\u{1F4AB}": [
"face with spiral eyes",
"sick",
"ill",
"confused",
"nauseous",
"nausea",
"dizzy",
"hypnotized",
"trouble",
"whoa"
],
"\u2764\uFE0F\u200D\u{1F525}": [
"heart on fire",
"passionate",
"enthusiastic",
"burn",
"love",
"lust",
"sacred"
],
"\u2764\uFE0F\u200D\u{1FA79}": [
"mending heart",
"broken heart",
"bandage",
"wounded",
"bandaged",
"healing",
"healthier",
"improving",
"recovering",
"recuperating",
"unbroken",
"well"
],
"\u{1F9D4}\u200D\u2642\uFE0F": [
"man beard",
"facial hair",
"bearded",
"bewhiskered",
"male",
"men"
],
"\u{1F9D4}\u200D\u2640\uFE0F": [
"woman beard",
"facial hair",
"bearded",
"bewhiskered",
"female",
"women"
],
"\u{1FAE0}": [
"melting face",
"hot",
"heat",
"disappear",
"dissolve",
"dread",
"liquid",
"melt",
"sarcasm"
],
"\u{1FAE2}": [
"face with open eyes and hand over mouth",
"silence",
"secret",
"shock",
"surprise",
"amazement",
"awe",
"disbelief",
"embarrass",
"gasp",
"scared"
],
"\u{1FAE3}": [
"face with peeking eye",
"scared",
"frightening",
"embarrassing",
"shy",
"captivated",
"peep",
"stare"
],
"\u{1FAE1}": [
"saluting face",
"respect",
"salute",
"ok",
"sunny",
"troops",
"yes"
],
"\u{1FAE5}": [
"dotted line face",
"invisible",
"lonely",
"isolation",
"depression",
"depressed",
"disappear",
"hide",
"introvert"
],
"\u{1FAE4}": [
"face with diagonal mouth",
"skeptic",
"confuse",
"frustrated",
"indifferent",
"confused",
"disappointed",
"meh",
"skeptical",
"unsure"
],
"\u{1F979}": [
"face holding back tears",
"touched",
"gratitude",
"cry",
"angry",
"proud",
"resist",
"sad"
],
"\u{1FAF1}": [
"rightwards hand",
"palm",
"offer",
"right",
"rightward"
],
"\u{1FAF2}": [
"leftwards hand",
"palm",
"offer",
"left",
"leftward"
],
"\u{1FAF3}": [
"palm down hand",
"palm",
"drop",
"dismiss",
"shoo"
],
"\u{1FAF4}": [
"palm up hand",
"lift",
"offer",
"demand",
"beckon",
"catch",
"come"
],
"\u{1FAF0}": [
"hand with index finger and thumb crossed",
"heart",
"love",
"money",
"expensive",
"snap"
],
"\u{1FAF5}": [
"index pointing at the viewer",
"you",
"recruit",
"point"
],
"\u{1FAF6}": [
"heart hands",
"love",
"appreciation",
"support"
],
"\u{1FAE6}": [
"biting lip",
"flirt",
"sexy",
"pain",
"worry",
"anxious",
"fear",
"flirting",
"nervous",
"uncomfortable",
"worried"
],
"\u{1FAC5}": [
"person with crown",
"royalty",
"power",
"monarch",
"noble",
"regal"
],
"\u{1FAC3}": [
"pregnant man",
"baby",
"belly",
"bloated",
"full"
],
"\u{1FAC4}": [
"pregnant person",
"baby",
"belly",
"bloated",
"full"
],
"\u{1F9CC}": [
"troll",
"mystical",
"monster",
"fairy",
"fantasy",
"tale",
"shrek"
],
"\u{1FAB8}": [
"coral",
"ocean",
"sea",
"reef"
],
"\u{1FAB7}": [
"lotus",
"flower",
"calm",
"meditation",
"buddhism",
"hinduism",
"india",
"purity",
"vietnam"
],
"\u{1FAB9}": [
"empty nest",
"bird",
"nesting"
],
"\u{1FABA}": [
"nest with eggs",
"bird",
"nesting"
],
"\u{1FAD8}": [
"beans",
"food",
"kidney",
"legume"
],
"\u{1FAD7}": [
"pouring liquid",
"cup",
"water",
"drink",
"empty",
"glass",
"spill"
],
"\u{1FAD9}": [
"jar",
"container",
"sauce",
"condiment",
"empty",
"store"
],
"\u{1F6DD}": [
"playground slide",
"fun",
"park",
"amusement",
"play"
],
"\u{1F6DE}": [
"wheel",
"car",
"transport",
"circle",
"tire",
"turn"
],
"\u{1F6DF}": [
"ring buoy",
"life saver",
"life preserver",
"float",
"rescue",
"safety"
],
"\u{1FAAC}": [
"hamsa",
"religion",
"protection",
"amulet",
"fatima",
"hand",
"mary",
"miriam"
],
"\u{1FAA9}": [
"mirror ball",
"disco",
"dance",
"party",
"glitter"
],
"\u{1FAAB}": [
"low battery",
"drained",
"dead",
"electronic",
"energy",
"no",
"red"
],
"\u{1FA7C}": [
"crutch",
"accessibility",
"assist",
"aid",
"cane",
"disability",
"hurt",
"mobility",
"stick"
],
"\u{1FA7B}": [
"x-ray",
"skeleton",
"medicine",
"bones",
"doctor",
"medical",
"ray",
"x"
],
"\u{1FAE7}": [
"bubbles",
"soap",
"fun",
"carbonation",
"sparkling",
"burp",
"clean",
"underwater"
],
"\u{1FAAA}": [
"identification card",
"document",
"credentials",
"id",
"license",
"security"
],
"\u{1F7F0}": [
"heavy equals sign",
"math",
"equality"
],
"\u{1FAE8}": [
"shaking face",
"dizzy",
"shock",
"blurry",
"earthquake"
],
"\u{1FA77}": [
"pink heart",
"valentines"
],
"\u{1FA75}": [
"light blue heart",
"ice",
"baby blue"
],
"\u{1FA76}": [
"grey heart",
"silver",
"monochrome"
],
"\u{1FAF7}": [
"leftwards pushing hand",
"highfive",
"pressing",
"stop"
],
"\u{1FAF8}": [
"rightwards pushing hand",
"highfive",
"pressing",
"stop"
],
"\u{1FACE}": [
"moose",
"canada",
"sweden",
"sven",
"cool"
],
"\u{1FACF}": [
"donkey",
"eeyore",
"mule"
],
"\u{1FABD}": [
"wing",
"angel",
"birds",
"flying",
"fly"
],
"\u{1F426}\u200D\u2B1B": [
"black bird",
"crow"
],
"\u{1FABF}": [
"goose",
"silly",
"jemima",
"goosebumps",
"honk"
],
"\u{1FABC}": [
"jellyfish",
"sting",
"tentacles"
],
"\u{1FABB}": [
"hyacinth",
"flower",
"lavender"
],
"\u{1FADA}": [
"ginger root",
"spice",
"yellow",
"cooking",
"gingerbread"
],
"\u{1FADB}": [
"pea pod",
"cozy",
"green"
],
"\u{1FAAD}": [
"folding hand fan",
"flamenco",
"hot",
"sensu"
],
"\u{1FAAE}": [
"hair pick",
"afro",
"comb"
],
"\u{1FA87}": [
"maracas",
"music",
"instrument",
"percussion",
"shaker"
],
"\u{1FA88}": [
"flute",
"bamboo",
"music",
"instrument",
"pied piper",
"recorder"
],
"\u{1FAAF}": [
"khanda",
"Sikhism",
"religion"
],
"\u{1F6DC}": [
"wireless",
"wifi",
"internet",
"contactless",
"signal"
],
"\u{1F642}\u200D\u2194\uFE0F": [
"head shaking horizontally",
"disapprove",
"indiffernt",
"left"
],
"\u{1F642}\u200D\u2195\uFE0F": [
"head shaking vertically",
"down",
"nod"
],
"\u{1F6B6}\u200D\u27A1\uFE0F": [
"person walking facing right",
"peerson",
"exercise"
],
"\u{1F6B6}\u200D\u2640\uFE0F\u200D\u27A1\uFE0F": [
"woman walking facing right",
"person",
"exercise"
],
"\u{1F6B6}\u200D\u2642\uFE0F\u200D\u27A1\uFE0F": [
"man walking facing right",
"person",
"exercise"
],
"\u{1F9CE}\u200D\u27A1\uFE0F": [
"person kneeling facing right",
"pray"
],
"\u{1F9CE}\u200D\u2640\uFE0F\u200D\u27A1\uFE0F": [
"woman kneeling facing right",
"pray",
"worship"
],
"\u{1F9CE}\u200D\u2642\uFE0F\u200D\u27A1\uFE0F": [
"man kneeling facing right",
"pray",
"worship"
],
"\u{1F9D1}\u200D\u{1F9AF}\u200D\u27A1\uFE0F": [
"person with white cane facing right",
"walk",
"walk",
"visually impaired",
"blind"
],
"\u{1F468}\u200D\u{1F9AF}\u200D\u27A1\uFE0F": [
"man with white cane facing right",
"visually impaired",
"blind",
"walk",
"stick"
],
"\u{1F469}\u200D\u{1F9AF}\u200D\u27A1\uFE0F": [
"woman with white cane facing right",
"stick",
"visually impaired",
"blind"
],
"\u{1F9D1}\u200D\u{1F9BC}\u200D\u27A1\uFE0F": [
"person in motorized wheelchair facing right",
"accessibility",
"disability"
],
"\u{1F468}\u200D\u{1F9BC}\u200D\u27A1\uFE0F": [
"man in motorized wheelchair facing right",
"disability",
"accessibility",
"mobility"
],
"\u{1F469}\u200D\u{1F9BC}\u200D\u27A1\uFE0F": [
"woman in motorized wheelchair facing right",
"mobility",
"accessibility",
"disability"
],
"\u{1F9D1}\u200D\u{1F9BD}\u200D\u27A1\uFE0F": [
"person in manual wheelchair facing right",
"mobility",
"accessibility",
"disability"
],
"\u{1F468}\u200D\u{1F9BD}\u200D\u27A1\uFE0F": [
"man in manual wheelchair facing right",
"mobility",
"accessibility",
"disability"
],
"\u{1F469}\u200D\u{1F9BD}\u200D\u27A1\uFE0F": [
"woman in manual wheelchair facing right",
"disability",
"mobility",
"accessibility"
],
"\u{1F3C3}\u200D\u27A1\uFE0F": [
"person running facing right",
"exercise",
"jog"
],
"\u{1F3C3}\u200D\u2640\uFE0F\u200D\u27A1\uFE0F": [
"woman running facing right",
"exercise",
"jog"
],
"\u{1F3C3}\u200D\u2642\uFE0F\u200D\u27A1\uFE0F": [
"man running facing right",
"jog",
"exercise"
],
"\u{1F9D1}\u200D\u{1F9D1}\u200D\u{1F9D2}": [
"family adult, adult, child",
"kid",
"parents"
],
"\u{1F9D1}\u200D\u{1F9D1}\u200D\u{1F9D2}\u200D\u{1F9D2}": [
"family adult, adult, child, child",
"children",
"parents"
],
"\u{1F9D1}\u200D\u{1F9D2}": [
"family adult, child",
"parent",
"kid"
],
"\u{1F9D1}\u200D\u{1F9D2}\u200D\u{1F9D2}": [
"family adult, child, child",
"parent",
"children"
],
"\u{1F426}\u200D\u{1F525}": [
"phoenix",
"immortal",
"bird",
"mythtical",
"reborn"
],
"\u{1F34B}\u200D\u{1F7E9}": [
"lime",
"fruit",
"acidic",
"citric"
],
"\u{1F344}\u200D\u{1F7EB}": [
"brown mushroom",
"toadstool",
"fungus"
],
"\u26D3\uFE0F\u200D\u{1F4A5}": [
"broken chain",
"constraint",
"break"
],
"\u{1FAE9}": [
"face with bags under eyes",
"tired",
"sleepy",
"exhausted"
],
"\u{1FAC6}": [
"fingerprint"
],
"\u{1FABE}": [
"leafless tree"
],
"\u{1FADC}": [
"root vegetable",
"radish"
],
"\u{1FA89}": [
"harp",
"music",
"instrument"
],
"\u{1FA8F}": [
"shovel",
"tool",
"dig"
],
"\u{1FADF}": [
"splatter"
]
};
}
});
// src/resources/emojis.js
var emojiData;
var init_emojis = __esm({
"src/resources/emojis.js"() {
init_emoji_en_US();
emojiData = Object.entries(emoji_en_US_default).map(([emoji, keywords]) => ({
emoji,
keywords: keywords.join(" ")
}));
}
});
// src/modal/modals/emojiSelectionModal.js
var import_obsidian19, EmojiSelectionModal;
var init_emojiSelectionModal = __esm({
"src/modal/modals/emojiSelectionModal.js"() {
import_obsidian19 = require("obsidian");
init_emojis();
init_modals();
EmojiSelectionModal = class extends import_obsidian19.Modal {
constructor(app, plugin, onChoose, skipTargetingModal = false) {
super(app);
this.plugin = plugin;
this.onChoose = onChoose;
this.searchQuery = "";
this.skipTargetingModal = skipTargetingModal;
this.closedByButton = false;
}
onOpen() {
var _a;
const { contentEl } = this;
contentEl.empty();
contentEl.addClass("pixel-banner-emoji-select-modal");
this.closedByButton = false;
const activeFile = this.app.workspace.getActiveFile();
const frontmatter = (_a = this.app.metadataCache.getFileCache(activeFile)) == null ? void 0 : _a.frontmatter;
const bannerIconField = Array.isArray(this.plugin.settings.customBannerIconField) ? this.plugin.settings.customBannerIconField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconField;
this.currentBannerIconField = (frontmatter == null ? void 0 : frontmatter[bannerIconField]) || "";
contentEl.createEl("h3", {
text: "\u{1F4F0} Set Banner Icon Emoji & Text",
cls: "banner-icon-title margin-top-0"
});
contentEl.createEl("p", {
text: `Use the form below to optionally set an emoji and/or text as this note's Banner Icon. Leave the value blank and click the "Update Banner Icon" button to clear any existing banner icon if it exists.`,
attr: {
style: `
padding-top: 20px;
border-top: 1px solid var(--background-modifier-border);
font-size: 0.8em;
color: var(--text-muted);
`
}
});
const bannerIconContainer = contentEl.createDiv({
cls: "banner-icon-input-container",
attr: {
style: `
display: flex;
gap: 8px;
align-items: center;
margin-top: 1em;
padding: 8px;
`
}
});
this.bannerIconInput = bannerIconContainer.createEl("input", {
type: "text",
placeholder: "Enter an emoji and/or text...",
cls: "banner-icon-input",
attr: {
style: `
font-size: 1.2em;
padding: 15px 10px;
`
},
value: this.currentBannerIconField || ""
});
const setBannerButton = bannerIconContainer.createEl("button", {
text: "\u{1F4BE} Insert / Update",
cls: "set-banner-button",
attr: {
style: `
background-color: var(--interactive-accent);
--text-color: var(--text-on-accent);
`
}
});
const removeBannerIconButton = bannerIconContainer.createEl("button", {
text: "\u{1F5D1}\uFE0F Remove",
cls: "remove-banner-icon-button cursor-pointer"
});
const handleSetBannerButtonClick = async () => {
this.closedByButton = true;
const trimmedValue = this.bannerIconInput.value.trim();
await this.onChoose(trimmedValue === "" ? null : trimmedValue);
if (trimmedValue === "") {
const activeFile3 = this.app.workspace.getActiveFile();
if (activeFile3) {
await this.app.fileManager.processFrontMatter(activeFile3, (frontmatter2) => {
const bannerIconField2 = Array.isArray(this.plugin.settings.customBannerIconField) ? this.plugin.settings.customBannerIconField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconField;
const bannerIconImageAlignmentField = Array.isArray(this.plugin.settings.customBannerIconImageAlignmentField) ? this.plugin.settings.customBannerIconImageAlignmentField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageAlignmentField;
const iconSizeField = Array.isArray(this.plugin.settings.customBannerIconSizeField) ? this.plugin.settings.customBannerIconSizeField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconSizeField;
const iconTextVerticalOffsetField = Array.isArray(this.plugin.settings.customBannerIconTextVerticalOffsetField) ? this.plugin.settings.customBannerIconTextVerticalOffsetField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconTextVerticalOffsetField;
const iconRotateField = Array.isArray(this.plugin.settings.customBannerIconRotateField) ? this.plugin.settings.customBannerIconRotateField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconRotateField;
const iconYPositionField = Array.isArray(this.plugin.settings.customBannerIconVerticalOffsetField) ? this.plugin.settings.customBannerIconVerticalOffsetField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconVerticalOffsetField;
const iconXPositionField = Array.isArray(this.plugin.settings.customBannerIconXPositionField) ? this.plugin.settings.customBannerIconXPositionField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconXPositionField;
const iconColorField = Array.isArray(this.plugin.settings.customBannerIconColorField) ? this.plugin.settings.customBannerIconColorField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconColorField;
const iconBgColorField = Array.isArray(this.plugin.settings.customBannerIconBackgroundColorField) ? this.plugin.settings.customBannerIconBackgroundColorField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconBackgroundColorField;
const iconXPaddingField = Array.isArray(this.plugin.settings.customBannerIconPaddingXField) ? this.plugin.settings.customBannerIconPaddingXField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconPaddingXField;
const iconYPaddingField = Array.isArray(this.plugin.settings.customBannerIconPaddingYField) ? this.plugin.settings.customBannerIconPaddingYField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconPaddingYField;
const iconBorderRadiusField = Array.isArray(this.plugin.settings.customBannerIconBorderRadiusField) ? this.plugin.settings.customBannerIconBorderRadiusField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconBorderRadiusField;
const bannerIconImageField = Array.isArray(this.plugin.settings.customBannerIconImageField) ? this.plugin.settings.customBannerIconImageField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageField;
const iconImageSizeMultiplierField = Array.isArray(this.plugin.settings.customBannerIconImageSizeMultiplierField) ? this.plugin.settings.customBannerIconImageSizeMultiplierField[0].split(",")[0].trim() : this.plugin.settings.customBannerIconImageSizeMultiplierField;
const hasBannerIconImage = frontmatter2[bannerIconImageField] !== void 0 && frontmatter2[bannerIconImageField] !== null && frontmatter2[bannerIconImageField] !== "";
if (!hasBannerIconImage) {
delete frontmatter2[bannerIconField2];
delete frontmatter2[bannerIconImageAlignmentField];
delete frontmatter2[iconSizeField];
delete frontmatter2[iconTextVerticalOffsetField];
delete frontmatter2[iconRotateField];
delete frontmatter2[iconYPositionField];
delete frontmatter2[iconXPositionField];
delete frontmatter2[iconColorField];
delete frontmatter2[iconBgColorField];
delete frontmatter2[iconXPaddingField];
delete frontmatter2[iconYPaddingField];
delete frontmatter2[iconBorderRadiusField];
delete frontmatter2[iconImageSizeMultiplierField];
} else {
delete frontmatter2[bannerIconField2];
delete frontmatter2[iconTextVerticalOffsetField];
delete frontmatter2[iconColorField];
delete frontmatter2[bannerIconImageAlignmentField];
}
});
}
}
this.close();
const activeFile2 = this.app.workspace.getActiveFile();
if (activeFile2) {
const activeView = this.app.workspace.getActiveViewOfType(import_obsidian19.MarkdownView);
if (activeView) {
const contentEl2 = activeView.contentEl;
if (contentEl2) {
const existingOverlays = contentEl2.querySelectorAll(".banner-icon-overlay");
existingOverlays.forEach((overlay) => {
this.plugin.returnIconOverlay(overlay);
});
}
await this.plugin.updateBanner(activeView, true, this.plugin.UPDATE_MODE.FULL_UPDATE);
}
}
if (!this.skipTargetingModal && this.plugin.settings.openTargetingModalAfterSelectingBannerOrIcon) {
const activeFile3 = this.app.workspace.getActiveFile();
if (activeFile3) {
await new Promise((resolve) => {
var _a2;
const initialFrontmatter = JSON.stringify(
((_a2 = this.app.metadataCache.getFileCache(activeFile3)) == null ? void 0 : _a2.frontmatter) || {}
);
const eventRef = this.app.metadataCache.on("changed", (file) => {
var _a3;
if (file.path !== activeFile3.path) return;
const updatedFrontmatter = JSON.stringify(
((_a3 = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a3.frontmatter) || {}
);
if (updatedFrontmatter !== initialFrontmatter) {
this.app.metadataCache.off("changed", eventRef);
resolve();
}
});
setTimeout(() => {
this.app.metadataCache.off("changed", eventRef);
resolve();
}, 500);
});
new TargetPositionModal(this.app, this.plugin).open();
}
}
};
setBannerButton.addEventListener("click", handleSetBannerButtonClick);
removeBannerIconButton.addEventListener("click", async () => {
this.bannerIconInput.value = "";
await handleSetBannerButtonClick();
});
contentEl.createEl("h5", { text: "Emoji Selector" });
const searchContainer = contentEl.createDiv({ cls: "emoji-search-container" });
const searchInput = searchContainer.createEl("input", {
type: "text",
placeholder: "Search emojis...",
cls: "emoji-search-input"
});
this.gridContainer = contentEl.createDiv({ cls: "emoji-grid-container" });
searchInput.addEventListener("input", () => {
this.searchQuery = searchInput.value.toLowerCase();
this.updateEmojiGrid();
});
this.updateEmojiGrid();
const modalEl = this.modalEl;
modalEl.style.position = "absolute";
modalEl.style.left = `${modalEl.getBoundingClientRect().left}px`;
modalEl.style.top = `${modalEl.getBoundingClientRect().top}px`;
}
updateEmojiGrid() {
this.gridContainer.empty();
const filteredEmojis = emojiData.filter(({ emoji, keywords }) => {
if (!this.searchQuery) return true;
return keywords.toLowerCase().includes(this.searchQuery);
});
filteredEmojis.forEach(({ emoji }) => {
const emojiButton = this.gridContainer.createEl("button", {
text: emoji,
cls: "emoji-button",
attr: {
"aria-label": this.getEmojiDescription(emoji)
}
});
emojiButton.addEventListener("click", () => {
this.bannerIconInput.value += emoji;
});
});
const style = document.createElement("style");
style.textContent = `
.pixel-banner-emoji-select-modal {
max-width: 600px;
max-height: 80vh;
}
.emoji-search-container {
margin-bottom: 1em;
}
.emoji-search-input {
width: 100%;
padding: 8px;
margin-bottom: 1em;
}
.emoji-grid-container {
overflow-y: auto;
max-height: 400px !important;
}
.emoji-button {
font-size: 1.5em;
padding: 8px;
background: var(--background-secondary);
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s ease;
margin: 4px;
}
.emoji-button:hover {
background: var(--background-modifier-hover);
}
.banner-icon-input {
flex: 1;
padding: 8px;
}
.set-banner-button {
padding: 8px 16px;
background: var(--interactive-accent);
color: var(--text-on-accent);
border: none;
border-radius: 4px;
cursor: pointer;
}
.set-banner-button:hover {
background: var(--interactive-accent-hover);
}
/* ------------------- */
/* -- mobile layout -- */
/* ------------------- */
@media screen and (max-width: 640px) {
.banner-icon-input-container { flex-direction: column !important; }
.banner-icon-input-container * { width: 100% !important; }
}
`;
document.head.appendChild(style);
}
getEmojiDescription(emoji) {
const emojiItem = emojiData.find((item) => item.emoji === emoji);
return emojiItem ? emojiItem.keywords : "";
}
onClose() {
const { contentEl } = this;
contentEl.empty();
const activeFile = this.app.workspace.getActiveFile();
const hasBanner = activeFile ? this.plugin.hasBannerFrontmatter(activeFile) : false;
if (!this.closedByButton && this.plugin.settings.openTargetingModalAfterSelectingBannerOrIcon && hasBanner) {
const openTargetingModal = async () => {
if (activeFile) {
await new Promise((resolve) => {
var _a;
const initialFrontmatter = JSON.stringify(
((_a = this.app.metadataCache.getFileCache(activeFile)) == null ? void 0 : _a.frontmatter) || {}
);
const eventRef = this.app.metadataCache.on("changed", (file) => {
var _a2;
if (file.path !== activeFile.path) return;
const updatedFrontmatter = JSON.stringify(
((_a2 = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a2.frontmatter) || {}
);
if (updatedFrontmatter !== initialFrontmatter) {
this.app.metadataCache.off("changed", eventRef);
resolve();
}
});
setTimeout(() => {
this.app.metadataCache.off("changed", eventRef);
resolve();
}, 300);
});
new TargetPositionModal(this.app, this.plugin).open();
}
};
setTimeout(openTargetingModal, 100);
}
}
};
}
});
// src/modal/modals/pixelBannerStoreModal.js
var import_obsidian20, PixelBannerStoreModal, ConfirmPurchaseModal;
var init_pixelBannerStoreModal = __esm({
"src/modal/modals/pixelBannerStoreModal.js"() {
import_obsidian20 = require("obsidian");
init_constants();
init_handlePinIconClick();
init_fractionTextDisplay();
init_modals();
init_flags();
init_selectPixelBannerModal();
PixelBannerStoreModal = class extends import_obsidian20.Modal {
constructor(app, plugin) {
super(app);
this.plugin = plugin;
this.categories = [];
this.selectedCategory = null;
this.selectedCategoryIndex = 0;
this.imageContainer = null;
this.loadingEl = null;
this.modalEl.addClass("pixel-banner-store-modal");
this.isLoading = true;
this.currentPage = 1;
this.totalPages = 1;
this.searchTerm = "";
this.isSearchMode = false;
this.itemsPerPage = 18;
this.voteStats = {};
this.userVotes = {};
this.storeVotingEnabled = this.plugin.settings.storeVotingEnabled !== false;
}
// ----------------
// -- Open Modal --
// ----------------
async onOpen() {
const { contentEl } = this;
contentEl.empty();
this.showLoadingSpinner(contentEl);
this.initializeModal().catch((error) => {
console.error("Error initializing modal:", error);
this.hideLoadingSpinner();
contentEl.createEl("p", {
text: "Failed to load store. Please try again later.",
cls: "pixel-banner-store-error"
});
});
}
// Show loading spinner
showLoadingSpinner(container) {
this.isLoading = true;
this.loadingOverlay = container.createDiv({
cls: "pixel-banner-store-loading-overlay",
attr: {
style: `
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--background-primary);
z-index: 100;
`
}
});
this.loadingOverlay.createDiv({
cls: "pixel-banner-store-spinner",
attr: {
style: `
width: 40px;
height: 40px;
border: 4px solid var(--background-modifier-border);
border-top: 4px solid var(--text-accent);
border-radius: 50%;
animation: pixel-banner-spin 1s linear infinite;
`
}
});
}
// Hide loading spinner
hideLoadingSpinner() {
this.isLoading = false;
if (this.loadingOverlay) {
this.loadingOverlay.remove();
this.loadingOverlay = null;
}
}
// Download and save banner (handles both images and videos)
async downloadAndSaveBanner(data, filename) {
try {
if (data.fileType === "video") {
await this.saveVideoFileWithPrompts(data.base64Image, filename);
} else {
await handlePinIconClick(data.base64Image, this.plugin, null, filename.replace(/\.[^/.]+$/, ""), false);
}
} catch (error) {
console.error("Error saving banner:", error);
new import_obsidian20.Notice("Failed to save banner. Please try again.");
}
}
// Save video file with user prompts (similar to handlePinIconClick flow)
async saveVideoFileWithPrompts(base64Video, suggestedFilename) {
try {
const base64Data = base64Video.split(",")[1];
const byteCharacters = atob(base64Data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const arrayBuffer = new Uint8Array(byteNumbers).buffer;
const { file, useAsBanner } = await this.saveVideoLocally(arrayBuffer, suggestedFilename);
const finalPath = await this.waitForFileRename(file);
if (!finalPath) {
console.error("\u274C Failed to resolve valid file path");
new import_obsidian20.Notice("Failed to save video - file not found");
return null;
}
if (useAsBanner) {
const { updateNoteFrontmatter: updateNoteFrontmatter2 } = await Promise.resolve().then(() => (init_frontmatterUtils(), frontmatterUtils_exports));
await updateNoteFrontmatter2(finalPath, this.plugin, null);
}
return finalPath;
} catch (error) {
console.error("Error saving video file:", error);
throw error;
}
}
// Save video locally with folder/filename prompts (adapted from handlePinIconClick)
async saveVideoLocally(arrayBuffer, suggestedFilename) {
const { FolderSelectionModal: FolderSelectionModal2 } = await Promise.resolve().then(() => (init_folderSelectionModal(), folderSelectionModal_exports));
const { SaveImageModal: SaveImageModal2 } = await Promise.resolve().then(() => (init_saveImageModal(), saveImageModal_exports));
const vault = this.plugin.app.vault;
const defaultFolderPath = this.plugin.settings.pinnedImageFolder;
const folderPath = await new Promise((resolve) => {
const modal = new FolderSelectionModal2(this.plugin.app, defaultFolderPath, (result) => {
resolve(result);
});
modal.open();
});
if (!folderPath) {
throw new Error("No folder selected");
}
if (!await vault.adapter.exists(folderPath)) {
await vault.createFolder(folderPath);
}
const filenameParts = suggestedFilename.match(/^(.+)\.(\w+)$/);
const baseName = filenameParts ? filenameParts[1] : suggestedFilename;
const extension = filenameParts ? filenameParts[2] : "mp4";
const userInput = await new Promise((resolve) => {
const modal = new SaveImageModal2(this.plugin.app, baseName, (name, useAsBanner) => {
resolve({ name, useAsBanner });
});
modal.open();
});
if (!userInput) {
throw new Error("No filename provided");
}
let finalBaseName = userInput.name.replace(/[^a-zA-Z0-9-_ ]/g, "").trim();
if (!finalBaseName) finalBaseName = "banner";
if (!finalBaseName.toLowerCase().endsWith(`.${extension}`)) {
finalBaseName += `.${extension}`;
}
let fileName = finalBaseName;
let counter = 1;
while (await vault.adapter.exists(`${folderPath}/${fileName}`)) {
const nameWithoutExt = finalBaseName.slice(0, -(extension.length + 1));
fileName = `${nameWithoutExt}-${counter}.${extension}`;
counter++;
}
const filePath = `${folderPath}/${fileName}`;
const savedFile = await vault.createBinary(filePath, arrayBuffer);
return {
initialPath: filePath,
file: savedFile,
useAsBanner: userInput.useAsBanner
};
}
// Wait for potential file rename (adapted from handlePinIconClick)
async waitForFileRename(file) {
const maxWaitTime = 5e3;
const checkInterval = 100;
let elapsedTime = 0;
while (elapsedTime < maxWaitTime) {
try {
const currentFile = this.plugin.app.vault.getAbstractFileByPath(file.path);
if (currentFile) {
return currentFile.path;
}
} catch (error) {
}
await new Promise((resolve) => setTimeout(resolve, checkInterval));
elapsedTime += checkInterval;
}
return file.path;
}
// Initialize modal content
async initializeModal() {
await this.plugin.verifyPixelBannerPlusCredentials();
const { contentEl } = this;
contentEl.createEl("h3", { text: "\u{1F3EA} Pixel Banner Plus Collection", cls: "margin-top-0" });
contentEl.createEl("p", {
text: `Browse the Pixel Banner Plus Collection to find the perfect banner for your needs. Banner Token prices are displayed on each card below (FREE or 1 Banner Token). Previous purchases will be listed as FREE.`,
attr: {
"style": "font-size: 12px; color: var(--text-muted);"
}
});
const selectContainer = contentEl.createDiv({ cls: "pixel-banner-store-select-container" });
if (this.plugin.pixelBannerPlusEnabled) {
this.searchContainer = selectContainer.createDiv({
cls: "pixel-banner-store-search-container",
attr: {
style: `
display: none;
align-items: center;
gap: 5px;
margin: 0 0 0 auto;
`
}
});
this.searchInput = this.searchContainer.createEl("input", {
type: "text",
placeholder: "Search banners...",
cls: "pixel-banner-store-search-input"
});
this.searchInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
this.searchBanners();
}
});
this.searchButton = this.searchContainer.createEl("button", {
text: "Search",
cls: "pixel-banner-store-search-button"
});
this.searchButton.addEventListener("click", () => {
this.searchBanners();
});
this.stopSearchButton = this.searchContainer.createEl("button", {
text: "Back to Categories",
cls: "pixel-banner-store-stop-search-button"
});
this.stopSearchButton.addEventListener("click", () => {
this.toggleSearchMode(false);
});
}
this.categorySelect = selectContainer.createEl("select", {
cls: "pixel-banner-store-select",
attr: {
style: `
display: ${this.plugin.pixelBannerPlusEnabled ? "block" : "none"};
`
}
});
const defaultOption = this.categorySelect.createEl("option", {
text: "Select a category...",
value: ""
});
defaultOption.disabled = true;
defaultOption.selected = true;
try {
const response = await fetch(
`${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.STORE_CATEGORIES}`,
{
headers: {
"x-user-email": this.plugin.settings.pixelBannerPlusEmail,
"x-api-key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json"
}
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
this.categories = Array.isArray(data) ? data : data.categories || [];
if (this.categories.length === 0) {
throw new Error("No categories found");
}
this.categories.forEach((category) => {
this.categorySelect.createEl("option", {
value: category.id,
text: category.category
});
});
this.categorySelect.addEventListener("change", async (e) => {
this.selectedCategory = e.target.value;
this.selectedCategoryIndex = e.target.selectedIndex;
await this.loadCategoryImages();
});
} catch (error) {
console.error("Failed to fetch categories:", error);
selectContainer.createEl("p", {
text: "Failed to load categories. Please try again later.",
cls: "pixel-banner-store-error",
attr: {
style: `
display: ${this.plugin.pixelBannerPlusEnabled ? "block" : "none"};
`
}
});
}
this.categoryControlsContainer = selectContainer.createDiv({
cls: "pixel-banner-store-category-controls",
attr: {
style: `
display: flex;
align-items: center;
gap: 10px;
`
}
});
this.nextCategoryButton = this.categoryControlsContainer.createEl("button", {
text: "\u25B6\uFE0F Next Category",
cls: "pixel-banner-store-next-category radial-pulse-animation",
attr: {
style: `
display: ${this.plugin.pixelBannerPlusEnabled ? "inline-flex" : "none"};
`
}
});
this.nextCategoryButton.addEventListener("click", async () => {
if (this.selectedCategoryIndex >= this.categories.length) {
this.selectedCategoryIndex = 1;
} else {
this.selectedCategoryIndex++;
}
this.categorySelect.selectedIndex = this.selectedCategoryIndex;
this.selectedCategory = this.categorySelect.value;
await this.loadCategoryImages();
});
if (this.plugin.pixelBannerPlusEnabled) {
this.searchAllButton = this.categoryControlsContainer.createEl("button", {
text: "\u{1F50D} Search All",
cls: "pixel-banner-store-search-all-button"
});
this.searchAllButton.addEventListener("click", () => {
this.toggleSearchMode(true);
});
}
const backToMainButton = selectContainer.createEl("button", {
text: "\u21E0 Main Menu",
cls: "pixel-banner-store-back-to-main"
});
backToMainButton.addEventListener("click", () => {
this.close();
new SelectPixelBannerModal(this.app, this.plugin).open();
});
if (this.plugin.pixelBannerPlusEnabled) {
this.imageContainer = contentEl.createDiv({ cls: "pixel-banner-store-image-grid -empty" });
if (!this.storeVotingEnabled) {
this.imageContainer.addClass("store-voting-off");
}
setTimeout(async () => {
if (this.categorySelect.selectedIndex === 0) {
this.categorySelect.selectedIndex = 1;
this.selectedCategoryIndex = 1;
this.selectedCategory = this.categorySelect.value;
await this.loadCategoryImages();
}
}, 50);
} else {
this.imageContainer = contentEl.createDiv({ cls: "pixel-banner-store-image-grid -not-connected" });
}
const bottomControlsContainer = contentEl.createDiv({
cls: "pixel-banner-store-bottom-controls",
attr: {
"style": `
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-top: 15px;
border-top: 1px solid var(--background-modifier-border);
padding-top: 10px;
`
}
});
const pixelBannerPlusAccountStatus = bottomControlsContainer.createDiv({
cls: "pixel-banner-store-account-status",
attr: {
"style": `
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
justify-content: flex-start;
font-size: .9em;
`
}
});
this.paginationContainer = bottomControlsContainer.createDiv({
cls: "pixel-banner-store-pagination-container",
attr: {
"style": `
display: flex;
align-items: center;
gap: 8px;
`
}
});
const storeVotingContainer = bottomControlsContainer.createDiv({
cls: "pixel-banner-store-voting-container",
attr: {
"style": `
display: flex;
align-items: center;
gap: 8px;
margin-left: 16px;
`
}
});
const storeVotingToggle = storeVotingContainer.createEl("input", {
type: "checkbox",
cls: "pixel-banner-store-voting-toggle",
attr: {
id: "store-voting-toggle",
style: `
cursor: pointer;
`
}
});
storeVotingToggle.checked = this.storeVotingEnabled === true;
storeVotingContainer.createEl("label", {
text: "Collection Voting",
cls: "pixel-banner-store-voting-label",
attr: {
for: "store-voting-toggle",
style: `
font-size: 0.8em;
cursor: pointer;
color: var(--text-muted);
letter-spacing: 0.05em;
`
}
});
storeVotingToggle.addEventListener("change", (e) => {
this.storeVotingEnabled = e.target.checked;
this.plugin.settings.storeVotingEnabled = this.storeVotingEnabled;
this.plugin.saveSettings();
if (this.storeVotingEnabled) {
this.imageContainer.removeClass("store-voting-off");
} else {
this.imageContainer.addClass("store-voting-off");
}
});
const isConnected = this.plugin.pixelBannerPlusEnabled;
const statusText = isConnected ? "\u2705 Connected" : "\u274C Not Connected";
const statusBorderColor = isConnected ? "#20bf6b" : "#FF0000";
const connectionStatusEl = pixelBannerPlusAccountStatus.createEl("span", {
text: statusText,
cls: "pixel-banner-status-value",
attr: {
style: `border: 1px dotted ${statusBorderColor};`
}
});
const tokenCount = this.plugin.pixelBannerPlusBannerTokens !== void 0 ? `\u{1FA99} ${decimalToFractionString(this.plugin.pixelBannerPlusBannerTokens)} Tokens` : "\u2753 Unknown";
const tokenCountEl = pixelBannerPlusAccountStatus.createEl("span", {
text: tokenCount,
cls: "pixel-banner-status-value",
attr: {
style: `
border: 1px dotted #F3B93B;
display: ${this.plugin.pixelBannerPlusEnabled ? "inline-flex" : "none"};
`
}
});
const openPlusSettings = async () => {
this.close();
await this.app.setting.open();
await new Promise((resolve) => setTimeout(resolve, 300));
const settingsTabs = document.querySelectorAll(".vertical-tab-header-group .vertical-tab-nav-item");
for (const tab of settingsTabs) {
if (tab.textContent.includes("Pixel Banner")) {
tab.click();
break;
}
}
const pixelBannerSettingsTabs = document.querySelectorAll(".pixel-banner-settings-tabs > button.pixel-banner-settings-tab");
for (const tab of pixelBannerSettingsTabs) {
if (tab.textContent.includes("Plus")) {
tab.click();
break;
}
}
};
connectionStatusEl.addEventListener("click", openPlusSettings);
tokenCountEl.addEventListener("click", openPlusSettings);
if (isConnected && this.plugin.pixelBannerPlusBannerTokens === 0) {
const buyTokensButton = pixelBannerPlusAccountStatus.createEl("button", {
cls: "pixel-banner-account-button pixel-banner-buy-tokens-button",
text: "\u{1F4B5} Buy More Tokens"
});
buyTokensButton.addEventListener("click", (event) => {
event.preventDefault();
window.open(PIXEL_BANNER_PLUS.SHOP_URL, "_blank");
});
} else if (!isConnected) {
const signupButton = pixelBannerPlusAccountStatus.createEl("button", {
cls: "pixel-banner-account-button pixel-banner-signup-button",
text: "\u{1F6A9} Signup for Free!"
});
signupButton.addEventListener("click", (event) => {
event.preventDefault();
const signupUrl = PIXEL_BANNER_PLUS.API_URL + PIXEL_BANNER_PLUS.ENDPOINTS.SIGNUP;
window.open(signupUrl, "_blank");
});
}
this.addStyle();
this.hideLoadingSpinner();
}
// --------------------------
// -- Load Category Images --
// --------------------------
async loadCategoryImages(page = 1) {
if (!this.selectedCategory) return;
this.isSearchMode = false;
this.currentPage = page;
if (page === 1) {
this.userVotes = {};
this.voteStats = {};
}
this.imageContainer.empty();
if (this.storeVotingEnabled) {
this.imageContainer.removeClass("store-voting-off");
} else {
this.imageContainer.addClass("store-voting-off");
}
this.loadingEl = this.imageContainer.createDiv({ cls: "pixel-banner-store-loading" });
this.loadingEl.innerHTML = `<div class="pixel-banner-store-spinner"></div>`;
try {
const response = await fetch(
`${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.STORE_CATEGORY_IMAGES}?categoryId=${this.selectedCategory}&page=${this.currentPage}&limit=${this.itemsPerPage}`,
{
headers: {
"x-user-email": this.plugin.settings.pixelBannerPlusEmail,
"x-api-key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json"
}
}
);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
let images, totalPages = 1, totalItems = 0;
if (Array.isArray(data)) {
images = data;
totalPages = Math.ceil(images.length / this.itemsPerPage);
totalItems = images.length;
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = startIndex + this.itemsPerPage;
images = images.slice(startIndex, endIndex);
} else {
images = data.images || data.banners || [];
totalPages = data.totalPages || 1;
totalItems = data.totalItems || data.totalCount || images.length;
}
this.totalPages = totalPages;
this.displayImages(images, false, totalItems);
if (totalPages > 1) {
this.addPaginationControls();
} else {
this.paginationContainer.style.display = "none";
}
} catch (error) {
console.error("Failed to fetch images:", error);
this.imageContainer.empty();
this.imageContainer.createEl("p", {
text: "Failed to load images. Please try again.",
cls: "pixel-banner-store-error"
});
}
}
// -----------------
// -- Search Banners --
// -----------------
async searchBanners(page = 1) {
this.searchTerm = this.searchInput.value.trim();
if (!this.searchTerm) {
new import_obsidian20.Notice("Please enter a search term");
return;
}
this.isSearchMode = true;
this.currentPage = page;
this.imageContainer.empty();
if (this.storeVotingEnabled) {
this.imageContainer.removeClass("store-voting-off");
} else {
this.imageContainer.addClass("store-voting-off");
}
this.loadingEl = this.imageContainer.createDiv({ cls: "pixel-banner-store-loading" });
this.loadingEl.innerHTML = `<div class="pixel-banner-store-spinner"></div>`;
try {
const response = await fetch(
`${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.STORE_IMAGE_SEARCH}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-user-email": this.plugin.settings.pixelBannerPlusEmail,
"x-api-key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json"
},
body: JSON.stringify({
searchTerm: this.searchTerm,
page: this.currentPage,
limit: this.itemsPerPage,
sort: "date_added",
order: "desc"
})
}
);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
this.totalPages = data.totalPages || data.total_pages || 1;
let imagesArray = [];
if (data.images && Array.isArray(data.images)) {
imagesArray = data.images;
} else if (data.banners && Array.isArray(data.banners)) {
imagesArray = data.banners;
} else if (data.results && Array.isArray(data.results)) {
imagesArray = data.results;
} else if (Array.isArray(data)) {
imagesArray = data;
} else if (data.image) {
imagesArray = [data.image];
} else if (data.banner) {
imagesArray = [data.banner];
} else if (typeof data === "object" && data !== null) {
imagesArray = [data];
}
if (!Array.isArray(imagesArray)) {
console.error("Failed to extract images array from response:", data);
throw new Error("Invalid response format: could not extract images array");
}
if (imagesArray.length === 0) {
this.imageContainer.empty();
this.imageContainer.createEl("p", {
text: `No results found for "${this.searchTerm}"`,
cls: "pixel-banner-store-no-results",
attr: {
style: `
text-align: center;
margin-top: 100px;
font-size: 16px;
`
}
});
return;
}
this.userVotes = {};
this.voteStats = {};
this.displayImages(imagesArray, true);
} catch (error) {
console.error("Failed to search images:", error);
this.imageContainer.empty();
this.imageContainer.createEl("p", {
text: `Failed to search images: ${error.message}. Please try again.`,
cls: "pixel-banner-store-error"
});
}
}
// --------------------
// -- Display Images --
// --------------------
displayImages(images, isSearchResult = false, totalItems = 0) {
if (this.loadingEl) {
this.loadingEl.remove();
this.loadingEl = null;
}
if (!Array.isArray(images)) {
console.error("displayImages received non-array:", images);
this.imageContainer.createEl("p", {
text: "Error displaying images: Invalid data format",
cls: "pixel-banner-store-error"
});
return;
}
if (images.length > 0) {
this.imageContainer.removeClass("-empty");
this.imageContainer.removeClass("-not-connected");
}
const container = this.imageContainer;
container.empty();
const imageIds = images.map((image) => image.id || image.imageId || image.bannerId).filter((id) => id && id !== "unknown");
if (this.plugin.pixelBannerPlusEnabled && imageIds.length > 0) {
images.forEach((image) => {
const id = image.id || image.imageId || image.bannerId;
if (!id || id === "unknown") return;
this.voteStats[id] = {
upvotes: image.upvotes || 0,
downvotes: image.downvotes || 0,
total: image.vote_score || 0
};
if (image.user_vote === 1 || image.user_vote === "up") {
this.userVotes[id] = "up";
} else if (image.user_vote === -1 || image.user_vote === "down") {
this.userVotes[id] = "down";
} else {
this.userVotes[id] = null;
}
this.updateVoteDisplay(id);
});
}
images.forEach((image) => {
try {
if (!image || typeof image !== "object") {
console.warn("Skipping invalid image entry:", image);
return;
}
const imageId = image.id || image.imageId || image.bannerId || "unknown";
const imageCost = typeof image.cost !== "undefined" ? image.cost : image.premium ? 1 : 0;
const card = container.createEl("div", {
cls: "pixel-banner-store-image-card",
attr: {
"data-image-id": imageId,
"data-image-cost": imageCost,
"data-image-new": true,
"data-image-hot": image.hot ? "true" : "false",
"style": "position: relative;"
// Ensure proper positioning for absolute children
}
});
const imageSource = image.base64Image || image.base64 || image.url || image.src || "";
const isVideo = image.fileType === "video";
if (isVideo) {
const imgEl = card.createEl("img", {
attr: {
src: imageSource,
alt: image.prompt || image.description || "Banner video thumbnail"
}
});
const videoBadge = card.createEl("div", {
text: "VIDEO",
cls: "pixel-banner-store-video-badge",
attr: {
style: `
position: absolute;
top: 8px;
right: 8px;
background: rgba(255, 255, 255, 0.9);
color: #333;
padding: 2px 6px;
border-radius: 3px;
font-size: 10px;
font-weight: bold;
z-index: 2;
pointer-events: none;
`
}
});
} else {
const imgEl = card.createEl("img", {
attr: {
src: imageSource,
alt: image.prompt || image.description || "Banner image"
}
});
}
const details = card.createDiv({ cls: "pixel-banner-store-image-details" });
const promptWrapper = details.createDiv({ cls: "pixel-banner-store-prompt-wrapper" });
const promptText = image.prompt || image.description || image.title || "No description";
const truncatedPrompt = promptText.length > 85 ? promptText.slice(0, 85) + "..." : promptText;
promptWrapper.createEl("p", { text: truncatedPrompt, cls: "pixel-banner-store-prompt" });
if (image.filesize) {
promptWrapper.createEl("div", {
text: image.filesize,
cls: "pixel-banner-store-filesize",
attr: {
style: `
color: var(--text-muted);
font-size: 10px;
margin: 2px 0 0 0;
text-align: left;
`
}
});
}
const metaDetails = details.createEl("div", { cls: "pixel-banner-store-meta-details" });
const costText = imageCost === 0 ? "FREE" : `\u{1FA99} ${decimalToFractionString(imageCost)}`;
const costEl = metaDetails.createEl("div", {
text: costText,
cls: "pixel-banner-store-cost",
attr: {
style: "color: var(--text-normal);"
}
});
if (imageCost === 0) {
costEl.addClass("free");
}
if (image.new) {
metaDetails.createEl("div", {
text: "NEW",
attr: {
style: `
color: var(--interactive-accent);
font-weight: bold;
font-size: 11px;
margin: 0;
text-align: right;
white-space: nowrap;
margin-left: 5px;
`
}
});
}
if (image.hot) {
metaDetails.createEl("div", {
text: "\u{1F525}HOT",
attr: {
style: `
color: var(--error-color);
font-weight: bold;
font-size: 11px;
margin: 0;
text-align: right;
white-space: nowrap;
margin-left: 5px;
`
}
});
}
if (this.plugin.pixelBannerPlusEnabled && imageId !== "unknown") {
const voteControls = card.createDiv({
cls: "pixel-banner-store-vote-controls",
attr: {
"data-banner-id": imageId
}
});
const upvoteButton = voteControls.createEl("button", {
cls: "pixel-banner-store-vote-button upvote",
attr: {
"aria-label": "Upvote Banner",
"data-banner-id": imageId
}
});
upvoteButton.innerHTML = "\u{1F44D}";
const voteCount = voteControls.createEl("span", {
cls: "pixel-banner-store-vote-count",
text: "0",
attr: {
"data-banner-id": imageId
}
});
const downvoteButton = voteControls.createEl("button", {
cls: "pixel-banner-store-vote-button downvote",
attr: {
"aria-label": "Downvote Banner",
"data-banner-id": imageId
}
});
downvoteButton.innerHTML = "\u{1F44E}";
upvoteButton.addEventListener("click", (e) => {
e.stopPropagation();
this.upvoteBanner(imageId);
});
downvoteButton.addEventListener("click", (e) => {
e.stopPropagation();
this.downvoteBanner(imageId);
});
this.updateVoteDisplay(imageId);
}
card.addEventListener("click", async () => {
var _a;
const cost = Number(card.getAttribute("data-image-cost"));
if (cost > 0) {
new ConfirmPurchaseModal(this.app, cost, image.prompt, image.base64Image, async () => {
var _a2;
try {
const response = await fetch(`${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.STORE_IMAGE_BY_ID}?bannerId=${image.id}`, {
headers: {
"x-user-email": this.plugin.settings.pixelBannerPlusEmail,
"x-api-key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json"
}
});
if (!response.ok) {
throw new Error("Failed to fetch banner");
}
await this.plugin.verifyPixelBannerPlusCredentials();
card.setAttribute("data-image-cost", "0");
card.querySelector(".pixel-banner-store-cost").classList.add("free");
card.querySelector(".pixel-banner-store-cost").textContent = "FREE";
const data = await response.json();
let filename = ((_a2 = image.prompt) == null ? void 0 : _a2.toLowerCase().replace(/[^a-zA-Z0-9-_ ]/g, "").trim()) || "banner";
filename = filename.replace(/\s+/g, "-").substring(0, 47);
if (data.fileType === "video") {
filename += `.${data.fileExtension || "mp4"}`;
} else {
filename += `.${data.fileExtension || "jpg"}`;
}
await this.downloadAndSaveBanner(data, filename);
this.close();
if (this.plugin.settings.openTargetingModalAfterSelectingBannerOrIcon) {
new TargetPositionModal(this.app, this.plugin).open();
}
} catch (error) {
console.error("Error purchasing banner:", error);
new import_obsidian20.Notice("Failed to purchase banner. Please try again.");
}
}, this.plugin, isVideo, image.fileType, image.file_extension).open();
} else {
try {
const response = await fetch(`${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.STORE_IMAGE_BY_ID}?bannerId=${image.id}`, {
headers: {
"x-user-email": this.plugin.settings.pixelBannerPlusEmail,
"x-api-key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Accept": "application/json"
}
});
if (!response.ok) {
throw new Error("Failed to fetch banner");
}
const data = await response.json();
let filename = ((_a = image.prompt) == null ? void 0 : _a.toLowerCase().replace(/[^a-zA-Z0-9-_ ]/g, "").trim()) || "banner";
filename = filename.replace(/\s+/g, "-").substring(0, 47);
if (data.fileType === "video") {
filename += `.${data.fileExtension || "mp4"}`;
} else {
filename += `.${data.fileExtension || "jpg"}`;
}
await this.downloadAndSaveBanner(data, filename);
this.close();
if (this.plugin.settings.openTargetingModalAfterSelectingBannerOrIcon) {
new TargetPositionModal(this.app, this.plugin).open();
}
} catch (error) {
console.error("Error fetching store banner:", error);
}
}
});
} catch (error) {
console.error("Error rendering image card:", error, image);
}
});
if (this.isSearchMode && this.totalPages > 1) {
this.addPaginationControls();
} else {
this.paginationContainer.style.display = "none";
}
}
// Update vote display for a specific banner
updateVoteDisplay(bannerId) {
if (!bannerId) return;
const voteControls = document.querySelector(`.pixel-banner-store-vote-controls[data-banner-id="${bannerId}"]`);
if (!voteControls) {
return;
}
const voteCount = voteControls.querySelector(".pixel-banner-store-vote-count");
const upvoteButton = voteControls.querySelector(".upvote");
const downvoteButton = voteControls.querySelector(".downvote");
if (!upvoteButton || !downvoteButton) {
return;
}
if (voteCount && this.voteStats[bannerId]) {
voteCount.textContent = this.voteStats[bannerId].total.toString();
voteCount.removeClass("positive", "negative", "neutral");
if (this.voteStats[bannerId].total > 0) {
voteCount.addClass("positive");
} else if (this.voteStats[bannerId].total < 0) {
voteCount.addClass("negative");
} else {
voteCount.addClass("neutral");
}
}
if (upvoteButton && downvoteButton) {
upvoteButton.removeClass("active");
upvoteButton.removeClass("active-upvote");
downvoteButton.removeClass("active");
downvoteButton.removeClass("active-downvote");
const userVote = this.userVotes[bannerId];
if (userVote === "up" || userVote === 1) {
upvoteButton.addClass("active");
upvoteButton.addClass("active-upvote");
} else if (userVote === "down" || userVote === -1) {
downvoteButton.addClass("active");
downvoteButton.addClass("active-downvote");
}
}
}
// Upvote a banner
async upvoteBanner(bannerId) {
if (!bannerId || !this.plugin.pixelBannerPlusEnabled) return;
try {
const endpoint = PIXEL_BANNER_PLUS.ENDPOINTS.BANNER_VOTES_UPVOTE.replace(":id", bannerId);
const response = await fetch(`${PIXEL_BANNER_PLUS.API_URL}${endpoint}`, {
method: "POST",
headers: {
"x-user-email": this.plugin.settings.pixelBannerPlusEmail,
"x-api-key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Content-Type": "application/json",
"Accept": "application/json"
}
});
if (response.ok) {
const data = await response.json();
this.voteStats[bannerId] = {
upvotes: data.votes.upvotes || 0,
downvotes: data.votes.downvotes || 0,
total: data.votes.total || 0
};
this.userVotes[bannerId] = this.userVotes[bannerId] === "up" ? null : "up";
this.updateVoteDisplay(bannerId);
}
} catch (error) {
console.error("Error upvoting banner:", error);
new import_obsidian20.Notice("Failed to upvote banner. Please try again.");
}
}
// Downvote a banner
async downvoteBanner(bannerId) {
if (!bannerId || !this.plugin.pixelBannerPlusEnabled) return;
try {
const endpoint = PIXEL_BANNER_PLUS.ENDPOINTS.BANNER_VOTES_DOWNVOTE.replace(":id", bannerId);
const response = await fetch(`${PIXEL_BANNER_PLUS.API_URL}${endpoint}`, {
method: "POST",
headers: {
"x-user-email": this.plugin.settings.pixelBannerPlusEmail,
"x-api-key": this.plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": this.plugin.settings.lastVersion,
"Content-Type": "application/json",
"Accept": "application/json"
}
});
if (response.ok) {
const data = await response.json();
this.voteStats[bannerId] = {
upvotes: data.votes.upvotes || 0,
downvotes: data.votes.downvotes || 0,
total: data.votes.total || 0
};
this.userVotes[bannerId] = this.userVotes[bannerId] === "down" ? null : "down";
this.updateVoteDisplay(bannerId);
}
} catch (error) {
console.error("Error downvoting banner:", error);
new import_obsidian20.Notice("Failed to downvote banner. Please try again.");
}
}
// Toggle between search mode and category browsing mode
toggleSearchMode(enableSearch) {
if (enableSearch) {
this.categorySelect.style.display = "none";
this.nextCategoryButton.style.display = "none";
this.searchAllButton.style.display = "none";
this.searchContainer.style.display = "flex";
this.searchInput.value = "";
this.searchInput.focus();
this.isSearchMode = true;
} else {
this.searchContainer.style.display = "none";
this.categorySelect.style.display = "block";
this.nextCategoryButton.style.display = "inline-flex";
this.searchAllButton.style.display = "inline-flex";
this.isSearchMode = false;
this.paginationContainer.style.display = "none";
if (this.selectedCategory) {
this.loadCategoryImages();
}
}
}
// --------------------------
// -- Add Pagination Controls --
// --------------------------
addPaginationControls() {
this.paginationContainer.empty();
this.paginationContainer.style.display = "flex";
const prevButton = this.paginationContainer.createEl("button", {
text: "\u2190 Previous",
cls: "pixel-banner-store-pagination-button",
attr: {
disabled: this.currentPage === 1 ? "disabled" : null
}
});
prevButton.addEventListener("click", () => {
if (this.currentPage > 1) {
if (this.isSearchMode) {
this.searchBanners(this.currentPage - 1);
} else {
this.loadCategoryImages(this.currentPage - 1);
}
}
});
this.paginationContainer.createEl("span", {
text: `Page ${this.currentPage} of ${this.totalPages}`,
cls: "pixel-banner-store-pagination-info"
});
const nextButton = this.paginationContainer.createEl("button", {
text: "Next \u2192",
cls: "pixel-banner-store-pagination-button",
attr: {
disabled: this.currentPage === this.totalPages ? "disabled" : null
}
});
nextButton.addEventListener("click", () => {
if (this.currentPage < this.totalPages) {
if (this.isSearchMode) {
this.searchBanners(this.currentPage + 1);
} else {
this.loadCategoryImages(this.currentPage + 1);
}
}
});
}
addStyle() {
const style = document.createElement("style");
style.textContent = `
.pixel-banner-store-modal {
top: unset !important;
width: var(--dialog-max-width);
max-width: 1100px;
animation: pixel-banner-fade-in 1300ms ease-in-out;
}
.pixel-banner-store-select-container {
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
justify-content: flex-end;
}
.pixel-banner-store-category-controls {
display: flex;
align-items: center;
gap: 10px;
}
.pixel-banner-store-select {
width: max-content;
font-size: 14px;
}
/* Search Styles */
.pixel-banner-store-search-container {
display: flex;
align-items: center;
gap: 5px;
margin: 0 0 0 auto;
}
.pixel-banner-store-search-input {
width: 200px;
font-size: 14px;
padding: 5px 10px;
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
}
.pixel-banner-store-search-button,
.pixel-banner-store-stop-search-button,
.pixel-banner-store-search-all-button {
font-size: 14px;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
border: none;
white-space: nowrap;
}
.pixel-banner-store-search-button,
.pixel-banner-store-stop-search-button {
background-color: var(--interactive-accent) !important;
color: var(--text-on-accent) !important;
}
.pixel-banner-store-search-button:hover,
.pixel-banner-store-stop-search-button:hover,
.pixel-banner-store-search-all-button:hover {
background-color: var(--interactive-accent-hover) !important;
}
/* Pagination Styles */
.pixel-banner-store-bottom-controls {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-top: 15px;
border-top: 1px solid var(--background-modifier-border);
padding-top: 10px;
}
.pixel-banner-store-pagination-container {
display: flex;
align-items: center;
gap: 8px;
}
.pixel-banner-store-pagination-button {
font-size: 12px;
padding: 4px 10px;
border-radius: 4px;
cursor: pointer;
background-color: var(--interactive-accent) !important;
color: var(--text-on-accent) !important;
border: none;
}
.pixel-banner-store-pagination-button:not([disabled]):hover {
background-color: var(--interactive-accent-hover) !important;
}
.pixel-banner-store-pagination-button[disabled] {
opacity: 0.5;
cursor: not-allowed;
color: var(--text-color);
background-color: var(--interactive-normal);
}
.pixel-banner-store-pagination-info {
font-size: 12px;
color: var(--text-normal);
white-space: nowrap;
}
.pixel-banner-store-next-category {
font-size: 14px;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
background-color: var(--interactive-accent) !important;
color: var(--text-on-accent) !important;
border: none;
}
.pixel-banner-store-next-category:hover {
background-color: var(--interactive-accent-hover) !important;
}
.pixel-banner-store-back-to-main {
font-size: 14px;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
border: none;
}
.pixel-banner-store-back-to-main:hover {
background-color: var(--interactive-accent) !important;
}
.pixel-banner-store-error {
color: var(--text-accent);
margin-top: 8px;
}
.pixel-banner-store-image-grid {
gap: 18px;
padding: 18px;
margin-top: 20px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: normal;
justify-content: center;
height: 800px;
max-height: 60vh;
overflow-y: auto;
overflow-x: hidden;
border: 1px solid var(--table-border-color);
position: relative; /* For the sticky pagination */
}
.pixel-banner-store-image-grid.-empty::after {
display: none;
// content: "\u{1FA84} Select a Category above, or click the Next Category button to cycle through them. A wonderful selection of banners awaits! The '\u2747\uFE0F Featured' category is updated often, and will be automatically displayed shortly if a selection is not made.";
// position: relative;
// top: 40%;
// max-width: 380px;
// font-size: 1.3em;
// color: var(--text-muted);
// max-height: 80px;
// text-align: center;
// opacity: 0.7;
}
.pixel-banner-store-image-grid.-not-connected::after {
content: "\u26A1 Please connect your Pixel Banner Plus account in Settings to access the store. If you don't have an account, you can sign up for FREE using the button below! No payment information is required, and you will get access to a wide range of FREE banners.";
position: relative;
top: 40%;
max-width: 458px;
font-size: 1.3em;
color: var(--text-muted);
max-height: 80px;
text-align: center;
opacity: 0.7;
}
.pixel-banner-store-image-card {
border: 5px solid transparent;
border-radius: 8px;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 12px 7px 12px;
width: 324px;
transition: transform 0.2s ease;
cursor: pointer;
height: max-content;
animation: pixel-banner-fade-in 1300ms ease-in-out;
background: var(--background-secondary);
}
.pixel-banner-store-image-card:hover {
transform: scale(1.1);
border-color: var(--modal-border-color);
z-index: 2;
}
.pixel-banner-store-image-card img {
max-width: 300px;
max-height: 300px;
object-fit: cover;
}
.pixel-banner-store-image-details {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 8px 0;
width: 100%;
}
.pixel-banner-store-prompt {
font-size: 12px;
margin: 0 0 4px;
text-transform: lowercase;
opacity: 0.8;
}
.pixel-banner-store-cost {
font-size: 12px;
color: var(--text-accent);
margin: 0;
text-align: right;
white-space: nowrap;
margin-left: 5px;
}
.pixel-banner-store-cost.free {
color: var(--text-success) !important;
font-weight: bold !important;
}
/* Vote Controls Styles */
.pixel-banner-store-vote-controls {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 5px 0 0;
gap: 7px;
}
.pixel-banner-store-vote-button {
background: transparent;
border: none;
cursor: pointer;
padding: 0;
font-size: 14px;
opacity: 0.4;
transition: all 0.2s ease;
width: 28px;
height: 27px;
line-height: 1.38;
border-radius: 50%;
border: 1px dashed transparent;
}
.pixel-banner-store-vote-button:hover {
transform: scale(1.2);
opacity: 1 !important;
}
.pixel-banner-store-vote-button.active {
opacity: 0.7;
transform: scale(1.1);
}
.pixel-banner-store-vote-button.active-upvote {
border-color: var(--text-success);
}
.pixel-banner-store-vote-button.active-downvote {
border-color: var(--text-error);
}
.pixel-banner-store-vote-button.disabled {
opacity: 0.3;
cursor: not-allowed;
transform: none;
}
.pixel-banner-store-vote-button.disabled:hover {
transform: none;
opacity: 0.3;
}
.pixel-banner-store-vote-count {
font-size: 14px;
font-weight: bold;
min-width: 30px;
text-align: center;
}
.pixel-banner-store-vote-count.positive {
color: var(--text-success);
}
.pixel-banner-store-vote-count.negative {
color: var(--text-error);
}
.pixel-banner-store-vote-count.neutral {
color: var(--text-muted);
}
.pixel-banner-store-loading {
display: flex;
justify-content: center;
padding: 32px;
}
.pixel-banner-store-spinner {
width: 40px;
height: 40px;
border: 4px solid var(--background-modifier-border);
border-top: 4px solid var(--text-accent);
border-radius: 50%;
animation: pixel-banner-spin 1s linear infinite;
}
.pixel-banner-store-loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--background-primary);
z-index: 100;
animation: pixel-banner-fade-in 0.3s ease-in-out;
}
.pixel-banner-store-status-value {
padding: 3px 7px;
border-radius: 0px;
font-size: .8em;
letter-spacing: 1px;
background-color: var(--background-primary);
display: inline-flex;
align-items: center;
cursor: help;
}
.pixel-banner-account-button {
padding: 3px 7px;
border-radius: 5px;
cursor: pointer;
text-transform: uppercase;
letter-spacing: 1px;
font-size: .7em;
transition: all 0.2s ease;
border: 1px solid var(--background-modifier-border);
}
.pixel-banner-account-button:hover {
opacity: 0.9;
transform: translateY(-2px);
}
.pixel-banner-buy-tokens-button {
background-color: darkgreen !important;
color: papayawhip !important;
opacity: 0.7;
}
.pixel-banner-signup-button {
background-color: var(--interactive-accent) !important;
color: var(--text-on-accent) !important;
}
.pixel-banner-store-image-grid.store-voting-off .pixel-banner-store-vote-controls {
display: none;
}
/* ------------------- */
/* -- mobile layout -- */
/* ------------------- */
@media screen and (max-width: 800px) {
.pixel-banner-store-select-container { flex-direction: column !important; width: 100% !important; }
.pixel-banner-store-select-container * { width: 100% !important; }
.pixel-banner-store-search-container { flex-direction: column !important; width: 100% !important; }
.pixel-banner-store-search-container * { width: 100% !important; }
}
`;
document.head.appendChild(style);
this.style = style;
}
onClose() {
this.contentEl.empty();
if (this.style) {
this.style.remove();
}
if (this.loadingOverlay) {
this.loadingOverlay.remove();
}
}
};
ConfirmPurchaseModal = class extends import_obsidian20.Modal {
constructor(app, cost, prompt, previewImage, onConfirm, plugin, isVideo = false, fileType = "image", fileExtension = "jpg") {
super(app);
this.cost = cost;
this.prompt = prompt;
this.previewImage = previewImage;
this.onConfirm = onConfirm;
this.plugin = plugin;
this.isVideo = isVideo;
this.fileType = fileType;
this.fileExtension = fileExtension;
}
onOpen() {
var _a;
const { contentEl } = this;
contentEl.empty();
this.addStyle();
const titleContainer = contentEl.createEl("h3", {
cls: "margin-top-0 pixel-banner-selector-title"
});
const flagImg = titleContainer.createEl("img", {
attr: {
src: flags[this.plugin.settings.selectImageIconFlag] || flags["red"],
alt: "Pixel Banner",
style: `
width: 20px;
height: 25px;
vertical-align: middle;
margin: -5px 10px 0 20px;
`
}
});
titleContainer.appendChild(document.createTextNode("Confirm Pixel Banner Purchase"));
const mediaContainer = contentEl.createDiv({ cls: "pixel-banner-store-confirm-media" });
if (this.isVideo) {
const videoPreview = mediaContainer.createDiv({ cls: "pixel-banner-store-video-preview" });
videoPreview.createEl("img", {
attr: {
src: this.previewImage,
alt: "Video Banner Thumbnail"
}
});
const playOverlay = videoPreview.createDiv({
cls: "pixel-banner-store-play-overlay",
attr: {
style: `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.7);
border-radius: 50%;
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24px;
pointer-events: none;
`
}
});
playOverlay.innerHTML = "\u25B6";
const typeIndicator = mediaContainer.createDiv({
text: `VIDEO (${this.fileExtension.toUpperCase()})`,
cls: "pixel-banner-store-type-indicator",
attr: {
style: `
text-align: center;
margin-top: 8px;
font-size: 0.8em;
color: var(--text-muted);
font-weight: bold;
`
}
});
} else {
mediaContainer.createEl("img", {
attr: {
src: this.previewImage,
alt: "Banner Preview"
}
});
}
contentEl.createEl("p", {
text: `"${(_a = this.prompt) == null ? void 0 : _a.toLowerCase().replace(/[^a-zA-Z0-9-_ ]/g, "").trim()}"`,
cls: "pixel-banner-store-confirm-prompt",
attr: {
"style": `
margin-top: -30px;
margin-bottom: 30px;
margin-left: auto;
margin-right: auto;
max-width: 320px;
text-align: center;
font-size: .9em;
font-style: italic;
`
}
});
const buttonContainer = contentEl.createDiv({
cls: "setting-item-control",
attr: {
style: `
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 10px;
align-items: flex-start;
`
}
});
const explanationText = buttonContainer.createEl("p", {
text: `\u{1FA99} ${decimalToFractionString(this.cost)} Banner Token${this.cost > 1 ? "s" : ""} (this is not a monitary transaction). Once purchased, the banner will be added to your vault, and will be free to download while listed in the store.`,
cls: "setting-item-description",
attr: {
style: `
text-align: left;
margin: 0 20px 0 0;
opacity: .8;
font-size: .8em;
`
}
});
const confirmButton = buttonContainer.createEl("button", {
text: `\u{1FA99} ${decimalToFractionString(this.cost)} Token${this.cost > 1 ? "s" : ""}`,
cls: "mod-cta radial-pulse-animation"
});
confirmButton.addEventListener("click", () => {
this.close();
this.onConfirm();
});
const cancelButton = buttonContainer.createEl("button", {
text: "Cancel"
});
cancelButton.addEventListener("click", () => this.close());
}
// Add styles for the preview media
addStyle() {
const style = document.createElement("style");
style.textContent = `
.pixel-banner-store-confirm-media {
display: flex;
flex-direction: column;
align-items: center;
margin: 40px 10px;
}
.pixel-banner-store-confirm-media img {
max-width: 100%;
height: auto;
border-radius: 4px;
}
.pixel-banner-store-video-preview {
position: relative;
display: inline-block;
}
.pixel-banner-store-video-preview img {
display: block;
}
.pixel-banner-store-image-card {
position: relative;
}
`;
document.head.appendChild(style);
this.style = style;
}
onClose() {
const { contentEl } = this;
contentEl.empty();
if (this.style) {
this.style.remove();
}
}
};
}
});
// src/modal/modals/webAddressModal.js
var import_obsidian21, WebAddressModal;
var init_webAddressModal = __esm({
"src/modal/modals/webAddressModal.js"() {
import_obsidian21 = require("obsidian");
init_modals();
WebAddressModal = class extends import_obsidian21.Modal {
constructor(app, plugin) {
super(app);
this.plugin = plugin;
}
async onOpen() {
const { contentEl } = this;
contentEl.empty();
const style = document.createElement("style");
style.textContent = `
/* ------------------- */
/* -- mobile layout -- */
/* ------------------- */
@media screen and (max-width: 375px) {
.pixel-banner-web-address-button-container { flex-direction: column !important; }
.pixel-banner-web-address-button-container button { width: 100% !important; }
}
`;
document.head.appendChild(style);
this.style = style;
const mainContainer = contentEl.createDiv({ cls: "pixel-banner-web-address-modal" });
const titleContainer = mainContainer.createEl("h2", {
cls: "pixel-banner-web-address-title",
text: "\u{1F310} Enter Banner URL",
attr: {
style: `
margin-top: 10px;
margin-bottom: 20px;
text-align: center;
`
}
});
const inputContainer = mainContainer.createDiv({
cls: "pixel-banner-web-address-input-container",
attr: {
style: `
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 20px;
`
}
});
inputContainer.createEl("label", {
text: "Image URL (http/https)",
attr: {
for: "pixel-banner-url-input",
style: `
font-size: 14px;
font-weight: 500;
`
}
});
const urlInput = inputContainer.createEl("input", {
cls: "pixel-banner-url-input",
attr: {
id: "pixel-banner-url-input",
type: "url",
placeholder: "https://example.com/image.jpg",
style: `
width: 100%;
padding: 8px 12px;
border-radius: 4px;
border: 1px solid var(--background-modifier-border);
background-color: var(--background-primary);
font-size: 14px;
`
}
});
const errorContainer = inputContainer.createDiv({
cls: "pixel-banner-url-error",
attr: {
style: `
color: var(--text-error);
font-size: 12px;
margin-top: 5px;
display: none;
`
}
});
const buttonContainer = mainContainer.createDiv({
cls: "pixel-banner-web-address-button-container",
attr: {
style: `
display: flex;
justify-content: space-between;
gap: 10px;
margin-top: 20px;
`
}
});
const setBannerButton = buttonContainer.createEl("button", {
cls: "pixel-banner-set-button",
text: "Set Banner",
attr: {
style: `
padding: 8px 16px;
border-radius: 4px;
background-color: var(--interactive-accent);
color: var(--text-on-accent);
font-weight: 500;
cursor: pointer;
border: none;
flex: 1;
`
}
});
const backToMenuButton = buttonContainer.createEl("button", {
cls: "pixel-banner-back-button",
text: "\u21E0 Main Menu",
attr: {
style: `
padding: 8px 16px;
border-radius: 4px;
background-color: var(--background-modifier-border);
color: var(--text-normal);
font-weight: 500;
cursor: pointer;
border: none;
`
}
});
const validateImageUrl = async (url) => {
try {
if (!url || !url.startsWith("http")) {
throw new Error("Please enter a valid http/https URL");
}
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(true);
img.onerror = () => reject(new Error("URL does not point to a valid image"));
img.src = url;
setTimeout(() => reject(new Error("Image loading timed out")), 5e3);
});
} catch (error) {
throw error;
}
};
const showError = (message) => {
errorContainer.setText(message);
errorContainer.style.display = "block";
urlInput.addClass("pixel-banner-url-input-error");
};
const clearError = () => {
errorContainer.setText("");
errorContainer.style.display = "none";
urlInput.removeClass("pixel-banner-url-input-error");
};
urlInput.addEventListener("input", () => {
clearError();
});
setBannerButton.addEventListener("click", async () => {
const url = urlInput.value.trim();
clearError();
try {
setBannerButton.disabled = true;
setBannerButton.setText("Validating...");
await validateImageUrl(url);
const activeFile = this.app.workspace.getActiveFile();
if (!activeFile) {
throw new Error("No active file to add banner to");
}
await this.plugin.app.fileManager.processFrontMatter(activeFile, (frontmatter) => {
const bannerField = this.plugin.settings.customBannerField[0];
frontmatter[bannerField] = url;
});
if (this.plugin.settings.openTargetingModalAfterSelectingBannerOrIcon) {
this.close();
new TargetPositionModal(this.app, this.plugin).open();
} else {
this.close();
}
} catch (error) {
showError(error.message);
setBannerButton.disabled = false;
setBannerButton.setText("Set Banner");
}
});
backToMenuButton.addEventListener("click", () => {
this.close();
new SelectPixelBannerModal(this.app, this.plugin).open();
});
this.addStyle();
urlInput.focus();
}
addStyle() {
const style = document.createElement("style");
style.textContent = `
.pixel-banner-web-address-modal {
padding: 16px;
max-width: 500px;
width: 100%;
}
.pixel-banner-url-input-error {
border-color: var(--text-error) !important;
}
@keyframes pixel-banner-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
`;
document.head.appendChild(style);
this.style = style;
}
onClose() {
this.contentEl.empty();
if (this.style) {
this.style.remove();
}
}
};
}
});
// src/modal/modals/dailyGame.js
var import_obsidian22, DailyGameModal;
var init_dailyGame = __esm({
"src/modal/modals/dailyGame.js"() {
import_obsidian22 = require("obsidian");
init_constants();
init_selectPixelBannerModal();
DailyGameModal = class extends import_obsidian22.Modal {
constructor(app, userEmail, apiKey, plugin) {
super(app);
this.userEmail = userEmail;
this.apiKey = apiKey;
this.iframe = null;
this.plugin = plugin;
}
onOpen() {
const style = document.createElement("style");
style.innerHTML = `
.pixel-banner-game-modal {
width: 800px;
max-width: 90vw;
}
.pixel-banner-game-modal .game-container {
margin: 1rem 0;
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
overflow: hidden;
height: 600px;
}
.pixel-banner-game-modal iframe {
border: none;
}
`;
const { contentEl } = this;
contentEl.addClass("pixel-banner-game-modal");
const gameContainer = contentEl.createDiv({ cls: "game-container" });
this.iframe = gameContainer.createEl("iframe", {
attr: {
src: `${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.DAILY_GAME}`,
width: "100%",
height: "600px",
frameborder: "0",
allowfullscreen: "true",
allow: "clipboard-write; encrypted-media",
sandbox: "allow-scripts allow-same-origin allow-popups allow-forms"
}
});
const buttonContainer = contentEl.createDiv({
cls: "button-container",
attr: {
style: `
display: flex;
align-items: center;
justify-content: space-between;
`
}
});
const buyTokensButton = buttonContainer.createEl("button", {
cls: "pixel-banner-account-button pixel-banner-buy-tokens-button",
text: "\u{1F4B5} Buy More Tokens"
});
buyTokensButton.addEventListener("click", (event) => {
event.preventDefault();
window.open(PIXEL_BANNER_PLUS.SHOP_URL, "_blank");
});
const backToMainButton = buttonContainer.createEl("button", {
text: "\u21E0 Main Menu",
cls: "cursor-pointer",
attr: {
style: `
margin-left: auto;
width: max-content;
min-width: auto;
`
}
});
backToMainButton.addEventListener("click", () => {
this.close();
new SelectPixelBannerModal(this.app, this.plugin).open();
});
this.iframe.onload = () => {
this.sendAuthToIframe();
};
}
sendAuthToIframe() {
if (!this.iframe.contentWindow) {
console.error("Iframe content window not available");
return;
}
this.iframe.contentWindow.postMessage({
type: "auth",
data: {
userEmail: this.userEmail,
apiKey: this.apiKey
}
}, PIXEL_BANNER_PLUS.API_URL);
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
};
}
});
// src/modal/modals.js
var modals_exports = {};
__export(modals_exports, {
DailyGameModal: () => DailyGameModal,
EmojiSelectionModal: () => EmojiSelectionModal,
FolderSelectionModal: () => FolderSelectionModal,
GenerateAIBannerModal: () => GenerateAIBannerModal,
IconFolderSelectionModal: () => IconFolderSelectionModal,
IconImageSelectionModal: () => IconImageSelectionModal,
ImageSelectionModal: () => ImageSelectionModal,
ImageViewModal: () => ImageViewModal,
PinChoiceModal: () => PinChoiceModal,
PixelBannerStoreModal: () => PixelBannerStoreModal,
ReleaseNotesModal: () => ReleaseNotesModal,
SaveImageModal: () => SaveImageModal,
SelectPixelBannerModal: () => SelectPixelBannerModal,
TargetPositionModal: () => TargetPositionModal,
WebAddressModal: () => WebAddressModal
});
var init_modals = __esm({
"src/modal/modals.js"() {
init_releaseNotesModal();
init_imageViewModal();
init_imageSelectionModal();
init_iconImageSelectionModal();
init_folderSelectionModal();
init_iconFolderSelectionModal();
init_generateAIBannerModal();
init_saveImageModal();
init_pinChoiceModal();
init_emojiSelectionModal();
init_targetPositionModal();
init_selectPixelBannerModal();
init_pixelBannerStoreModal();
init_webAddressModal();
init_dailyGame();
}
});
// src/main.js
var main_exports = {};
__export(main_exports, {
default: () => main_default
});
module.exports = __toCommonJS(main_exports);
// src/core/pixelBannerPlugin.js
var import_obsidian31 = require("obsidian");
// virtual-module:virtual:release-notes
var releaseNotes = '<a href="https://www.youtube.com/watch?v=tfNqEAQuhXs">\n <img src="https://pixel-banner.online/img/pixel-banner-v3.6.jpg" alt="Pixel Banner" style="max-width: 400px;">\n</a>\n\n<h2>\u{1F389} What&#39;s New</h2>\n<h3>v3.6.0</h3>\n<h4>\u2728 Added</h4>\n<ul>\n<li>Support for \u{1F3AC} Video Banners!<ul>\n<li>Upload and choose Video files as banners from your vault</li>\n<li>Downloadable \u{1F3AC} Video Banners from the <code>Pixel Banner Plus Collection</code></li>\n</ul>\n</li>\n<li>Added paging controls to the <code>Pixel Banner Plus Collection</code></li>\n<li>New global <code>Banner Max Width</code> setting to control the default max width for all banners</li>\n</ul>\n<h4>\u{1F4E6} Updated</h4>\n<ul>\n<li>Moved <code>Default Saved Banners Folder</code> setting to the <code>General</code> tab</li>\n<li>Renamed <code>Pixel Banner Plus Store</code> to <code>Pixel Banner Plus Collection</code> as many items are free</li>\n</ul>\n<h3>v3.6.1</h3>\n<h4>\u{1F41B} Fixed</h4>\n<ul>\n<li>Resolved issue with Icon Image selection modal not setting the selected icon image</li>\n</ul>\n<h3>v3.6.2</h3>\n<h4>\u{1F4E6} Updated</h4>\n<ul>\n<li>Improved debounce logic to prevent multiple banner reloads when opening a note</li>\n</ul>\n<h3>v3.6.3</h3>\n<h4>\u2728 Added</h4>\n<ul>\n<li>Added <code>filesize</code> display to the store modal</li>\n</ul>\n<h3>v3.6.4</h3>\n<h4>\u2728 Added</h4>\n<ul>\n<li>Banner images now support local <code>file</code> protocol for images outside of your vault (e.g. <code>file:///C:\\path\\banner.jpg</code>)</li>\n</ul>\n<h4>\u{1F4E6} Updated</h4>\n<ul>\n<li>Allow commas in banner filenames</li>\n</ul>\n<h4>\u{1F41B} Fixed</h4>\n<ul>\n<li>Ensure pinned banner is the currently displayed image when saving API banners</li>\n<li>Ensure banner icons are only rendered when a main banner image is present</li>\n<li>Banner Icon Image not always rendered until the note was clicked/focused</li>\n</ul>\n<h3>v3.6.5</h3>\n<h4>\u{1F41B} Fixed</h4>\n<ul>\n<li>Fix refresh button to use original comma-separated keywords from frontmatter instead of the cached single keyword</li>\n<li>Resolved issue with the default x/y frontmatter fields not being hidden when the &quot;Hide Pixel Banner Fields&quot; option is enabled</li>\n<li>Updated API call for <code>Pexels</code> to conform to spec changes on their side</li>\n</ul>\n<h3>v3.6.6</h3>\n<h4>\u{1F41B} Fixed</h4>\n<ul>\n<li>New folder group entries now inherit the user&#39;s default Content Start Position setting instead of being hardcoded to 150px</li>\n</ul>\n<h3>v3.6.8</h3>\n<h4>\u2728 Added</h4>\n<ul>\n<li><strong>Pin Choice Modal</strong>: When pinning API images, users can now choose between saving locally or pinning the URL directly to frontmatter<ul>\n<li>New choice modal presents &quot;Save Image Locally&quot; vs &quot;Pin Image URL&quot; options</li>\n<li>URL pinning saves no storage space in vault but requires internet connection</li>\n<li>Local saving remains available for offline access and permanence</li>\n<li>Choice only appears for user-initiated pin actions (pin icon, command palette)</li>\n<li>AI generation and Pixel Banner Plus continue to save locally automatically</li>\n</ul>\n</li>\n<li><strong>Auto-Focus Enhancement</strong>: Folder selection modal now automatically focuses and selects the text input for improved workflow</li>\n</ul>\n<h3>v3.6.7</h3>\n<h4>\u{1F41B} Fixed</h4>\n<ul>\n<li>Fixed ImageViewModal to properly display banner images and videos when clicking the &quot;Show View Image Icon&quot;<ul>\n<li>Added support for MP4 and MOV video files in the ImageViewModal with proper video player controls</li>\n<li>Correctly display actual image URLs instead of keywords for 3rd party API banners in the ImageViewModal</li>\n<li>Local images, videos, and file:/// paths maintain original display behavior</li>\n</ul>\n</li>\n</ul>\n<h3>v3.6.8</h3>\n<h4>\u2728 Added</h4>\n<ul>\n<li><strong>Pin Choice Modal</strong>: When pinning API images, users can now choose between saving locally or pinning the URL directly to frontmatter<ul>\n<li>New choice modal presents &quot;Save Image Locally&quot; vs &quot;Pin Image URL&quot; options</li>\n<li>URL pinning saves no storage space in vault but requires internet connection</li>\n<li>Local saving remains available for offline access and permanence</li>\n<li>Choice only appears for user-initiated pin actions (pin icon, command palette)</li>\n<li>AI generation and Pixel Banner Plus continue to save locally automatically</li>\n</ul>\n</li>\n<li><strong>Auto-Focus Enhancement</strong>: Folder selection modal now automatically focuses and selects the text input for improved workflow</li>\n<li><code>Enter</code> button support for submitting the save image form in the <code>Save Image Modal</code></li>\n<li>New <code>Pin Image URL</code> option to save API images directly as URL references in frontmatter without downloading to vault</li>\n</ul>\n<h4>\u{1F4E6} Updated</h4>\n<ul>\n<li>Replaced manual frontmatter string manipulation with Obsidian&#39;s native processFrontMatter API for more reliable metadata updates</li>\n</ul>\n<h3>v3.6.9</h3>\n<h4>\u2728 Added</h4>\n<ul>\n<li>New <code>Icon Image Size Multiplier</code> setting to the <code>General</code> settings tab to control the global size of banner icon images</li>\n<li>Check for version updates when opening <code>General</code> settings and show update button if available</li>\n</ul>\n<h4>\u{1F4E6} Updated</h4>\n<ul>\n<li>Moved <code>AI Model</code> selection from radio buttons to a dropdown for better organization</li>\n<li>Changed default banner fade value from <code>-70</code> to <code>-40</code></li>\n</ul>\n<h3>v3.6.10</h3>\n<h4>\u{1F41B} Fixed</h4>\n<ul>\n<li>Icon images and emojis not being displayed properly</li>\n</ul>\n<h3>v3.6.11</h3>\n<h4>\u2728 Added</h4>\n<ul>\n<li>New toggle to turn on/off <code>Pixel Banner Plus</code> in the main Pixel Banner select modal</li>\n<li><strong>Plain image format support</strong>: Added <code>image</code> option to Image Property Format setting (without brackets), improving compatibility with Make.md and other plugins</li>\n</ul>\n<h4>\u{1F4E6} Updated</h4>\n<ul>\n<li>Added unquoted wiki-link support for Image Icons paths (e.g. <code>[[path/icon.png]]</code>)</li>\n<li>Misc code cleanup</li>\n</ul>\n<h3>v3.6.12</h3>\n<h4>\u{1F41B} Fixed</h4>\n<ul>\n<li>Resolved incorrect <code>Pixel Banner Plus Server</code> URL</li>\n</ul>\n<h3>v3.6.13</h3>\n<h4>\u2728 Added</h4>\n<ul>\n<li>Support for Multiple Image Reference for new AI Image Generation models<ul>\n<li><code>Nano Banana</code></li>\n<li><code>Seedream 4</code></li>\n</ul>\n</li>\n</ul>\n<h3>v3.6.14</h3>\n<h4>\u{1F41B} Fixed</h4>\n<ul>\n<li>Resolved issue with <code>.webp</code> images not being displayed</li>\n</ul>\n<h3>v3.6.15</h3>\n<h4>\u{1F41B} Fixed</h4>\n<ul>\n<li>Resolved issue with plain paths not working for video files (<code>.mp4</code>, <code>.mov</code>)</li>\n</ul>\n<h3>v3.6.16</h3>\n<h4>\u{1F41B} Fixed</h4>\n<ul>\n<li>Fixed an occasional error that could prevent banners from displaying correctly</li>\n</ul>\n<h3>v3.6.18 - 2026-03-08</h3>\n<h4>\u{1F41B} Fixed</h4>\n<ul>\n<li>Fixed banner flickering on every keystroke when a note contains frontmatter and uses folder group banners (issue #318)</li>\n</ul>\n<h3>v3.6.17 - 2026-03-08</h3>\n<h4>\u{1F41B} Fixed</h4>\n<ul>\n<li>Fixed content start position not applying correctly when changed in settings (issue #297)</li>\n</ul>\n<h3>v3.6.18 - 2026-03-08</h3>\n<h4>\u{1F41B} Fixed</h4>\n<ul>\n<li>Fixed banner flickering on every keystroke when a note contains frontmatter and uses folder group banners (issue #318)</li>\n</ul>\n<a href="https://www.youtube.com/watch?v=pJFsMfrWak4">\n <img src="https://pixel-banner.online/img/pixel-banner-transparent-bg.png" alt="Pixel Banner" style="max-width: 400px;">\n</a>\n';
// src/settings/settings.js
var import_obsidian6 = require("obsidian");
// src/settings/tabs/settingsTabAPISettings.js
var import_obsidian = require("obsidian");
async function testPexelsApi(apiKey) {
try {
const response = await fetch(`https://api.pexels.com/v1/search?query=${random20characters()}&per_page=3`, {
headers: {
"Authorization": apiKey
}
});
if (!response.ok) {
throw new Error("\u274C Invalid Pexels API key");
}
const data = await response.json();
return data.photos;
} catch (error) {
return false;
}
}
async function testPixabayApi(apiKey) {
try {
const response = await fetch(`https://pixabay.com/api/?key=${apiKey}&q=test&per_page=3`);
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
return true;
} catch (error) {
return false;
}
}
async function testFlickrApi(apiKey) {
try {
const response = await fetch(`https://www.flickr.com/services/rest/?method=flickr.test.echo&api_key=${apiKey}&format=json&nojsoncallback=1`);
const data = await response.json();
return data.stat === "ok";
} catch (error) {
return false;
}
}
async function testUnsplashApi(apiKey) {
try {
const response = await fetch("https://api.unsplash.com/photos/random", {
headers: {
"Authorization": `Client-ID ${apiKey}`
}
});
return response.ok;
} catch (error) {
return false;
}
}
function random20characters() {
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < 20; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}
function createAPISettings(containerEl, plugin) {
const calloutEl = containerEl.createEl("div", { cls: "tab-callout" });
calloutEl.createEl("div", { text: "\u{1F310} Optionally select which 3rd party API provider to use for displaying random images." });
new import_obsidian.Setting(containerEl).setName("API Provider").setDesc("Select the API provider for fetching images").addDropdown((dropdown) => dropdown.addOption("all", "All (Random)").addOption("pexels", "Pexels").addOption("pixabay", "Pixabay").addOption("flickr", "Flickr").addOption("unsplash", "Unsplash").setValue(plugin.settings.apiProvider).onChange(async (value) => {
plugin.settings.apiProvider = value;
await plugin.saveSettings();
plugin.settingTab.display();
}));
new import_obsidian.Setting(containerEl).setName("Pexels API Key");
containerEl.createEl("span", { text: "Enter your Pexels API key. Get your API key from ", cls: "setting-item-description" }).createEl("a", { href: "https://www.pexels.com/api/", text: "Pexels API" });
const pexelsApiKeySetting = new import_obsidian.Setting(containerEl).setClass("full-width-control").addText((text) => {
text.setPlaceholder("Pexels API key").setValue(plugin.settings.pexelsApiKey).onChange(async (value) => {
plugin.settings.pexelsApiKey = value;
await plugin.saveSettings();
});
text.inputEl.style.width = "calc(100% - 100px)";
}).addButton((button) => button.setButtonText("Test API").onClick(async () => {
const apiKey = plugin.settings.pexelsApiKey;
if (!apiKey) {
new import_obsidian.Notice("Please enter an API key first");
return;
}
button.setButtonText("Testing...");
button.setDisabled(true);
const isValid = await testPexelsApi(apiKey);
button.setButtonText("Test API");
button.setDisabled(false);
new import_obsidian.Notice(isValid ? "\u2705 Pexels API key is valid!" : "\u274C Invalid Pexels API key");
}));
pexelsApiKeySetting.settingEl.style.width = "100%";
new import_obsidian.Setting(containerEl).setName("Pixabay API Key");
containerEl.createEl("span", { text: "Enter your Pixabay API key. Get your API key from ", cls: "setting-item-description" }).createEl("a", { href: "https://pixabay.com/api/docs/", text: "Pixabay API" });
const pixabayApiKeySetting = new import_obsidian.Setting(containerEl).setClass("full-width-control").addText((text) => {
text.setPlaceholder("Pixabay API key").setValue(plugin.settings.pixabayApiKey).onChange(async (value) => {
plugin.settings.pixabayApiKey = value;
await plugin.saveSettings();
});
text.inputEl.style.width = "calc(100% - 100px)";
}).addButton((button) => button.setButtonText("Test API").onClick(async () => {
const apiKey = plugin.settings.pixabayApiKey;
if (!apiKey) {
new import_obsidian.Notice("Please enter an API key first");
return;
}
button.setButtonText("Testing...");
button.setDisabled(true);
const isValid = await testPixabayApi(apiKey);
button.setButtonText("Test API");
button.setDisabled(false);
new import_obsidian.Notice(isValid ? "\u2705 Pixabay API key is valid!" : "\u274C Invalid Pixabay API key");
}));
pixabayApiKeySetting.settingEl.style.width = "100%";
new import_obsidian.Setting(containerEl).setName("Flickr API Key");
containerEl.createEl("span", { text: "Enter your Flickr API key. Get your API key from ", cls: "setting-item-description" }).createEl("a", { href: "https://www.flickr.com/services/api/", text: "Flickr API" });
const flickrApiKeySetting = new import_obsidian.Setting(containerEl).setClass("full-width-control").addText((text) => {
text.setPlaceholder("Flickr API key").setValue(plugin.settings.flickrApiKey).onChange(async (value) => {
plugin.settings.flickrApiKey = value;
await plugin.saveSettings();
});
text.inputEl.style.width = "calc(100% - 100px)";
}).addButton((button) => button.setButtonText("Test API").onClick(async () => {
const apiKey = plugin.settings.flickrApiKey;
if (!apiKey) {
new import_obsidian.Notice("Please enter an API key first");
return;
}
button.setButtonText("Testing...");
button.setDisabled(true);
const isValid = await testFlickrApi(apiKey);
button.setButtonText("Test API");
button.setDisabled(false);
new import_obsidian.Notice(isValid ? "\u2705 Flickr API key is valid!" : "\u274C Invalid Flickr API key");
}));
new import_obsidian.Setting(containerEl).setName("Unsplash API Key");
containerEl.createEl("span", { text: "Enter your Unsplash API key (Access Key). Get your API key from ", cls: "setting-item-description" }).createEl("a", { href: "https://unsplash.com/oauth/applications", text: "Unsplash API" });
const unsplashApiKeySetting = new import_obsidian.Setting(containerEl).setClass("full-width-control").addText((text) => {
text.setPlaceholder("Unsplash API key").setValue(plugin.settings.unsplashApiKey).onChange(async (value) => {
plugin.settings.unsplashApiKey = value;
await plugin.saveSettings();
});
text.inputEl.style.width = "calc(100% - 100px)";
}).addButton((button) => button.setButtonText("Test API").onClick(async () => {
const apiKey = plugin.settings.unsplashApiKey;
if (!apiKey) {
new import_obsidian.Notice("Please enter an API key first");
return;
}
button.setButtonText("Testing...");
button.setDisabled(true);
const isValid = await testUnsplashApi(apiKey);
button.setButtonText("Test API");
button.setDisabled(false);
new import_obsidian.Notice(isValid ? "\u2705 Unsplash API key is valid!" : "\u274C Invalid Unsplash API key");
}));
const exampleContainer = containerEl.createEl("div", {
attr: {
style: `
margin: 20px 0;
padding: 20px;
border: 1px solid var(--modal-border-color);
border-radius: 7px;
`
}
});
exampleContainer.createEl("div", {
text: "Usage Example",
attr: {
style: `
font-weight: bold;
font-size: 1.1rem;
color: var(--color-accent);
`
}
});
exampleContainer.createEl("div", {
text: "Add keyword(s) to the banner frontmatter to display random API images matching the keywords.",
attr: {
style: `
margin: 10px 0;
`
}
});
exampleContainer.createEl("img", {
attr: {
src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/3rd-party-apis-example.jpg",
style: `
width: auto;
max-width: 100%;
`
}
});
new import_obsidian.Setting(containerEl).setName("Images").setDesc("Configure settings for images fetched from API. These settings apply when using keywords to fetch random images.").setHeading();
new import_obsidian.Setting(containerEl).setName("Show Pin Icon").setDesc("Show a pin icon on random banner images that allows saving them to your vault. Once pinned, your frontmatter will be updated to use the local image instead of the API image.").addToggle((toggle) => toggle.setValue(plugin.settings.showPinIcon).onChange(async (value) => {
plugin.settings.showPinIcon = value;
refreshIconSetting.settingEl.style.display = value ? "flex" : "none";
await plugin.saveSettings();
}));
new import_obsidian.Setting(containerEl).setName("Pinned Image Filename").setDesc("Set the default filename for pinned images.").addText((text) => text.setPlaceholder("pixel-banner-image").setValue(plugin.settings.pinnedImageFilename).onChange(async (value) => {
plugin.settings.pinnedImageFilename = value;
await plugin.saveSettings();
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
plugin.settings.pinnedImageFilename = DEFAULT_SETTINGS.pinnedImageFilename;
await plugin.saveSettings();
const textInput = button.extraSettingsEl.parentElement.querySelector("input");
textInput.value = DEFAULT_SETTINGS.pinnedImageFilename;
}));
const refreshIconSetting = new import_obsidian.Setting(containerEl).setName("Show Refresh Icon").setDesc("Show a refresh icon on random banner images that allows fetching a new random image.").addToggle((toggle) => toggle.setValue(plugin.settings.showRefreshIcon).onChange(async (value) => {
plugin.settings.showRefreshIcon = value;
await plugin.saveSettings();
}));
refreshIconSetting.settingEl.style.display = plugin.settings.showPinIcon ? "flex" : "none";
new import_obsidian.Setting(containerEl).setName("Size").setDesc("Select the size of the image - (API only)").addDropdown((dropdown) => dropdown.addOption("small", "Small").addOption("medium", "Medium").addOption("large", "Large").setValue(plugin.settings.imageSize).onChange(async (value) => {
plugin.settings.imageSize = value;
await plugin.saveSettings();
}));
new import_obsidian.Setting(containerEl).setName("Orientation").setDesc("Select the orientation of the image - (API only)").addDropdown((dropdown) => dropdown.addOption("landscape", "Landscape").addOption("portrait", "Portrait").addOption("square", "Square").setValue(plugin.settings.imageOrientation).onChange(async (value) => {
plugin.settings.imageOrientation = value;
await plugin.saveSettings();
}));
new import_obsidian.Setting(containerEl).setName("Number of images").setDesc("Enter the number of random images to fetch (3-50) - (API only)").addText((text) => text.setPlaceholder("10").setValue(String(plugin.settings.numberOfImages || 10)).onChange(async (value) => {
let numValue = Number(value);
if (!isNaN(numValue)) {
numValue = Math.max(3, Math.min(numValue, 50));
plugin.settings.numberOfImages = numValue;
await plugin.saveSettings();
}
})).then((setting) => {
const inputEl = setting.controlEl.querySelector("input");
inputEl.type = "number";
inputEl.min = "3";
inputEl.max = "50";
inputEl.style.width = "50px";
});
const defaultKeywordsSetting = new import_obsidian.Setting(containerEl).setName("Default keywords").setDesc("Enter a comma-separated list of default keywords to be used when no keyword is provided in the frontmatter, or when the provided keyword does not return any results. - (API only)").addTextArea((text) => {
text.setPlaceholder("Enter keywords, separated by commas").setValue(plugin.settings.defaultKeywords).onChange(async (value) => {
plugin.settings.defaultKeywords = value;
await plugin.saveSettings();
});
text.inputEl.style.width = "100%";
text.inputEl.style.marginTop = "15px";
text.inputEl.style.height = "90px";
}).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
plugin.settings.defaultKeywords = DEFAULT_SETTINGS.defaultKeywords;
await plugin.saveSettings();
plugin.settingTab.display();
}));
defaultKeywordsSetting.settingEl.dataset.id = "defaultKeywords";
defaultKeywordsSetting.settingEl.style.display = "flex";
defaultKeywordsSetting.settingEl.style.flexDirection = "column";
}
// src/settings/tabs/settingsTabFolderImages.js
var import_obsidian2 = require("obsidian");
function createFolderSettings(containerEl, plugin) {
const calloutEl = containerEl.createEl("div", { cls: "tab-callout" });
calloutEl.createEl("div", { text: "\u{1F5C3}\uFE0F Configure banner settings for specific folders. These settings will override the default settings for all notes in the specified folder." });
const folderImagesContainer = containerEl.createEl("div", { cls: "folder-images-container" });
plugin.settings.folderImages.forEach((folderImage, index) => {
new FolderImageSetting(
folderImagesContainer,
plugin,
folderImage,
index,
() => updateFolderSettings(containerEl, plugin)
);
});
const addFolderImageSetting = new import_obsidian2.Setting(containerEl).setClass("add-folder-image-setting").addButton((button) => button.setButtonText("Add Folder Image").onClick(async () => {
const newFolderImage = {
folder: "",
image: "",
imageDisplay: plugin.settings.imageDisplay,
imageRepeat: plugin.settings.imageRepeat,
yPosition: plugin.settings.yPosition,
xPosition: plugin.settings.xPosition,
contentStartPosition: plugin.settings.contentStartPosition,
bannerHeight: plugin.settings.bannerHeight,
fade: plugin.settings.fade,
borderRadius: plugin.settings.borderRadius,
titleColor: "var(--inline-title-color)",
directChildrenOnly: false,
enableImageShuffle: false,
shuffleFolder: ""
};
plugin.settings.folderImages.push(newFolderImage);
await plugin.saveSettings();
updateFolderSettings(containerEl, plugin);
}));
}
function updateFolderSettings(containerEl, plugin) {
containerEl.empty();
createFolderSettings(containerEl, plugin);
}
var FolderImageSetting = class extends import_obsidian2.Setting {
constructor(containerEl, plugin, folderImage, index, onDelete) {
super(containerEl);
this.plugin = plugin;
this.folderImage = folderImage;
this.index = index;
this.onDelete = onDelete;
this.setClass("folder-image-setting");
this.settingEl.empty();
const folderImageDeleteContainer = this.settingEl.createDiv("folder-image-delete-container");
this.addDeleteButton(folderImageDeleteContainer);
const infoEl = this.settingEl.createDiv("setting-item-info");
infoEl.createDiv("setting-item-name");
infoEl.createDiv("setting-item-description");
this.addFolderInput();
this.addImageInput();
this.addImageDisplaySettings();
this.addYPostionAndContentStart();
this.addFadeAndBannerHeight();
const controlEl = this.settingEl.createDiv("setting-item-control full-width-control");
this.addContentStartInput(controlEl);
this.addBorderRadiusInput(controlEl);
const controlEl2 = this.settingEl.createDiv("setting-item-control full-width-control");
this.addColorSettings(controlEl2);
this.addBannerIconSettings();
this.addDirectChildrenOnlyToggle();
}
addDeleteButton(containerEl) {
const deleteButton = containerEl.createEl("button", { cls: "pixel-banner-setting--delete-button" });
deleteButton.style.marginLeft = "20px";
deleteButton.style.width = "30px";
deleteButton.style.height = "30px";
deleteButton.style.padding = "0";
deleteButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-trash-2"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>`;
deleteButton.addEventListener("click", async () => {
this.plugin.settings.folderImages.splice(this.index, 1);
await this.plugin.saveSettings();
this.settingEl.remove();
if (this.onDelete) {
this.onDelete();
}
});
}
addFolderInput() {
const folderInputContainer = this.settingEl.createDiv("folder-input-container");
const folderInput = new import_obsidian2.Setting(folderInputContainer).setName("Folder Path").addText((text) => {
text.setValue(this.folderImage.folder || "").onChange(async (value) => {
this.folderImage.folder = value;
await this.plugin.saveSettings();
});
this.folderInputEl = text.inputEl;
this.folderInputEl.style.width = "300px";
});
folderInput.addButton((button) => button.setButtonText("Browse").onClick(() => {
new FolderSuggestModal(this.plugin.app, (chosenPath) => {
this.folderImage.folder = chosenPath;
this.folderInputEl.value = chosenPath;
this.plugin.saveSettings();
}).open();
}));
const shuffleContainer = this.settingEl.createDiv("shuffle-container");
const shuffleToggle = new import_obsidian2.Setting(shuffleContainer).setName("Enable Image Shuffle").setDesc("Randomly select an image from a specified folder each time the note loads").addToggle((toggle) => {
toggle.setValue(this.folderImage.enableImageShuffle || false).onChange(async (value) => {
this.folderImage.enableImageShuffle = value;
if (value) {
shuffleFolderInput.settingEl.style.display = "flex";
this.imageInputContainer.style.display = "none";
} else {
shuffleFolderInput.settingEl.style.display = "none";
this.imageInputContainer.style.display = "block";
}
await this.plugin.saveSettings();
});
});
const shuffleFolderInput = new import_obsidian2.Setting(shuffleContainer).setName("Image Shuffle Folder").setDesc("Folder containing images to randomly select from").addText((text) => {
text.setValue(this.folderImage.shuffleFolder || "").onChange(async (value) => {
this.folderImage.shuffleFolder = value;
await this.plugin.saveSettings();
});
text.inputEl.style.width = "300px";
});
shuffleFolderInput.addButton((button) => button.setButtonText("Browse").onClick(() => {
new FolderSuggestModal(this.plugin.app, (chosenPath) => {
this.folderImage.shuffleFolder = chosenPath;
shuffleFolderInput.controlEl.querySelector("input").value = chosenPath;
this.plugin.saveSettings();
}).open();
}));
if (!this.folderImage.enableImageShuffle) {
shuffleFolderInput.settingEl.style.display = "none";
}
}
addImageInput() {
this.imageInputContainer = this.settingEl.createDiv("folder-input-container");
if (this.folderImage.enableImageShuffle) {
this.imageInputContainer.style.display = "none";
}
const imageInput = new import_obsidian2.Setting(this.imageInputContainer).setName("Image URL or Keyword").addText((text) => {
text.setValue(this.folderImage.image || "").onChange(async (value) => {
this.folderImage.image = value;
await this.plugin.saveSettings();
});
this.imageInputEl = text.inputEl;
this.imageInputEl.style.width = "306px";
});
}
addImageDisplaySettings(containerEl) {
const displayContainer = this.settingEl.createDiv("display-and-repeat-container");
const displaySetting = new import_obsidian2.Setting(displayContainer).setName("Image Display").addDropdown((dropdown) => {
dropdown.addOption("auto", "Auto").addOption("cover", "Cover").addOption("contain", "Contain").setValue(this.folderImage.imageDisplay || "cover").onChange(async (value) => {
this.folderImage.imageDisplay = value;
await this.plugin.saveSettings();
});
dropdown.selectEl.style.marginRight = "20px";
});
const repeatSetting = new import_obsidian2.Setting(displayContainer).setName("repeat").addToggle((toggle) => {
toggle.setValue(this.folderImage.imageRepeat || false).onChange(async (value) => {
this.folderImage.imageRepeat = value;
await this.plugin.saveSettings();
});
});
const toggleEl = repeatSetting.controlEl.querySelector(".checkbox-container");
if (toggleEl) toggleEl.style.justifyContent = "flex-start";
}
addYPostionAndContentStart() {
const controlEl = this.settingEl.createDiv("setting-item-control full-width-control");
this.addYPositionInput(controlEl);
this.addXPositionInput(controlEl);
}
addFadeAndBannerHeight() {
const controlEl = this.settingEl.createDiv("setting-item-control full-width-control");
this.addFadeInput(controlEl);
this.addBannerHeightInput(controlEl);
}
addYPositionInput(containerEl) {
const label = containerEl.createEl("label", { text: "Y-Position", cls: "setting-item-name__label" });
const sliderContainer = containerEl.createEl("div", { cls: "slider-container" });
const slider = sliderContainer.createEl("input", {
type: "range",
cls: "slider",
attr: {
min: "0",
max: "100",
step: "1"
}
});
slider.value = this.folderImage.yPosition || "50";
slider.style.width = "100px";
slider.style.marginLeft = "10px";
const valueDisplay = sliderContainer.createEl("div", { cls: "slider-value" });
valueDisplay.style.marginLeft = "10px";
const updateValueDisplay = (value) => {
valueDisplay.textContent = value;
};
updateValueDisplay(slider.value);
slider.addEventListener("input", (event) => {
updateValueDisplay(event.target.value);
});
slider.addEventListener("change", async () => {
this.folderImage.yPosition = parseInt(slider.value);
await this.plugin.saveSettings();
});
label.appendChild(sliderContainer);
containerEl.appendChild(label);
}
addXPositionInput(containerEl) {
const label = containerEl.createEl("label", { text: "X-Position", cls: "setting-item-name__label" });
label.style.marginLeft = "20px";
const sliderContainer = containerEl.createEl("div", { cls: "slider-container" });
const slider = sliderContainer.createEl("input", {
type: "range",
cls: "slider",
attr: {
min: "0",
max: "100",
step: "1"
}
});
slider.value = this.folderImage.xPosition || "50";
slider.style.width = "100px";
slider.style.marginLeft = "10px";
const valueDisplay = sliderContainer.createEl("div", { cls: "slider-value" });
valueDisplay.style.marginLeft = "10px";
const updateValueDisplay = (value) => {
valueDisplay.textContent = value;
};
updateValueDisplay(slider.value);
slider.addEventListener("input", (event) => {
updateValueDisplay(event.target.value);
});
slider.addEventListener("change", async () => {
this.folderImage.xPosition = parseInt(slider.value);
await this.plugin.saveSettings();
});
label.appendChild(sliderContainer);
containerEl.appendChild(label);
}
addBannerHeightInput(containerEl) {
const label = containerEl.createEl("label", { text: "Banner Height", cls: "setting-item-name__label" });
label.style.marginLeft = "20px";
const heightInput = containerEl.createEl("input", {
type: "number",
attr: {
min: "0",
max: "1280"
}
});
heightInput.style.width = "50px";
heightInput.style.marginLeft = "10px";
heightInput.value = this.folderImage.bannerHeight || "";
heightInput.placeholder = String(this.plugin.settings.bannerHeight || 350);
heightInput.addEventListener("change", async () => {
let value = heightInput.value ? parseInt(heightInput.value) : null;
if (value !== null) {
value = Math.max(0, Math.min(1280, value));
this.folderImage.bannerHeight = value;
heightInput.value = value;
} else {
delete this.folderImage.bannerHeight;
heightInput.value = "";
}
await this.plugin.saveSettings();
});
label.appendChild(heightInput);
containerEl.appendChild(label);
}
addFadeInput(containerEl) {
const label = containerEl.createEl("label", { text: "Fade", cls: "setting-item-name__label" });
const sliderContainer = containerEl.createEl("div", { cls: "slider-container" });
const slider = sliderContainer.createEl("input", {
type: "range",
cls: "slider",
attr: {
min: "-1500",
max: "100",
step: "5"
}
});
slider.value = this.folderImage.fade !== void 0 ? this.folderImage.fade : "-75";
slider.style.width = "100px";
slider.style.marginLeft = "10px";
const valueDisplay = sliderContainer.createEl("div", { cls: "slider-value" });
valueDisplay.style.marginLeft = "10px";
const updateValueDisplay = (value) => {
valueDisplay.textContent = value;
};
updateValueDisplay(slider.value);
slider.addEventListener("input", (event) => {
updateValueDisplay(event.target.value);
});
slider.addEventListener("change", async () => {
this.folderImage.fade = parseInt(slider.value);
await this.plugin.saveSettings();
});
label.appendChild(sliderContainer);
containerEl.appendChild(label);
}
addColorSettings(containerEl) {
const colorContainer = containerEl.createDiv("color-settings-container");
new import_obsidian2.Setting(colorContainer).setName("Inline Title Color").addColorPicker((color) => color.setValue((() => {
const currentColor = this.folderImage.titleColor || this.plugin.settings.titleColor;
if (currentColor.startsWith("var(--")) {
const temp = document.createElement("div");
temp.style.color = currentColor;
document.body.appendChild(temp);
const computedColor = getComputedStyle(temp).color;
document.body.removeChild(temp);
const rgbMatch = computedColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (rgbMatch) {
const [_, r, g, b] = rgbMatch;
const hexColor = "#" + parseInt(r).toString(16).padStart(2, "0") + parseInt(g).toString(16).padStart(2, "0") + parseInt(b).toString(16).padStart(2, "0");
return hexColor;
}
return "#000000";
}
return currentColor;
})()).onChange(async (value) => {
this.folderImage.titleColor = value;
await this.plugin.saveSettings();
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
this.folderImage.titleColor = this.plugin.settings.titleColor;
await this.plugin.saveSettings();
const colorPickerEl = button.extraSettingsEl.parentElement.querySelector('input[type="color"]');
if (colorPickerEl) {
const currentColor = this.plugin.settings.titleColor;
if (currentColor.startsWith("var(--")) {
const temp = document.createElement("div");
temp.style.color = currentColor;
document.body.appendChild(temp);
const computedColor = getComputedStyle(temp).color;
document.body.removeChild(temp);
const rgbMatch = computedColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (rgbMatch) {
const [_, r, g, b] = rgbMatch;
const hexColor = "#" + parseInt(r).toString(16).padStart(2, "0") + parseInt(g).toString(16).padStart(2, "0") + parseInt(b).toString(16).padStart(2, "0");
colorPickerEl.value = hexColor;
}
} else {
colorPickerEl.value = currentColor;
}
}
}));
}
addDirectChildrenOnlyToggle() {
new import_obsidian2.Setting(this.settingEl).setName("Direct Children Only").setDesc("Apply banner only to direct children of the folder").addToggle((toggle) => {
toggle.setValue(this.folderImage.directChildrenOnly || false).onChange(async (value) => {
this.folderImage.directChildrenOnly = value;
await this.plugin.saveSettings();
});
});
}
addContentStartInput(containerEl) {
var _a;
const label = containerEl.createEl("label", { text: "Content Start", cls: "setting-item-name__label" });
label.style.marginRight = "20px";
const contentStartInput = containerEl.createEl("input", {
type: "number",
attr: {
min: "0"
}
});
contentStartInput.style.width = "50px";
contentStartInput.style.marginLeft = "10px";
contentStartInput.value = (_a = this.folderImage.contentStartPosition) != null ? _a : "355";
contentStartInput.addEventListener("change", async () => {
this.folderImage.contentStartPosition = parseInt(contentStartInput.value);
await this.plugin.saveSettings();
});
label.appendChild(contentStartInput);
containerEl.appendChild(label);
}
addBorderRadiusInput(containerEl) {
var _a;
const label = containerEl.createEl("label", { text: "Border Radius", cls: "setting-item-name__label" });
const radiusInput = containerEl.createEl("input", {
type: "number",
attr: {
min: "0",
max: "50"
}
});
radiusInput.style.width = "50px";
radiusInput.style.marginLeft = "10px";
radiusInput.value = (_a = this.folderImage.borderRadius) != null ? _a : "";
radiusInput.placeholder = String(this.plugin.settings.borderRadius || 17);
radiusInput.addEventListener("change", async () => {
let value = radiusInput.value ? parseInt(radiusInput.value) : null;
if (value !== null) {
value = Math.max(0, Math.min(50, value));
this.folderImage.borderRadius = value;
radiusInput.value = String(value);
} else {
delete this.folderImage.borderRadius;
radiusInput.value = "";
}
await this.plugin.saveSettings();
});
label.appendChild(radiusInput);
containerEl.appendChild(label);
}
addBannerIconSettings() {
const controlEl1 = this.settingEl.createDiv("setting-item-control full-width-control");
new import_obsidian2.Setting(controlEl1).setName("Icon Size").addSlider((slider) => slider.setLimits(10, 200, 1).setValue(this.folderImage.bannerIconSize || this.plugin.settings.bannerIconSize).setDynamicTooltip().onChange(async (value) => {
this.folderImage.bannerIconSize = value;
await this.plugin.saveSettings();
}));
new import_obsidian2.Setting(controlEl1).setName("Icon X Position").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(this.folderImage.bannerIconXPosition || this.plugin.settings.bannerIconXPosition).setDynamicTooltip().onChange(async (value) => {
this.folderImage.bannerIconXPosition = value;
await this.plugin.saveSettings();
}));
const controlEl2 = this.settingEl.createDiv("setting-item-control full-width-control");
new import_obsidian2.Setting(controlEl2).setName("Icon Opacity").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(this.folderImage.bannerIconOpacity || this.plugin.settings.bannerIconOpacity).setDynamicTooltip().onChange(async (value) => {
this.folderImage.bannerIconOpacity = value;
await this.plugin.saveSettings();
}));
new import_obsidian2.Setting(controlEl2).setName("Icon Color").addText((text) => {
text.setPlaceholder("(e.g., #ffffff or white)").setValue(this.folderImage.bannerIconColor || this.plugin.settings.bannerIconColor).onChange(async (value) => {
this.folderImage.bannerIconColor = value;
await this.plugin.saveSettings();
});
text.inputEl.style.width = "160px";
});
const controlEl3 = this.settingEl.createDiv("setting-item-control full-width-control");
new import_obsidian2.Setting(controlEl3).setName("Icon Font Weight").addDropdown((dropdown) => {
dropdown.addOption("lighter", "Lighter").addOption("normal", "Normal").addOption("bold", "Bold").setValue(this.folderImage.bannerIconFontWeight || this.plugin.settings.bannerIconFontWeight).onChange(async (value) => {
this.folderImage.bannerIconFontWeight = value;
await this.plugin.saveSettings();
});
});
new import_obsidian2.Setting(controlEl3).setName("Icon BG Color").addText((text) => {
text.setPlaceholder("(e.g., #ffffff or transparent)").setValue(this.folderImage.bannerIconBackgroundColor || this.plugin.settings.bannerIconBackgroundColor).onChange(async (value) => {
this.folderImage.bannerIconBackgroundColor = value;
await this.plugin.saveSettings();
});
text.inputEl.style.width = "160px";
});
const controlEl4 = this.settingEl.createDiv("setting-item-control full-width-control");
new import_obsidian2.Setting(controlEl4).setName("Icon Padding X").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(this.folderImage.bannerIconPaddingX || this.plugin.settings.bannerIconPaddingX).setDynamicTooltip().onChange(async (value) => {
this.folderImage.bannerIconPaddingX = value;
await this.plugin.saveSettings();
}));
new import_obsidian2.Setting(controlEl4).setName("Icon Padding Y").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(this.folderImage.bannerIconPaddingY || this.plugin.settings.bannerIconPaddingY).setDynamicTooltip().onChange(async (value) => {
this.folderImage.bannerIconPaddingY = value;
await this.plugin.saveSettings();
}));
const controlEl5 = this.settingEl.createDiv("setting-item-control full-width-control");
new import_obsidian2.Setting(controlEl5).setName("Icon Border Radius").addSlider((slider) => slider.setLimits(0, 50, 1).setValue(this.folderImage.bannerIconBorderRadius || this.plugin.settings.bannerIconBorderRadius).setDynamicTooltip().onChange(async (value) => {
this.folderImage.bannerIconBorderRadius = value;
await this.plugin.saveSettings();
}));
new import_obsidian2.Setting(controlEl5).setName("Icon Vertical Offset").addSlider((slider) => slider.setLimits(-100, 100, 1).setValue(this.folderImage.bannerIconVerticalOffset || this.plugin.settings.bannerIconVerticalOffset).setDynamicTooltip().onChange(async (value) => {
this.folderImage.bannerIconVerticalOffset = value;
await this.plugin.saveSettings();
}));
}
};
// src/settings/tabs/settingsTabCustomFieldNames.js
var import_obsidian3 = require("obsidian");
function arrayToString(arr) {
return Array.isArray(arr) ? arr.join(", ") : arr;
}
function stringToArray(str) {
return str.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
}
function validateFieldNames(settings, allFields, currentField, newNames) {
const validNamePattern = /^[a-zA-Z0-9_-]+$/;
const invalidNames = newNames.filter((name) => !validNamePattern.test(name));
if (invalidNames.length > 0) {
return {
isValid: false,
message: `Invalid characters in field names (only letters, numbers, dashes, and underscores allowed): ${invalidNames.join(", ")}`
};
}
const otherFields = allFields.filter((f) => f !== currentField);
const otherFieldNames = otherFields.flatMap((f) => settings[f]);
const duplicates = newNames.filter((name) => otherFieldNames.includes(name));
if (duplicates.length > 0) {
return {
isValid: false,
message: `Duplicate field names found: ${duplicates.join(", ")}`
};
}
return { isValid: true };
}
function createCustomFieldsSettings(containerEl, plugin) {
const calloutEl = containerEl.createEl("div", { cls: "tab-callout" });
calloutEl.createEl("div", { text: '\u{1F5FA}\uFE0F Customize the frontmatter field names used for Pixel Banner. You can define multiple names for each field, separated by commas. Field names can only contain letters, numbers, dashes, and underscores. Example: "banner, pixel-banner, header_image" could all be used as the banner field name.' });
const customFields = [
{
setting: "customBannerField",
name: "Banner Field Names",
desc: "Set custom field names for the banner in frontmatter",
values: '[[image.png]], "images/image.jpg"',
placeholder: "banner, pixel-banner, header-image"
},
{
setting: "customYPositionField",
name: "Y-Position Field Names",
desc: "Set custom field names for the Y-position in frontmatter",
values: "5, 70, 100",
placeholder: "banner-y, y-position, banner-offset"
},
{
setting: "customXPositionField",
name: "X-Position Field Names",
desc: "Set custom field names for the X-position in frontmatter",
values: "0, 30, 90",
placeholder: "banner-x, x-position, banner-offset-x"
},
{
setting: "customContentStartField",
name: "Content Start Position Field Names",
desc: "Set custom field names for the content start position in frontmatter",
values: "75, 150, 450",
placeholder: "content-start, start-position, content-offset"
},
{
setting: "customImageDisplayField",
name: "Image Display Field Names",
desc: "Set custom field names for the image display in frontmatter",
values: "cover, contain, auto, 200%, 70%",
placeholder: "banner-display, image-display, display-mode"
},
{
setting: "customImageRepeatField",
name: "Image Repeat Field Names",
desc: "Set custom field names for the image repeat in frontmatter",
values: "true, false",
placeholder: "banner-repeat, image-repeat, repeat"
},
{
setting: "customBannerMaxWidthField",
name: "Banner Max Width Field Names",
desc: "Set custom field names for the banner maximum width in frontmatter",
values: "800px, 100%, 1200px",
placeholder: "banner-max-width, max-width, banner-width"
},
{
setting: "customBannerAlignmentField",
name: "Banner Alignment Field Names",
desc: "Set custom field names for the banner alignment in frontmatter",
values: "left, center, right",
placeholder: "banner-alignment"
},
{
setting: "customBannerHeightField",
name: "Banner Height Field Names",
desc: "Set custom field names for the banner height in frontmatter",
values: "150, 350, 500",
placeholder: "banner-height, height, banner-size"
},
{
setting: "customFadeField",
name: "Fade Field Names",
desc: "Set custom field names for the fade in frontmatter",
values: "-300, -50, 0, 100",
placeholder: "banner-fade, fade, fade-amount"
},
{
setting: "customBorderRadiusField",
name: "Border Radius Field Names",
desc: "Set custom field names for the border radius in frontmatter",
values: "0, 17, 25",
placeholder: "banner-radius, radius, border-radius"
},
{
setting: "customTitleColorField",
name: "Title Color Field Names",
desc: "Set custom field names for the title color in frontmatter",
values: "#ffffff, white, var(--text-normal)",
placeholder: "banner-title-color, title-color, inline-title-color"
},
{
setting: "customBannerShuffleField",
name: "Banner Shuffle Field Names",
desc: "Set custom field names for the banner shuffle in frontmatter",
values: "true, false",
placeholder: "banner-shuffle, shuffle, random-banner"
},
{
setting: "customFlagColorField",
name: "Pixel Banner Flag Color Field Names",
desc: "Set custom field names for the pixel banner flag color in frontmatter",
values: "red, blue, green, orange, purple, yellow",
placeholder: "pixel-banner-flag-color, banner-flag-color, flag-color"
},
{
setting: "customBannerIconField",
name: "Banner Icon Field Names",
desc: "Set custom field names for the banner icon in frontmatter",
values: "\u{1F31F}, \u{1F3A8}, \u{1F4DD}",
placeholder: "banner-icon, icon, header-icon"
},
{
setting: "customBannerIconImageField",
name: "Banner Icon Image Field Names",
desc: "Set custom field names for the banner icon image in frontmatter",
values: '[[image.png]], "images/icon.jpg"',
placeholder: "banner-icon-image, icon-image"
},
{
setting: "customBannerIconSizeField",
name: "Banner Icon Size Field Names",
desc: "Set custom field names for the banner icon size in frontmatter",
values: "50, 70, 100",
placeholder: "banner-icon-size, icon-size"
},
{
setting: "customBannerIconImageSizeMultiplierField",
name: "Banner Icon Image Size Multiplier Field Names",
desc: "Set custom field names for the banner icon image size multiplier in frontmatter",
values: ".5, 1.5, 2",
placeholder: "banner-icon-image-size-multiplier, icon-image-size-multiplier"
},
{
setting: "customBannerIconTextVerticalOffsetField",
name: "Banner Icon Text Vertical Offset Field Names",
desc: "Set custom field names for the banner icon text vertical offset in frontmatter",
values: "-10, 0, 10",
placeholder: "banner-icon-text-vertical-offset, icon-text-vertical-offset"
},
{
setting: "customBannerIconRotateField",
name: "Banner Icon Rotate Field Names",
desc: "Set custom field names for the banner icon rotate in frontmatter",
values: "50, 70, 100",
placeholder: "banner-icon-rotate, icon-rotate"
},
{
setting: "customBannerIconXPositionField",
name: "Banner Icon X Position Field Names",
desc: "Set custom field names for the banner icon X position in frontmatter",
values: "25, 50, 75",
placeholder: "banner-icon-x, icon-x"
},
{
setting: "customBannerIconOpacityField",
name: "Banner Icon Opacity Field Names",
desc: "Set custom field names for the banner icon opacity in frontmatter",
values: "50, 75, 100",
placeholder: "banner-icon-opacity, icon-opacity"
},
{
setting: "customBannerIconColorField",
name: "Banner Icon Color Field Names",
desc: "Set custom field names for the banner icon color in frontmatter",
values: "#ffffff, white, var(--text-normal)",
placeholder: "banner-icon-color, icon-color"
},
{
setting: "customBannerIconFontWeightField",
name: "Banner Icon Font Weight Field Names",
desc: "Set custom field names for the banner icon font weight in frontmatter",
values: "lighter, normal, bold",
placeholder: "banner-icon-font-weight, icon-font-weight"
},
{
setting: "customBannerIconBackgroundColorField",
name: "Banner Icon Background Color Field Names",
desc: "Set custom field names for the banner icon background color in frontmatter",
values: "#000000, black, transparent",
placeholder: "banner-icon-bg-color, icon-bg-color"
},
{
setting: "customBannerIconPaddingXField",
name: "Banner Icon Padding X Field Names",
desc: "Set custom field names for the banner icon padding X in frontmatter",
values: "0, 10, 20",
placeholder: "banner-icon-padding-x, icon-padding-x"
},
{
setting: "customBannerIconPaddingYField",
name: "Banner Icon Padding Y Field Names",
desc: "Set custom field names for the banner icon padding Y in frontmatter",
values: "0, 10, 20",
placeholder: "banner-icon-padding-y, icon-padding-y"
},
{
setting: "customBannerIconBorderRadiusField",
name: "Banner Icon Border Radius Field Names",
desc: "Set custom field names for the banner icon border radius in frontmatter",
values: "0, 17, 25",
placeholder: "banner-icon-border-radius, icon-border-radius"
},
{
setting: "customBannerIconVerticalOffsetField",
name: "Banner Icon Vertical Offset Field Names",
desc: "Set custom field names for the banner icon vertical offset in frontmatter",
values: "-50, 0, 50",
placeholder: "banner-icon-y, icon-y"
},
{
setting: "customBannerIconImageAlignmentField",
name: "Banner Icon Image Alignment Field Names",
desc: "Set custom field names for the banner icon image alignment in frontmatter",
values: "left, right",
placeholder: "banner-icon-image-alignment, icon-image-alignment"
}
];
customFields.forEach((field) => {
new import_obsidian3.Setting(containerEl).setName(field.name).setDesc(createFragment((el) => {
el.createEl("div", { text: field.desc });
el.createEl("div", { cls: "setting-item-example" }).innerHTML = `Example frontmatter values: <code>${field.values}</code>`;
})).addText((text) => {
text.setPlaceholder(field.placeholder).setValue(arrayToString(plugin.settings[field.setting])).onChange(async (value) => {
const newNames = stringToArray(value);
const allFields = customFields.map((f) => f.setting);
const validation = validateFieldNames(plugin.settings, allFields, field.setting, newNames);
if (!validation.isValid) {
text.inputEl.addClass("is-invalid");
text.inputEl.title = validation.message;
return;
}
text.inputEl.removeClass("is-invalid");
text.inputEl.title = "";
plugin.settings[field.setting] = newNames;
await plugin.saveSettings();
});
text.inputEl.style.width = "300px";
}).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
plugin.settings[field.setting] = DEFAULT_SETTINGS[field.setting];
await plugin.saveSettings();
const settingEl = button.extraSettingsEl.parentElement;
const textInput = settingEl.querySelector('input[type="text"]');
textInput.value = arrayToString(DEFAULT_SETTINGS[field.setting]);
const event = new Event("input", { bubbles: true, cancelable: true });
textInput.dispatchEvent(event);
}));
});
}
// src/settings/tabs/settingsTabGeneral.js
var import_obsidian4 = require("obsidian");
init_flags();
init_semver();
function addUpdateButtonIfNeeded(containerEl, plugin, insertAfterCallout = false) {
if (!plugin.pixelBannerVersion) {
console.log("[Pixel Banner] No cloud version available, skipping update button");
return;
}
const cloudVersion = plugin.pixelBannerVersion;
const currentVersion = plugin.settings.lastVersion;
const isCloudVersionGreater = semver.gt(cloudVersion, currentVersion);
if (isCloudVersionGreater) {
if (containerEl.querySelector(".pixel-banner-update-button")) {
return;
}
const updateContainer = document.createElement("div");
updateContainer.style.cssText = `
display: flex;
justify-content: flex-end;
margin-top: 15px;
`;
const updateButton = document.createElement("button");
updateButton.textContent = "\u{1F504} Update Available!";
updateButton.className = "pixel-banner-scale-up-down-animation pixel-banner-update-button";
updateButton.style.cssText = `
padding: 6px 12px;
background-color: var(--interactive-accent);
color: var(--text-on-accent);
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
`;
updateContainer.appendChild(updateButton);
const calloutEl = containerEl.querySelector(".tab-callout");
if (calloutEl && calloutEl.nextSibling) {
calloutEl.parentNode.insertBefore(updateContainer, calloutEl.nextSibling);
} else {
containerEl.appendChild(updateContainer);
}
updateButton.addEventListener("click", async () => {
await plugin.app.setting.open();
await new Promise((resolve) => setTimeout(resolve, 300));
const settingsTabs = document.querySelectorAll(".vertical-tab-header-group .vertical-tab-nav-item");
for (const tab of settingsTabs) {
if (tab.textContent.includes("Community plugins")) {
tab.click();
break;
}
}
await new Promise((resolve) => setTimeout(resolve, 500));
const allTheButtons = document.querySelectorAll("button.mod-cta");
for (const button of allTheButtons) {
if (button.textContent.includes("Check for updates")) {
button.click();
break;
}
}
});
}
}
function createGeneralSettings(containerEl, plugin) {
const calloutEl = containerEl.createEl("div", { cls: "tab-callout margin-bottom-0" });
calloutEl.createEl("div", { text: `v${plugin.settings.lastVersion} \u22C5 Configure default settings for all notes.` });
if (!plugin.pixelBannerVersion) {
plugin.getPixelBannerInfo().then(() => {
addUpdateButtonIfNeeded(containerEl, plugin);
}).catch((error) => {
console.log("[Pixel Banner] Failed to fetch version info:", error.message);
});
} else if (!plugin.pixelBannerVersion) {
console.log("[Pixel Banner] Version not available, skipping version check");
} else {
addUpdateButtonIfNeeded(containerEl, plugin);
}
const SelectImageSettingsGroup = containerEl.createDiv({ cls: "setting-group" });
const showSelectImageIconSetting = new import_obsidian4.Setting(SelectImageSettingsGroup).setName("Show Pixel Banner Flag").setDesc("Show the banner selector icon in the top-left corner of notes").addToggle((toggle) => toggle.setValue(plugin.settings.showSelectImageIcon).onChange(async (value) => {
try {
plugin.settings.showSelectImageIcon = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.showSelectImageIcon = DEFAULT_SETTINGS.showSelectImageIcon;
await plugin.saveSettings();
const toggleComponent = showSelectImageIconSetting.components[0];
if (toggleComponent) {
toggleComponent.setValue(DEFAULT_SETTINGS.showSelectImageIcon);
}
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const selectImageIconOpacitySetting = new import_obsidian4.Setting(SelectImageSettingsGroup).setName("Pixel Banner Flag Opacity").setDesc("Set the opacity of the banner selector icon in the top-left corner (0-100)").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.selectImageIconOpacity).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.selectImageIconOpacity = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.selectImageIconOpacity = DEFAULT_SETTINGS.selectImageIconOpacity;
await plugin.saveSettings();
const sliderEl = button.extraSettingsEl.parentElement.querySelector(".slider");
sliderEl.value = DEFAULT_SETTINGS.selectImageIconOpacity;
sliderEl.dispatchEvent(new Event("input"));
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const selectImageIconFlagSetting = new import_obsidian4.Setting(SelectImageSettingsGroup).setName("Select Pixel Banner Flag Color").setDesc("Choose which flag color to use for the banner selector");
const flagRadioContainer = selectImageIconFlagSetting.controlEl.createDiv({
cls: "pixel-banner-flag-radio-container",
attr: {
style: `
max-width: 600px;
`
}
});
flagRadioContainer.style.display = "flex";
flagRadioContainer.style.flexWrap = "wrap";
flagRadioContainer.style.gap = "10px";
Object.keys(flags).forEach((color) => {
const radioContainer = flagRadioContainer.createDiv({
cls: "pixel-banner-flag-radio"
});
const radio = radioContainer.createEl("input", {
type: "radio",
attr: {
id: `flag-${color}`,
name: "pixel-banner-flag",
value: color
}
});
radio.checked = plugin.settings.selectImageIconFlag === color;
radio.addEventListener("change", async () => {
if (radio.checked) {
try {
plugin.settings.selectImageIconFlag = color;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}
});
const label = radioContainer.createEl("label", {
attr: {
for: `flag-${color}`
}
});
const img = label.createEl("img", {
attr: {
src: flags[color],
alt: `${color} flag`
}
});
img.style.width = "20px";
img.style.height = "25px";
img.style.verticalAlign = "middle";
img.style.marginLeft = "5px";
});
selectImageIconFlagSetting.addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.selectImageIconFlag = DEFAULT_SETTINGS.selectImageIconFlag;
await plugin.saveSettings();
const radios = selectImageIconFlagSetting.controlEl.querySelectorAll('input[type="radio"]');
radios.forEach((radio) => {
radio.checked = radio.value === DEFAULT_SETTINGS.selectImageIconFlag;
});
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const defaultSavedBannersFolderSetting = new import_obsidian4.Setting(SelectImageSettingsGroup).setName("Default Saved Banners Folder").setDesc("Default folder where Banners will be saved").addText((text) => {
text.setPlaceholder("pixel-banner-images").setValue(plugin.settings.pinnedImageFolder).onChange(async (value) => {
try {
plugin.settings.pinnedImageFolder = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
text.inputEl.addEventListener("blur", async (event) => {
let value = text.inputEl.value.trim();
if (!value) {
value = "pixel-banner-images";
}
text.setValue(value);
plugin.settings.pinnedImageFolder = value;
try {
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
return text;
}).addButton((button) => button.setButtonText("Browse").onClick(() => {
new FolderSuggestModal(plugin.app, (chosenPath) => {
plugin.settings.pinnedImageFolder = chosenPath;
const textInput = defaultSavedBannersFolderSetting.components[0];
if (textInput) {
textInput.setValue(chosenPath);
}
plugin.saveSettings();
}).open();
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.pinnedImageFolder = DEFAULT_SETTINGS.pinnedImageFolder;
await plugin.saveSettings();
const textComponent = defaultSavedBannersFolderSetting.components[0];
if (textComponent) {
textComponent.setValue(DEFAULT_SETTINGS.pinnedImageFolder);
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const defaultSelectImagePathSetting = new import_obsidian4.Setting(SelectImageSettingsGroup).setName("Default Select Image Path").setDesc("Set a default folder path to filter images when opening the Select Image modal").addText((text) => {
text.setPlaceholder("Example: Images/Banners").setValue(plugin.settings.defaultSelectImagePath).onChange(async (value) => {
try {
plugin.settings.defaultSelectImagePath = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
text.inputEl.style.width = "200px";
return text;
}).addButton((button) => button.setButtonText("Browse").onClick(() => {
new FolderSuggestModal(plugin.app, (chosenPath) => {
plugin.settings.defaultSelectImagePath = chosenPath;
const textInput = defaultSelectImagePathSetting.components[0];
if (textInput) {
textInput.setValue(chosenPath);
}
plugin.saveSettings();
}).open();
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.defaultSelectImagePath = DEFAULT_SETTINGS.defaultSelectImagePath;
await plugin.saveSettings();
const textComponent = defaultSelectImagePathSetting.components[0];
if (textComponent) {
textComponent.setValue(DEFAULT_SETTINGS.defaultSelectImagePath);
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const defaultSelectIconPathSetting = new import_obsidian4.Setting(SelectImageSettingsGroup).setName("Default Select Icon Path").setDesc("Set a default folder path to filter images when selecting a banner icon image").addText((text) => {
text.setPlaceholder("Example: Images/Icons").setValue(plugin.settings.defaultSelectIconPath).onChange(async (value) => {
try {
plugin.settings.defaultSelectIconPath = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
text.inputEl.style.width = "200px";
return text;
}).addButton((button) => button.setButtonText("Browse").onClick(() => {
new FolderSuggestModal(plugin.app, (chosenPath) => {
plugin.settings.defaultSelectIconPath = chosenPath;
const textInput = defaultSelectIconPathSetting.components[0];
if (textInput) {
textInput.setValue(chosenPath);
}
plugin.saveSettings();
}).open();
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.defaultSelectIconPath = DEFAULT_SETTINGS.defaultSelectIconPath;
await plugin.saveSettings();
const textComponent = defaultSelectIconPathSetting.components[0];
if (textComponent) {
textComponent.setValue(DEFAULT_SETTINGS.defaultSelectIconPath);
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const openTargetingModalSetting = new import_obsidian4.Setting(SelectImageSettingsGroup).setName("Open Targeting Modal after selecting a Banner or Icon").setDesc("Automatically open the Targeting Modal after selecting a banner image or icon").addToggle((toggle) => toggle.setValue(plugin.settings.openTargetingModalAfterSelectingBannerOrIcon).onChange(async (value) => {
try {
plugin.settings.openTargetingModalAfterSelectingBannerOrIcon = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.openTargetingModalAfterSelectingBannerOrIcon = DEFAULT_SETTINGS.openTargetingModalAfterSelectingBannerOrIcon;
await plugin.saveSettings();
const toggleComponent = openTargetingModalSetting.components[0];
if (toggleComponent) {
toggleComponent.setValue(DEFAULT_SETTINGS.openTargetingModalAfterSelectingBannerOrIcon);
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Banner Max Width").setDesc("Set the maximum width for banner images (100-2560 pixels)").addSlider((slider) => slider.setLimits(100, 2560, 10).setValue(plugin.settings.bannerMaxWidth).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerMaxWidth = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerMaxWidth = DEFAULT_SETTINGS.bannerMaxWidth;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerMaxWidth;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Image Horizontal Position").setDesc("Set the horizontal position of the image (0-100)").addSlider(
(slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.xPosition).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.xPosition = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})
).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.xPosition = DEFAULT_SETTINGS.xPosition;
await plugin.saveSettings();
plugin.updateAllBanners();
const sliderEl = button.extraSettingsEl.parentElement.querySelector(".slider");
sliderEl.value = DEFAULT_SETTINGS.xPosition;
sliderEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Image Vertical Position").setDesc("Set the vertical position of the image (0-100)").addSlider(
(slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.yPosition).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.yPosition = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})
).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.yPosition = DEFAULT_SETTINGS.yPosition;
await plugin.saveSettings();
plugin.updateAllBanners();
const sliderEl = button.extraSettingsEl.parentElement.querySelector(".slider");
sliderEl.value = DEFAULT_SETTINGS.yPosition;
sliderEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Content Start Position").setDesc("Set the default vertical position where the content starts (in pixels)").addText((text) => text.setPlaceholder("150").setValue(String(plugin.settings.contentStartPosition)).onChange(async (value) => {
try {
const numValue = Number(value);
if (!isNaN(numValue) && numValue >= 0) {
plugin.settings.contentStartPosition = numValue;
await plugin.saveSettings();
plugin.updateAllBanners();
}
} catch (error) {
console.error("Failed to save settings:", error);
}
})).then((setting) => {
const inputEl = setting.controlEl.querySelector("input");
inputEl.type = "number";
inputEl.min = "0";
inputEl.style.width = "60px";
}).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.contentStartPosition = DEFAULT_SETTINGS.contentStartPosition;
await plugin.saveSettings();
plugin.updateAllBanners();
const inputEl = button.extraSettingsEl.parentElement.querySelector("input");
inputEl.value = DEFAULT_SETTINGS.contentStartPosition;
inputEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Image Display").setDesc("Set how the banner image should be displayed").addDropdown((dropdown) => {
dropdown.addOption("auto", "Auto").addOption("cover", "Cover").addOption("contain", "Contain").setValue(plugin.settings.imageDisplay || "cover").onChange(async (value) => {
try {
plugin.settings.imageDisplay = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
return dropdown;
}).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.imageDisplay = DEFAULT_SETTINGS.imageDisplay;
await plugin.saveSettings();
const dropdownEl = button.extraSettingsEl.parentElement.querySelector("select");
dropdownEl.value = DEFAULT_SETTINGS.imageDisplay;
dropdownEl.dispatchEvent(new Event("change"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Image Repeat").setDesc('Enable image repetition when "Contain" is selected').addToggle((toggle) => {
toggle.setValue(plugin.settings.imageRepeat).onChange(async (value) => {
try {
plugin.settings.imageRepeat = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
return toggle;
}).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.imageRepeat = DEFAULT_SETTINGS.imageRepeat;
await plugin.saveSettings();
plugin.updateAllBanners();
const checkboxContainer = button.extraSettingsEl.parentElement.querySelector(".checkbox-container");
const toggleEl = checkboxContainer.querySelector("input");
if (toggleEl) {
toggleEl.checked = DEFAULT_SETTINGS.imageRepeat;
checkboxContainer.classList.toggle("is-enabled", DEFAULT_SETTINGS.imageRepeat);
const event = new Event("change", { bubbles: true });
toggleEl.dispatchEvent(event);
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Banner Height").setDesc("Set the default height of the banner image (0-1280 pixels)").addText((text) => {
text.setPlaceholder("350").setValue(String(plugin.settings.bannerHeight)).onChange(async (value) => {
try {
if (value === "" || !isNaN(Number(value))) {
await plugin.saveSettings();
}
} catch (error) {
console.error("Failed to save settings:", error);
}
});
text.inputEl.addEventListener("blur", async (event) => {
try {
let numValue = Number(event.target.value);
if (isNaN(numValue) || event.target.value === "") {
numValue = 350;
} else {
numValue = Math.max(0, Math.min(1280, numValue));
}
plugin.settings.bannerHeight = numValue;
text.setValue(String(numValue));
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
text.inputEl.type = "number";
text.inputEl.min = "0";
text.inputEl.max = "1280";
text.inputEl.style.width = "50px";
}).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerHeight = DEFAULT_SETTINGS.bannerHeight;
await plugin.saveSettings();
plugin.updateAllBanners();
const inputEl = button.extraSettingsEl.parentElement.querySelector("input");
inputEl.value = DEFAULT_SETTINGS.bannerHeight;
inputEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Banner Fade").setDesc("Set the default fade effect for the banner image (-300 to 100)").addSlider(
(slider) => slider.setLimits(-300, 100, 5).setValue(plugin.settings.fade).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.fade = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})
).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.fade = DEFAULT_SETTINGS.fade;
await plugin.saveSettings();
plugin.updateAllBanners();
const sliderEl = button.extraSettingsEl.parentElement.querySelector(".slider");
sliderEl.value = DEFAULT_SETTINGS.fade;
sliderEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Banner Fade In Animation Duration").setDesc("Set the default fade in animation duration for the banner image (0-1000 milliseconds)").addSlider(
(slider) => slider.setLimits(0, 1e3, 1).setValue(plugin.settings.bannerFadeInAnimationDuration).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerFadeInAnimationDuration = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})
).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerFadeInAnimationDuration = DEFAULT_SETTINGS.bannerFadeInAnimationDuration;
await plugin.saveSettings();
plugin.updateAllBanners();
const sliderEl = button.extraSettingsEl.parentElement.querySelector(".slider");
sliderEl.value = DEFAULT_SETTINGS.bannerFadeInAnimationDuration;
sliderEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Border Radius").setDesc("Set the default border radius of the banner image (0-50 pixels)").addSlider((slider) => slider.setLimits(0, 50, 1).setValue(plugin.settings.borderRadius).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.borderRadius = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.borderRadius = DEFAULT_SETTINGS.borderRadius;
await plugin.saveSettings();
plugin.updateAllBanners();
const sliderEl = button.extraSettingsEl.parentElement.querySelector(".slider");
sliderEl.value = DEFAULT_SETTINGS.borderRadius;
sliderEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Banner Gap").setDesc("Set the gap between the banner and the window edges (0-50 pixels)").addSlider(
(slider) => slider.setLimits(0, 50, 1).setValue(plugin.settings.bannerGap).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerGap = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})
).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerGap = DEFAULT_SETTINGS.bannerGap;
await plugin.saveSettings();
plugin.updateAllBanners();
const sliderEl = button.extraSettingsEl.parentElement.querySelector(".slider");
sliderEl.value = DEFAULT_SETTINGS.bannerGap;
sliderEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Inline Title Color").setDesc("Set the default inline title color for all banners").addColorPicker((color) => color.setValue((() => {
const currentColor = plugin.settings.titleColor;
if (currentColor && currentColor.startsWith("var(--")) {
const temp = document.createElement("div");
temp.style.color = currentColor;
document.body.appendChild(temp);
const computedColor = getComputedStyle(temp).color;
document.body.removeChild(temp);
const rgbMatch = computedColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (rgbMatch) {
const [_, r, g, b] = rgbMatch;
const hexColor = "#" + parseInt(r).toString(16).padStart(2, "0") + parseInt(g).toString(16).padStart(2, "0") + parseInt(b).toString(16).padStart(2, "0");
return hexColor;
}
return "#000000";
}
return currentColor || "#000000";
})()).onChange(async (value) => {
try {
plugin.settings.titleColor = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.titleColor = DEFAULT_SETTINGS.titleColor;
await plugin.saveSettings();
const colorPickerEl = button.extraSettingsEl.parentElement.querySelector('input[type="color"]');
if (colorPickerEl) {
const temp = document.createElement("div");
temp.style.color = DEFAULT_SETTINGS.titleColor;
document.body.appendChild(temp);
const computedColor = getComputedStyle(temp).color;
document.body.removeChild(temp);
const rgbMatch = computedColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (rgbMatch) {
const [_, r, g, b] = rgbMatch;
const hexColor = "#" + parseInt(r).toString(16).padStart(2, "0") + parseInt(g).toString(16).padStart(2, "0") + parseInt(b).toString(16).padStart(2, "0");
colorPickerEl.value = hexColor;
}
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const hideEmbeddedNoteTitlesSetting = new import_obsidian4.Setting(containerEl).setName("Hide Embedded Note Titles").setDesc(`Hide titles of embedded notes if Obsidian's "Show inline title" setting is enabled`).addToggle((toggle) => toggle.setValue(plugin.settings.hideEmbeddedNoteTitles).onChange(async (value) => {
try {
plugin.settings.hideEmbeddedNoteTitles = value;
await plugin.saveSettings();
plugin.updateEmbeddedTitlesVisibility();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.hideEmbeddedNoteTitles = DEFAULT_SETTINGS.hideEmbeddedNoteTitles;
await plugin.saveSettings();
const toggleComponent = hideEmbeddedNoteTitlesSetting.components[0];
if (toggleComponent) {
toggleComponent.setValue(DEFAULT_SETTINGS.hideEmbeddedNoteTitles);
}
plugin.updateEmbeddedTitlesVisibility();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const hideEmbeddedNoteBannersSetting = new import_obsidian4.Setting(containerEl).setName("Hide Embedded Note Banners").setDesc("Hide banners of embedded notes").addToggle((toggle) => toggle.setValue(plugin.settings.hideEmbeddedNoteBanners).onChange(async (value) => {
try {
plugin.settings.hideEmbeddedNoteBanners = value;
await plugin.saveSettings();
plugin.updateEmbeddedBannersVisibility();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.hideEmbeddedNoteBanners = DEFAULT_SETTINGS.hideEmbeddedNoteBanners;
await plugin.saveSettings();
const toggleComponent = hideEmbeddedNoteBannersSetting.components[0];
if (toggleComponent) {
toggleComponent.setValue(DEFAULT_SETTINGS.hideEmbeddedNoteBanners);
}
plugin.updateEmbeddedBannersVisibility();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Show Banner in Popover Previews").setDesc("Show banners in popover note previews").addToggle((toggle) => toggle.setValue(plugin.settings.showBannerInPopoverPreviews).onChange(async (value) => {
try {
plugin.settings.showBannerInPopoverPreviews = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.showBannerInPopoverPreviews = DEFAULT_SETTINGS.showBannerInPopoverPreviews;
await plugin.saveSettings();
plugin.updateAllBanners();
const checkboxContainer = button.extraSettingsEl.parentElement.querySelector(".checkbox-container");
const toggleEl = checkboxContainer.querySelector("input");
if (toggleEl) {
toggleEl.checked = DEFAULT_SETTINGS.showBannerInPopoverPreviews;
checkboxContainer.classList.toggle("is-enabled", DEFAULT_SETTINGS.showBannerInPopoverPreviews);
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const showViewImageIconSetting = new import_obsidian4.Setting(containerEl).setName("Show View Image Icon").setDesc("Show an icon to view the banner image in full screen").addToggle((toggle) => toggle.setValue(plugin.settings.showViewImageIcon).onChange(async (value) => {
try {
plugin.settings.showViewImageIcon = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.showViewImageIcon = DEFAULT_SETTINGS.showViewImageIcon;
await plugin.saveSettings();
const toggleComponent = showViewImageIconSetting.components[0];
if (toggleComponent) {
toggleComponent.setValue(DEFAULT_SETTINGS.showViewImageIcon);
}
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Image Property Format").setDesc("Set the format for the banner property value.").addDropdown((dropdown) => dropdown.addOption("image", "image").addOption("[[image]]", "[[image]]").addOption("![[image]]", "![[image]]").setValue(plugin.settings.imagePropertyFormat).onChange(async (value) => {
try {
plugin.settings.imagePropertyFormat = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.imagePropertyFormat = DEFAULT_SETTINGS.imagePropertyFormat;
await plugin.saveSettings();
const dropdown = button.extraSettingsEl.parentElement.querySelector("select");
dropdown.value = DEFAULT_SETTINGS.imagePropertyFormat;
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const hideSettingsGroup = containerEl.createDiv({ cls: "setting-group" });
const hidePixelBannerFieldsSetting = new import_obsidian4.Setting(hideSettingsGroup).setName("Hide Pixel Banner Fields").setDesc("Hide banner-related frontmatter fields in Reading mode").addToggle((toggle) => toggle.setValue(plugin.settings.hidePixelBannerFields).onChange(async (value) => {
try {
plugin.settings.hidePixelBannerFields = value;
if (!value) {
plugin.settings.hidePropertiesSectionIfOnlyBanner = false;
const dependentToggle = hidePropertiesSection.components[0];
if (dependentToggle) {
dependentToggle.setValue(false);
dependentToggle.setDisabled(true);
}
hidePropertiesSection.settingEl.addClass("is-disabled");
plugin.app.workspace.iterateAllLeaves((leaf) => {
if (leaf.view instanceof import_obsidian4.MarkdownView && leaf.view.contentEl) {
const propertiesContainer = leaf.view.contentEl.querySelector(".metadata-container");
if (propertiesContainer) {
propertiesContainer.classList.remove("pixel-banner-hidden-section");
const hiddenFields = propertiesContainer.querySelectorAll(".pixel-banner-hidden-field");
hiddenFields.forEach((field) => {
field.classList.remove("pixel-banner-hidden-field");
});
}
}
});
} else {
const dependentToggle = hidePropertiesSection.components[0];
if (dependentToggle) {
dependentToggle.setDisabled(false);
}
hidePropertiesSection.settingEl.removeClass("is-disabled");
}
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.hidePixelBannerFields = DEFAULT_SETTINGS.hidePixelBannerFields;
plugin.settings.hidePropertiesSectionIfOnlyBanner = DEFAULT_SETTINGS.hidePropertiesSectionIfOnlyBanner;
await plugin.saveSettings();
const mainToggle = hidePixelBannerFieldsSetting.components[0];
if (mainToggle) {
mainToggle.setValue(DEFAULT_SETTINGS.hidePixelBannerFields);
}
const dependentToggle = hidePropertiesSection.components[0];
if (dependentToggle) {
dependentToggle.setValue(DEFAULT_SETTINGS.hidePropertiesSectionIfOnlyBanner);
dependentToggle.setDisabled(!DEFAULT_SETTINGS.hidePixelBannerFields);
}
hidePropertiesSection.settingEl.toggleClass("is-disabled", !DEFAULT_SETTINGS.hidePixelBannerFields);
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const hidePropertiesSection = new import_obsidian4.Setting(hideSettingsGroup).setName("Hide Properties Section").setDesc("Hide the entire Properties section in Reading mode if it only contains Pixel Banner fields").addToggle((toggle) => toggle.setValue(plugin.settings.hidePropertiesSectionIfOnlyBanner).setDisabled(!plugin.settings.hidePixelBannerFields).onChange(async (value) => {
try {
plugin.settings.hidePropertiesSectionIfOnlyBanner = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.hidePropertiesSectionIfOnlyBanner = DEFAULT_SETTINGS.hidePropertiesSectionIfOnlyBanner;
await plugin.saveSettings();
const toggle = hidePropertiesSection.components[0];
if (toggle) {
toggle.setValue(DEFAULT_SETTINGS.hidePropertiesSectionIfOnlyBanner);
}
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
hidePropertiesSection.settingEl.addClass("setting-dependent");
if (!plugin.settings.hidePixelBannerFields) {
hidePropertiesSection.settingEl.addClass("is-disabled");
}
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Size").setDesc("Set the default size for the banner icon").addSlider((slider) => slider.setLimits(10, 200, 1).setValue(plugin.settings.bannerIconSize).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconSize = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconSize = DEFAULT_SETTINGS.bannerIconSize;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconSize;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Image Size Multiplier").setDesc("Set the default size multiplier for banner icon images (0.1-5)").addSlider((slider) => slider.setLimits(0.1, 5, 0.1).setValue(plugin.settings.bannerIconImageSizeMultiplier).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconImageSizeMultiplier = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconImageSizeMultiplier = DEFAULT_SETTINGS.bannerIconImageSizeMultiplier;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconImageSizeMultiplier;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon X Position").setDesc("Set the default X position for the banner icon (0-100)").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.bannerIconXPosition).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconXPosition = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconXPosition = DEFAULT_SETTINGS.bannerIconXPosition;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconXPosition;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Opacity").setDesc("Set the default opacity for the banner icon (0-100)").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.bannerIconOpacity).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconOpacity = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconOpacity = DEFAULT_SETTINGS.bannerIconOpacity;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconOpacity;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Text Color").setDesc("Set the default text color for the banner icon").addText((text) => text.setPlaceholder("Enter color (e.g., #ffffff or white)").setValue(plugin.settings.bannerIconColor).onChange(async (value) => {
try {
plugin.settings.bannerIconColor = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconColor = DEFAULT_SETTINGS.bannerIconColor;
await plugin.saveSettings();
const textInput = button.extraSettingsEl.parentElement.querySelector('input[type="text"]');
textInput.value = DEFAULT_SETTINGS.bannerIconColor;
const event = new Event("input", { bubbles: true, cancelable: true });
textInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Font Weight").setDesc("Set the default font weight for the banner icon").addDropdown((dropdown) => {
dropdown.addOption("lighter", "Lighter").addOption("normal", "Normal").addOption("bold", "Bold").setValue(plugin.settings.bannerIconFontWeight || "normal").onChange(async (value) => {
try {
plugin.settings.bannerIconFontWeight = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
return dropdown;
}).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconFontWeight = DEFAULT_SETTINGS.bannerIconFontWeight;
await plugin.saveSettings();
const dropdownEl = button.extraSettingsEl.parentElement.querySelector("select");
dropdownEl.value = DEFAULT_SETTINGS.bannerIconFontWeight;
dropdownEl.dispatchEvent(new Event("change"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Background Color").setDesc("Set the default background color for the banner icon").addText((text) => text.setPlaceholder("Enter color (e.g., #ffffff or transparent)").setValue(plugin.settings.bannerIconBackgroundColor).onChange(async (value) => {
try {
plugin.settings.bannerIconBackgroundColor = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconBackgroundColor = DEFAULT_SETTINGS.bannerIconBackgroundColor;
await plugin.saveSettings();
const textInput = button.extraSettingsEl.parentElement.querySelector('input[type="text"]');
textInput.value = DEFAULT_SETTINGS.bannerIconBackgroundColor;
const event = new Event("input", { bubbles: true, cancelable: true });
textInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Padding X").setDesc("Set the default padding X for the banner icon").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.bannerIconPaddingX).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconPaddingX = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconPaddingX = DEFAULT_SETTINGS.bannerIconPaddingX;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconPaddingX;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Padding Y").setDesc("Set the default padding Y for the banner icon").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.bannerIconPaddingY).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconPaddingY = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconPaddingY = DEFAULT_SETTINGS.bannerIconPaddingY;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconPaddingY;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Border Radius").setDesc("Set the default border radius for the banner icon").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.bannerIconBorderRadius).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconBorderRadius = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconBorderRadius = DEFAULT_SETTINGS.bannerIconBorderRadius;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconBorderRadius;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Vertical Offset").setDesc("Set the default vertical offset for the banner icon").addSlider((slider) => slider.setLimits(-100, 100, 1).setValue(plugin.settings.bannerIconVerticalOffset).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconVerticalOffset = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconVerticalOffset = DEFAULT_SETTINGS.bannerIconVerticalOffset;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconVerticalOffset;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const showReleaseNotesSetting = new import_obsidian4.Setting(containerEl).setName("Show Release Notes").setDesc("Show release notes after plugin updates").addToggle((toggle) => toggle.setValue(plugin.settings.showReleaseNotes).onChange(async (value) => {
try {
plugin.settings.showReleaseNotes = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.showReleaseNotes = DEFAULT_SETTINGS.showReleaseNotes;
await plugin.saveSettings();
const toggleComponent = showReleaseNotesSetting.components[0];
if (toggleComponent) {
toggleComponent.setValue(DEFAULT_SETTINGS.showReleaseNotes);
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const promotionalLinks = containerEl.createDiv({
cls: "pixel-banner-promotional-links",
attr: {
style: `
display: flex;
gap: 10px;
justify-content: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid var(--background-modifier-border);
`
}
});
const discordLink = promotionalLinks.createEl("a", {
href: "https://discord.gg/sp8AQQhMJ7",
target: "discord"
});
discordLink.createEl("img", {
attr: {
height: "36",
src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/discord.png?raw=true",
alt: "Discord"
}
});
const kofiLink = promotionalLinks.createEl("a", {
href: "https://ko-fi.com/Z8Z212UMBI",
target: "kofi"
});
kofiLink.createEl("img", {
attr: {
height: "36",
src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/support.png?raw=true",
alt: "Buy Me a Coffee at ko-fi.com"
}
});
}
// src/settings/tabs/settingsTabPixelBannerPlus.js
var import_obsidian5 = require("obsidian");
init_constants();
function createPixelBannerPlusSettings(containerEl, plugin) {
plugin.pixelBannerPlusEnabled = plugin.settings.pixelBannerPlusEnabled !== false;
const calloutElPixelBannerPlus = containerEl.createEl("div", { cls: "tab-callout margin-bottom-0" });
calloutElPixelBannerPlus.createEl("h4", {
text: "\u2728 Pixel Banner Plus \u2728",
attr: {
style: "margin-top: 5px;"
}
});
calloutElPixelBannerPlus.createEl("div", { text: "Pixel Banner Plus enhances your notes with AI-generated, high-quality banners. Using a token-based system, you can instantly create stunning, customized visuals\u2014no design skills needed. Sign up for free to access the banner store, which includes a selection of zero-token banners at no cost. No subscription required\u2014simply purchase tokens whenever you need AI-generated designs. Transform your Obsidian workspace with professional banners, starting for free and only adding tokens as needed." });
const pixelBannerPlusSettingsGroup = containerEl.createDiv({ cls: "setting-group" });
const plusDependentSettings = containerEl.createDiv({ cls: "pixel-banner-plus-dependent-settings" });
function updatePlusDependentSettingsVisibility(isEnabled) {
if (isEnabled) {
plusDependentSettings.style.display = "block";
} else {
plusDependentSettings.style.display = "none";
}
}
new import_obsidian5.Setting(pixelBannerPlusSettingsGroup).setName("Pixel Banner Plus Enabled").setDesc("Enable or disable Pixel Banner Plus features").addToggle((toggle) => toggle.setValue(plugin.settings.pixelBannerPlusEnabled !== false).onChange(async (value) => {
plugin.pixelBannerPlusEnabled = value;
plugin.settings.pixelBannerPlusEnabled = value;
await plugin.saveSettings();
updatePlusDependentSettingsVisibility(value);
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
plugin.settings.pixelBannerPlusEnabled = DEFAULT_SETTINGS.pixelBannerPlusEnabled;
plugin.pixelBannerPlusEnabled = plugin.settings.pixelBannerPlusEnabled;
await plugin.saveSettings();
const toggleComponent = button.extraSettingsEl.parentElement.querySelector(".checkbox-container input");
if (toggleComponent) {
toggleComponent.checked = plugin.settings.pixelBannerPlusEnabled;
toggleComponent.parentElement.classList.toggle("is-enabled", plugin.settings.pixelBannerPlusEnabled);
toggleComponent.dispatchEvent(new Event("change"));
}
updatePlusDependentSettingsVisibility(plugin.settings.pixelBannerPlusEnabled);
})).then((setting) => {
const nameEl = setting.nameEl;
if (nameEl) {
nameEl.innerHTML = '<span class="pixel-banner-twinkle-animation">\u2728</span> Pixel Banner Plus Enabled';
}
});
updatePlusDependentSettingsVisibility(plugin.settings.pixelBannerPlusEnabled !== false);
const accountSettingsGroup = plusDependentSettings.createDiv({ cls: "setting-group margin-top-0" });
new import_obsidian5.Setting(accountSettingsGroup).setName("Pixel Banner Plus Email Address").setDesc("Your email address for Pixel Banner Plus authentication").addText(
(text) => text.setPlaceholder("Enter your email address").setValue(plugin.settings.pixelBannerPlusEmail).onChange(async (value) => {
plugin.settings.pixelBannerPlusEmail = value;
await plugin.saveSettings();
if (!value) {
plugin.pixelBannerPlusEnabled = false;
plugin.settings.pixelBannerPlusEnabled = false;
}
}).inputEl.style = "width: 100%; max-width: 275px; padding: 5px 10px;"
);
new import_obsidian5.Setting(accountSettingsGroup).setName("Pixel Banner Plus API Key").setDesc("Your API key for Pixel Banner Plus authentication").addText(
(text) => text.setPlaceholder("Enter your API key").setValue(plugin.settings.pixelBannerPlusApiKey).onChange(async (value) => {
plugin.settings.pixelBannerPlusApiKey = value;
await plugin.saveSettings();
if (!value) {
plugin.pixelBannerPlusEnabled = false;
plugin.settings.pixelBannerPlusEnabled = false;
}
}).inputEl.style = "width: 100%; max-width: 275px; padding: 5px 10px;"
);
new import_obsidian5.Setting(accountSettingsGroup).setName("Establish Connection").setDesc("Establish a connection to your Pixel Banner Plus account").addButton((button) => {
const buttonEl = button.buttonEl;
buttonEl.style.textTransform = "uppercase";
buttonEl.style.letterSpacing = "1px";
buttonEl.style.fontWeight = "bold";
buttonEl.style.borderRadius = "5px";
buttonEl.style.padding = "5px 10px";
buttonEl.style.fontSize = ".9em";
buttonEl.style.cursor = "pointer";
button.buttonEl.innerHTML = "\u26A1 Refresh / Connect Account";
button.setCta();
button.onClick(async () => {
const email = plugin.settings.pixelBannerPlusEmail;
const apiKey = plugin.settings.pixelBannerPlusApiKey;
if (!email || !apiKey) {
new import_obsidian5.Notice("Please enter both email and API key");
return;
}
button.buttonEl.innerHTML = "Verifying Account...";
button.setDisabled(true);
try {
const data = await plugin.verifyPixelBannerPlusCredentials();
if (data) {
if (data.verified) {
new import_obsidian5.Notice(`\u2705 Pixel Banner Plus connection successful
\u{1FA99} Banner Tokens Remaining: ${data.bannerTokens}`);
plugin.pixelBannerPlusServerOnline = data.serverOnline;
plugin.pixelBannerPlusEnabled = true;
plugin.pixelBannerPlusBannerTokens = data.bannerTokens;
updateAccountStatusSection(accountStatusGroup, plugin);
updateSignupSection(pixelBannerPlusSettingsGroup, plugin);
} else {
if (data.serverOnline) {
new import_obsidian5.Notice("\u274C Invalid credentials");
} else {
new import_obsidian5.Notice("\u274C Cannot connect to Pixel Banner Plus servers. Please try again later.");
}
plugin.pixelBannerPlusServerOnline = data.serverOnline;
plugin.pixelBannerPlusEnabled = false;
plugin.pixelBannerPlusBannerTokens = 0;
updateAccountStatusSection(accountStatusGroup, plugin);
updateSignupSection(pixelBannerPlusSettingsGroup, plugin);
}
} else {
new import_obsidian5.Notice("\u274C Cannot connect to Pixel Banner Plus servers. Please try again later.");
plugin.pixelBannerPlusServerOnline = false;
plugin.pixelBannerPlusEnabled = false;
plugin.pixelBannerPlusBannerTokens = 0;
updateAccountStatusSection(accountStatusGroup, plugin);
updateSignupSection(pixelBannerPlusSettingsGroup, plugin);
}
} catch (error) {
new import_obsidian5.Notice("\u274C Connection failed.");
plugin.pixelBannerPlusEnabled = false;
updateAccountStatusSection(accountStatusGroup, plugin);
updateSignupSection(pixelBannerPlusSettingsGroup, plugin);
}
button.buttonEl.innerHTML = "\u26A1 Refresh / Authorize Account";
button.setDisabled(false);
});
});
new import_obsidian5.Setting(accountSettingsGroup).setName("Show Daily Game").setDesc("Enable the daily game feature in the banner selection modal for a chance to win the daily jackpot's Tokens").addToggle((toggle) => toggle.setValue(plugin.settings.enableDailyGame).onChange(async (value) => {
plugin.settings.enableDailyGame = value;
await plugin.saveSettings();
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
plugin.settings.enableDailyGame = DEFAULT_SETTINGS.enableDailyGame;
await plugin.saveSettings();
const toggleComponent = button.extraSettingsEl.parentElement.querySelector(".checkbox-container input");
if (toggleComponent) {
toggleComponent.checked = DEFAULT_SETTINGS.enableDailyGame;
toggleComponent.parentElement.classList.toggle("is-enabled", DEFAULT_SETTINGS.enableDailyGame);
toggleComponent.dispatchEvent(new Event("change"));
}
})).then((setting) => {
const nameEl = setting.nameEl;
if (nameEl) {
nameEl.innerHTML = '<span class="pixel-banner-twinkle-animation">\u{1F579}\uFE0F</span> Show Daily Game';
}
});
updateSignupSection(plusDependentSettings, plugin);
const accountStatusGroup = plusDependentSettings.createDiv({ cls: "setting-group" });
accountStatusGroup.createEl("h3", { text: "Account Status" });
updateAccountStatusSection(accountStatusGroup, plugin);
}
function updateSignupSection(containerEl, plugin) {
containerEl.querySelectorAll(".setting-item").forEach((el) => {
var _a;
if (((_a = el.querySelector(".setting-item-name")) == null ? void 0 : _a.textContent) === "Signup") {
el.remove();
}
});
if (!plugin.pixelBannerPlusEnabled && plugin.pixelBannerPlusServerOnline) {
new import_obsidian5.Setting(containerEl).setName("Signup").setDesc("Create a FREE Pixel Banner Plus account to get started. You will receive \u{1FA99} 5 FREE banner tokens at signup (no payment info required). This form can also be use to recover your account if you forget your API Key.").addButton((button) => {
const buttonEl = button.buttonEl;
buttonEl.style.textTransform = "uppercase";
buttonEl.style.letterSpacing = "1px";
buttonEl.style.fontWeight = "bold";
buttonEl.style.borderRadius = "5px";
buttonEl.style.padding = "5px 10px";
buttonEl.style.fontSize = ".9em";
buttonEl.style.cursor = "pointer";
buttonEl.classList.add("scale-up-down-animation");
button.buttonEl.innerHTML = "\u{1F6A9} Signup for Free!";
button.setCta();
button.onClick(() => {
const signupUrl = PIXEL_BANNER_PLUS.API_URL + PIXEL_BANNER_PLUS.ENDPOINTS.SIGNUP;
window.open(signupUrl, "_blank");
});
});
}
}
function updateAccountStatusSection(containerEl, plugin) {
containerEl.empty();
containerEl.createEl("h3", {
text: "Account Status",
attr: {
style: `
margin-top: 0;
margin-bottom: 0;
`
}
});
new import_obsidian5.Setting(containerEl).setName("Connection Status").setDesc("Current status of your Pixel Banner Plus account").addText((text) => {
const statusText = plugin.pixelBannerPlusServerOnline ? plugin.pixelBannerPlusEnabled ? "\u2705 Authorized" : "\u274C Not Authorized" : "\u{1F6A8} Servers Offline \u{1F6A8}";
const isLightMode = document.body.classList.contains("theme-light");
const statusColor = isLightMode ? "black" : "white";
const statusBGColor = isLightMode ? "white" : "black";
const statusBorderColor = plugin.pixelBannerPlusEnabled ? "#20bf6b" : "#FF0000";
const span = text.inputEl.parentElement.createSpan({
text: statusText,
attr: {
style: `
color: ${statusColor};
background-color: ${statusBGColor};
border: 1px dotted ${statusBorderColor};
padding: 5px 10px;
border-radius: 0px;
text-transform: uppercase;
font-size: .9em;
letter-spacing: 1.5px;
`
}
});
text.inputEl.style.display = "none";
});
new import_obsidian5.Setting(containerEl).setName("Available Tokens").setDesc("Number of banner tokens available in your account").addText((text) => {
const tokenCount = plugin.pixelBannerPlusBannerTokens !== void 0 ? `\u{1FA99} ${plugin.pixelBannerPlusBannerTokens.toString()}` : "\u2753 Unknown";
const isLightMode = document.body.classList.contains("theme-light");
const tokenColor = isLightMode ? "black" : "white";
const tokenBGColor = isLightMode ? "white" : "black";
const span = text.inputEl.parentElement.createSpan({
text: tokenCount,
attr: {
style: `
font-weight: bold;
color: ${tokenColor};
background-color: ${tokenBGColor};
border: 1px dotted #F3B93B;
padding: 5px 10px;
border-radius: 0px;
text-transform: uppercase;
font-size: .9em;
letter-spacing: 1.5px;
`
}
});
text.inputEl.style.display = "none";
});
if (plugin.pixelBannerPlusServerOnline && plugin.pixelBannerPlusEnabled) {
new import_obsidian5.Setting(containerEl).setName("Buy Tokens").setDesc(createFragment((el) => {
el.createEl("div", { text: "Purchase tokens to download Banners from the Store or Generate them with AI \u2728" });
el.createEl("div").innerHTML = 'This greatly helps support the development of this plugin and keep the AI Servers running \u{1F917}. In addition to buying tokens directly, any donation on our <a href="https://ko-fi.com/jparkerweb" target="_blank" style="text-transform: uppercase;background: white;border: 1px solid green;color: black;font-size: 0.9em;font-weight: bold;padding: 0 3px;cursor: pointer;text-decoration: none;border-radius: 5px;">Ko-fi page</a> will also add tokens to your account \u{1F496}';
})).addButton((button) => {
const buttonEl = button.buttonEl;
buttonEl.style.textTransform = "uppercase";
buttonEl.style.letterSpacing = "1px";
buttonEl.style.borderRadius = "5px";
buttonEl.style.border = "1px solid papayawhip";
buttonEl.style.padding = "5px 10px";
buttonEl.style.fontSize = ".9em";
buttonEl.style.lineHeight = "1";
buttonEl.style.backgroundColor = "darkgreen";
buttonEl.style.color = "papayawhip";
buttonEl.style.cursor = "pointer";
buttonEl.classList.add("scale-up-down-animation");
button.buttonEl.innerHTML = "\u{1F4B5} Buy More Tokens";
button.onClick(() => {
window.open(PIXEL_BANNER_PLUS.SHOP_URL, "_blank");
});
});
}
}
// src/settings/settings.js
var DEFAULT_SETTINGS = {
pixelBannerPlusEmail: "",
pixelBannerPlusApiKey: "",
pixelBannerPlusEnabled: true,
apiProvider: "all",
pexelsApiKey: "",
pixabayApiKey: "",
flickrApiKey: "",
unsplashApiKey: "",
imageSize: "medium",
imageOrientation: "landscape",
numberOfImages: 10,
defaultKeywords: "nature, abstract, landscape, technology, art, cityscape, wildlife, ocean, mountains, forest, space, architecture, food, travel, science, music, sports, fashion, business, education, health, culture, history, weather, transportation, industry, people, animals, plants, patterns",
xPosition: 50,
yPosition: 60,
customBannerField: ["banner"],
customXPositionField: ["banner-x", "x"],
customYPositionField: ["banner-y", "y"],
customContentStartField: ["content-start"],
customImageDisplayField: ["banner-display"],
customImageRepeatField: ["banner-repeat"],
customBannerMaxWidthField: ["banner-max-width"],
customBannerAlignmentField: ["banner-align"],
customBannerHeightField: ["banner-height"],
customFadeField: ["banner-fade"],
customBorderRadiusField: ["banner-radius"],
customTitleColorField: ["banner-inline-title-color"],
customBannerShuffleField: ["banner-shuffle"],
customBannerIconField: ["icon"],
customBannerIconImageField: ["icon-image"],
customBannerIconSizeField: ["icon-size"],
customBannerIconImageSizeMultiplierField: ["icon-image-size-multiplier"],
customBannerIconTextVerticalOffsetField: ["icon-text-vertical-offset"],
customBannerIconRotateField: ["icon-rotate"],
customBannerIconXPositionField: ["icon-x"],
customBannerIconOpacityField: ["icon-opacity"],
customBannerIconColorField: ["icon-color"],
customBannerIconFontWeightField: ["icon-font-weight"],
customBannerIconBackgroundColorField: ["icon-bg-color"],
customBannerIconPaddingXField: ["icon-padding-x"],
customBannerIconPaddingYField: ["icon-padding-y"],
customBannerIconBorderRadiusField: ["icon-border-radius"],
customBannerIconVerticalOffsetField: ["icon-y"],
customBannerIconImageAlignmentField: ["banner-icon-image-alignment"],
customFlagColorField: ["pixel-banner-flag-color"],
folderImages: [],
contentStartPosition: 355,
imageDisplay: "cover",
imageRepeat: false,
bannerHeight: 350,
bannerMaxWidth: 2560,
fade: -40,
bannerFadeInAnimationDuration: 300,
borderRadius: 17,
showPinIcon: false,
pinnedImageFolder: "pixel-banner-images",
pinnedImageFilename: "pixel-banner-image",
imagePropertyFormat: "image",
showReleaseNotes: true,
lastVersion: null,
showRefreshIcon: false,
showViewImageIcon: false,
hidePixelBannerFields: true,
hidePropertiesSectionIfOnlyBanner: true,
titleColor: "var(--inline-title-color)",
enableImageShuffle: false,
hideEmbeddedNoteTitles: false,
hideEmbeddedNoteBanners: false,
showBannerInPopoverPreviews: true,
showSelectImageIcon: true,
selectImageIconOpacity: 40,
selectImageIconFlag: "red",
defaultSelectImagePath: "",
defaultSelectIconPath: "",
useShortPath: true,
bannerGap: 12,
bannerIconSize: 70,
bannerIconImageSizeMultiplier: 1,
bannerIconTextVerticalOffset: 0,
bannerIconXPosition: 75,
bannerIconOpacity: 100,
bannerIconColor: "",
bannerIconFontWeight: "normal",
bannerIconBackgroundColor: "",
bannerIconPaddingX: "10",
bannerIconPaddingY: "10",
bannerIconBorderRadius: "17",
bannerIconVerticalOffset: "0",
bannerIconImageAlignment: "left",
openTargetingModalAfterSelectingBannerOrIcon: true,
enableDailyGame: false
};
var FolderSuggestModal = class extends import_obsidian6.FuzzySuggestModal {
constructor(app, onChoose) {
super(app);
this.onChoose = onChoose;
}
getItems() {
return this.app.vault.getAllLoadedFiles().filter((file) => file.children).map((folder) => folder.path);
}
getItemText(item) {
return item;
}
onChooseItem(item) {
this.onChoose(item);
}
};
var PixelBannerSettingTab = class extends import_obsidian6.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() {
const { containerEl } = this;
containerEl.empty();
containerEl.addClass("pixel-banner-settings");
const mainContent = containerEl.createEl("div", { cls: "pixel-banner-main-content" });
const { tabsEl, tabContentContainer } = this.createTabs(mainContent, [
"\u2699\uFE0F General",
"\u2728 Plus",
"\u{1F5FA}\uFE0F Custom Fields",
"\u{1F5C3}\uFE0F Folder Groups",
"\u{1F310} 3rd Party APIs"
]);
const generalTab = tabContentContainer.createEl("div", { cls: "tab-content", attr: { "data-tab": "\u2699\uFE0F General" } });
createGeneralSettings(generalTab, this.plugin);
const pixelBannerPlusTab = tabContentContainer.createEl("div", { cls: "tab-content", attr: { "data-tab": "\u2728 Plus" } });
createPixelBannerPlusSettings(pixelBannerPlusTab, this.plugin);
const customFieldsTab = tabContentContainer.createEl("div", { cls: "tab-content", attr: { "data-tab": "\u{1F5FA}\uFE0F Custom Fields" } });
createCustomFieldsSettings(customFieldsTab, this.plugin);
const apiTab = tabContentContainer.createEl("div", { cls: "tab-content", attr: { "data-tab": "\u{1F310} 3rd Party APIs" } });
createAPISettings(apiTab, this.plugin);
const foldersTab = tabContentContainer.createEl("div", { cls: "tab-content", attr: { "data-tab": "\u{1F5C3}\uFE0F Folder Groups" } });
createFolderSettings(foldersTab, this.plugin);
tabsEl.firstChild.click();
}
createTabs(containerEl, tabNames) {
const tabsEl = containerEl.createEl("div", { cls: "pixel-banner-settings-tabs" });
const tabContentContainer = containerEl.createEl("div", { cls: "pixel-banner-settings-tab-content-container" });
tabNames.forEach((tabName) => {
const tabEl = tabsEl.createEl("button", { cls: "pixel-banner-settings-tab", text: tabName });
tabEl.addEventListener("click", () => {
tabsEl.querySelectorAll(".pixel-banner-settings-tab").forEach((tab) => tab.removeClass("active"));
tabContentContainer.querySelectorAll(".tab-content").forEach((content) => content.style.display = "none");
tabEl.addClass("active");
tabContentContainer.querySelector(`.tab-content[data-tab="${tabName}"]`).style.display = "flex";
});
});
return { tabsEl, tabContentContainer };
}
};
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// src/core/pixelBannerPlugin.js
init_modals();
init_handlePinIconClick();
// src/core/settings.js
var import_obsidian23 = require("obsidian");
async function loadSettings(plugin) {
plugin.settings = Object.assign({}, DEFAULT_SETTINGS, await plugin.loadData());
if (!Array.isArray(plugin.settings.folderImages)) {
plugin.settings.folderImages = [];
}
if (!Array.isArray(plugin.settings.customBannerField)) {
plugin.settings.customBannerField = DEFAULT_SETTINGS.customBannerField;
}
if (typeof plugin.settings.xPosition !== "number") {
plugin.settings.xPosition = parseInt(plugin.settings.xPosition) || DEFAULT_SETTINGS.xPosition;
}
if (typeof plugin.settings.yPosition !== "number") {
plugin.settings.yPosition = parseInt(plugin.settings.yPosition) || DEFAULT_SETTINGS.yPosition;
}
if (plugin.settings.folderImages) {
plugin.settings.folderImages.forEach((folderImage) => {
folderImage.imageDisplay = folderImage.imageDisplay || "cover";
folderImage.imageRepeat = folderImage.imageRepeat || false;
folderImage.directChildrenOnly = folderImage.directChildrenOnly || false;
});
}
}
async function saveSettings(plugin) {
await plugin.saveData(plugin.settings);
plugin.loadedImages.clear();
plugin.lastKeywords.clear();
plugin.imageCache.clear();
plugin.lastFrontmatter.clear();
plugin.app.workspace.iterateAllLeaves((leaf) => {
if (leaf.view instanceof import_obsidian23.MarkdownView) {
plugin.updateBanner(leaf.view, true);
if (plugin.settings.hidePixelBannerFields) {
plugin.updateFieldVisibility(leaf.view);
}
}
});
}
// src/core/bannerIconHelpers.js
var import_obsidian24 = require("obsidian");
init_modals();
function normalizeColor(color) {
if (!color || color === "transparent" || color === "none") return "transparent";
return color.toLowerCase().replace(/\s+/g, "");
}
function getIconOverlay(plugin) {
if (plugin.iconOverlayPool.length > 0) {
return plugin.iconOverlayPool.pop();
}
const overlay = document.createElement("div");
overlay.className = "banner-icon-overlay";
return overlay;
}
function returnIconOverlay(plugin, overlay) {
if (!overlay) return;
if (plugin.iconOverlayPool.length < plugin.MAX_POOL_SIZE) {
overlay.style.cssText = "";
overlay.className = "banner-icon-overlay";
overlay.textContent = "";
overlay.remove();
plugin.iconOverlayPool.push(overlay);
}
}
function shouldUpdateIconOverlay(plugin, existingOverlay, newIconState, viewType) {
if (!existingOverlay || !newIconState) return true;
if (!existingOverlay._isPersistentBannerIcon || existingOverlay.dataset.viewType !== viewType || existingOverlay.textContent !== newIconState.icon) {
return true;
}
const computedStyle = window.getComputedStyle(existingOverlay);
const styleChecks = {
fontSize: `${newIconState.size}px`,
left: `${newIconState.xPosition}%`,
opacity: `${newIconState.opacity}%`,
color: newIconState.color,
fontWeight: newIconState.fontWeight,
backgroundColor: newIconState.backgroundColor,
borderRadius: `${newIconState.borderRadius}px`,
marginTop: `${newIconState.verticalOffset}px`
};
const currentPadding = computedStyle.padding.split(" ");
const expectedPadding = `${newIconState.paddingY}px ${newIconState.paddingX}px`;
if (currentPadding.join(" ") !== expectedPadding) {
return true;
}
return Object.entries(styleChecks).some(([prop, value]) => {
const current = computedStyle[prop];
return current !== value && // Handle special cases for colors
!(prop.includes("color") && normalizeColor(current) === normalizeColor(value));
});
}
async function handleSetBannerIcon(plugin) {
const activeFile = plugin.app.workspace.getActiveFile();
if (!activeFile) {
new import_obsidian24.Notice("No active file");
return;
}
new EmojiSelectionModal(
plugin.app,
plugin,
async (selectedEmoji) => {
if (!selectedEmoji) {
await plugin.app.fileManager.processFrontMatter(activeFile, (frontmatter) => {
const bannerIconField2 = Array.isArray(plugin.settings.customBannerIconField) && plugin.settings.customBannerIconField.length > 0 ? plugin.settings.customBannerIconField[0] : "banner-icon";
delete frontmatter[bannerIconField2];
});
await new Promise((resolve) => setTimeout(resolve, 300));
const view = plugin.app.workspace.getActiveViewOfType(import_obsidian24.MarkdownView);
if (view) {
await plugin.updateBanner(view, true);
}
new import_obsidian24.Notice("Banner icon removed");
return;
}
const bannerIconField = Array.isArray(plugin.settings.customBannerIconField) && plugin.settings.customBannerIconField.length > 0 ? plugin.settings.customBannerIconField[0] : "banner-icon";
await plugin.app.fileManager.processFrontMatter(activeFile, (frontmatter) => {
plugin.settings.customBannerIconField.forEach((field) => {
if (field in frontmatter) {
delete frontmatter[field];
}
});
frontmatter[bannerIconField] = selectedEmoji;
});
const metadataUpdated = new Promise((resolve) => {
let eventRef = null;
let resolved = false;
const cleanup = () => {
if (eventRef) {
plugin.app.metadataCache.off("changed", eventRef);
eventRef = null;
}
};
const timeoutId = setTimeout(() => {
if (!resolved) {
resolved = true;
cleanup();
resolve();
}
}, 2e3);
eventRef = plugin.app.metadataCache.on("changed", (file) => {
if (file.path === activeFile.path && !resolved) {
resolved = true;
clearTimeout(timeoutId);
cleanup();
setTimeout(resolve, 50);
}
});
});
await metadataUpdated;
const maxRetries = 3;
const retryDelay = 150;
let success = false;
for (let i = 0; i < maxRetries && !success; i++) {
const view = plugin.app.workspace.getActiveViewOfType(import_obsidian24.MarkdownView);
if (view) {
try {
const cache = plugin.app.metadataCache.getFileCache(activeFile);
if (!cache || !cache.frontmatter || cache.frontmatter[bannerIconField] !== selectedEmoji) {
await new Promise((resolve) => setTimeout(resolve, 100));
continue;
}
await plugin.updateBanner(view, true);
success = true;
} catch (error) {
if (i < maxRetries - 1) {
await new Promise((resolve) => setTimeout(resolve, retryDelay));
}
}
}
}
if (!success) {
await new Promise((resolve) => setTimeout(resolve, 500));
const view = plugin.app.workspace.getActiveViewOfType(import_obsidian24.MarkdownView);
if (view) {
await plugin.updateBanner(view, true);
}
}
new import_obsidian24.Notice("Banner icon updated");
if (plugin.settings.openTargetingModalAfterSelectingBannerOrIcon) {
await new Promise((resolve) => setTimeout(resolve, 200));
const { TargetPositionModal: TargetPositionModal2 } = (init_modals(), __toCommonJS(modals_exports));
new TargetPositionModal2(plugin.app, plugin).open();
}
},
true
// Skip the targeting modal in EmojiSelectionModal since we handle it in the callback
).open();
}
function cleanupIconOverlay(plugin, view) {
if (!view || !view.contentEl) return;
const bannerIconOverlays = view.contentEl.querySelectorAll(".banner-icon-overlay");
bannerIconOverlays.forEach((overlay) => {
if (overlay) {
returnIconOverlay(plugin, overlay);
}
});
}
async function handleSetBannerIconImage(plugin) {
const activeFile = plugin.app.workspace.getActiveFile();
if (!activeFile) {
new import_obsidian24.Notice("No active file");
return;
}
new IconImageSelectionModal(
plugin.app,
plugin,
async (selectedImage) => {
if (!selectedImage) {
return;
}
const imagePath = selectedImage.path ? selectedImage.path : selectedImage;
const bannerIconImageField = Array.isArray(plugin.settings.customBannerIconImageField) && plugin.settings.customBannerIconImageField.length > 0 ? plugin.settings.customBannerIconImageField[0].split(",")[0].trim() : "icon-image";
await plugin.app.fileManager.processFrontMatter(activeFile, (frontmatter) => {
if (Array.isArray(plugin.settings.customBannerIconImageField)) {
plugin.settings.customBannerIconImageField.forEach((field) => {
const fieldNames = field.split(",").map((f) => f.trim());
fieldNames.forEach((fieldName) => {
if (fieldName in frontmatter) {
delete frontmatter[fieldName];
}
});
});
}
const format = plugin.settings.imagePropertyFormat;
let iconValue;
if (format === "image") {
iconValue = imagePath;
} else if (format === "[[image]]") {
iconValue = `[[${imagePath}]]`;
} else {
iconValue = `![[${imagePath}]]`;
}
frontmatter[bannerIconImageField] = iconValue;
});
const metadataUpdated = new Promise((resolve) => {
let eventRef = null;
let resolved = false;
const cleanup = () => {
if (eventRef) {
plugin.app.metadataCache.off("changed", eventRef);
eventRef = null;
}
};
const timeoutId = setTimeout(() => {
if (!resolved) {
resolved = true;
cleanup();
resolve();
}
}, 2e3);
eventRef = plugin.app.metadataCache.on("changed", (file) => {
if (file.path === activeFile.path && !resolved) {
resolved = true;
clearTimeout(timeoutId);
cleanup();
setTimeout(resolve, 50);
}
});
});
await metadataUpdated;
const maxRetries = 3;
const retryDelay = 150;
let success = false;
for (let i = 0; i < maxRetries && !success; i++) {
const view = plugin.app.workspace.getActiveViewOfType(import_obsidian24.MarkdownView);
if (view) {
try {
const cache = plugin.app.metadataCache.getFileCache(activeFile);
if (!cache || !cache.frontmatter || cache.frontmatter[bannerIconImageField] !== imagePath) {
await new Promise((resolve) => setTimeout(resolve, 100));
continue;
}
await plugin.updateBanner(view, true);
success = true;
new import_obsidian24.Notice(`Banner icon image updated: ${imagePath.split("/").pop()}`);
} catch (error) {
if (i < maxRetries - 1) {
await new Promise((resolve) => setTimeout(resolve, retryDelay));
}
}
}
}
if (!success) {
new import_obsidian24.Notice("Banner icon image set, but banner update failed. Try reopening the note.");
}
}
).open();
}
// src/core/cacheHelpers.js
var import_obsidian25 = require("obsidian");
function generateCacheKey(filePath, leafId, isShuffled = false) {
const encodedPath = encodeURIComponent(filePath);
return `${encodedPath}-${leafId}${isShuffled ? "-shuffle" : ""}`;
}
function getCacheEntriesForFile(filePath) {
const encodedPath = encodeURIComponent(filePath);
return Array.from(this.bannerStateCache.entries()).filter(([key]) => key.startsWith(`${encodedPath}-`));
}
function cleanupCache(force = false) {
var _a, _b;
const now = Date.now();
for (const [key, entry] of this.bannerStateCache) {
const maxAge = entry.isShuffled ? this.SHUFFLE_CACHE_AGE : this.MAX_CACHE_AGE;
const isOrphaned = entry.leafId && !this.app.workspace.getLeafById(entry.leafId);
if (force || now - entry.timestamp > maxAge || isOrphaned) {
if (entry.leafId) {
const leaf = this.app.workspace.getLeafById(entry.leafId);
if ((leaf == null ? void 0 : leaf.view) instanceof import_obsidian25.MarkdownView) {
const contentEl = leaf.view.contentEl;
["cm-sizer", "markdown-preview-sizer"].forEach((selector) => {
const container = contentEl.querySelector(`.${selector}`);
if (container) {
const iconOverlays = container.querySelectorAll('.banner-icon-overlay[data-persistent="true"]');
iconOverlays.forEach((overlay) => overlay.remove());
}
});
}
}
if (((_a = entry.state) == null ? void 0 : _a.imageUrl) && typeof entry.state.imageUrl === "string" && entry.state.imageUrl.startsWith("blob:")) {
URL.revokeObjectURL(entry.state.imageUrl);
}
this.bannerStateCache.delete(key);
}
}
if (!force && this.bannerStateCache.size > this.MAX_CACHE_ENTRIES) {
const entries = Array.from(this.bannerStateCache.entries()).sort(([, a], [, b]) => a.timestamp - b.timestamp);
while (entries.length > this.MAX_CACHE_ENTRIES) {
const [key, entry] = entries.shift();
if (entry.leafId) {
const leaf = this.app.workspace.getLeafById(entry.leafId);
if ((leaf == null ? void 0 : leaf.view) instanceof import_obsidian25.MarkdownView) {
const contentEl = leaf.view.contentEl;
["cm-sizer", "markdown-preview-sizer"].forEach((selector) => {
const container = contentEl.querySelector(`.${selector}`);
if (container) {
const iconOverlays = container.querySelectorAll('.banner-icon-overlay[data-persistent="true"]');
iconOverlays.forEach((overlay) => overlay.remove());
}
});
}
}
if (((_b = entry.state) == null ? void 0 : _b.imageUrl) && typeof entry.state.imageUrl === "string" && entry.state.imageUrl.startsWith("blob:")) {
URL.revokeObjectURL(entry.state.imageUrl);
}
this.bannerStateCache.delete(key);
}
}
}
function invalidateLeafCache(leafId) {
var _a;
for (const [key, entry] of this.bannerStateCache) {
if (key && typeof key === "string" && key.includes(`-${leafId}`)) {
const leaf = this.app.workspace.getLeafById(leafId);
if ((leaf == null ? void 0 : leaf.view) instanceof import_obsidian25.MarkdownView) {
const contentEl = leaf.view.contentEl;
["cm-sizer", "markdown-preview-sizer"].forEach((selector) => {
const container = contentEl.querySelector(`.${selector}`);
if (container) {
const iconOverlays = container.querySelectorAll('.banner-icon-overlay[data-persistent="true"]');
iconOverlays.forEach((overlay) => overlay.remove());
}
});
}
if (((_a = entry.state) == null ? void 0 : _a.imageUrl) && typeof entry.state.imageUrl === "string" && entry.state.imageUrl.startsWith("blob:")) {
URL.revokeObjectURL(entry.state.imageUrl);
}
this.bannerStateCache.delete(key);
}
}
}
// src/services/apiService.js
var import_obsidian26 = require("obsidian");
var rateLimiter = {
lastRequestTime: 0,
// Use shorter interval in test environment to speed up tests
minInterval: typeof globalThis.vitest !== "undefined" ? 10 : 1e3
// 10ms in tests, 1s in production
};
async function makeRequest(url, options = {}) {
const now = Date.now();
if (now - rateLimiter.lastRequestTime < rateLimiter.minInterval) {
await new Promise((resolve) => setTimeout(resolve, rateLimiter.minInterval));
}
rateLimiter.lastRequestTime = Date.now();
try {
const response = await (0, import_obsidian26.requestUrl)({
url,
headers: options.headers || {},
...options
});
return response;
} catch (error) {
console.error("Request failed:", error);
throw new Error(`Request failed: ${error.message}`);
}
}
async function fetchPexelsImage(plugin, keyword, disableInternalFallback = false) {
const apiKey = plugin.settings.pexelsApiKey;
if (!apiKey) return null;
const useInternalFallback = !disableInternalFallback;
const defaultKeywords = useInternalFallback && plugin.settings.defaultKeywords ? plugin.settings.defaultKeywords.split(",").map((k) => k.trim()) : [];
const fallbackKeyword = defaultKeywords.length > 0 ? defaultKeywords[Math.floor(Math.random() * defaultKeywords.length)] : null;
const keywords = useInternalFallback && fallbackKeyword ? [keyword, fallbackKeyword] : [keyword];
for (const currentKeyword of keywords) {
try {
const response = await makeRequest(
`https://api.pexels.com/v1/search?query=${encodeURIComponent(currentKeyword)}&per_page=${plugin.settings.numberOfImages}`,
{
headers: {
"Authorization": apiKey
}
}
);
if (response.status !== 200) {
console.error("Failed to fetch images:", response.status, response.text);
continue;
}
const data = response.json;
if (data.photos && data.photos.length > 0) {
const randomIndex = Math.floor(Math.random() * data.photos.length);
const imageUrl = data.photos[randomIndex].src[plugin.settings.imageSize];
return imageUrl;
}
} catch (error) {
console.error(`Error fetching image from Pexels for keyword "${currentKeyword}":`, error);
}
}
return null;
}
async function fetchPixabayImage(plugin, keyword) {
var _a;
const apiKey = plugin.settings.pixabayApiKey;
if (!apiKey) return null;
const apiUrl = "https://pixabay.com/api/";
const params = new URLSearchParams({
key: apiKey,
q: encodeURIComponent(keyword),
image_type: "photo",
per_page: plugin.settings.numberOfImages,
safesearch: true
});
try {
const response = await makeRequest(`${apiUrl}?${params}`);
if (response.status !== 200) {
return null;
}
let data;
if (response.json) {
data = response.json;
} else if (response.arrayBuffer) {
data = JSON.parse(new TextDecoder().decode(response.arrayBuffer));
} else {
return null;
}
if (((_a = data.hits) == null ? void 0 : _a.length) > 0) {
const imageUrls = data.hits.map((hit) => hit.largeImageURL);
return imageUrls[Math.floor(Math.random() * imageUrls.length)];
}
return null;
} catch (error) {
console.error(`Error fetching image from Pixabay for keyword "${keyword}":`, error);
return null;
}
}
async function fetchFlickrImage(plugin, keyword) {
var _a, _b;
const apiKey = plugin.settings.flickrApiKey;
if (!apiKey) return null;
try {
const searchUrl = `https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=${apiKey}&text=${encodeURIComponent(keyword)}&per_page=${plugin.settings.numberOfImages}&format=json&nojsoncallback=1&sort=relevance&content_type=1&media=photos&safe_search=1`;
const response = await makeRequest(searchUrl);
if (response.status !== 200) {
return null;
}
let data;
if (response.text && response.text.includes("jsonFlickrApi(")) {
const jsonpMatch = response.text.match(/jsonFlickrApi\((.*)\)/);
if (jsonpMatch && jsonpMatch[1]) {
try {
data = JSON.parse(jsonpMatch[1]);
} catch (e) {
console.error("Error parsing JSONP response:", e);
return null;
}
} else {
console.error("Invalid JSONP format");
return null;
}
} else {
try {
if (response.json) {
data = response.json;
} else if (response.arrayBuffer) {
data = JSON.parse(new TextDecoder().decode(response.arrayBuffer));
} else {
return null;
}
} catch (e) {
console.error("Error parsing JSON response:", e);
return null;
}
}
if (data.stat && data.stat !== "ok" || !((_b = (_a = data.photos) == null ? void 0 : _a.photo) == null ? void 0 : _b.length)) {
return null;
}
const photos = data.photos.photo;
const photo = photos[Math.floor(Math.random() * photos.length)];
let size = "z";
if (plugin.settings && plugin.settings.imageSize) {
switch (plugin.settings.imageSize) {
case "small":
size = "n";
break;
case "medium":
size = "z";
break;
case "large":
size = "b";
break;
default:
size = "z";
break;
}
}
return `https://live.staticflickr.com/${photo.server}/${photo.id}_${photo.secret}_${size}.jpg`;
} catch (error) {
console.error(`Error fetching image from Flickr for keyword "${keyword}":`, error);
return null;
}
}
async function fetchUnsplashImage(plugin, keyword) {
var _a;
const apiKey = plugin.settings.unsplashApiKey;
if (!apiKey) return null;
try {
const apiUrl = "https://api.unsplash.com/search/photos";
const params = new URLSearchParams({
query: keyword,
per_page: plugin.settings.numberOfImages,
orientation: plugin.settings.imageOrientation
});
const response = await makeRequest(`${apiUrl}?${params}`, {
headers: {
"Authorization": `Client-ID ${apiKey}`,
"Accept-Version": "v1"
}
});
if (response.status !== 200) {
return null;
}
let data;
if (response.json) {
data = response.json;
} else if (response.arrayBuffer) {
data = JSON.parse(new TextDecoder().decode(response.arrayBuffer));
} else {
return null;
}
if (!((_a = data.results) == null ? void 0 : _a.length)) {
return null;
}
const photo = data.results[Math.floor(Math.random() * data.results.length)];
return photo.urls[plugin.settings.imageSize === "small" ? "small" : plugin.settings.imageSize === "medium" ? "regular" : "full"];
} catch (error) {
console.error(`Error fetching image from Unsplash for keyword "${keyword}":`, error);
return null;
}
}
// src/services/apiPIxelBannerPlus.js
init_constants();
async function verifyPixelBannerPlusCredentials(plugin) {
if (!plugin.settings.pixelBannerPlusEmail || !plugin.settings.pixelBannerPlusApiKey) {
return { serverOnline: true, verified: false, bannerTokens: 0 };
}
try {
const response = await makeRequest(
`${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.VERIFY}`,
{
method: "GET",
headers: {
"X-User-Email": plugin.settings.pixelBannerPlusEmail,
"X-API-Key": plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": plugin.settings.lastVersion,
"Accept": "application/json"
}
}
);
if (response.status === 200) {
const data = response.json;
return {
serverOnline: true,
verified: data.success,
bannerTokens: data.banner_tokens,
jackpot: data.jackpot,
dailyGameName: data.daily_game,
highScore: data.high_score,
topUser: data.top_user,
timeLeft: data.time_left
};
}
return { serverOnline: true, verified: false, bannerTokens: 0, jackpot: 0, dailyGameName: "", highScore: 0, topUser: "", timeLeft: "0" };
} catch (error) {
console.error("Failed to verify Pixel Banner Plus credentials:", error);
const errorMessage = error.message.toLowerCase();
const errorName = error.name.toLowerCase();
const isConnectionError = errorName === "typeerror" || errorName === "error" || errorMessage.includes("network error") || errorMessage.includes("failed to fetch") || errorMessage.includes("network") || errorMessage.startsWith("err_") || !navigator.onLine;
const isUnauthorized = errorMessage.includes("401") || errorMessage.includes("unauthorized");
console.log(`pixel banner plus error.message: ${error.message}`);
console.log(`pixel banner plus isConnectionError: ${isConnectionError}`);
console.log(`pixel banner plus isUnauthorized: ${isUnauthorized}`);
return {
serverOnline: !isConnectionError || isUnauthorized,
verified: false,
bannerTokens: 0,
jackpot: 0,
dailyGameName: "",
highScore: 0,
topUser: "",
timeLeft: "0"
};
}
}
async function getPixelBannerInfo() {
try {
const response = await makeRequest(
`${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.INFO}`,
{
method: "GET"
}
);
if (response.status === 200) {
const data = response.json;
return {
version: data.version
};
}
} catch (error) {
console.error("Failed to get Pixel Banner Plus info:", error);
}
return { version: "0.0.0" };
}
// src/core/bannerManager.js
var import_obsidian27 = require("obsidian");
init_modals();
init_frontmatterUtils();
init_handlePinIconClick();
init_flags();
// src/utils/debounce.js
function debounceFunction(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function debounceAndSwallow(func, wait) {
let lastCallTime = 0;
return function executedFunction(...args) {
const now = Date.now();
if (now - lastCallTime >= wait) {
lastCallTime = now;
return func(...args);
}
};
}
// src/core/bannerManager.js
var markdownPostProcessorDebounceMap = /* @__PURE__ */ new Map();
var MARKDOWN_PROCESSOR_DEBOUNCE_DELAY = 200;
var updateBannerDebounceMap = /* @__PURE__ */ new Map();
var UPDATE_BANNER_DEBOUNCE_DELAY = 250;
var SUPPORTED_IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "avif"];
var SUPPORTED_MOVIE_EXTENSIONS = ["mp4", "mov"];
var SUPPORTED_EXTENSIONS = [...SUPPORTED_IMAGE_EXTENSIONS, ...SUPPORTED_MOVIE_EXTENSIONS];
var debouncedAddPixelBanner = debounceAndSwallow(addPixelBanner, 350);
async function addPixelBanner(plugin, el, ctx) {
var _a, _b, _c, _d, _e;
const { frontmatter, file, isContentChange, yPosition, xPosition, contentStartPosition, bannerImage, isReadingView, updateMode } = ctx;
const viewContent = el;
const isEmbedded = viewContent.classList.contains("internal-embed") && viewContent.classList.contains("markdown-embed");
const isHoverPopover = viewContent.closest(".hover-popover") !== null;
const hideEmbeddedNoteBanners = getFrontmatterValue(frontmatter, plugin.settings.customHideEmbeddedNoteBannersField) || ((_a = plugin.getFolderSpecificImage(file.path)) == null ? void 0 : _a.hideEmbeddedNoteBanners) || plugin.settings.hideEmbeddedNoteBanners || false;
if (!isEmbedded && !isHoverPopover && viewContent.classList.contains("view-content")) {
const frontmatterContentStartRaw = getFrontmatterValue(frontmatter, plugin.settings.customContentStartField);
const initialContentStart = (_c = (_b = frontmatterContentStartRaw !== null ? Number(frontmatterContentStartRaw) : null) != null ? _b : contentStartPosition) != null ? _c : plugin.settings.contentStartPosition;
applyContentStartPosition(plugin, viewContent, initialContentStart);
const sourceEl = viewContent.querySelector(":scope > .markdown-source-view .cm-sizer");
if (sourceEl) {
sourceEl.style.paddingTop = "var(--pixel-banner-content-start, 355px)";
sourceEl.style.paddingBottom = "0px !important";
}
const previewEl = viewContent.querySelector(":scope > .markdown-reading-view .markdown-preview-sizer");
if (previewEl) {
previewEl.style.paddingTop = "var(--pixel-banner-content-start, 355px)";
previewEl.style.paddingBottom = "0px !important";
}
viewContent.classList.add("pixel-banner");
plugin.setupResizeObserver(viewContent);
plugin.applyBannerWidth(viewContent);
} else if (isEmbedded) {
viewContent.classList.add("pixel-banner");
plugin.applyContentStartPosition(viewContent, contentStartPosition);
const embedContentDiv = viewContent.querySelector(":scope > .markdown-embed-content");
if (embedContentDiv && !hideEmbeddedNoteBanners) {
const previewViewEl = embedContentDiv.querySelector(":scope > .markdown-preview-view");
if (previewViewEl) {
const folderSpecific = plugin.getFolderSpecificImage(file.path);
const maxWidth = getFrontmatterValue(frontmatter, plugin.settings.customBannerMaxWidthField) || (folderSpecific == null ? void 0 : folderSpecific.bannerMaxWidth) || plugin.settings.bannerMaxWidth || "unset";
const maxWidthValue = maxWidth === "unset" ? "unset" : `${maxWidth}px`;
const bannerYPosition = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customYPositionField),
folderSpecific == null ? void 0 : folderSpecific.bannerYPosition,
plugin.settings.bannerYPosition,
0
]);
const bannerXPosition = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customXPositionField),
folderSpecific == null ? void 0 : folderSpecific.bannerXPosition,
plugin.settings.bannerXPosition,
0
]);
const bannerTitleColor = getFrontmatterValue(frontmatter, plugin.settings.customTitleColorField) || (folderSpecific == null ? void 0 : folderSpecific.titleColor) || plugin.settings.titleColor || "var(--inline-title-color)";
previewViewEl.style.setProperty("--pixel-banner-y-position", `${bannerYPosition}%`);
previewViewEl.style.setProperty("--pixel-banner-x-position", `${bannerXPosition}%`);
previewViewEl.style.setProperty("--pixel-banner-max-width", maxWidthValue);
previewViewEl.style.setProperty("--pixel-banner-title-color", bannerTitleColor);
const bannerIconSize = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconSizeField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconSize) || plugin.settings.bannerIconSize || 70;
const bannerIconXPosition = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconXPositionField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconXPosition,
plugin.settings.bannerIconXPosition,
25
]);
const bannerIconOpacity = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconOpacityField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconOpacity,
plugin.settings.bannerIconOpacity,
100
]);
const bannerIconColor = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconColorField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconColor) || plugin.settings.bannerIconColor || "var(--text-normal)";
const bannerIconFontWeight = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconFontWeightField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconFontWeight) || plugin.settings.bannerIconFontWeight || "normal";
const bannerIconBackgroundColor = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconBackgroundColorField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconBackgroundColor) || plugin.settings.bannerIconBackgroundColor || "transparent";
const bannerIconPaddingX = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconPaddingXField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconPaddingX,
plugin.settings.bannerIconPaddingX,
10
]);
const bannerIconPaddingY = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconPaddingYField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconPaddingY,
plugin.settings.bannerIconPaddingY,
10
]);
const bannerIconBorderRadius = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconBorderRadiusField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconBorderRadius,
plugin.settings.bannerIconBorderRadius,
17
]);
const bannerIconVerticalOffset = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconVerticalOffsetField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconVerticalOffset,
plugin.settings.bannerIconVerticalOffset,
0
]);
const bannerIconRotate = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconRotateField),
0
]);
const bannerIconImageSizeMultiplier = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconImageSizeMultiplierField),
plugin.settings.bannerIconImageSizeMultiplier,
1
]);
const bannerIconTextVerticalOffset = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconTextVerticalOffsetField),
plugin.settings.bannerIconTextVerticalOffset,
0
]);
previewViewEl.style.setProperty("--pixel-banner-icon-size", `${bannerIconSize}px`);
previewViewEl.style.setProperty("--pixel-banner-icon-x", `${bannerIconXPosition}%`);
previewViewEl.style.setProperty("--pixel-banner-icon-opacity", `${bannerIconOpacity}%`);
previewViewEl.style.setProperty("--pixel-banner-icon-color", bannerIconColor);
previewViewEl.style.setProperty("--pixel-banner-icon-font-weight", bannerIconFontWeight);
previewViewEl.style.setProperty("--pixel-banner-icon-background-color", bannerIconBackgroundColor);
previewViewEl.style.setProperty("--pixel-banner-icon-padding-x", `${bannerIconPaddingX}px`);
previewViewEl.style.setProperty("--pixel-banner-icon-padding-y", `${bannerIconPaddingY}px`);
previewViewEl.style.setProperty("--pixel-banner-icon-border-radius", `${bannerIconBorderRadius}px`);
previewViewEl.style.setProperty("--pixel-banner-icon-vertical-offset", `${bannerIconVerticalOffset}px`);
previewViewEl.style.setProperty("--pixel-banner-icon-rotate", `${bannerIconRotate}deg`);
previewViewEl.style.setProperty("--pixel-banner-icon-image-size-multiplier", `${bannerIconImageSizeMultiplier}em`);
previewViewEl.style.setProperty("--pixel-banner-icon-text-vertical-offset", `${bannerIconTextVerticalOffset}px`);
const bannerHeight = getFrontmatterValue(frontmatter, plugin.settings.customBannerHeightField) || (folderSpecific == null ? void 0 : folderSpecific.bannerHeight) || plugin.settings.bannerHeight || 150;
const embedMinHeight = `${parseInt(bannerHeight) + parseInt(bannerIconSize) / 2}px`;
previewViewEl.style.setProperty("--pixel-banner-embed-min-height", embedMinHeight);
previewViewEl.style.setProperty("--pixel-banner-height", `${bannerHeight}px`);
const bannerFade = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerFadeField),
folderSpecific == null ? void 0 : folderSpecific.bannerFade,
plugin.settings.fade,
-75
]);
previewViewEl.style.setProperty("--pixel-banner-fade", `${bannerFade}%`);
const bannerRadius = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBorderRadiusField),
folderSpecific == null ? void 0 : folderSpecific.borderRadius,
plugin.settings.borderRadius,
17
]);
previewViewEl.style.setProperty("--pixel-banner-radius", `${bannerRadius}px`);
const bannerAlignment = getFrontmatterValue(frontmatter, plugin.settings.customBannerAlignmentField) || "center";
let alignmentValue = "0 auto";
if (bannerAlignment === "left") {
alignmentValue = "0 auto 0 0";
} else if (bannerAlignment === "right") {
alignmentValue = "0 0 0 auto";
}
previewViewEl.style.setProperty("--pixel-banner-alignment", alignmentValue);
previewViewEl.style.setProperty("--pixel-banner-icon-start", `${bannerHeight - bannerIconSize / 2}px`);
const contentStart = !hideEmbeddedNoteBanners ? `${parseInt(bannerHeight) + parseInt(bannerIconSize) / 2 + parseInt(bannerIconVerticalOffset) + parseInt(bannerIconPaddingY)}px` : "0px";
previewViewEl.style.setProperty("--pixel-banner-content-start", contentStart);
}
}
} else if (isHoverPopover && plugin.settings.showBannerInPopoverPreviews) {
const previewEl = viewContent.querySelector(".markdown-preview-view");
if (previewEl) {
previewEl.classList.add("pixel-banner");
plugin.setupResizeObserver(previewEl);
plugin.applyBannerWidth(previewEl);
}
}
let container;
if (isEmbedded) {
container = viewContent.querySelector(".markdown-preview-sizer");
if (!container) {
container = viewContent.querySelector(".markdown-embed-content");
}
if (!container) {
container = viewContent;
}
} else if (isHoverPopover && plugin.settings.showBannerInPopoverPreviews) {
container = viewContent.querySelector(".markdown-preview-sizer") || viewContent.querySelector(".markdown-preview-view");
if (!container) {
if (viewContent.classList.contains("markdown-preview-view")) {
container = viewContent;
} else if (viewContent.parentElement && viewContent.parentElement.classList.contains("markdown-preview-view")) {
container = viewContent.parentElement;
}
}
} else {
container = isReadingView ? viewContent.querySelector(".markdown-preview-sizer:not(.internal-embed .markdown-preview-sizer)") || viewContent.querySelector(".markdown-preview-view") : viewContent.querySelector(".cm-sizer") || viewContent.querySelector(".markdown-source-view");
if (!container && viewContent.classList.contains("markdown-preview-view")) {
container = viewContent;
}
}
if (!container) {
return;
}
let bannerDiv = container.querySelector(":scope > .pixel-banner-image");
if (!bannerDiv) {
bannerDiv = createDiv({ cls: "pixel-banner-image" });
container.insertBefore(bannerDiv, container.firstChild);
bannerDiv._isPersistentBanner = true;
}
const oldViewIcons = container.querySelectorAll(".view-image-icon");
const oldPinIcons = container.querySelectorAll(".pin-icon");
const oldRefreshIcons = container.querySelectorAll(".refresh-icon");
const oldSelectIcons = container.querySelectorAll(".select-image-icon");
[...oldViewIcons, ...oldPinIcons, ...oldRefreshIcons, ...oldSelectIcons].forEach((el2) => el2.remove());
if (isEmbedded || isHoverPopover) {
plugin.updateEmbeddedBannersVisibility();
} else {
let leftOffset = plugin.settings.bannerGap + 15;
if (plugin.settings.showSelectImageIcon) {
const opacity = plugin.settings.selectImageIconOpacity / 100;
const selectImageIcon = createDiv({
cls: "select-image-icon",
attr: {
style: `
position: absolute;
top: 10px;
left: ${leftOffset}px;
font-size: 1.8em;
cursor: pointer;
opacity: ${opacity};
`
}
});
const flagColor = getFrontmatterValue(frontmatter, plugin.settings.customFlagColorField) || plugin.settings.selectImageIconFlag;
selectImageIcon.innerHTML = `<img src="${flags[flagColor] || flags["red"]}" alt="Select Banner" style="width: 25px; height: 30px;">`;
selectImageIcon._isPersistentSelectImage = true;
selectImageIcon.onclick = () => plugin.handleBannerIconClick();
container.appendChild(selectImageIcon);
leftOffset += 35;
}
if (bannerImage && plugin.settings.showViewImageIcon && !isEmbedded) {
const viewImageIcon = createDiv({
cls: "view-image-icon",
attr: {
style: `
display: none;
position: absolute;
top: 10px;
left: ${leftOffset}px;
font-size: 1.5em;
cursor: pointer;
`
}
});
viewImageIcon.innerHTML = "\u{1F5BC}\uFE0F";
viewImageIcon._isPersistentViewImage = true;
viewImageIcon.innerHTML = "\u{1F5BC}\uFE0F";
viewImageIcon._updateVisibility = (newUrl, originalPath) => {
viewImageIcon.style.display = newUrl ? "block" : "none";
if (newUrl) {
viewImageIcon.onclick = () => {
new ImageViewModal(plugin.app, newUrl, originalPath).open();
};
}
};
container.appendChild(viewImageIcon);
leftOffset += 35;
}
const activeFile = plugin.app.workspace.getActiveFile();
const hasBanner = activeFile && plugin.hasBannerFrontmatter(activeFile);
}
if (!container._hasOverriddenSetChildrenInPlace) {
const originalSetChildrenInPlace = container.setChildrenInPlace;
container.setChildrenInPlace = function(children) {
const bannerElement = this.querySelector(":scope > .pixel-banner-image");
const viewImageElement = this.querySelector(":scope > .view-image-icon");
const pinElement = this.querySelector(":scope > .pin-icon");
const refreshElement = this.querySelector(":scope > .refresh-icon");
const selectImageElement = this.querySelector(":scope > .select-image-icon");
const bannerIconOverlay = this.querySelector(":scope > .banner-icon-overlay");
children = Array.from(children).filter(
(child) => {
var _a2, _b2, _c2, _d2, _e2, _f;
return !((_a2 = child.classList) == null ? void 0 : _a2.contains("pixel-banner-image")) && !((_b2 = child.classList) == null ? void 0 : _b2.contains("view-image-icon")) && !((_c2 = child.classList) == null ? void 0 : _c2.contains("pin-icon")) && !((_d2 = child.classList) == null ? void 0 : _d2.contains("refresh-icon")) && !((_e2 = child.classList) == null ? void 0 : _e2.contains("select-image-icon")) && !((_f = child.classList) == null ? void 0 : _f.contains("banner-icon-overlay"));
}
);
if (bannerElement == null ? void 0 : bannerElement._isPersistentBanner) {
children.unshift(bannerElement);
}
if (bannerIconOverlay == null ? void 0 : bannerIconOverlay._isPersistentBannerIcon) {
children.push(bannerIconOverlay);
}
if (selectImageElement == null ? void 0 : selectImageElement._isPersistentSelectImage) {
children.push(selectImageElement);
}
if (viewImageElement == null ? void 0 : viewImageElement._isPersistentViewImage) {
children.push(viewImageElement);
}
if (pinElement == null ? void 0 : pinElement._isPersistentPin) {
children.push(pinElement);
}
if (refreshElement == null ? void 0 : refreshElement._isPersistentRefresh) {
children.push(refreshElement);
}
return originalSetChildrenInPlace.call(this, children);
};
container._hasOverriddenSetChildrenInPlace = true;
}
if (bannerImage) {
let imageUrl = plugin.loadedImages.get(file.path);
const lastInput = plugin.lastKeywords.get(file.path);
const inputType = plugin.getInputType(bannerImage, file.path);
const folderSpecific = plugin.getFolderSpecificImage(file.path);
const hasShufflePath = getFrontmatterValue(frontmatter, plugin.settings.customBannerShuffleField);
const isShuffled = hasShufflePath || (folderSpecific == null ? void 0 : folderSpecific.enableImageShuffle);
const effectiveUpdateMode = updateMode || plugin.UPDATE_MODE.FULL_UPDATE;
let shouldFetchNewImage = false;
if (effectiveUpdateMode === plugin.UPDATE_MODE.ENSURE_VISIBILITY) {
shouldFetchNewImage = !imageUrl;
} else {
shouldFetchNewImage = effectiveUpdateMode === plugin.UPDATE_MODE.FULL_UPDATE || !imageUrl || isShuffled || isContentChange && bannerImage !== lastInput;
}
if (shouldFetchNewImage) {
imageUrl = await plugin.getImageUrl(inputType, bannerImage, file.path);
if (imageUrl) {
plugin.loadedImages.set(file.path, imageUrl);
plugin.lastKeywords.set(file.path, bannerImage);
}
}
if (imageUrl) {
const imageDisplay = getFrontmatterValue(frontmatter, plugin.settings.customImageDisplayField) || (folderSpecific == null ? void 0 : folderSpecific.imageDisplay) || plugin.settings.imageDisplay;
const imageRepeat = getFrontmatterValue(frontmatter, plugin.settings.customImageRepeatField) || (folderSpecific == null ? void 0 : folderSpecific.imageRepeat) || plugin.settings.imageRepeat;
let isVideoFile = false;
let fileUrl = "";
if (typeof imageUrl === "object" && imageUrl !== null) {
isVideoFile = imageUrl.isVideo === true;
fileUrl = imageUrl.url;
} else {
fileUrl = imageUrl;
if (typeof fileUrl === "string" && fileUrl.includes("/media-attachment/") && (fileUrl.toLowerCase().endsWith(".mp4") || fileUrl.toLowerCase().endsWith(".mov"))) {
isVideoFile = true;
}
}
const isSvg = fileUrl.includes("image/svg+xml") || file.path && file.path.toLowerCase().endsWith(".svg");
if (fileUrl.startsWith("blob:")) {
try {
const response = await fetch(fileUrl);
if (!response.ok) {
throw new Error("Blob URL validation failed");
}
} catch (error) {
console.log("Blob URL invalid, refreshing file:", error);
plugin.loadedImages.delete(file.path);
URL.revokeObjectURL(fileUrl);
const inputType2 = plugin.getInputType(bannerImage, file.path);
const freshResult = await plugin.getImageUrl(inputType2, bannerImage, file.path);
if (freshResult) {
if (typeof freshResult === "object" && freshResult !== null) {
isVideoFile = freshResult.isVideo === true;
fileUrl = freshResult.url;
} else {
fileUrl = freshResult;
}
plugin.loadedImages.set(file.path, fileUrl);
}
}
}
if (isVideoFile) {
bannerDiv.style.backgroundImage = "";
const existingVideo = bannerDiv.querySelector("video");
if (existingVideo) {
existingVideo.remove();
}
const videoEl = document.createElement("video");
videoEl.className = "pixel-banner-video";
videoEl.src = fileUrl;
videoEl.autoplay = true;
videoEl.loop = true;
videoEl.muted = true;
videoEl.playsInline = true;
videoEl.style.width = "100%";
videoEl.style.height = "100%";
videoEl.style.objectFit = imageDisplay || "cover";
videoEl.style.position = "absolute";
videoEl.style.top = "0";
videoEl.style.left = "0";
bannerDiv.appendChild(videoEl);
} else {
const existingVideo = bannerDiv.querySelector("video");
if (existingVideo) {
existingVideo.remove();
}
bannerDiv.style.backgroundImage = `url('${fileUrl}')`;
if (isSvg) {
bannerDiv.style.backgroundSize = imageDisplay === "contain" ? "contain" : "100% 100%";
} else {
bannerDiv.style.backgroundSize = imageDisplay || "cover";
}
bannerDiv.style.backgroundRepeat = imageRepeat ? "repeat" : "no-repeat";
}
bannerDiv.style.display = "block";
if (isEmbedded) {
bannerDiv.style.setProperty("--pixel-banner-image", `url('${imageUrl}')`);
}
const viewImageIcon = container.querySelector(":scope > .view-image-icon");
if (viewImageIcon && viewImageIcon._updateVisibility) {
const bannerValue = getFrontmatterValue(frontmatter, plugin.settings.customBannerField);
let displayUrl = bannerValue || file.path;
if (inputType === "keyword") {
if (imageUrl && typeof imageUrl === "object" && imageUrl.url) {
displayUrl = imageUrl.url;
} else if (imageUrl && typeof imageUrl === "string") {
displayUrl = imageUrl;
}
}
viewImageIcon._updateVisibility(imageUrl, displayUrl);
}
plugin.applyBannerSettings(bannerDiv, ctx, isEmbedded);
const hideEmbeddedNoteBanners2 = getFrontmatterValue(frontmatter, plugin.settings.customHideEmbeddedNoteBannersField) || (folderSpecific == null ? void 0 : folderSpecific.hideEmbeddedNoteBanners) || plugin.settings.hideEmbeddedNoteBanners || false;
let effectiveContentStart = 0;
if (!hideEmbeddedNoteBanners2 || !isEmbedded) {
const frontmatterContentStart = getFrontmatterValue(frontmatter, plugin.settings.customContentStartField);
const parsedFrontmatterStart = frontmatterContentStart ? Number(frontmatterContentStart) : null;
effectiveContentStart = (_e = (_d = parsedFrontmatterStart != null ? parsedFrontmatterStart : contentStartPosition) != null ? _d : folderSpecific == null ? void 0 : folderSpecific.contentStartPosition) != null ? _e : plugin.settings.contentStartPosition;
}
plugin.applyContentStartPosition(viewContent, effectiveContentStart);
plugin.applyBannerWidth(viewContent);
const canPin = (inputType === "keyword" || inputType === "url") && plugin.settings.showPinIcon && !isEmbedded && !isHoverPopover;
if (canPin) {
let leftOffset = plugin.settings.bannerGap + 5;
const iconEls = container.querySelectorAll(".select-image-icon, .view-image-icon");
if (iconEls == null ? void 0 : iconEls.length) {
leftOffset = 10 + 35 * iconEls.length + plugin.settings.bannerGap;
}
const pinIcon = createDiv({ cls: "pin-icon" });
pinIcon.style.position = "absolute";
pinIcon.style.top = "10px";
pinIcon.style.left = `${leftOffset}px`;
pinIcon.style.fontSize = "1.5em";
pinIcon.style.cursor = "pointer";
pinIcon.innerHTML = "\u{1F4CC}";
pinIcon._isPersistentPin = true;
pinIcon.onclick = async () => {
try {
const currentImage = plugin.loadedImages.get(file.path);
if (!currentImage) {
new import_obsidian27.Notice("Could not find the current image URL to pin.");
console.error("Error pinning image: currentImage is null or undefined for file.path:", file.path);
return;
}
const imageUrlToPin = typeof currentImage === "object" && currentImage.url ? currentImage.url : currentImage;
await handlePinIconClick(imageUrlToPin, plugin);
} catch (error) {
console.error("Error pinning image:", error);
new import_obsidian27.Notice("Failed to pin the image.");
}
};
container.appendChild(pinIcon);
leftOffset += 35;
if (inputType === "keyword" && plugin.settings.showRefreshIcon) {
const refreshIcon = createDiv({
cls: "refresh-icon",
attr: {
style: `
position: absolute;
top: 10px;
left: ${leftOffset}px;
font-size: 1.5em;
cursor: pointer;
`
}
});
refreshIcon.innerHTML = "\u{1F504}";
refreshIcon._isPersistentRefresh = true;
refreshIcon.onclick = async () => {
try {
plugin.loadedImages.delete(file.path);
plugin.lastKeywords.delete(file.path);
const originalBannerValue = getFrontmatterValue(frontmatter, plugin.settings.customBannerField);
const result = await plugin.getImageUrl(inputType, originalBannerValue || bannerImage, file.path);
if (result) {
let isVideoFile2 = false;
let fileUrl2 = "";
if (typeof result === "object" && result !== null) {
isVideoFile2 = result.isVideo === true;
fileUrl2 = result.url;
} else {
fileUrl2 = result;
}
plugin.loadedImages.set(file.path, fileUrl2);
plugin.lastKeywords.set(file.path, bannerImage);
if (isVideoFile2) {
bannerDiv.style.backgroundImage = "";
const existingVideo = bannerDiv.querySelector("video");
if (existingVideo) {
existingVideo.remove();
}
const videoEl = document.createElement("video");
videoEl.className = "pixel-banner-video";
videoEl.src = fileUrl2;
videoEl.autoplay = true;
videoEl.loop = true;
videoEl.muted = true;
videoEl.playsInline = true;
videoEl.style.width = "100%";
videoEl.style.height = "100%";
videoEl.style.objectFit = "cover";
videoEl.style.position = "absolute";
videoEl.style.top = "0";
videoEl.style.left = "0";
bannerDiv.appendChild(videoEl);
} else {
bannerDiv.style.backgroundImage = `url('${fileUrl2}')`;
const existingVideo = bannerDiv.querySelector("video");
if (existingVideo) {
existingVideo.remove();
}
}
const viewImageIcon2 = container.querySelector(":scope > .view-image-icon");
if (viewImageIcon2 && viewImageIcon2._updateVisibility) {
const bannerValue = getFrontmatterValue(frontmatter, plugin.settings.customBannerField);
let displayUrl = bannerValue || file.path;
const refreshInputType = plugin.getInputType(bannerValue, file.path);
if (refreshInputType === "keyword") {
if (fileUrl2 && typeof fileUrl2 === "object" && fileUrl2.url) {
displayUrl = fileUrl2.url;
} else if (fileUrl2 && typeof fileUrl2 === "string") {
displayUrl = fileUrl2;
}
}
viewImageIcon2._updateVisibility(fileUrl2, displayUrl);
}
new import_obsidian27.Notice("\u{1F504} Refreshed banner");
}
} catch (error) {
console.error("Error refreshing banner:", error);
new import_obsidian27.Notice("Failed to refresh the banner.");
}
};
container.appendChild(refreshIcon);
}
}
} else {
bannerDiv.style.display = "none";
plugin.loadedImages.delete(file.path);
plugin.lastKeywords.delete(file.path);
if (!isEmbedded) {
viewContent.classList.remove("pixel-banner");
}
}
}
}
var debouncedUpdateBanner = debounceFunction(updateBanner, 50);
async function updateBanner(plugin, view, isContentChange, updateMode = plugin.UPDATE_MODE.FULL_UPDATE) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
if (!view || !view.file) {
return;
}
const filePath = view.file.path;
const currentTime = Date.now();
const lastUpdateTime = updateBannerDebounceMap.get(filePath);
if (lastUpdateTime && currentTime - lastUpdateTime < UPDATE_BANNER_DEBOUNCE_DELAY) {
return;
}
updateBannerDebounceMap.set(filePath, currentTime);
if (!isContentChange) {
await new Promise((resolve) => setTimeout(resolve, 50));
}
const frontmatter = (_a = plugin.app.metadataCache.getFileCache(view.file)) == null ? void 0 : _a.frontmatter;
const contentEl = view.contentEl;
const isEmbedded = contentEl.classList.contains("internal-embed") && contentEl.classList.contains("markdown-embed");
const isHoverPopover = contentEl.closest(".hover-popover") !== null;
const viewContent = contentEl;
const nonPersistentOverlays = viewContent.querySelectorAll('.banner-icon-overlay:not([data-persistent="true"])');
nonPersistentOverlays.forEach((overlay) => overlay.remove());
["markdown-preview-sizer", "cm-sizer"].forEach((container) => {
const containerEl = viewContent.querySelector(`.${container}`);
if (containerEl) {
const bannerImage2 = containerEl.querySelector(":scope > .pixel-banner-image");
if (bannerImage2) {
const allOverlays = containerEl.querySelectorAll(':scope > .banner-icon-overlay[data-persistent="true"]');
allOverlays.forEach((overlay) => {
if (overlay.previousElementSibling !== bannerImage2) {
overlay.remove();
}
});
}
}
});
const existingBanner = contentEl.querySelector(".pixel-banner-image");
const folderSpecific = plugin.getFolderSpecificImage(view.file.path);
let bannerImage = null;
const shufflePath = getFrontmatterValue(frontmatter, plugin.settings.customBannerShuffleField) || (folderSpecific == null ? void 0 : folderSpecific.enableImageShuffle);
if (shufflePath && updateMode !== plugin.UPDATE_MODE.ENSURE_VISIBILITY) {
const randomImagePath = await plugin.getRandomImageFromFolder(shufflePath);
if (randomImagePath) {
bannerImage = randomImagePath;
}
} else if (shufflePath && updateMode === plugin.UPDATE_MODE.ENSURE_VISIBILITY) {
const cacheKey = plugin.generateCacheKey(view.file.path, plugin.app.workspace.activeLeaf.id, true);
const cachedState = plugin.bannerStateCache.get(cacheKey);
if ((_b = cachedState == null ? void 0 : cachedState.state) == null ? void 0 : _b.imageUrl) {
bannerImage = cachedState.state.imageUrl;
} else {
const randomImagePath = await plugin.getRandomImageFromFolder(shufflePath);
if (randomImagePath) {
bannerImage = randomImagePath;
}
}
}
if (!bannerImage) {
bannerImage = getFrontmatterValue(frontmatter, plugin.settings.customBannerField) || (folderSpecific == null ? void 0 : folderSpecific.image);
}
if (!isEmbedded && !bannerImage) {
contentEl.classList.remove("pixel-banner");
const previewSizer = contentEl.querySelector(".markdown-preview-sizer");
if (previewSizer) {
previewSizer.style.removeProperty("padding-top");
}
const sourceSizer = contentEl.querySelector(".cm-sizer");
if (sourceSizer) {
sourceSizer.style.removeProperty("padding-top");
}
if (existingBanner) {
existingBanner.style.backgroundImage = "";
existingBanner.style.display = "none";
}
} else if (isEmbedded && !bannerImage) {
const embedRoot = viewContent.closest(".internal-embed.markdown-embed");
if (embedRoot) {
embedRoot.style.setProperty("--pixel-banner-embed-min-height", "1%");
embedRoot.style.setProperty("--pixel-banner-content-start", "0");
}
}
if (isContentChange) {
plugin.loadedImages.delete(view.file.path);
plugin.lastKeywords.delete(view.file.path);
}
let yPosition = (_c = folderSpecific == null ? void 0 : folderSpecific.yPosition) != null ? _c : plugin.settings.yPosition;
let xPosition = (_d = folderSpecific == null ? void 0 : folderSpecific.xPosition) != null ? _d : plugin.settings.xPosition;
let contentStartPosition = (_e = folderSpecific == null ? void 0 : folderSpecific.contentStartPosition) != null ? _e : plugin.settings.contentStartPosition;
if (bannerImage) {
if (Array.isArray(bannerImage)) {
bannerImage = bannerImage.flat()[0];
}
if (typeof bannerImage === "string" && !bannerImage.startsWith("[[") && !bannerImage.startsWith("![[") && !bannerImage.startsWith("http")) {
if (bannerImage.includes(",")) {
const parts = bannerImage.split(",").map((p) => p.trim());
const isFile = (str) => SUPPORTED_EXTENSIONS.some((ext) => str.toLowerCase().endsWith(`.${ext}`));
const isKeyword = (str) => !str.includes(".");
if (parts.length > 1 && (parts.every(isFile) || parts.every(isKeyword))) {
const bannerValues = parts.filter((v) => v.length > 0);
if (bannerValues.length > 0) {
bannerImage = bannerValues[Math.floor(Math.random() * bannerValues.length)];
} else {
bannerImage = null;
}
}
}
}
if (bannerImage && typeof bannerImage === "string" && !bannerImage.startsWith("[[") && !bannerImage.startsWith("![[") && !bannerImage.startsWith("http")) {
const file = plugin.app.vault.getAbstractFileByPath(bannerImage);
if (file && "extension" in file) {
if (file.extension.match(/^(jpg|jpeg|png|gif|bmp|svg)$/i)) {
bannerImage = `![[${bannerImage}]]`;
}
}
}
}
let imageDisplay = getFrontmatterValue(frontmatter, plugin.settings.customImageDisplayField) || (folderSpecific == null ? void 0 : folderSpecific.imageDisplay) || plugin.settings.imageDisplay;
let imageRepeat = (_g = (_f = getFrontmatterValue(frontmatter, plugin.settings.customImageRepeatField)) != null ? _f : folderSpecific == null ? void 0 : folderSpecific.imageRepeat) != null ? _g : plugin.settings.imageRepeat;
let bannerHeight = (_i = (_h = getFrontmatterValue(frontmatter, plugin.settings.customBannerHeightField)) != null ? _h : folderSpecific == null ? void 0 : folderSpecific.bannerHeight) != null ? _i : plugin.settings.bannerHeight;
let fade = (_k = (_j = getFrontmatterValue(frontmatter, plugin.settings.customFadeField)) != null ? _j : folderSpecific == null ? void 0 : folderSpecific.fade) != null ? _k : plugin.settings.fade;
let borderRadius = (_m = (_l = getFrontmatterValue(frontmatter, plugin.settings.customBorderRadiusField)) != null ? _l : folderSpecific == null ? void 0 : folderSpecific.borderRadius) != null ? _m : plugin.settings.borderRadius;
let maxWidth = getFrontmatterValue(frontmatter, plugin.settings.customBannerMaxWidthField) || (folderSpecific == null ? void 0 : folderSpecific.bannerMaxWidth) || "unset";
if (bannerImage) {
await addPixelBanner(plugin, contentEl, {
frontmatter,
file: view.file,
isContentChange,
yPosition,
xPosition,
contentStartPosition,
bannerImage,
imageDisplay,
imageRepeat,
bannerHeight,
fade,
borderRadius,
maxWidth,
isReadingView: view.getMode && view.getMode() === "preview",
updateMode
});
plugin.lastYPositions.set(view.file.path, yPosition);
} else if (existingBanner) {
existingBanner.style.display = "none";
}
if (!isEmbedded) {
const embeddedNotes = contentEl.querySelectorAll(".internal-embed.markdown-embed");
for (const embed of embeddedNotes) {
const embedSrc = embed.getAttribute("src");
if (!embedSrc) continue;
const embedFile = plugin.app.metadataCache.getFirstLinkpathDest(embedSrc, view.file.path);
if (embedFile) {
const embedView = {
file: embedFile,
contentEl: embed,
getMode: () => "preview"
};
await updateBanner(plugin, embedView, false);
}
}
}
if (!bannerImage) {
const viewContent2 = view.contentEl;
const isReadingView = view.getMode && view.getMode() === "preview";
let container = isReadingView ? viewContent2.querySelector(".markdown-preview-sizer:not(.internal-embed .markdown-preview-sizer)") || viewContent2.querySelector(".markdown-preview-view") : viewContent2.querySelector(".cm-sizer") || viewContent2.querySelector(".markdown-source-view");
if (!container && viewContent2.classList.contains("markdown-preview-view")) {
container = viewContent2;
}
if (container) {
const oldViewIcons = container.querySelectorAll(".view-image-icon");
const oldPinIcons = container.querySelectorAll(".pin-icon");
const oldRefreshIcons = container.querySelectorAll(".refresh-icon");
const oldSelectIcons = container.querySelectorAll(".select-image-icon");
[...oldViewIcons, ...oldPinIcons, ...oldRefreshIcons, ...oldSelectIcons].forEach((el) => el.remove());
}
if (!isEmbedded && container && plugin.settings.showSelectImageIcon) {
const opacity = plugin.settings.selectImageIconOpacity / 100;
const existingSelectIcon = container.querySelector(".select-image-icon");
if (!existingSelectIcon) {
const selectImageIcon = createDiv({
cls: "select-image-icon",
attr: {
style: `
position: absolute;
top: 10px;
left: ${plugin.settings.bannerGap + 5}px;
font-size: 1.8em;
cursor: pointer;
opacity: ${opacity};
`
}
});
const flagColor = getFrontmatterValue(frontmatter, plugin.settings.customFlagColorField) || plugin.settings.selectImageIconFlag;
selectImageIcon.innerHTML = `<img src="${flags[flagColor] || flags["red"]}" alt="Select Banner" style="width: 25px; height: 30px;">`;
selectImageIcon._isPersistentSelectImage = true;
selectImageIcon.onclick = () => plugin.handleBannerIconClick();
container.insertBefore(selectImageIcon, container.firstChild);
}
}
if (container) {
const existingViewImageIcon = container.querySelector(".view-image-icon");
if (existingViewImageIcon) {
existingViewImageIcon.remove();
}
}
}
if (plugin.settings.hidePixelBannerFields && view.getMode() === "preview") {
plugin.updateFieldVisibility(view);
}
const bannerIcon = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconField);
let bannerIconImage = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconImageField);
if (Array.isArray(bannerIconImage)) {
bannerIconImage = bannerIconImage.flat()[0];
if (bannerIconImage && !bannerIconImage.startsWith("[[") && !bannerIconImage.startsWith("![[")) {
bannerIconImage = `[[${bannerIconImage}]]`;
}
}
if (isEmbedded) {
const embedContainer = contentEl.querySelector(".markdown-preview-sizer") || contentEl.querySelector(".markdown-embed-content") || contentEl;
const thisEmbedOverlays = embedContainer.querySelectorAll(':scope > .banner-icon-overlay:not([data-persistent="true"])');
thisEmbedOverlays.forEach((overlay) => overlay.remove());
} else {
["markdown-preview-view", "markdown-source-view"].forEach((viewType) => {
const viewContainer = contentEl.querySelector(`.${viewType}`);
if (viewContainer) {
const mainOverlays = viewContainer.querySelectorAll(':scope > .banner-icon-overlay:not([data-persistent="true"])');
mainOverlays.forEach((overlay) => overlay.remove());
}
});
}
if ((!bannerIcon || typeof bannerIcon === "string" && !bannerIcon.trim()) && (!bannerIconImage || typeof bannerIconImage === "string" && !bannerIconImage.trim())) {
if (isEmbedded) {
const embedContainer = contentEl.querySelector(".markdown-preview-sizer") || contentEl.querySelector(".markdown-embed-content") || contentEl;
const persistentOverlays = embedContainer.querySelectorAll(':scope > .banner-icon-overlay[data-persistent="true"]');
persistentOverlays.forEach((overlay) => {
plugin.returnIconOverlay(overlay);
overlay.remove();
});
} else {
const previewContainer = contentEl.querySelector("div.markdown-preview-sizer");
const sourceContainer = contentEl.querySelector("div.cm-sizer");
if (previewContainer) {
const previewOverlays = previewContainer.querySelectorAll(':scope > .banner-icon-overlay[data-persistent="true"]');
previewOverlays.forEach((overlay) => {
plugin.returnIconOverlay(overlay);
overlay.remove();
});
}
if (sourceContainer) {
const sourceOverlays = sourceContainer.querySelectorAll(':scope > .banner-icon-overlay[data-persistent="true"]');
sourceOverlays.forEach((overlay) => {
plugin.returnIconOverlay(overlay);
overlay.remove();
});
}
}
}
if (bannerIcon && typeof bannerIcon === "string" && bannerIcon.trim() || bannerIconImage && typeof bannerIconImage === "string" && bannerIconImage.trim()) {
const cleanIcon = bannerIcon ? bannerIcon.trim() : "";
const cacheKey = plugin.generateCacheKey(view.file.path, plugin.app.workspace.activeLeaf.id);
const cachedState = plugin.bannerStateCache.get(cacheKey);
const cachedIconState = (_n = cachedState == null ? void 0 : cachedState.state) == null ? void 0 : _n.iconState;
const createOrUpdateIconOverlay = async (banner, viewType) => {
var _a2, _b2;
if (!banner) {
return;
}
const currentIconState = {
icon: cleanIcon,
size: getFrontmatterValue(frontmatter, plugin.settings.customBannerIconSizeField) || plugin.settings.bannerIconSize,
xPosition: getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconXPositionField),
plugin.settings.bannerIconXPosition
]),
opacity: getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconOpacityField),
plugin.settings.bannerIconOpacity
]),
color: getFrontmatterValue(frontmatter, plugin.settings.customBannerIconColorField) || plugin.settings.bannerIconColor,
fontWeight: getFrontmatterValue(frontmatter, plugin.settings.customBannerIconFontWeightField) || plugin.settings.bannerIconFontWeight,
backgroundColor: getFrontmatterValue(frontmatter, plugin.settings.customBannerIconBackgroundColorField) || plugin.settings.bannerIconBackgroundColor,
paddingX: getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconPaddingXField),
plugin.settings.bannerIconPaddingX
]),
paddingY: getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconPaddingYField),
plugin.settings.bannerIconPaddingY
]),
borderRadius: getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconBorderRadiusField),
plugin.settings.bannerIconBorderRadius
]),
verticalOffset: getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconVerticalOffsetField),
plugin.settings.bannerIconVerticalOffset
]),
imageAlignment: getFrontmatterValue(frontmatter, plugin.settings.customBannerIconImageAlignmentField) || plugin.settings.bannerIconImageAlignment,
viewType
};
const existingOverlay = ((_b2 = (_a2 = banner.nextElementSibling) == null ? void 0 : _a2.classList) == null ? void 0 : _b2.contains("banner-icon-overlay")) ? banner.nextElementSibling : null;
if (existingOverlay) {
if (!plugin.shouldUpdateIconOverlay(existingOverlay, currentIconState, viewType)) {
return existingOverlay;
}
plugin.returnIconOverlay(existingOverlay);
}
const bannerIconOverlay = plugin.getIconOverlay();
bannerIconOverlay.dataset.viewType = viewType;
bannerIconOverlay.dataset.persistent = "true";
bannerIconOverlay.textContent = "";
bannerIconOverlay._isPersistentBannerIcon = true;
bannerIconOverlay.innerHTML = "";
const imageAlignment = currentIconState.imageAlignment === "right" ? "right" : "left";
let imgElement = null;
if (bannerIconImage) {
const inputType = plugin.getIconImageInputType(bannerIconImage, view.file.path);
let imagePath = null;
let resolvedVaultPath = null;
if (inputType === "invalid") {
console.warn("Invalid banner icon image value detected:", bannerIconImage);
} else {
switch (inputType) {
case "obsidianLink":
const file = plugin.getPathFromObsidianLink(bannerIconImage);
if (file) {
imagePath = plugin.loadedImages.get(file.path);
if (!imagePath) {
imagePath = await plugin.getVaultImageUrl(file.path);
if (imagePath) plugin.loadedImages.set(file.path, imagePath);
}
}
break;
case "markdownImage":
const mdPath = plugin.getPathFromMarkdownImage(bannerIconImage);
if (typeof mdPath === "string") {
try {
new URL(mdPath);
imagePath = mdPath;
} catch (_) {
imagePath = plugin.loadedImages.get(mdPath);
if (!imagePath) {
imagePath = await plugin.getVaultImageUrl(mdPath);
if (imagePath) plugin.loadedImages.set(mdPath, imagePath);
}
}
} else if (mdPath) {
imagePath = plugin.loadedImages.get(mdPath.path);
if (!imagePath) {
imagePath = await plugin.getVaultImageUrl(mdPath.path);
if (imagePath) plugin.loadedImages.set(mdPath.path, imagePath);
}
}
break;
case "vaultPath":
const cleanedInput = bannerIconImage.trim().replace(/^["'](.*)["']$/, "$1");
let resolvedFile = plugin.app.vault.getAbstractFileByPath(cleanedInput);
if (!resolvedFile) {
resolvedFile = plugin.app.metadataCache.getFirstLinkpathDest(cleanedInput, view.file.path);
}
if (resolvedFile && "path" in resolvedFile) {
resolvedVaultPath = resolvedFile.path;
imagePath = plugin.loadedImages.get(resolvedVaultPath);
if (!imagePath) {
imagePath = await plugin.getVaultImageUrl(resolvedVaultPath);
if (imagePath) plugin.loadedImages.set(resolvedVaultPath, imagePath);
}
} else {
console.warn("[Icon Image] Could not resolve vault path:", cleanedInput);
}
break;
case "url":
imagePath = bannerIconImage;
break;
case "fileUrl":
console.warn("file:/// URLs are not supported for banner icon images");
break;
case "keyword":
console.warn("Keywords are not supported for banner icon images");
break;
}
if (imagePath) {
imgElement = document.createElement("img");
const imageUrl = typeof imagePath === "object" && imagePath.url ? imagePath.url : imagePath;
imgElement.src = imageUrl;
imgElement.className = "banner-icon-image";
}
}
}
let textElement = null;
if (cleanIcon) {
textElement = document.createElement("div");
textElement.className = "banner-icon-text";
textElement.textContent = cleanIcon;
}
if (imageAlignment === "right") {
if (textElement) bannerIconOverlay.appendChild(textElement);
if (imgElement) bannerIconOverlay.appendChild(imgElement);
} else {
if (imgElement) bannerIconOverlay.appendChild(imgElement);
if (textElement) bannerIconOverlay.appendChild(textElement);
}
bannerIconOverlay.style.display = "block";
bannerIconOverlay.style.fontSize = `${currentIconState.size}px`;
bannerIconOverlay.style.left = `${currentIconState.xPosition}%`;
bannerIconOverlay.style.opacity = `${currentIconState.opacity}%`;
bannerIconOverlay.style.color = currentIconState.color;
bannerIconOverlay.style.fontWeight = currentIconState.fontWeight;
bannerIconOverlay.style.backgroundColor = currentIconState.backgroundColor;
bannerIconOverlay.style.padding = `${currentIconState.paddingY}px ${currentIconState.paddingX}px`;
bannerIconOverlay.style.borderRadius = `${currentIconState.borderRadius}px`;
bannerIconOverlay.style.marginTop = `${currentIconState.verticalOffset}px`;
banner.insertAdjacentElement("afterend", bannerIconOverlay);
return bannerIconOverlay;
};
if (isEmbedded) {
const embedContainer = contentEl.querySelector(".markdown-preview-sizer") || contentEl.querySelector(".markdown-embed-content") || contentEl;
const previewBanner = embedContainer.querySelector(":scope > .pixel-banner-image");
await createOrUpdateIconOverlay(previewBanner, "preview");
} else {
const previewContainer = contentEl.querySelector("div.markdown-preview-sizer");
const sourceContainer = contentEl.querySelector("div.cm-sizer");
if (previewContainer) {
const previewBanner = previewContainer.querySelector(":scope > .pixel-banner-image");
await createOrUpdateIconOverlay(previewBanner, "preview");
}
if (sourceContainer) {
const sourceBanner = sourceContainer.querySelector(":scope > .pixel-banner-image");
await createOrUpdateIconOverlay(sourceBanner, "source");
}
}
}
}
var debouncedApplyBannerSettings = debounceAndSwallow(applyBannerSettings, 350);
function applyBannerSettings(plugin, bannerDiv, ctx, isEmbedded) {
const { frontmatter, imageDisplay, imageRepeat, bannerHeight, fade, borderRadius, maxWidth } = ctx;
const folderSpecific = plugin.getFolderSpecificImage(ctx.file.path);
let pixelBannerYPosition = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customYPositionField),
folderSpecific == null ? void 0 : folderSpecific.yPosition,
plugin.settings.yPosition
]);
let pixelBannerXPosition = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customXPositionField),
folderSpecific == null ? void 0 : folderSpecific.xPosition,
plugin.settings.xPosition
]);
const pixelBannerMaxWidth = getFrontmatterValue(frontmatter, plugin.settings.customBannerMaxWidthField) || (folderSpecific == null ? void 0 : folderSpecific.bannerMaxWidth) || plugin.settings.bannerMaxWidth || "unset";
const titleColor = getFrontmatterValue(frontmatter, plugin.settings.customTitleColorField) || (folderSpecific == null ? void 0 : folderSpecific.titleColor) || plugin.settings.titleColor;
const bannerIconSize = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconSizeField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconSize) || plugin.settings.bannerIconSize || 70;
const bannerIconXPosition = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconXPositionField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconXPosition,
plugin.settings.bannerIconXPosition,
25
]);
const bannerIconOpacity = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconOpacityField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconOpacity,
plugin.settings.bannerIconOpacity,
100
]);
const bannerIconColor = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconColorField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconColor) || plugin.settings.bannerIconColor || "var(--text-normal)";
const bannerIconFontWeight = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconFontWeightField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconFontWeight) || plugin.settings.bannerIconFontWeight || "normal";
const bannerIconBackgroundColor = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconBackgroundColorField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconBackgroundColor) || plugin.settings.bannerIconBackgroundColor || "transparent";
const bannerIconPaddingX = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconPaddingXField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconPaddingX,
plugin.settings.bannerIconPaddingX,
10
]);
const bannerIconPaddingY = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconPaddingYField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconPaddingY,
plugin.settings.bannerIconPaddingY,
10
]);
const bannerIconBorderRadius = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconBorderRadiusField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconBorderRadius,
plugin.settings.bannerIconBorderRadius,
17
]);
const bannerIconVerticalOffset = getValueWithZeroCheck([
Number(getFrontmatterValue(frontmatter, plugin.settings.customBannerIconVerticalOffsetField)),
folderSpecific == null ? void 0 : folderSpecific.bannerIconVerticalOffset,
plugin.settings.bannerIconVerticalOffset,
0
]);
const bannerIconRotate = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconRotateField),
0
]);
const bannerIconImageSizeMultiplier = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconImageSizeMultiplierField),
plugin.settings.bannerIconImageSizeMultiplier,
1
]);
const bannerIconTextVerticalOffset = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconTextVerticalOffsetField),
plugin.settings.bannerIconTextVerticalOffset,
0
]);
const hideEmbeddedNoteBanners = getFrontmatterValue(frontmatter, plugin.settings.customHideEmbeddedNoteBannersField) || (folderSpecific == null ? void 0 : folderSpecific.hideEmbeddedNoteBanners) || plugin.settings.hideEmbeddedNoteBanners || false;
const bannerAlignment = getFrontmatterValue(frontmatter, plugin.settings.customBannerAlignmentField) || "center";
let alignmentValue = "0 auto";
if (bannerAlignment === "left") {
alignmentValue = "0 auto 0 0";
} else if (bannerAlignment === "right") {
alignmentValue = "0 0 0 auto";
}
const cssVars = {
"--pixel-banner-height": hideEmbeddedNoteBanners && isEmbedded ? "0px" : `${bannerHeight}px`,
"--pixel-banner-fade": `${fade}%`,
"--pixel-banner-fade-in-animation-duration": `${plugin.settings.bannerFadeInAnimationDuration}ms`,
"--pixel-banner-radius": `${borderRadius}px`,
"--pixel-banner-max-width": pixelBannerMaxWidth === "unset" ? "unset" : `${pixelBannerMaxWidth}px`,
"--pixel-banner-y-position": `${pixelBannerYPosition}%`,
"--pixel-banner-x-position": `${pixelBannerXPosition}%`,
"--pixel-banner-title-color": titleColor,
"--pixel-banner-icon-size": `${bannerIconSize}px`,
"--pixel-banner-icon-start": `${bannerHeight - bannerIconSize / 2}px`,
"--pixel-banner-icon-x": `${bannerIconXPosition}%`,
"--pixel-banner-icon-opacity": `${bannerIconOpacity}%`,
"--pixel-banner-icon-color": bannerIconColor,
"--pixel-banner-icon-font-weight": bannerIconFontWeight,
"--pixel-banner-icon-background-color": bannerIconBackgroundColor,
"--pixel-banner-icon-padding-x": `${bannerIconPaddingX}px`,
"--pixel-banner-icon-padding-y": `${bannerIconPaddingY}px`,
"--pixel-banner-icon-border-radius": `${bannerIconBorderRadius}px`,
"--pixel-banner-icon-vertical-offset": `${bannerIconVerticalOffset}px`,
"--pixel-banner-icon-rotate": `${bannerIconRotate}deg`,
"--pixel-banner-icon-image-size-multiplier": `${bannerIconImageSizeMultiplier}em`,
"--pixel-banner-icon-text-vertical-offset": `${bannerIconTextVerticalOffset}px`,
"--pixel-banner-embed-min-height": !hideEmbeddedNoteBanners ? `${parseInt(bannerHeight) + parseInt(bannerIconSize) / 2 + parseInt(bannerIconVerticalOffset) + parseInt(bannerIconPaddingY)}px` : "0px",
"--pixel-banner-alignment": alignmentValue
};
bannerDiv.style.backgroundSize = imageDisplay || "cover";
bannerDiv.style.backgroundRepeat = imageRepeat ? "repeat" : "no-repeat";
Object.entries(cssVars).forEach(([property, value]) => {
bannerDiv.style.setProperty(property, value);
});
const container = bannerDiv.closest(".markdown-preview-view, .markdown-source-view");
if (container) {
Object.entries(cssVars).forEach(([property, value]) => {
container.style.setProperty(property, value);
});
}
if (isEmbedded) {
const embedContainer = bannerDiv.closest(".internal-embed");
if (embedContainer) {
embedContainer.style.setProperty("--pixel-banner-content-start", cssVars["--pixel-banner-embed-min-height"]);
const embedContentDiv = embedContainer.querySelector(":scope > .markdown-embed-content");
if (embedContentDiv) {
Object.entries(cssVars).forEach(([property, value]) => {
embedContentDiv.style.setProperty(property, value);
});
const previewViewEl = embedContentDiv.querySelector(":scope > .markdown-preview-view");
if (previewViewEl) {
Object.entries(cssVars).forEach(([property, value]) => {
previewViewEl.style.setProperty(property, value);
});
}
}
}
}
}
var debouncedApplyContentStartPosition = debounceAndSwallow(applyContentStartPosition, 350);
function applyContentStartPosition(plugin, el, contentStartPosition) {
if (!el) {
return;
}
el.style.setProperty("--pixel-banner-content-start", `${contentStartPosition}px`);
}
var debouncedApplyBannerWidth = debounceAndSwallow(applyBannerWidth, 350);
function applyBannerWidth(plugin, el) {
if (!el) return;
setTimeout(() => {
if (!el.classList.contains("view-content")) {
return;
}
const theWidth = el.clientWidth;
const bannerGap = plugin.settings.bannerGap;
el.style.setProperty("--pixel-banner-width", `${theWidth - bannerGap * 2}px`);
el.style.setProperty("--pixel-banner-banner-gap", `${bannerGap}px`);
}, 50);
}
var debouncedUpdateAllBanners = debounceAndSwallow(updateAllBanners, 350);
function updateAllBanners(plugin) {
plugin.app.workspace.iterateAllLeaves((leaf) => {
if (leaf.view.getViewType() === "markdown") {
updateBanner(plugin, leaf.view, true);
}
});
}
var debouncedUpdateBannerPosition = debounceAndSwallow(updateBannerPosition, 350);
async function updateBannerPosition(plugin, file, position) {
if (!file) return;
const metadata = plugin.app.metadataCache.getFileCache(file);
if (!(metadata == null ? void 0 : metadata.frontmatter)) return;
await plugin.app.fileManager.processFrontMatter(file, (frontmatter) => {
frontmatter.banner_x = position.x;
frontmatter.banner_y = position.y;
});
}
function registerMarkdownPostProcessor(plugin) {
plugin.registerMarkdownPostProcessor((el, ctx) => {
var _a;
const isPreview = ctx.containerEl.classList.contains("markdown-preview-view");
const isHoverPopover = ctx.containerEl.closest(".hover-popover");
const debounceFile = ctx.sourcePath ? plugin.app.vault.getAbstractFileByPath(ctx.sourcePath) : null;
if (debounceFile) {
const currentTime = Date.now();
const lastProcessTime = markdownPostProcessorDebounceMap.get(debounceFile.path);
if (lastProcessTime && currentTime - lastProcessTime < MARKDOWN_PROCESSOR_DEBOUNCE_DELAY) {
return;
}
markdownPostProcessorDebounceMap.set(debounceFile.path, currentTime);
}
if (!isPreview && !isHoverPopover) return;
const file = ctx.sourcePath ? plugin.app.vault.getAbstractFileByPath(ctx.sourcePath) : null;
if (!file) return;
const frontmatter = (_a = plugin.app.metadataCache.getFileCache(file)) == null ? void 0 : _a.frontmatter;
let bannerImage = null;
for (const field of plugin.settings.customBannerField) {
if (frontmatter == null ? void 0 : frontmatter[field]) {
bannerImage = frontmatter[field];
break;
}
}
if (!bannerImage) {
const folderSpecific2 = plugin.getFolderSpecificImage(file.path);
if (folderSpecific2 == null ? void 0 : folderSpecific2.image) {
bannerImage = folderSpecific2.image;
}
}
if (!bannerImage) return;
const folderSpecific = plugin.getFolderSpecificImage(file.path);
const yPosition = getFrontmatterValue(frontmatter, plugin.settings.customYPositionField) || (folderSpecific == null ? void 0 : folderSpecific.yPosition) || plugin.settings.yPosition;
const xPosition = getFrontmatterValue(frontmatter, plugin.settings.customXPositionField) || (folderSpecific == null ? void 0 : folderSpecific.xPosition) || plugin.settings.xPosition;
const contentStartPosition = getFrontmatterValue(frontmatter, plugin.settings.customContentStartField) || (folderSpecific == null ? void 0 : folderSpecific.contentStartPosition) || plugin.settings.contentStartPosition;
addPixelBanner(plugin, ctx.containerEl, {
frontmatter,
file,
isContentChange: false,
yPosition,
xPosition,
contentStartPosition,
bannerImage,
isReadingView: true,
updateMode: plugin.UPDATE_MODE.FULL_UPDATE
});
});
}
// src/core/bannerUtils.js
var import_obsidian28 = require("obsidian");
function getInputType(input, sourcePath = "") {
if (Array.isArray(input)) {
input = input.flat()[0];
}
if (typeof input !== "string") {
return "invalid";
}
let cleanedInput = input.trim().replace(/^["'](.*)["']$/, "$1");
cleanedInput = cleanedInput.replace(/^!\[\[(.*)\]\]$/, "$1").replace(/^\[\[(.*)\]\]$/, "$1");
if (cleanedInput.includes("file:///")) {
return "fileUrl";
}
if (input.match(/^!?\[{2}.*\]{2}$/) || input.match(/^"?!?\[{2}.*\]{2}"?$/)) {
return "obsidianLink";
}
if (input.match(/^!\[\]\(.*\)$/) || input.match(/^"?!\[\]\(.*\)"?$/)) {
return "markdownImage";
}
try {
new URL(cleanedInput);
return "url";
} catch (_) {
const file = this.app.vault.getAbstractFileByPath(cleanedInput);
if (file && "extension" in file) {
if (file.extension.match(/^(jpg|jpeg|png|gif|bmp|svg|webp|avif|mp4|mov)$/i)) {
return "vaultPath";
}
}
const resolvedFile = this.app.metadataCache.getFirstLinkpathDest(cleanedInput, sourcePath);
if (resolvedFile && "extension" in resolvedFile) {
if (resolvedFile.extension.match(/^(jpg|jpeg|png|gif|bmp|svg|webp|avif|mp4|mov)$/i)) {
return "vaultPath";
}
}
return "keyword";
}
}
function getPathFromObsidianLink(link) {
let cleanLink = link.replace(/^["'](.*)["']$/, "$1");
cleanLink = cleanLink.startsWith("!") ? cleanLink.slice(1) : cleanLink;
let innerLink = cleanLink.startsWith("[[") ? cleanLink.slice(2) : cleanLink;
innerLink = innerLink.endsWith("]]") ? innerLink.slice(0, -2) : innerLink;
const path = innerLink.split("|")[0];
return this.app.metadataCache.getFirstLinkpathDest(path, "");
}
function getPathFromMarkdownImage(link) {
let cleanLink = link.replace(/^["'](.*)["']$/, "$1");
const match = cleanLink.match(/^!\[\]\((.*)\)$/);
if (match && match[1]) {
const path = match[1];
try {
new URL(path);
return path;
} catch (_) {
return this.app.metadataCache.getFirstLinkpathDest(path, "");
}
}
return null;
}
async function getVaultImageUrl(path) {
const file = this.app.vault.getAbstractFileByPath(path);
if (file && "extension" in file) {
try {
const fileExt = file.extension.toLowerCase();
const videoExtensions = ["mp4", "mov"];
if (videoExtensions.includes(fileExt)) {
const resourcePath = this.app.vault.getResourcePath(file);
return {
url: resourcePath,
isVideo: true,
fileType: fileExt,
// Store original path to help with caching
originalPath: path
};
}
const arrayBuffer = await this.app.vault.readBinary(file);
const mimeType = fileExt === "svg" ? "image/svg+xml" : `image/${fileExt}`;
const blob = new Blob([arrayBuffer], { type: mimeType });
const url = URL.createObjectURL(blob);
return {
url,
isVideo: false,
fileType: fileExt
};
} catch (error) {
console.error("Error reading vault file:", error);
return null;
}
}
return null;
}
function preloadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(url);
img.onerror = reject;
img.src = url;
});
}
function getIconImageInputType(input, sourcePath = "") {
if (Array.isArray(input)) {
input = input.flat()[0];
}
if (typeof input !== "string") {
return "invalid";
}
let cleanedInput = input.trim().replace(/^["'](.*)["']$/, "$1");
cleanedInput = cleanedInput.replace(/^!\[\[(.*)\]\]$/, "$1").replace(/^\[\[(.*)\]\]$/, "$1");
if (cleanedInput.includes("file:///")) {
return "fileUrl";
}
if (input.match(/^!?\[{2}.*\]{2}$/) || input.match(/^"?!?\[{2}.*\]{2}"?$/)) {
return "obsidianLink";
}
if (input.match(/^!\[\]\(.*\)$/) || input.match(/^"?!\[\]\(.*\)"?$/)) {
return "markdownImage";
}
try {
new URL(cleanedInput);
return "url";
} catch (_) {
const file = this.app.vault.getAbstractFileByPath(cleanedInput);
if (file && "extension" in file) {
if (file.extension.match(/^(jpg|jpeg|png|gif|bmp|svg|webp|avif|mp4|mov)$/i)) {
return "vaultPath";
}
}
const resolvedFile = this.app.metadataCache.getFirstLinkpathDest(cleanedInput, sourcePath);
if (resolvedFile && "extension" in resolvedFile) {
if (resolvedFile.extension.match(/^(jpg|jpeg|png|gif|bmp|svg|webp|avif|mp4|mov)$/i)) {
return "vaultPath";
}
}
return "keyword";
}
}
function getFolderPath(filePath) {
if (!filePath) return "/";
if (!filePath.includes("/")) {
return "/";
}
const lastSlashIndex = filePath.lastIndexOf("/");
return lastSlashIndex !== -1 ? filePath.substring(0, lastSlashIndex) : "";
}
function getFolderSpecificImage(filePath) {
if (!filePath) return null;
const folderPath = this.getFolderPath(filePath);
const sortedFolderImages = [...this.settings.folderImages].sort(
(a, b) => {
var _a, _b;
return (((_a = b.folder) == null ? void 0 : _a.length) || 0) - (((_b = a.folder) == null ? void 0 : _b.length) || 0);
}
);
for (const folderImage of sortedFolderImages) {
if (!folderImage.folder) continue;
if (folderImage.folder === "/") {
if (folderImage.directChildrenOnly) {
if (!filePath.includes("/")) {
return this.createFolderImageSettings(folderImage);
}
} else {
return this.createFolderImageSettings(folderImage);
}
continue;
}
const normalizedFolderPath = folderImage.folder.startsWith("/") ? folderImage.folder : "/" + folderImage.folder;
const normalizedFileFolderPath = "/" + folderPath;
if (folderImage.directChildrenOnly) {
if (normalizedFileFolderPath === normalizedFolderPath) {
return this.createFolderImageSettings(folderImage);
}
} else {
if (normalizedFileFolderPath.startsWith(normalizedFolderPath)) {
return this.createFolderImageSettings(folderImage);
}
}
}
return null;
}
function getFolderSpecificSetting(filePath, settingName) {
var _a;
const folderPath = this.getFolderPath(filePath);
for (const folderImage of this.settings.folderImages) {
if (folderPath.startsWith(folderImage.folder)) {
return (_a = folderImage[settingName]) != null ? _a : void 0;
}
}
return void 0;
}
function getRandomImageFromFolder(folderPath) {
try {
const folder = this.app.vault.getAbstractFileByPath(folderPath);
if (!folder || !folder.children) return null;
const imageFiles = folder.children.filter(
(file) => file.extension && ["png", "jpg", "jpeg", "gif", "webp", "svg", "avif"].includes(file.extension.toLowerCase())
);
if (imageFiles.length === 0) return null;
const randomImage = imageFiles[Math.floor(Math.random() * imageFiles.length)];
return randomImage.path;
} catch (error) {
console.error("Error getting random image:", error);
return null;
}
}
function getActiveApiProvider() {
if (this.settings.apiProvider !== "all") {
return this.settings.apiProvider;
}
if (this.settings.apiProviders && Array.isArray(this.settings.apiProviders)) {
for (const provider of this.settings.apiProviders) {
const hasKey = provider === "pexels" && this.settings.pexelsApiKey || provider === "pixabay" && this.settings.pixabayApiKey || provider === "flickr" && this.settings.flickrApiKey || provider === "unsplash" && this.settings.unsplashApiKey;
if (hasKey) {
return provider;
}
}
}
const availableProviders = [];
if (this.settings.pexelsApiKey) availableProviders.push("pexels");
if (this.settings.pixabayApiKey) availableProviders.push("pixabay");
if (this.settings.flickrApiKey) availableProviders.push("flickr");
if (this.settings.unsplashApiKey) availableProviders.push("unsplash");
if (availableProviders.length === 0) {
return null;
}
return availableProviders[Math.floor(Math.random() * availableProviders.length)];
}
function hasBannerFrontmatter(file) {
const metadata = this.app.metadataCache.getFileCache(file);
if (!(metadata == null ? void 0 : metadata.frontmatter)) return false;
return this.settings.customBannerField.some(
(fieldName) => metadata.frontmatter[fieldName] !== void 0
);
}
function createFolderImageSettings(folderImage) {
const settings = { ...folderImage };
if (folderImage.enableImageShuffle && folderImage.shuffleFolder) {
const randomImagePath = this.getRandomImageFromFolder(folderImage.shuffleFolder);
if (randomImagePath) {
settings.image = randomImagePath;
}
}
return settings;
}
// src/core/eventHandler.js
var import_obsidian29 = require("obsidian");
init_modals();
init_frontmatterUtils();
var bannerUpdateDebounceMap = /* @__PURE__ */ new Map();
var BANNER_UPDATE_DEBOUNCE_DELAY = 300;
async function handleActiveLeafChange(leaf) {
var _a;
if (!leaf || !(leaf.view instanceof import_obsidian29.MarkdownView) || !leaf.view.file) {
return;
}
const filePath = leaf.view.file.path;
const debounceCheckTime = Date.now();
const lastUpdateTime = bannerUpdateDebounceMap.get(filePath);
if (lastUpdateTime && debounceCheckTime - lastUpdateTime < BANNER_UPDATE_DEBOUNCE_DELAY) {
return;
}
bannerUpdateDebounceMap.set(filePath, debounceCheckTime);
this.cleanupCache();
const previousLeaf = this.app.workspace.activeLeaf;
if (previousLeaf && previousLeaf.view instanceof import_obsidian29.MarkdownView && previousLeaf !== leaf) {
this.cleanupPreviousLeaf(previousLeaf);
this.cleanupIconOverlay(previousLeaf.view);
}
const currentPath = leaf.view.file.path;
const leafId = leaf.id;
const frontmatter = (_a = this.app.metadataCache.getFileCache(leaf.view.file)) == null ? void 0 : _a.frontmatter;
const currentTime = Date.now();
const hasBannerIcon = this.settings.customBannerIconField.some((field) => frontmatter == null ? void 0 : frontmatter[field]);
if (!hasBannerIcon) {
this.cleanupIconOverlay(leaf.view);
}
try {
const hasShufflePath = !!getFrontmatterValue(frontmatter, this.settings.customBannerShuffleField);
const folderSpecific = this.getFolderSpecificImage(currentPath);
const isShuffled = hasShufflePath || (folderSpecific == null ? void 0 : folderSpecific.enableImageShuffle) || false;
const cacheKey = this.generateCacheKey(currentPath, leafId, isShuffled);
const cachedState = this.bannerStateCache.get(cacheKey);
const loadedImage = this.loadedImages.get(currentPath);
let shouldUpdateBanner = false;
if (cachedState) {
if (isShuffled && currentTime - cachedState.timestamp > this.SHUFFLE_CACHE_AGE) {
shouldUpdateBanner = true;
this.loadedImages.delete(currentPath);
this.lastKeywords.delete(currentPath);
this.imageCache.delete(currentPath);
this.bannerStateCache.delete(cacheKey);
} else {
cachedState.timestamp = currentTime;
const relevantFields = [
...this.settings.customBannerField,
...this.settings.customYPositionField,
...this.settings.customXPositionField,
...this.settings.customContentStartField,
...this.settings.customImageDisplayField,
...this.settings.customImageRepeatField,
...this.settings.customBannerHeightField,
...this.settings.customFadeField,
...this.settings.customBorderRadiusField,
...this.settings.customTitleColorField,
...this.settings.customBannerShuffleField,
...this.settings.customBannerIconField,
...this.settings.customBannerIconSizeField,
...this.settings.customBannerIconXPositionField,
...this.settings.customBannerIconOpacityField,
...this.settings.customBannerIconColorField,
...this.settings.customBannerIconFontWeightField,
...this.settings.customBannerIconBackgroundColorField,
...this.settings.customBannerIconPaddingXField,
...this.settings.customBannerIconPaddingYField,
...this.settings.customBannerIconBorderRadiusField,
...this.settings.customBannerIconVerticalOffsetField
];
const hasRelevantChanges = relevantFields.some(
(field) => {
var _a2;
return (frontmatter == null ? void 0 : frontmatter[field]) !== ((_a2 = cachedState.frontmatter) == null ? void 0 : _a2[field]);
}
);
if (hasRelevantChanges) {
shouldUpdateBanner = true;
}
}
} else {
shouldUpdateBanner = true;
}
if (!loadedImage) {
shouldUpdateBanner = true;
}
if (shouldUpdateBanner) {
await this.updateBanner(leaf.view, false, this.UPDATE_MODE.FULL_UPDATE);
const bannerIcon = getFrontmatterValue(frontmatter, this.settings.customBannerIconField);
const iconState = bannerIcon ? {
icon: bannerIcon,
size: getFrontmatterValue(frontmatter, this.settings.customBannerIconSizeField) || this.settings.bannerIconSize,
xPosition: getFrontmatterValue(frontmatter, this.settings.customBannerIconXPositionField) || this.settings.bannerIconXPosition,
opacity: getFrontmatterValue(frontmatter, this.settings.customBannerIconOpacityField) || this.settings.bannerIconOpacity,
color: getFrontmatterValue(frontmatter, this.settings.customBannerIconColorField) || this.settings.bannerIconColor,
fontWeight: getFrontmatterValue(frontmatter, this.settings.customBannerIconFontWeightField) || this.settings.bannerIconFontWeight,
backgroundColor: getFrontmatterValue(frontmatter, this.settings.customBannerIconBackgroundColorField) || this.settings.bannerIconBackgroundColor,
paddingX: getFrontmatterValue(frontmatter, this.settings.customBannerIconPaddingXField) || this.settings.bannerIconPaddingX,
paddingY: getFrontmatterValue(frontmatter, this.settings.customBannerIconPaddingYField) || this.settings.bannerIconPaddingY,
borderRadius: getFrontmatterValue(frontmatter, this.settings.customBannerIconBorderRadiusField) || this.settings.bannerIconBorderRadius,
verticalOffset: getFrontmatterValue(frontmatter, this.settings.customBannerIconVerticalOffsetField) || this.settings.bannerIconVerticalOffset
} : null;
this.bannerStateCache.set(cacheKey, {
timestamp: currentTime,
frontmatter: frontmatter ? { ...frontmatter } : null,
leafId,
isShuffled,
state: {
imageUrl: this.loadedImages.get(currentPath),
iconState
}
});
} else {
await this.updateBanner(leaf.view, false, this.UPDATE_MODE.ENSURE_VISIBILITY);
}
} catch (error) {
console.error("Error in handleActiveLeafChange:", error);
this.invalidateLeafCache(leafId);
try {
await this.updateBanner(leaf.view, false);
} catch (recoveryError) {
console.error("Failed to recover from error:", recoveryError);
}
}
}
function handleLayoutChange() {
var _a;
const currentLeafIds = new Set(
this.app.workspace.getLeavesOfType("markdown").map((leaf) => leaf.id)
);
for (const [key, entry] of this.bannerStateCache) {
if (entry.leafId && !currentLeafIds.has(entry.leafId)) {
if (((_a = entry.state) == null ? void 0 : _a.imageUrl) && typeof entry.state.imageUrl === "string" && entry.state.imageUrl.startsWith("blob:")) {
URL.revokeObjectURL(entry.state.imageUrl);
}
this.bannerStateCache.delete(key);
}
}
setTimeout(() => {
const activeLeaf = this.app.workspace.activeLeaf;
if (activeLeaf && activeLeaf.view instanceof import_obsidian29.MarkdownView) {
const contentEl = activeLeaf.view.contentEl;
if (contentEl) {
const hasBanner = contentEl.querySelector(".pixel-banner-image");
if (hasBanner) {
const cacheKey = activeLeaf.id;
const cachedState = this.bannerStateCache.get(cacheKey);
if (!cachedState) {
this.updateBanner(activeLeaf.view, false);
}
}
}
}
}, 100);
}
async function handleModeChange(leaf) {
if (leaf && leaf.view instanceof import_obsidian29.MarkdownView && leaf.view.file) {
await this.updateBanner(leaf.view, true);
if (this.settings.hidePixelBannerFields) {
this.updateFieldVisibility(leaf.view);
}
}
}
async function handleSelectImage() {
const activeFile = this.app.workspace.getActiveFile();
if (!activeFile) {
new import_obsidian29.Notice("No active file");
return;
}
new ImageSelectionModal(
this.app,
this,
async (selectedFile) => {
let imageReference = selectedFile.path;
if (this.settings.useShortPath) {
const allFiles = this.app.vault.getFiles();
const matchingFiles = allFiles.filter((f) => f.name === selectedFile.name);
imageReference = matchingFiles.length === 1 ? selectedFile.name : selectedFile.path;
}
let fileContent = await this.app.vault.read(activeFile);
const frontmatterRegex = /^---\n([\s\S]*?)\n---/;
const hasFrontmatter = frontmatterRegex.test(fileContent);
const bannerField = Array.isArray(this.settings.customBannerField) && this.settings.customBannerField.length > 0 ? this.settings.customBannerField[0] : "banner";
fileContent = fileContent.replace(/^\s+/, "");
let updatedContent;
if (hasFrontmatter) {
updatedContent = fileContent.replace(frontmatterRegex, (match, frontmatter) => {
let cleanedFrontmatter = frontmatter.trim();
this.settings.customBannerField.forEach((field) => {
const fieldRegex = new RegExp(`${field}:\\s*.+\\n?`, "g");
cleanedFrontmatter = cleanedFrontmatter.replace(fieldRegex, "");
});
const format = this.settings.imagePropertyFormat;
const bannerValue = format === "[[image]]" ? `[[${imageReference}]]` : `![[${imageReference}]]`;
const newFrontmatter = `${bannerField}: "${bannerValue}"${cleanedFrontmatter ? "\n" + cleanedFrontmatter : ""}`;
return `---
${newFrontmatter}
---`;
});
} else {
const cleanContent = fileContent.replace(/^\s+/, "");
const format = this.settings.imagePropertyFormat;
const bannerValue = format === "[[image]]" ? `[[${imageReference}]]` : `![[${imageReference}]]`;
updatedContent = `---
${bannerField}: "${bannerValue}"
---
${cleanContent}`;
}
updatedContent = updatedContent.replace(/^\s+/, "");
if (updatedContent !== fileContent) {
await this.app.vault.modify(activeFile, updatedContent);
if (this.settings.useShortPath && imageReference === selectedFile.path) {
new import_obsidian29.Notice("Banner image updated (full path used due to duplicate filenames)");
} else {
new import_obsidian29.Notice("Banner image updated");
}
}
},
this.settings.defaultSelectImagePath
).open();
}
function handleBannerIconClick() {
new SelectPixelBannerModal(this.app, this).open();
}
// src/core/domManager.js
var import_obsidian30 = require("obsidian");
function setupMutationObserver() {
if (this.observer && typeof this.observer.disconnect === "function") {
this.observer.disconnect();
}
this.observer = new MutationObserver((mutations) => {
for (let mutation of mutations) {
if (mutation.type === "childList") {
const removedNodes = Array.from(mutation.removedNodes);
const addedNodes = Array.from(mutation.addedNodes);
const bannerRemoved = removedNodes.some(
(node) => node.classList && node.classList.contains("pixel-banner-image")
);
const structuralChange = addedNodes.some(
(node) => node.nodeType === Node.ELEMENT_NODE && (node.classList.contains("markdown-preview-section") || node.classList.contains("cm-sizer"))
// Changed from cm-content to cm-sizer
);
if (bannerRemoved || structuralChange) {
const activeLeaf = this.app.workspace.activeLeaf;
if (activeLeaf && activeLeaf.view instanceof import_obsidian30.MarkdownView) {
const contentEl = activeLeaf.view.contentEl;
const hasBanner = contentEl.querySelector('.pixel-banner-image[style*="display: block"]');
if (!hasBanner) {
contentEl.classList.remove("pixel-banner");
}
if (bannerRemoved || structuralChange) {
this.debouncedEnsureBanner();
}
}
}
}
}
});
if (this.observer && typeof this.observer.observe === "function") {
this.observer.observe(document.body, {
childList: true,
subtree: true
});
}
}
function setupResizeObserver(viewContent) {
if (!viewContent.classList.contains("view-content")) {
return;
}
if (!viewContent._resizeObserver) {
const debouncedResize = debounce(() => {
this.applyBannerWidth(viewContent);
}, 100);
viewContent._resizeObserver = new ResizeObserver(debouncedResize);
if (viewContent._resizeObserver && typeof viewContent._resizeObserver.observe === "function") {
viewContent._resizeObserver.observe(viewContent);
}
}
}
function updateFieldVisibility(view) {
if (!view || view.getMode() !== "preview") return;
const fieldsToHide = [
...this.settings.customBannerField,
...this.settings.customYPositionField,
...this.settings.customXPositionField,
...this.settings.customContentStartField,
...this.settings.customImageDisplayField,
...this.settings.customImageRepeatField,
...this.settings.customBannerMaxWidthField,
...this.settings.customBannerHeightField,
...this.settings.customBannerAlignmentField,
...this.settings.customFadeField,
...this.settings.customBorderRadiusField,
...this.settings.customTitleColorField,
...this.settings.customBannerShuffleField,
...this.settings.customBannerIconField,
...this.settings.customBannerIconImageField,
...this.settings.customBannerIconImageAlignmentField,
...this.settings.customBannerIconSizeField,
...this.settings.customBannerIconImageSizeMultiplierField,
...this.settings.customBannerIconTextVerticalOffsetField,
...this.settings.customBannerIconRotateField,
...this.settings.customBannerIconXPositionField,
...this.settings.customBannerIconOpacityField,
...this.settings.customBannerIconColorField,
...this.settings.customBannerIconFontWeightField,
...this.settings.customBannerIconBackgroundColorField,
...this.settings.customBannerIconPaddingXField,
...this.settings.customBannerIconPaddingYField,
...this.settings.customBannerIconBorderRadiusField,
...this.settings.customBannerIconVerticalOffsetField,
...this.settings.customFlagColorField
];
const propertiesContainer = view.contentEl.querySelector(".metadata-container");
if (!propertiesContainer) {
return;
}
const propertyElements = propertiesContainer.querySelectorAll(".metadata-property");
let visiblePropertiesCount = 0;
let bannerPropertiesCount = 0;
propertyElements.forEach((propertyEl) => {
const key = propertyEl.getAttribute("data-property-key");
if (fieldsToHide.includes(key)) {
propertyEl.classList.add("pixel-banner-hidden-field");
bannerPropertiesCount++;
} else {
visiblePropertiesCount++;
}
});
if (this.settings.hidePropertiesSectionIfOnlyBanner && this.settings.hidePixelBannerFields && visiblePropertiesCount === 0 && bannerPropertiesCount > 0) {
propertiesContainer.classList.add("pixel-banner-hidden-section");
} else {
propertiesContainer.classList.remove("pixel-banner-hidden-section");
}
}
function updateEmbeddedTitlesVisibility() {
const styleId = "pixel-banner-embedded-titles";
let styleEl = document.getElementById(styleId);
if (this.settings.hideEmbeddedNoteTitles) {
if (!styleEl) {
styleEl = document.createElement("style");
styleEl.id = styleId;
document.head.appendChild(styleEl);
}
styleEl.textContent = ".embed-title.markdown-embed-title { display: none !important; }";
} else if (styleEl) {
styleEl.remove();
}
}
function updateEmbeddedBannersVisibility() {
const styleId = "pixel-banner-embedded-banners";
let styleEl = document.getElementById(styleId);
if (this.settings.hideEmbeddedNoteBanners) {
if (!styleEl) {
styleEl = document.createElement("style");
styleEl.id = styleId;
document.head.appendChild(styleEl);
}
styleEl.textContent = `
.internal-embed .pixel-banner-image,
.internal-embed .banner-icon-overlay {
display: none !important;
}
.internal-embed > .markdown-embed-content .cm-sizer:first-of-type,
.internal-embed > .markdown-embed-content .markdown-preview-sizer:first-of-type {
padding-top: unset !important;
}
/* hide pusher to prevent content from being pushed down */
.internal-embed > .markdown-embed-content .cm-sizer:first-of-type > .pixel-banner-image + .markdown-preview-pusher,
.internal-embed > .markdown-embed-content .markdown-preview-sizer:first-of-type > .pixel-banner-image + .markdown-preview-pusher {
display: none !important;
}
`;
} else if (styleEl) {
styleEl.remove();
}
}
function cleanupPreviousLeaf(previousLeaf) {
const previousContentEl = previousLeaf.view.contentEl;
if (!previousContentEl) {
return;
}
previousContentEl.classList.remove("pixel-banner");
["cm-sizer", "markdown-preview-sizer"].forEach((selector) => {
const container = previousContentEl.querySelector(`div.${selector}`);
if (container) {
const previousBanner = container.querySelector(".pixel-banner-image");
if (previousBanner) {
if (previousLeaf.view.file) {
const existingUrl = this.loadedImages.get(previousLeaf.view.file.path);
if (existingUrl && typeof existingUrl === "string" && existingUrl.startsWith("blob:")) {
URL.revokeObjectURL(existingUrl);
}
this.loadedImages.delete(previousLeaf.view.file.path);
}
previousBanner.style.backgroundImage = "";
previousBanner.style.display = "none";
}
const iconOverlays = container.querySelectorAll(".banner-icon-overlay");
iconOverlays.forEach((overlay) => {
if (!overlay.dataset.persistent) {
this.returnIconOverlay(overlay);
}
});
}
});
}
// src/core/pixelBannerPlugin.js
init_frontmatterUtils();
var PixelBannerPlugin = class extends import_obsidian31.Plugin {
constructor() {
super(...arguments);
// Update modes for banner refresh
__publicField(this, "UPDATE_MODE", {
FULL_UPDATE: "FULL_UPDATE",
// Complete update including new images
ENSURE_VISIBILITY: "ENSURE_VISIBILITY",
// Only ensure banner is visible with current image
SHUFFLE_UPDATE: "SHUFFLE_UPDATE"
// Update for shuffle banners only
});
__publicField(this, "debounceTimer", null);
__publicField(this, "loadedImages", /* @__PURE__ */ new Map());
__publicField(this, "lastKeywords", /* @__PURE__ */ new Map());
__publicField(this, "imageCache", /* @__PURE__ */ new Map());
__publicField(this, "rateLimiter", {
lastRequestTime: 0,
minInterval: 1e3
// 1 second between requests
});
__publicField(this, "lastYPositions", /* @__PURE__ */ new Map());
__publicField(this, "lastFrontmatter", /* @__PURE__ */ new Map());
// Enhanced cache management properties
__publicField(this, "bannerStateCache", /* @__PURE__ */ new Map());
__publicField(this, "MAX_CACHE_AGE", 30 * 60 * 1e3);
// 30 minutes in milliseconds
__publicField(this, "MAX_CACHE_ENTRIES", 30);
// Maximum number of entries to keep in cache
__publicField(this, "SHUFFLE_CACHE_AGE", 5 * 1e3);
// 5 seconds in milliseconds for shuffled banners
// Add element pool for icon overlays
__publicField(this, "iconOverlayPool", []);
__publicField(this, "MAX_POOL_SIZE", 10);
// -----------------------------
// -- debounced ensure banner --
// -----------------------------
__publicField(this, "debouncedEnsureBanner", debounce(() => {
const activeLeaf = this.app.workspace.activeLeaf;
if (activeLeaf && activeLeaf.view instanceof import_obsidian31.MarkdownView) {
const contentEl = activeLeaf.view.contentEl;
const hasBanner = contentEl.querySelector(".pixel-banner-image");
if (hasBanner) {
this.updateBanner(activeLeaf.view, false);
}
}
}, 100));
}
// ----------------------------------------------------
// -- bind imported functions to the plugin instance --
// ----------------------------------------------------
async loadSettings() {
await loadSettings(this);
}
async saveSettings() {
await saveSettings(this);
}
getIconOverlay() {
return getIconOverlay(this);
}
returnIconOverlay(overlay) {
returnIconOverlay(this, overlay);
}
shouldUpdateIconOverlay(existingOverlay, newIconState, viewType) {
return shouldUpdateIconOverlay(this, existingOverlay, newIconState, viewType);
}
generateCacheKey(filePath, leafId, isShuffled = false) {
return generateCacheKey.call(this, filePath, leafId, isShuffled);
}
getCacheEntriesForFile(filePath) {
return getCacheEntriesForFile.call(this, filePath);
}
cleanupCache(force = false) {
return cleanupCache.call(this, force);
}
invalidateLeafCache(leafId) {
return invalidateLeafCache.call(this, leafId);
}
handleSetBannerIcon() {
return handleSetBannerIcon(this);
}
addPixelBanner(el, ctx) {
return debouncedAddPixelBanner(this, el, ctx);
}
updateBanner(view, isContentChange, updateMode) {
return debouncedUpdateBanner(this, view, isContentChange, updateMode);
}
applyBannerSettings(bannerDiv, ctx, isEmbedded) {
return debouncedApplyBannerSettings(this, bannerDiv, ctx, isEmbedded);
}
applyContentStartPosition(el, contentStartPosition) {
return debouncedApplyContentStartPosition(this, el, contentStartPosition);
}
applyBannerWidth(el) {
return debouncedApplyBannerWidth(this, el);
}
updateAllBanners() {
return debouncedUpdateAllBanners(this);
}
updateBannerPosition(file, position) {
return debouncedUpdateBannerPosition(this, file, position);
}
cleanupIconOverlay(view) {
return cleanupIconOverlay(this, view);
}
// --------------------------------------------
// -- add bindings for the utility functions --
// --------------------------------------------
getInputType(input, sourcePath = "") {
return getInputType.call(this, input, sourcePath);
}
getIconImageInputType(input, sourcePath = "") {
return getIconImageInputType.call(this, input, sourcePath);
}
getPathFromObsidianLink(link) {
return getPathFromObsidianLink.call(this, link);
}
getPathFromMarkdownImage(link) {
return getPathFromMarkdownImage.call(this, link);
}
getVaultImageUrl(path) {
return getVaultImageUrl.call(this, path);
}
preloadImage(url) {
return preloadImage.call(this, url);
}
getFolderPath(filePath) {
return getFolderPath.call(this, filePath);
}
getFolderSpecificImage(filePath) {
return getFolderSpecificImage.call(this, filePath);
}
getFolderSpecificSetting(filePath, settingName) {
return getFolderSpecificSetting.call(this, filePath, settingName);
}
getRandomImageFromFolder(folderPath) {
return getRandomImageFromFolder.call(this, folderPath);
}
getActiveApiProvider() {
return getActiveApiProvider.call(this);
}
hasBannerFrontmatter(file) {
return hasBannerFrontmatter.call(this, file);
}
createFolderImageSettings(folderImage) {
return createFolderImageSettings.call(this, folderImage);
}
// -------------------------------------
// -- add bindings for event handlers --
// -------------------------------------
handleActiveLeafChange(leaf) {
return handleActiveLeafChange.call(this, leaf);
}
handleLayoutChange() {
return handleLayoutChange.call(this);
}
handleModeChange(leaf) {
return handleModeChange.call(this, leaf);
}
handleSelectImage() {
return handleSelectImage.call(this);
}
handleBannerIconClick() {
return handleBannerIconClick.call(this);
}
// -------------------------------------
// -- add bindings for DOM management --
// -------------------------------------
setupMutationObserver() {
return setupMutationObserver.call(this);
}
setupResizeObserver(viewContent) {
return setupResizeObserver.call(this, viewContent);
}
updateFieldVisibility(view) {
return updateFieldVisibility.call(this, view);
}
updateEmbeddedTitlesVisibility() {
return updateEmbeddedTitlesVisibility.call(this);
}
updateEmbeddedBannersVisibility() {
return updateEmbeddedBannersVisibility.call(this);
}
cleanupPreviousLeaf(previousLeaf) {
return cleanupPreviousLeaf.call(this, previousLeaf);
}
// --------------------------------------
// -- onload method / main entry point --
// --------------------------------------
async onload() {
await this.loadSettings();
this.pixelBannerPlusEnabled = false;
this.pixelBannerPlusBannerTokens = 0;
this.verifyPixelBannerPlusCredentials();
this.updateEmbeddedTitlesVisibility();
await this.checkVersion();
this.addSettingTab(new PixelBannerSettingTab(this.app, this));
this.addCommand({
id: "generate-banner-with-ai",
name: "\u2728 Generate Banner with AI",
checkCallback: (checking) => {
if (checking) {
return this.pixelBannerPlusEnabled;
}
new GenerateAIBannerModal(this.app, this).open();
}
});
this.addCommand({
id: "play-daily-game",
name: "\u{1F579}\uFE0F Play Daily Game",
callback: () => {
new DailyGameModal(this.app, this.settings.pixelBannerPlusEmail, this.settings.pixelBannerPlusApiKey, this).open();
}
});
this.registerEvent(
this.app.workspace.on("active-leaf-change", this.handleActiveLeafChange.bind(this))
);
this.registerEvent(
this.app.workspace.on("layout-change", this.handleLayoutChange.bind(this))
);
this.registerEvent(
this.app.workspace.on("resize", this.debouncedEnsureBanner.bind(this))
);
this.registerEvent(
this.app.metadataCache.on("changed", async (file) => {
var _a;
const frontmatter = (_a = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a.frontmatter;
if (!frontmatter) {
return;
}
const previousFrontmatter = this.lastFrontmatter.get(file.path);
const currentFrontmatterStr = JSON.stringify(frontmatter);
if (currentFrontmatterStr === JSON.stringify(previousFrontmatter)) {
return;
}
this.lastFrontmatter.set(file.path, JSON.parse(currentFrontmatterStr));
const relevantFields = [
...this.settings.customBannerField,
...this.settings.customYPositionField,
...this.settings.customXPositionField,
...this.settings.customContentStartField,
...this.settings.customImageDisplayField,
...this.settings.customImageRepeatField,
...this.settings.customBannerMaxWidthField,
...this.settings.customBannerHeightField,
...this.settings.customFadeField,
...this.settings.customBorderRadiusField,
...this.settings.customTitleColorField,
...this.settings.customBannerShuffleField,
...this.settings.customBannerIconField,
...this.settings.customBannerIconSizeField,
...this.settings.customBannerIconImageSizeMultiplierField,
...this.settings.customBannerIconTextVerticalOffsetField,
...this.settings.customBannerIconRotateField,
...this.settings.customBannerIconImageAlignmentField,
...this.settings.customBannerIconXPositionField,
...this.settings.customBannerIconOpacityField,
...this.settings.customBannerIconColorField,
...this.settings.customBannerIconFontWeightField,
...this.settings.customBannerIconBackgroundColorField,
...this.settings.customBannerIconPaddingXField,
...this.settings.customBannerIconPaddingYField,
...this.settings.customBannerIconBorderRadiusField,
...this.settings.customBannerIconVerticalOffsetField
];
const changedFields = relevantFields.filter(
(field) => frontmatter[field] !== (previousFrontmatter == null ? void 0 : previousFrontmatter[field])
);
if (changedFields.length === 0) {
return;
}
const leaves = this.app.workspace.getLeavesOfType("markdown");
for (const leaf of leaves) {
if (leaf.view instanceof import_obsidian31.MarkdownView && leaf.view.file === file) {
this.loadedImages.delete(file.path);
this.lastKeywords.delete(file.path);
await this.updateBanner(leaf.view, true);
}
}
})
);
this.registerMarkdownPostProcessor((el, ctx) => {
var _a;
const isPreview = ctx.containerEl.classList.contains("markdown-preview-view");
const isHoverPopover = ctx.containerEl.closest(".hover-popover") !== null;
if (!isPreview && !isHoverPopover) return;
const file = ctx.sourcePath ? this.app.vault.getAbstractFileByPath(ctx.sourcePath) : null;
if (!file) return;
if (isHoverPopover && !this.settings.showBannerInPopoverPreviews) return;
const frontmatter = (_a = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a.frontmatter;
let bannerImage = null;
for (const field of this.settings.customBannerField) {
if (frontmatter == null ? void 0 : frontmatter[field]) {
bannerImage = frontmatter[field];
break;
}
}
if (!bannerImage) {
const folderSpecific2 = this.getFolderSpecificImage(file.path);
if (folderSpecific2 == null ? void 0 : folderSpecific2.image) {
bannerImage = folderSpecific2.image;
}
}
if (!bannerImage) return;
const folderSpecific = this.getFolderSpecificImage(file.path);
const yPosition = getFrontmatterValue(frontmatter, this.settings.customYPositionField) || (folderSpecific == null ? void 0 : folderSpecific.yPosition) || this.settings.yPosition;
const xPosition = getFrontmatterValue(frontmatter, this.settings.customXPositionField) || (folderSpecific == null ? void 0 : folderSpecific.xPosition) || this.settings.xPosition;
const contentStartPosition = getFrontmatterValue(frontmatter, this.settings.customContentStartField) || (folderSpecific == null ? void 0 : folderSpecific.contentStartPosition) || this.settings.contentStartPosition;
if (isHoverPopover) {
this.addPixelBanner(ctx.containerEl, {
frontmatter,
file,
isContentChange: false,
yPosition,
xPosition,
contentStartPosition,
bannerImage,
isReadingView: true
});
} else {
this.updateBanner({
file,
contentEl: ctx.containerEl,
getMode: () => "preview"
}, false, this.UPDATE_MODE.ENSURE_VISIBILITY);
}
});
this.setupMutationObserver();
this.addCommand({
id: "pin-banner-image",
name: "\u{1F4CC} Pin current banner image",
checkCallback: (checking) => {
var _a;
const activeView = this.app.workspace.getActiveViewOfType(import_obsidian31.MarkdownView);
if (!activeView || !activeView.file) return false;
const imageUrl = this.loadedImages.get(activeView.file.path);
const frontmatter = (_a = this.app.metadataCache.getFileCache(activeView.file)) == null ? void 0 : _a.frontmatter;
let bannerImage, usedField;
for (const field of this.settings.customBannerField) {
if (frontmatter == null ? void 0 : frontmatter[field]) {
bannerImage = frontmatter[field];
usedField = field;
break;
}
}
const inputType = this.getInputType(bannerImage);
const canPin = imageUrl && (inputType === "keyword" || inputType === "url") && this.settings.showPinIcon;
if (checking) return canPin;
if (canPin) {
setTimeout(() => handlePinIconClick(imageUrl, this, usedField), 0);
}
return true;
}
});
this.addCommand({
id: "refresh-banner-image",
name: "\u{1F504} Refresh current banner image",
checkCallback: (checking) => {
var _a;
const activeView = this.app.workspace.getActiveViewOfType(import_obsidian31.MarkdownView);
if (!activeView || !activeView.file) return false;
const frontmatter = (_a = this.app.metadataCache.getFileCache(activeView.file)) == null ? void 0 : _a.frontmatter;
let bannerImage;
for (const field of this.settings.customBannerField) {
if (frontmatter == null ? void 0 : frontmatter[field]) {
bannerImage = frontmatter[field];
break;
}
}
const inputType = this.getInputType(bannerImage);
const canRefresh = inputType === "keyword" && this.settings.showPinIcon && this.settings.showRefreshIcon;
if (checking) return canRefresh;
if (canRefresh) {
this.loadedImages.delete(activeView.file.path);
this.lastKeywords.delete(activeView.file.path);
this.updateBanner(activeView, true).then(() => {
new import_obsidian31.Notice("\u{1F504} Refreshed banner image");
}).catch((error) => {
console.error("Error refreshing image:", error);
new import_obsidian31.Notice("\u{1F62D} Failed to refresh image");
});
}
return true;
}
});
this.addCommand({
id: "open-pixel-banner-select",
name: "\u{1F6A9} Pixel Banner Menu",
callback: () => {
this.handleBannerIconClick();
}
});
this.addCommand({
id: "set-banner-image",
name: "\u{1F4BE} Select Banner from Vault",
callback: () => this.handleSelectImage()
});
this.addCommand({
id: "open-banner-store",
name: "\u{1F3EA} Open Pixel Banner Plus Collection",
callback: () => this.openBannerStore()
});
this.addCommand({
id: "set-banner-icon-image",
name: "\u2B50 Select Banner Icon Image",
checkCallback: (checking) => {
var _a;
const activeView = this.app.workspace.getActiveViewOfType(import_obsidian31.MarkdownView);
if (!activeView || !activeView.file) return false;
const frontmatter = (_a = this.app.metadataCache.getFileCache(activeView.file)) == null ? void 0 : _a.frontmatter;
let hasBanner = false;
for (const field of this.settings.customBannerField) {
if (frontmatter == null ? void 0 : frontmatter[field]) {
hasBanner = true;
break;
}
}
if (checking) return hasBanner;
if (hasBanner) {
handleSetBannerIconImage(this);
}
return true;
}
});
this.addCommand({
id: "set-banner-icon",
name: "\u{1F4F0} Set Banner Icon Text / Emoji",
checkCallback: (checking) => {
var _a;
const activeView = this.app.workspace.getActiveViewOfType(import_obsidian31.MarkdownView);
if (!activeView || !activeView.file) return false;
const frontmatter = (_a = this.app.metadataCache.getFileCache(activeView.file)) == null ? void 0 : _a.frontmatter;
let hasBanner = false;
for (const field of this.settings.customBannerField) {
if (frontmatter == null ? void 0 : frontmatter[field]) {
hasBanner = true;
break;
}
}
if (checking) return hasBanner;
if (hasBanner) {
handleSetBannerIcon(this);
}
return true;
}
});
this.addCommand({
id: "set-banner-position",
name: "\u{1F3AF} Adjust Position, Size, & Style",
checkCallback: (checking) => {
const activeFile = this.app.workspace.getActiveFile();
const hasBanner = activeFile && this.hasBannerFrontmatter(activeFile);
if (checking) {
return hasBanner;
}
if (hasBanner) {
new TargetPositionModal(
this.app,
this,
(position) => this.updateBannerPosition(activeFile, position)
).open();
return true;
}
return false;
}
});
if (this.settings.bannerGap === void 0) {
this.settings.bannerGap = DEFAULT_SETTINGS.bannerGap;
}
this.registerEvent(
this.app.metadataCache.on("resolved", () => {
var _a;
const leaf = this.app.workspace.activeLeaf;
if (leaf && leaf.view instanceof import_obsidian31.MarkdownView) {
const contentEl = leaf.view.contentEl;
const hasBanner = contentEl.querySelector(".pixel-banner-image");
if (hasBanner) {
const file = leaf.view.file;
const frontmatter = (_a = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a.frontmatter;
if (!frontmatter) return;
const previousFrontmatter = this.lastFrontmatter.get(file.path);
const currentStr = JSON.stringify(frontmatter);
if (currentStr !== JSON.stringify(previousFrontmatter)) {
this.lastFrontmatter.set(file.path, JSON.parse(currentStr));
const relevantFields = [
...this.settings.customBannerField,
...this.settings.customYPositionField,
...this.settings.customXPositionField,
...this.settings.customContentStartField,
...this.settings.customImageDisplayField,
...this.settings.customImageRepeatField,
...this.settings.customBannerMaxWidthField,
...this.settings.customBannerHeightField,
...this.settings.customFadeField,
...this.settings.customBorderRadiusField,
...this.settings.customTitleColorField,
...this.settings.customBannerShuffleField,
...this.settings.customBannerIconField,
...this.settings.customBannerIconSizeField,
...this.settings.customBannerIconImageSizeMultiplierField,
...this.settings.customBannerIconTextVerticalOffsetField,
...this.settings.customBannerIconRotateField,
...this.settings.customBannerIconImageAlignmentField,
...this.settings.customBannerIconXPositionField,
...this.settings.customBannerIconOpacityField,
...this.settings.customBannerIconColorField,
...this.settings.customBannerIconFontWeightField,
...this.settings.customBannerIconBackgroundColorField,
...this.settings.customBannerIconPaddingXField,
...this.settings.customBannerIconPaddingYField,
...this.settings.customBannerIconBorderRadiusField,
...this.settings.customBannerIconVerticalOffsetField
];
const hasRelevantChange = relevantFields.some(
(field) => frontmatter[field] !== (previousFrontmatter == null ? void 0 : previousFrontmatter[field])
);
if (hasRelevantChange) {
this.updateBanner(leaf.view, false);
}
}
}
}
})
);
this.addRibbonIcon("flag", "\u{1F6A9} Pixel Banner Menu", () => {
this.handleBannerIconClick();
});
registerMarkdownPostProcessor(this);
}
// -------------------
// -- get image url --
// -------------------
async getImageUrl(type, input, sourcePath = "") {
if (type === "url" || type === "path") {
return input;
}
if (type === "fileUrl") {
if (import_obsidian31.Platform.isMobile) {
new import_obsidian31.Notice("Local file paths are only supported on desktop.");
return null;
}
try {
const fs = require("fs");
const path = require("path");
let filePath = decodeURIComponent(input.substring(process.platform === "win32" ? 8 : 7));
filePath = filePath.replace('"', "").replace("![[", "").replace("[[", "").replace("]]", "").replace("//", "");
if (process.platform === "win32" && /^[a-zA-Z]:/.test(filePath)) {
} else if (process.platform === "win32" && filePath.startsWith("/")) {
filePath = filePath.substring(1);
}
if (!fs.existsSync(filePath)) {
console.error(`Pixel Banner: File not found at path: ${filePath}`);
new import_obsidian31.Notice(`Pixel Banner: File not found at path: ${filePath}`);
return null;
}
const data = fs.readFileSync(filePath);
const base64 = Buffer.from(data).toString("base64");
const mimeType = "image/" + path.extname(filePath).substring(1);
return `data:${mimeType};base64,${base64}`;
} catch (err) {
console.error("Pixel Banner: Error reading local file:", err);
new import_obsidian31.Notice("Pixel Banner: Error reading local file. Check console for details.");
return null;
}
}
if (type === "obsidianLink") {
const file = this.getPathFromObsidianLink(input);
if (file) {
return this.getVaultImageUrl(file.path);
}
return null;
}
if (type === "markdownImage") {
const path = this.getPathFromMarkdownImage(input);
if (typeof path === "string") {
try {
new URL(path);
return path;
} catch (_) {
return this.getVaultImageUrl(path);
}
}
if (path) {
return this.getVaultImageUrl(path.path);
}
return null;
}
if (type === "vaultPath") {
let file = this.app.vault.getAbstractFileByPath(input);
if (file && "extension" in file) {
return this.getVaultImageUrl(input);
}
const resolvedFile = this.app.metadataCache.getFirstLinkpathDest(input, sourcePath);
if (resolvedFile && "extension" in resolvedFile) {
return this.getVaultImageUrl(resolvedFile.path);
}
return null;
}
if (type === "keyword") {
const keywords = input.includes(",") ? input.split(",").map((k) => k.trim()).filter((k) => k.length > 0).filter(Boolean) : [input];
if (keywords.length > 0) {
const selectedKeyword = keywords[Math.floor(Math.random() * keywords.length)];
const availableProviders = [];
if (this.settings.apiProviders && Array.isArray(this.settings.apiProviders)) {
for (const provider of this.settings.apiProviders) {
const hasKey = provider === "pexels" && this.settings.pexelsApiKey || provider === "pixabay" && this.settings.pixabayApiKey || provider === "flickr" && this.settings.flickrApiKey || provider === "unsplash" && this.settings.unsplashApiKey;
if (hasKey) {
availableProviders.push(provider);
}
}
} else {
if (this.settings.pexelsApiKey) availableProviders.push("pexels");
if (this.settings.pixabayApiKey) availableProviders.push("pixabay");
if (this.settings.flickrApiKey) availableProviders.push("flickr");
if (this.settings.unsplashApiKey) availableProviders.push("unsplash");
}
if (availableProviders.length === 0) {
return null;
}
for (const provider of availableProviders) {
try {
let result = null;
switch (provider) {
case "pexels":
result = await fetchPexelsImage(this, selectedKeyword, true);
break;
case "pixabay":
result = await fetchPixabayImage(this, selectedKeyword);
break;
case "flickr":
result = await fetchFlickrImage(this, selectedKeyword);
break;
case "unsplash":
result = await fetchUnsplashImage(this, selectedKeyword);
break;
}
if (result) {
return result;
}
} catch (error) {
console.error(`Error with ${provider} for keyword "${selectedKeyword}":`, error);
}
}
const defaultKeywords = this.settings.defaultKeywords ? this.settings.defaultKeywords.split(",").map((k) => k.trim()).filter((k) => k.length > 0) : [];
for (const fallbackKeyword of defaultKeywords) {
for (const provider of availableProviders) {
try {
let result = null;
switch (provider) {
case "pexels":
result = await fetchPexelsImage(this, fallbackKeyword, true);
break;
case "pixabay":
result = await fetchPixabayImage(this, fallbackKeyword);
break;
case "flickr":
result = await fetchFlickrImage(this, fallbackKeyword);
break;
case "unsplash":
result = await fetchUnsplashImage(this, fallbackKeyword);
break;
}
if (result) {
return result;
}
} catch (error) {
console.error(`Error with ${provider} for fallback keyword "${fallbackKeyword}":`, error);
}
}
}
return null;
}
}
return null;
}
// --------------------
// -- post processor --
// --------------------
async postProcessor(el, ctx) {
const frontmatter = ctx.frontmatter;
let bannerImageValue = null;
if (frontmatter) {
for (const field of this.settings.customBannerField) {
if (frontmatter[field]) {
bannerImageValue = frontmatter[field];
break;
}
}
}
if (bannerImageValue) {
await this.addPixelBanner(el, {
frontmatter,
file: ctx.sourcePath,
isContentChange: false,
yPosition: getFrontmatterValue(frontmatter, this.settings.customYPositionField) || this.settings.yPosition,
contentStartPosition: getFrontmatterValue(frontmatter, this.settings.customContentStartField) || this.settings.contentStartPosition,
customBannerField: this.settings.customBannerField,
customYPositionField: this.settings.customYPositionField,
customXPositionField: this.settings.customXPositionField,
customContentStartField: this.settings.customContentStartField,
customImageDisplayField: this.settings.customImageDisplayField,
customImageRepeatField: this.settings.customImageRepeatField,
customBannerMaxWidthField: this.settings.customBannerMaxWidthField,
bannerImage: bannerImageValue
});
if (this.settings.hidePixelBannerFields) {
const frontmatterEl = el.querySelector(".frontmatter");
if (frontmatterEl) {
const fieldsToHide = [
...this.settings.customBannerField,
...this.settings.customYPositionField,
...this.settings.customXPositionField,
...this.settings.customContentStartField,
...this.settings.customImageDisplayField,
...this.settings.customImageRepeatField,
...this.settings.customBannerMaxWidthField,
...this.settings.customBannerHeightField,
...this.settings.customBannerAlignmentField,
...this.settings.customFadeField,
...this.settings.customBorderRadiusField,
...this.settings.customTitleColorField,
...this.settings.customBannerShuffleField,
...this.settings.customBannerIconField,
...this.settings.customBannerIconSizeField,
...this.settings.customBannerIconImageSizeMultiplierField,
...this.settings.customBannerIconTextVerticalOffsetField,
...this.settings.customBannerIconRotateField,
...this.settings.customBannerIconImageAlignmentField,
...this.settings.customBannerIconXPositionField,
...this.settings.customBannerIconOpacityField,
...this.settings.customBannerIconColorField,
...this.settings.customBannerIconFontWeightField,
...this.settings.customBannerIconBackgroundColorField,
...this.settings.customBannerIconPaddingXField,
...this.settings.customBannerIconPaddingYField,
...this.settings.customBannerIconBorderRadiusField,
...this.settings.customBannerIconVerticalOffsetField
];
const rows = frontmatterEl.querySelectorAll(".frontmatter-container .frontmatter-section-label");
rows.forEach((row) => {
const label = row.textContent.replace(":", "").trim();
if (fieldsToHide.includes(label)) {
row.closest(".frontmatter-section").classList.add("pixel-banner-hidden-field");
}
});
}
}
}
}
// --------------
// -- onunload --
// --------------
onunload() {
if (this.observer && typeof this.observer.disconnect === "function") {
this.observer.disconnect();
}
this.app.workspace.iterateAllLeaves((leaf) => {
if (leaf.view instanceof import_obsidian31.MarkdownView) {
const viewContent = leaf.view.contentEl;
if (viewContent._resizeObserver && typeof viewContent._resizeObserver.disconnect === "function") {
viewContent._resizeObserver.disconnect();
delete viewContent._resizeObserver;
}
}
});
this.iconOverlayPool = [];
const styleElTitle = document.getElementById("pixel-banner-embedded-titles");
if (styleElTitle) styleElTitle.remove();
const styleElBanner = document.getElementById("pixel-banner-embedded-banners");
if (styleElBanner) styleElBanner.remove();
}
// -------------------------
// -- clean orphaned pins --
// -------------------------
async cleanOrphanedPins() {
var _a;
const vault = this.app.vault;
const folderPath = this.settings.pinnedImageFolder;
let cleaned = 0;
try {
if (!await vault.adapter.exists(folderPath)) {
return { cleaned };
}
const pinnedFolder = vault.getAbstractFileByPath(folderPath);
if (!pinnedFolder || !pinnedFolder.children) {
return { cleaned };
}
const imageExtensions = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "avif"];
const pinnedImages = pinnedFolder.children.filter((file) => imageExtensions.includes(file.extension.toLowerCase())).map((file) => file.path);
if (!pinnedImages.length) {
return { cleaned };
}
const markdownFiles = this.app.vault.getMarkdownFiles();
const bannerFields = this.settings.customBannerField;
const referencedImages = /* @__PURE__ */ new Set();
for (const file of markdownFiles) {
const frontmatter = (_a = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a.frontmatter;
if (frontmatter) {
for (const field of bannerFields) {
const bannerValue = frontmatter[field];
if (bannerValue && typeof bannerValue === "string") {
let cleanPath;
if (bannerValue.startsWith("[[") && bannerValue.endsWith("]]")) {
cleanPath = bannerValue.slice(2, -2).replace(/["']/g, "");
} else {
cleanPath = bannerValue.replace(/["']/g, "");
}
if (!cleanPath.startsWith(folderPath)) {
const resolvedFile = this.app.metadataCache.getFirstLinkpathDest(cleanPath, file.path);
if (resolvedFile) {
cleanPath = resolvedFile.path;
}
}
referencedImages.add(cleanPath);
}
}
}
}
for (const imagePath of pinnedImages) {
if (!referencedImages.has(imagePath)) {
await vault.trash(vault.getAbstractFileByPath(imagePath), true);
cleaned++;
}
}
return { cleaned };
} catch (error) {
console.error("Error in cleanOrphanedPins:", error);
throw error;
}
}
// -----------------------------------------
// -- show release notes for new versions --
// -----------------------------------------
async checkVersion() {
const currentVersion = this.manifest.version;
const lastVersion = this.settings.lastVersion;
if (this.settings.showReleaseNotes && (!lastVersion || lastVersion !== currentVersion)) {
const releaseNotes2 = await this.getReleaseNotes(currentVersion);
new ReleaseNotesModal(this.app, currentVersion, releaseNotes2).open();
this.settings.lastVersion = currentVersion;
await this.saveSettings();
}
if (lastVersion && lastVersion !== currentVersion) {
console.log("[Pixel Banner] Plugin updated from", lastVersion, "to", currentVersion, "- invalidating cached cloud version");
this.pixelBannerVersion = void 0;
}
}
// -----------------------------------------------
// -- get release notes for the current version --
// -----------------------------------------------
async getReleaseNotes(version) {
return releaseNotes;
}
// ------------------------------------------
// -- verify pixel banner plus credentials --
// ------------------------------------------
async verifyPixelBannerPlusCredentials() {
if (this.settings.pixelBannerPlusEnabled) {
const result = await verifyPixelBannerPlusCredentials(this);
this.pixelBannerPlusServerOnline = result.serverOnline;
this.pixelBannerPlusEnabled = result.verified;
this.pixelBannerPlusBannerTokens = result.bannerTokens;
this.pixelBannerPlusJackpot = result.jackpot;
this.pixelBannerPlusDailyGameName = result.dailyGameName;
this.pixelBannerPlusHighScore = result.highScore || "0";
this.pixelBannerPlusTopUser = result.topUser;
this.pixelBannerPlusTimeLeft = result.timeLeft;
return result;
}
return {
serverOnline: false,
verified: false,
bannerTokens: 0,
jackpot: 0,
dailyGameName: "",
highScore: "0",
topUser: "",
timeLeft: 0
};
}
// -------------------------------- //
// -- get pixel banner plus info -- //
// -------------------------------- //
async getPixelBannerInfo() {
const result = await getPixelBannerInfo(this);
this.pixelBannerVersion = result.version;
return result;
}
// --------------------------
// -- open the banner store --
// --------------------------
openBannerStore() {
new PixelBannerStoreModal(this.app, this).open();
}
};
// src/main.js
var main_default = PixelBannerPlugin;
/* nosourcemap */