diff --git a/src/webpage/icons/gif.svg b/src/webpage/icons/gif.svg
new file mode 100644
index 0000000..ccfb358
--- /dev/null
+++ b/src/webpage/icons/gif.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/webpage/index.html b/src/webpage/index.html
index 55ef721..f15bb36 100644
--- a/src/webpage/index.html
+++ b/src/webpage/index.html
@@ -96,6 +96,7 @@
diff --git a/src/webpage/index.ts b/src/webpage/index.ts
index 49ae8d2..fe28af6 100644
--- a/src/webpage/index.ts
+++ b/src/webpage/index.ts
@@ -275,4 +275,12 @@ import {I18n} from "./i18n.js";
thisUser.TBEmojiMenu(emojiTB.getBoundingClientRect());
};
emojiTB.onclick = (e) => e.stopImmediatePropagation();
+
+ const gifTB = document.getElementById("gifTB") as HTMLElement;
+ gifTB.onmousedown = (e) => {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ thisUser.makeGifBox(gifTB.getBoundingClientRect());
+ };
+ gifTB.onclick = (e) => e.stopImmediatePropagation();
})();
diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts
index d653a00..92e535a 100644
--- a/src/webpage/localuser.ts
+++ b/src/webpage/localuser.ts
@@ -31,6 +31,7 @@ import {Message} from "./message.js";
import {badgeArr} from "./Dbadges.js";
import {Rights} from "./rights.js";
import {Contextmenu} from "./contextmenu.js";
+import {Search} from "./search.js";
const wsCodesRetry = new Set([4000, 4001, 4002, 4003, 4005, 4007, 4008, 4009]);
interface CustomHTMLDivElement extends HTMLDivElement {
@@ -2238,12 +2239,122 @@ class Localuser {
this.search(document.getElementById("searchOptions") as HTMLDivElement, typeMd, str, pre);
};
}
+ async makeGifBox(rect: DOMRect) {
+ interface fullgif {
+ id: string;
+ title: string;
+ url: string;
+ src: string;
+ gif_src: string;
+ width: number;
+ height: number;
+ preview: string;
+ }
+ const menu = document.createElement("div");
+ menu.classList.add("flexttb", "gifmenu");
+ menu.style.bottom = window.innerHeight - rect.top + 15 + "px";
+ menu.style.right = window.innerWidth - rect.right + "px";
+ document.body.append(menu);
+ Contextmenu.keepOnScreen(menu);
+ if (Contextmenu.currentmenu !== "") {
+ Contextmenu.currentmenu.remove();
+ }
+ Contextmenu.currentmenu = menu;
+ const trending = (await (
+ await fetch(
+ this.info.api + "/gifs/trending?" + new URLSearchParams([["locale", I18n.lang]]),
+ {headers: this.headers},
+ )
+ ).json()) as {
+ categories: {
+ name: string;
+ src: string;
+ }[];
+ gifs: [fullgif];
+ };
+ const gifbox = document.createElement("div");
+ gifbox.classList.add("gifbox");
+ const search = document.createElement("input");
+ let gifs = gifbox;
+ const searchBox = async () => {
+ gifs.remove();
+ if (search.value === "") {
+ menu.append(gifbox);
+ gifs = gifbox;
+ return;
+ }
+ gifs = document.createElement("div");
+ gifs.classList.add("gifbox");
+ menu.append(gifs);
+ const sValue = search.value;
+ const gifReturns = (await (
+ await fetch(
+ this.info.api +
+ "/gifs/search?" +
+ new URLSearchParams([
+ ["locale", I18n.lang],
+ ["q", sValue],
+ ["limit", "500"],
+ ]),
+ {headers: this.headers},
+ )
+ ).json()) as fullgif[];
+ if (sValue !== search.value) {
+ return;
+ }
+ for (const gif of gifReturns) {
+ const div = document.createElement("div");
+ div.classList.add("gifBox");
+ const img = document.createElement("img");
+ img.src = gif.gif_src;
+ img.alt = gif.title;
+ const scale = gif.width / 196;
+
+ img.width = gif.width / scale;
+ img.height = gif.height / scale;
+ div.append(img);
+ gifs.append(div);
+ div.onclick = () => {
+ if (this.channelfocus) {
+ this.channelfocus.sendMessage(gif.url, {embeds: [], attachments: [], replyingto: null});
+ menu.remove();
+ }
+ };
+ }
+ };
+ let last = "";
+ search.onkeyup = () => {
+ if (last === search.value) {
+ return;
+ }
+ last = search.value;
+ searchBox();
+ };
+ search.classList.add("searchGifBar");
+ search.placeholder = I18n.searchGifs();
+ for (const category of trending.categories) {
+ const div = document.createElement("div");
+ div.classList.add("gifPreviewBox");
+ const img = document.createElement("img");
+ img.src = category.src;
+ const title = document.createElement("span");
+ title.textContent = category.name;
+ div.append(img, title);
+ gifbox.append(div);
+ div.onclick = (e) => {
+ e.stopImmediatePropagation();
+ search.value = category.name;
+ searchBox();
+ };
+ }
+ menu.append(search, gifbox);
+ search.focus();
+ }
async TBEmojiMenu(rect: DOMRect) {
const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
const p = saveCaretPosition(typebox);
if (!p) return;
const original = MarkDown.getText();
- console.log(original);
const emoji = await Emoji.emojiPicker(
-0 + rect.right - window.innerWidth,
diff --git a/src/webpage/message.ts b/src/webpage/message.ts
index 733d955..1a4fb44 100644
--- a/src/webpage/message.ts
+++ b/src/webpage/message.ts
@@ -663,8 +663,10 @@ class Message extends SnowFlake {
} else {
this.content.onUpdate = () => {};
const messaged = this.content.makeHTML();
- messagedwrap.classList.add("flexttb");
- messagedwrap.appendChild(messaged);
+ if (!this.embeds.find((_) => _.json.url === messaged.textContent)) {
+ messagedwrap.classList.add("flexttb");
+ messagedwrap.appendChild(messaged);
+ }
}
text.appendChild(messagedwrap);
build.appendChild(text);
diff --git a/src/webpage/style.css b/src/webpage/style.css
index d8f3870..3f86bb5 100644
--- a/src/webpage/style.css
+++ b/src/webpage/style.css
@@ -355,6 +355,9 @@ textarea {
.svg-emoji {
mask: url(/icons/emoji.svg);
}
+.svg-gif {
+ mask: url(/icons/gif.svg);
+}
.svg-edit {
mask: url(/icons/edit.svg);
}
@@ -433,11 +436,19 @@ textarea {
aspect-ratio: 1/1;
flex-shrink: 0;
}
-#emojiTB{
- width:.2in;
- height:.2in;
+#emojiTB {
+ width: 0.2in;
+ height: 0.2in;
cursor: pointer;
flex-shrink: 0;
+ margin-left: 6px;
+}
+#gifTB {
+ width: 0.2in;
+ height: 0.2in;
+ cursor: pointer;
+ flex-shrink: 0;
+ mask-size: 0.2in 0.2in;
}
.selectarrow {
position: absolute;
@@ -1158,7 +1169,7 @@ span.instanceStatus {
flex-shrink: 1;
text-wrap: auto;
overflow-y: auto;
- margin-right: .03in;
+ margin-right: 0.03in;
}
.outerTypeBox {
max-height: 50svh;
@@ -2395,7 +2406,8 @@ fieldset input[type="radio"] {
background: var(--primary-bg);
transition: left 0.3s;
}
- #sideContainDiv, #sideContainDiv.searchDiv {
+ #sideContainDiv,
+ #sideContainDiv.searchDiv {
display: block;
right: -100svw;
width: 100svw;
@@ -2424,7 +2436,8 @@ fieldset input[type="radio"] {
#page:has(#maintoggle:checked) #mainarea {
left: 0;
}
- #page:has(#memberlisttoggle:checked) #sideContainDiv, #sideContainDiv.searchDiv {
+ #page:has(#memberlisttoggle:checked) #sideContainDiv,
+ #sideContainDiv.searchDiv {
right: 0;
}
#page:has(#maintoggle:checked) #maintoggleicon {
@@ -2631,3 +2644,63 @@ fieldset input[type="radio"] {
right: 0.2in;
cursor: pointer;
}
+
+.gifmenu {
+ position: absolute;
+ width: 4.5in;
+ height: 5in;
+ background: var(--secondary-bg);
+ border-radius: 8px;
+}
+.gifPreviewBox {
+ position: relative;
+ width: 2in;
+ margin-bottom: 10px;
+ border-radius: 7px;
+ overflow: hidden;
+ cursor: pointer;
+
+ img {
+ width: 2in;
+ height: 1in;
+ object-fit: cover;
+ }
+ span {
+ top: 0px;
+ left: 0px;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background: #00000099;
+ font-weight: bold;
+ }
+}
+.gifbox {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: space-around;
+ overflow-y: auto;
+ margin: 0.1in;
+ align-items: center;
+}
+.searchGifBar {
+ height: 0.3in;
+ margin: 0.15in 0.15in 0 0.15in;
+ flex-shrink: 0;
+ background: var(--black);
+ border: none;
+ border-radius: 4px;
+ font-size: 0.2in;
+ padding: 0 0.1in;
+}
+.gifBox {
+ img {
+ max-width: 196px;
+ }
+ cursor: pointer;
+ cursor: p;
+}
diff --git a/translations/en.json b/translations/en.json
index 77a6fbb..89fac01 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -149,6 +149,7 @@
"name": "Accessibility",
"roleColors": "Disable role colors"
},
+ "searchGifs": "Search Tenor",
"channel": {
"creating": "Creating channel",
"name": "Channel",