-
Notifications
You must be signed in to change notification settings - Fork 12
/
PostViewer.ts
498 lines (421 loc) · 21.5 KB
/
PostViewer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
import { Danbooru } from "../../components/api/Danbooru";
import { Page, PageDefinition } from "../../components/data/Page";
import { ModuleController } from "../../components/ModuleController";
import { Post } from "../../components/post/Post";
import { PostActions } from "../../components/post/PostActions";
import { RE6Module, Settings } from "../../components/RE6Module";
import { Util } from "../../components/utility/Util";
import { RISSizeLimit } from "../../components/utility/UtilSize";
import { ThemeCustomizer } from "../general/ThemeCustomizer";
/**
* Add various symbols to the titlebar depending on the posts state
*/
export class PostViewer extends RE6Module {
private post: Post;
public constructor() {
super([PageDefinition.post, PageDefinition.changes, PageDefinition.iqdb], true);
const reqPage = PageDefinition.post;
this.registerHotkeys(
{ keys: "hotkeyUpvote", fnct: this.triggerUpvote, page: reqPage },
{ keys: "hotkeyUpvoteNU", fnct: this.triggerUpvoteNU, page: reqPage },
{ keys: "hotkeyDownvote", fnct: this.triggerDownvote, page: reqPage },
{ keys: "hotkeyDownvoteNU", fnct: this.triggerDownvoteNU, page: reqPage },
{ keys: "hotkeyFavorite", fnct: this.toggleFavorite, page: reqPage },
{ keys: "hotkeyAddFavorite", fnct: this.addFavorite, page: reqPage },
{ keys: "hotkeyRemoveFavorite", fnct: this.removeFavorite, page: reqPage },
{ keys: "hotkeyHideNotes", fnct: () => { this.toggleNotes(); }, page: reqPage },
{ keys: "hotkeyNewNote", fnct: this.switchNewNote, page: reqPage },
{ keys: "hotkeyAddSet", fnct: this.openSetDialogue, page: reqPage },
{ keys: "hotkeyAddPool", fnct: this.openPoolDialogue, page: reqPage },
{ keys: "hotkeyToggleSetLatest", fnct: this.toggleSetLatest, page: reqPage },
{ keys: "hotkeyAddSetLatest", fnct: this.addSetLatest, page: reqPage },
{ keys: "hotkeyRemoveSetLatest", fnct: this.removeSetLatest, page: reqPage },
{ keys: "hotkeyAddSetCustom1", fnct: () => { this.addSetCustom("hotkeyAddSetCustom1_data"); }, page: reqPage },
{ keys: "hotkeyAddSetCustom2", fnct: () => { this.addSetCustom("hotkeyAddSetCustom2_data"); }, page: reqPage },
{ keys: "hotkeyAddSetCustom3", fnct: () => { this.addSetCustom("hotkeyAddSetCustom3_data"); }, page: reqPage },
{ keys: "hotkeyAddSetCustom4", fnct: () => { this.addSetCustom("hotkeyAddSetCustom4_data"); }, page: reqPage },
{ keys: "hotkeyAddSetCustom5", fnct: () => { this.addSetCustom("hotkeyAddSetCustom5_data"); }, page: reqPage },
{ keys: "hotkeyOpenHistory", fnct: this.openImageHistory, },
{ keys: "hotkeyOpenArtist", fnct: this.openArtist, page: reqPage },
{ keys: "hotkeyOpenSource", fnct: this.openSource, page: reqPage },
{ keys: "hotkeyOpenParent", fnct: this.openParent, page: reqPage },
{ keys: "hotkeyToggleRel", fnct: this.toggleRelSection, page: reqPage },
{ keys: "hotkeyOpenIQDB", fnct: this.openIQDB },
{ keys: "hotkeyOpenAPI", fnct: this.openAPI, },
{ keys: "hotkeyOpenSauceNAO", fnct: this.openSauceNAO },
{ keys: "hotkeyOpenKheina", fnct: this.openKheina },
{ keys: "hotkeyOpenGoogle", fnct: this.openGoogle },
{ keys: "hotkeyOpenYandex", fnct: this.openYandex },
{ keys: "hotkeyOpenDerpibooru", fnct: this.openDerpibooru },
{ keys: "hotkeyOpenInkbunny", fnct: this.openInkbunny },
);
}
/**
* Returns a set of default settings values
* @returns Default settings
*/
protected getDefaultSettings(): Settings {
return {
enabled: true,
hotkeyUpvote: "w", // vote up on the current post
hotkeyUpvoteNU: "", // vote up, don't unvote
hotkeyDownvote: "s", // vote down on the current post
hotkeyDownvoteNU: "", // vote down, don't unvote
hotkeyFavorite: "f", // toggle the favorite state of the post
hotkeyAddFavorite: "", // add current post to favorites
hotkeyRemoveFavorite: "", // remove current post from favorites
hotkeyHideNotes: "o", // toggle note visibility
hotkeyNewNote: "p", // add new note
hotkeyAddSet: "", // open the "add to set" dialogue
hotkeyAddPool: "", // open the "add to pool" dialogue
hotkeyToggleSetLatest: "", // toggles the current post's set
hotkeyAddSetLatest: "", // adds the current post to the last used set
hotkeyRemoveSetLatest: "", // removes the current post from the last used set
hotkeyAddSetCustom1: "",
hotkeyAddSetCustom1_data: "0",
hotkeyAddSetCustom2: "",
hotkeyAddSetCustom2_data: "0",
hotkeyAddSetCustom3: "",
hotkeyAddSetCustom3_data: "0",
hotkeyAddSetCustom4: "",
hotkeyAddSetCustom4_data: "0",
hotkeyAddSetCustom5: "",
hotkeyAddSetCustom5_data: "0",
hotkeyOpenHistory: "", // Opens the post history for the current image
hotkeyOpenArtist: "", // Opens the search page for the post's artist
hotkeyOpenSource: "", // Opens the first image source in a new tab
hotkeyOpenParent: "", // Opens the parent/child post, if there is one
hotkeyToggleRel: "", // Toggles the relationship section
hotkeyOpenIQDB: "", // Searches for similar posts
hotkeyOpenAPI: "", // Shows raw post data
hotkeyOpenSauceNAO: "", // Open SauceNAO search
hotkeyOpenKheina: "", // Open SauceNAO search
hotkeyOpenGoogle: "", // Open SauceNAO search
hotkeyOpenYandex: "", // Open SauceNAO search
hotkeyOpenDerpibooru: "", // Open SauceNAO search
hotkeyOpenInkbunny: "", // Open SauceNAO search
upvoteOnFavorite: true, // add an upvote when adding the post to favorites
hideNotes: false, // should the notes be hidden by default
moveChildThumbs: true, // Moves the parent/child post thumbnails to under the searchbar
boldenTags: true, // Restores the classic bold look on non-general tags
betterImageSearch: true, // Uses larger version of the image for reverse image searches
};
}
/**
* Creates the module's structure.
* Should be run immediately after the constructor finishes.
*/
public create(): void {
super.create();
if (!Page.matches(PageDefinition.post)) return;
this.post = Post.getViewingPost()
// Move the add to set / pool buttons
const $addToContainer = $("<div>").attr("id", "image-add-links").insertAfter("div#image-download-link");
$("li#add-to-set-list > a")
.addClass("image-add-set")
.addClass("button btn-neutral")
.html("+ Set")
.appendTo($addToContainer);
$("li#add-to-pool-list > a")
.addClass("image-add-pool")
.addClass("button btn-neutral")
.html("+ Pool")
.appendTo($addToContainer);
// Create the Note Toggle button
const $noteToggleContainer = $("<div>").attr("id", "image-toggle-notes").insertAfter("div#image-add-links");
$("<a>")
.attr({
"id": "image-note-button",
"href": "#",
})
.addClass("button btn-neutral")
.html(this.fetchSettings("hideNotes") ? "Notes: OFF" : "Notes: ON")
.appendTo($noteToggleContainer)
.on("click", (event) => {
event.preventDefault();
this.toggleNotes();
});
const $noteContainer = $("#note-container")
.css("display", "")
.attr("data-hidden", this.fetchSettings("hideNotes"));
// Move the note preview to root
$("#note-preview").insertBefore("#page");
$("#translate")
.appendTo("#image-toggle-notes")
.addClass("button btn-neutral")
.html("+ Note")
.on("click", async () => {
if (!await Danbooru.Note.TranslationMode.active()) return;
if ($noteContainer.attr("data-hidden") == "true")
this.toggleNotes(false);
});
// Move child/parent indicator, leave others as is, like marked for deletion
if (this.fetchSettings("moveChildThumbs"))
$(".parent-children")
.addClass("children-moved")
.insertAfter($("#search-box"));
// Add a "left" option for navbars
if (Page.matches(PageDefinition.post)) {
const navbarContainer = $("#nav-links-top");
if (navbarContainer.length > 0) {
navbarContainer.clone().insertAfter("#re621-search").attr("id", "nav-links-left");
for (const el of $("#nav-links-left").find("li.post-nav").get()) {
const navbar = $(el);
const lower = $("<div>").addClass("nav-left-down").appendTo(navbar);
navbar.find("div.post-nav-spacer").remove();
navbar.find(".first, .prev, .next, .last").appendTo(lower);
}
}
if (Util.LS.getItem("re621-theme-nav") == "left") {
$("body").attr("re621-data-th-nav", "true");
ThemeCustomizer.trigger("switch.navbar", "left");
} else $("body").attr("re621-data-th-nav", "false");
}
// Bolden the tags
this.toggleBoldenedTags(this.fetchSettings<boolean>("boldenTags"));
// Listen to favorites button click
$("#add-fav-button, #add-to-favorites").on("click", () => {
if (!this.fetchSettings("upvoteOnFavorite") || $("a.post-vote-up-link span").hasClass("score-positive")) return;
Danbooru.Post.vote(this.post.id, 1, true);
});
// Fix reverse image search links
// Google 20MB
// SauceNAO 15MB
// Derpibooru 20MB
// Kheina 8MB weakest link
if ($("#post-related-images").length == 0) {
$("<section>")
.attr("id", "post-related-images")
.html(`
<h1>Related</h1>
<ul>
<li><a href="/post_sets?post_id=${this.post.id}">Sets with this post</a></li>
<li><a href="/iqdb_queries?post_id=${this.post.id}">Visually similar on E6</a></li>
</ul>
`)
.insertAfter("#post-history")
} else {
const useSample = !this.fetchSettings("betterImageSearch");
const links = [
["/post_sets?post_id=" + this.post.id, "Sets with this post", true],
["/iqdb_queries?post_id=" + this.post.id, "Visually similar on E6", true],
null,
["https://saucenao.com/search.php?url=" + this.getSourceLink(RISSizeLimit.SauceNAO, useSample), "SauceNAO"],
["https://kheina.com/?url=" + this.getSourceLink(RISSizeLimit.Kheina, useSample), "Kheina"],
["https://www.google.com/searchbyimage?image_url=" + this.getSourceLink(RISSizeLimit.Google, useSample)+"&client=e621", "Google"],
["https://yandex.ru/images/search?url=" + this.getSourceLink(RISSizeLimit.Yandex, useSample) + "&rpt=imageview", "Yandex"],
null,
["https://derpibooru.org/search/reverse?url=" + this.getSourceLink(RISSizeLimit.Derpibooru, useSample), "Derpibooru"],
["https://inkbunny.net/search_process.php?text=" + this.post.file.md5 + "&md5=yes", "Inkbunny"],
];
$("#post-related-images ul").html(() => {
const htmlContent = [];
for (const link of links)
htmlContent.push(
link == null
? `<li class="list-break"></li>`
: `<li><a href="${link[0]}" ${link[2] ? "" : `target="_blank" rel="noopener noreferrer"`} lookup="${link[1]}">${link[1]}</a></li>`);
return htmlContent.join("\n");
});
}
}
private getSourceLink(limit: RISSizeLimit, useSample: boolean): string {
// console.log(limit.toString());
return (useSample || !limit.test(this.post))
? this.post.file.sample
: this.post.file.original;
}
/** Toggles the boldened look on sidebar tags */
public toggleBoldenedTags(state = true): void {
$("#tag-list").toggleClass("tags-boldened", state);
}
/** Emulates a click on the upvote button */
private triggerUpvote(): void {
Danbooru.Post.vote(Post.getViewingPost().id, 1);
}
/** Same as above, but does not unvote */
private triggerUpvoteNU(): void {
const id = Post.getViewingPost().id;
PostActions.vote(id, 1, true).then((response) => {
if (!response.success) {
Danbooru.error("An error occurred while processing votes");
return;
}
$("span.post-vote-up-" + id)
.removeClass("score-neutral")
.addClass("score-positive");
$("span.post-vote-down-" + id)
.removeClass("score-negative")
.addClass("score-neutral");
$("span.post-score-" + id)
.removeClass("score-positive score-negative score-neutral")
.addClass(PostViewer.getScoreClass(response.score))
.attr("title", response.up + " up / " + response.down + " down")
.html(response.score + "")
if (response.score > 0) Danbooru.notice("Post Score Updated");
});
}
/** Emulates a click on the downvote button */
private triggerDownvote(): void {
Danbooru.Post.vote(Post.getViewingPost().id, -1);
}
/** Same as above, but does not unvote */
private triggerDownvoteNU(): void {
const id = Post.getViewingPost().id;
PostActions.vote(id, -1, true).then((response) => {
if (!response.success) {
Danbooru.error("An error occurred while processing votes");
return;
}
$("span.post-vote-down-" + id)
.addClass("score-negative")
.removeClass("score-neutral");
$("span.post-vote-up-" + id)
.removeClass("score-positive")
.addClass("score-neutral");
$("span.post-score-" + id)
.removeClass("score-positive score-negative score-neutral")
.addClass(PostViewer.getScoreClass(response.score))
.attr("title", response.up + " up / " + response.down + " down")
.html(response.score + "")
if (response.score < 0) Danbooru.notice("Post Score Updated");
});
}
private static getScoreClass(score: number): string {
if (score > 0) return "score-positive";
if (score < 0) return "score-negative";
return "score-neutral";
}
/** Toggles the favorite state */
private toggleFavorite(): void {
if ($("div.fav-buttons").hasClass("fav-buttons-false")) { $("#add-fav-button")[0].click(); }
else { $("#remove-fav-button")[0].click(); }
}
/** Adds the post to favorites, does not remove it */
private addFavorite(): void {
if ($("div.fav-buttons").hasClass("fav-buttons-false")) {
$("#add-fav-button")[0].click();
}
}
/** Removes the post from favorites, does not add it */
private removeFavorite(): void {
if (!$("div.fav-buttons").hasClass("fav-buttons-false")) {
$("#remove-fav-button")[0].click();
}
}
/** Switches the notes container to its opposite state */
private async toggleNotes(updateSettings = true): Promise<void> {
const module = ModuleController.get(PostViewer),
hideNotes = module.fetchSettings("hideNotes");
if (hideNotes) {
$("#note-container").attr("data-hidden", "false");
$("a#image-note-button").html("Notes: ON");
} else {
$("#note-container").attr("data-hidden", "true");
$("a#image-note-button").html("Notes: OFF");
}
if (updateSettings)
await module.pushSettings("hideNotes", !hideNotes);
}
/** Toggles the note editing interface */
private async switchNewNote(): Promise<void> {
$("#note-container").attr("data-hidden", "false");
$("a#image-note-button").html("Notes: ON");
await ModuleController.get(PostViewer).pushSettings("hideNotes", false);
Danbooru.Note.TranslationMode.toggle();
}
/** Opens the dialog to add the post to the set */
private openSetDialogue(): void {
$("a#set")[0].click();
}
/** Adds or removes the current post from the latest used set */
private toggleSetLatest(): void {
const lastSet = parseInt(window.localStorage.getItem("set"));
if (!lastSet) {
Danbooru.error(`Error: no set selected`);
return;
}
PostActions.toggleSet(lastSet, Post.getViewingPost().id);
}
/** Adds the current post to the latest used set */
private addSetLatest(): void {
const lastSet = parseInt(window.localStorage.getItem("set"));
if (!lastSet) {
Danbooru.error(`Error: no set selected`);
return;
}
PostActions.addSet(lastSet, Post.getViewingPost().id);
}
/** Removes the current post frp, the latest used set */
private removeSetLatest(): void {
const lastSet = parseInt(window.localStorage.getItem("set"));
if (!lastSet) {
Danbooru.error(`Error: no set selected`);
return;
}
PostActions.removeSet(lastSet, Post.getViewingPost().id);
}
/** Adds the current post to the set defined in the config */
private addSetCustom(dataKey: string): void {
PostActions.addSet(
this.fetchSettings<number>(dataKey),
Post.getViewingPost().id
);
}
/** Opens the dialog to add the post to the pool */
private async openPoolDialogue(): Promise<void> {
await Util.sleep(50);
$("a#pool")[0].click();
}
/** Redirects the page to the post history */
private openImageHistory(): void {
if (Page.matches(PageDefinition.post)) {
location.href = "/post_versions?search[post_id]=" + Page.getPageID();
} else if (Page.matches(PageDefinition.iqdb)) {
if (!Page.hasQueryParameter("post_id")) return;
location.href = "/post_versions?search[post_id]=" + Page.getQueryParameter("post_id");
} else if (Page.matches(PageDefinition.changes)) {
if (!Page.hasQueryParameter("search[post_id]")) return;
location.href = "/posts/" + Page.getQueryParameter("search[post_id]");
}
}
private static lookupClick(query: string): void {
const lookup = $(query).first();
if (lookup.length == 0) return;
lookup[0].click();
}
/** Searches for other works by the artist, if there is one */
private openArtist(): void { PostViewer.lookupClick("li.category-1 a.search-tag"); }
/** Opens the first source link */
private openSource(): void { PostViewer.lookupClick("div.source-link a"); }
/** Opens the first source link */
private openParent(): void { PostViewer.lookupClick("#has-parent-relationship-preview a, #has-children-relationship-preview a"); }
/** Toggles the visibility of the parent/child thumbnails */
private toggleRelSection(): void { PostViewer.lookupClick("#has-children-relationship-preview-link, #has-parent-relationship-preview-link"); }
/** Opens IQDB page for the current page */
private openIQDB(): void {
if (Page.matches(PageDefinition.post)) {
location.href = "/iqdb_queries?post_id=" + Page.getPageID();
} else if (Page.matches(PageDefinition.iqdb)) {
if (!Page.hasQueryParameter("post_id")) return;
location.href = "/posts/" + Page.getQueryParameter("post_id");
} else if (Page.matches(PageDefinition.changes)) {
if (!Page.hasQueryParameter("search[post_id]")) return;
location.href = "/iqdb_queries?post_id=" + Page.getQueryParameter("search[post_id]");
}
}
private static openSourceLookup(source: "SauceNAO" | "Kheina" | "Google" | "Yandex" | "Derpibooru" | "Inkbunny"): void {
if (!Page.matches(PageDefinition.post)) return;
const link = $(`a[lookup="${source}"]`).first();
if (!link.length) return;
link[0].click();
}
private openSauceNAO(): void { PostViewer.openSourceLookup("SauceNAO"); }
private openKheina(): void { PostViewer.openSourceLookup("Kheina"); }
private openGoogle(): void { PostViewer.openSourceLookup("Google"); }
private openYandex(): void { PostViewer.openSourceLookup("Yandex"); }
private openDerpibooru(): void { PostViewer.openSourceLookup("Derpibooru"); }
private openInkbunny(): void { PostViewer.openSourceLookup("Inkbunny"); }
/** Opens the raw API data for the current post */
private openAPI(): void { location.href = location.origin + location.pathname + ".json" + location.search; }
}