`
tagBox.parentElement.style.position = 'relative'
tagBox.parentElement.appendChild(modal)
draw_categoryarea()
removeUnlistedButton = modal.querySelector('.gts-remove-unlisted')
searchBox = document.querySelector('#gts-search')
searchBox.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || (event.key === 'Tab' && foundTags.length === 1)) {
event.preventDefault()
event.stopPropagation()
}
})
searchBox.addEventListener('keyup', (event) => {
if (event.key === 'Tab' && foundTags.length === 1) {
add_tag(foundTags[0])
} else if (event.key === 'Enter') {
let tag = event.target.value.trim()
tag = tag.replaceAll(' ', '.')
if (tag.length > 0) {
add_tag(tag)
}
}
let query = event.target.value.trim()
if (query === '') {
query = SEPERATOR
}
query = query.toLowerCase()
draw_categoryarea(query)
if (event.code === 'Escape') {
hide_gts()
}
})
draw_presetarea()
draw_favoritearea()
draw_currenttagsarea()
}
function insert_preset_button() {
presetButton = document.createElement('button')
presetButton.id = 'gts-add-preset'
presetButton.classList.add('gts-add-preset')
presetButton.type = 'button'
presetButton.setAttribute('tabindex', '-1')
presetButton.textContent = 'Add Preset'
if (!isGroupPage) {
tagBox.after(presetButton)
if (!isUploadPage) presetButton.style.marginLeft = '5px'
} else {
const div = document.createElement('div')
const submitButton = tagBox.nextElementSibling
div.style.cssText = `
display: flex;
justify-content: end;
align-items: center;
`
tagBox.after(div)
div.append(presetButton, submitButton)
}
presetButton.addEventListener('click', () => {
const preset = tagBox.value.trim()
add_preset(preset).then(() => {
draw_presetarea()
})
})
}
// actions
function add_tag(tag) {
const currentValue = tagBox.value.trim()
tag = tag.trim().toLowerCase()
if (currentValue === "") {
tagBox.value = tag
} else {
let tags = currentValue.split(TAGSEPERATOR)
if (!tags.includes(tag)) {
tags.push(tag)
}
tagBox.value = tags.join(TAGSEPERATOR)
}
tagBox.focus()
tagBox.setSelectionRange(-1, -1)
searchBox.focus()
searchBox.value = ''
draw_categoryarea()
}
async function add_favorite(tag) {
const currentFavorites = currentFavoritesDict[currentUploadCategory] || []
if (currentFavorites.length < 9 && !currentFavorites.includes(tag)) {
currentFavoritesDict[currentUploadCategory] = currentFavorites.concat(tag)
return GM.setValue('gts_favorites', currentFavoritesDict)
}
}
async function remove_favorite(tag) {
const currentFavorites = currentFavoritesDict[currentUploadCategory] || []
let _temp = []
for (const fav of currentFavorites) {
if (fav !== tag) {
_temp.push(fav)
}
}
currentFavoritesDict[currentUploadCategory] = _temp
return GM.setValue('gts_favorites', currentFavoritesDict)
}
function parse_text_to_tag_list(text) {
let tagList = []
for (let tag of text.split(TAGSEPERATOR.trim())) {
tag = tag.trim()
if (tag !== '') {
tagList.push(tag)
}
}
return tagList
}
async function add_preset(rawPreset) {
let preset = parse_text_to_tag_list(rawPreset)
const currentPresets = currentPresetsDict[currentUploadCategory] || []
preset = preset.join(TAGSEPERATOR)
if (!currentPresets.includes(preset)) {
currentPresetsDict[currentUploadCategory] = currentPresets.concat(preset)
return GM.setValue('gts_presets', currentPresetsDict)
}
}
async function remove_preset(preset) {
let _temp = []
const currentPresets = currentPresetsDict[currentUploadCategory] || []
for (const pres of currentPresets) {
if (pres !== preset) {
_temp.push(pres)
}
}
currentPresetsDict[currentUploadCategory] = _temp
return GM.setValue('gts_presets', currentPresetsDict)
}
function check_favorite() {
return document.querySelector('#gts-favorite-checkbox').checked
}
function check_remove() {
return document.querySelector('#gts-remove-checkbox').checked
}
function check_gts_element(element) {
if (typeof element === 'undefined' || !(element instanceof HTMLElement)) {
return false
}
const _id = element.id || ''
const _class = element.getAttribute('class') || ''
return (_id === 'tags' ||
_id.includes('gts-') ||
_class.includes('gts-'))
}
function hide_gts() {
modal.style.display = 'none'
presetButton.style.display = 'none'
}
function show_gts() {
if (!check_gts_active()) {
modal.style.display = 'grid'
presetButton.style.display = 'inline'
searchBox.focus()
draw_currenttagsarea()
}
}
function hide_indices() {
document.querySelector('#gts-categoryarea').classList.add('hide-idx')
showIndicess = false
}
function show_indices() {
document.querySelector('#gts-categoryarea').classList.remove('hide-idx')
showIndicess = true
}
function check_gts_active() {
return (modal.style.display === 'grid') && (presetButton.style.display === 'block')
}
function check_query_exists() {
// returns true if there is query
return searchBox.value.trim() !== ''
}
function get_index_from_code(code) {
if (code.indexOf('Digit') === 0) {
return parseInt(code.replaceAll('Digit', ''), 10) - 1
}
return null
}
function get_current_upload_category(defaultCategory = 'Games') {
if (isSearchPage || isRequestPage) {
const list = document.querySelectorAll('input[type=checkbox][name^=filter_cat]:checked')
if (list.length < 1) return defaultCategory
const lastChecked = list[list.length - 1]
return {
1: "Games",
2: "Applications",
3: "E-Books",
4: "OST",
}[/\d/.exec(lastChecked.id)[0]]
}
let categoryElement = document.querySelector('#categories')
if (categoryElement) {
return categoryElement.value
}
categoryElement = document.querySelector('#group_nofo_bigdiv .head:first-child')
const s = categoryElement.innerText.trim()
if (s.indexOf('Application') !== -1) {
return 'Applications'
} else if (s.indexOf('OST') !== -1) {
return 'OST'
} else if (s.indexOf('Book') !== -1) {
return 'E-Books'
} else if (s.indexOf('Game') !== -1) {
return 'Games'
}
return defaultCategory
}
function check_hotkey_prefix(event, type) {
let eventModifiers = [event.shiftKey, event.altKey, event.ctrlKey, event.metaKey]
const targetKeys = hotkeyPrefixes[type].split(' + ').map((key) => key.trim().toLowerCase())
for (let i = 0; i < modifiers.length; i++) {
if (targetKeys.includes(modifiers[i]) !== eventModifiers[i]) {
return false
}
}
return true
}
function get_hotkey_target(event, type) {
for (const [idx, hotkey] of Object.entries(hotkeys[type])) {
let normalKeys = []
const targetKeys = hotkey.split('+').map((s) => {
key = s.toLowerCase().trim()
if (!modifiers.includes(key)) {
normalKeys.push(key)
}
return key
})
let modifierMismatch = false
let eventModifiers = [event.shiftKey, event.altKey, event.ctrlKey, event.metaKey]
for (let i = 0; i < modifiers.length; i++) {
if (targetKeys.includes(modifiers[i]) !== eventModifiers[i]) {
modifierMismatch = true
break
}
}
if (modifierMismatch) {
continue
}
if (normalKeys.length > 0 && (
!(normalKeys.includes(event.key.toLowerCase()) || normalKeys.includes(event.code.toLowerCase()))
)) {
continue
}
return idx
}
return null
}
function register_hotkeys(type) {
if (['favorite', 'preset'].includes(type) && !windowEvents.includes(`hotkey-${type}`)) {
window.addEventListener('keydown', (event) => {
if (!check_gts_active()) {
return
}
const target = get_hotkey_target(event, type)
let currentList
if (type === 'favorite') {
if (check_query_exists()) {
return // return early if query is active
}
currentList = currentFavoritesDict[currentUploadCategory] || []
} else if (type === 'preset') {
// if we're working with presets,
// we proceed anyway
currentList = currentPresetsDict[currentUploadCategory] || []
}
if (target !== null) {
if (target < currentList.length) {
event.preventDefault()
if (type === 'favorite') {
add_tag(currentList[target])
} else if (type === 'preset') {
tagBox.value = currentList[target]
tagBox.focus()
}
searchBox.focus()
}
}
}, true)
} else if (type === 'show_indices' && !windowEvents.includes(!`hotkey-${type}`)) {
window.addEventListener('keydown', (event) => {
if (!check_gts_active() || !check_query_exists()) {
return
}
if (check_hotkey_prefix(event, type)) {
show_indices()
const idx = get_index_from_code(event.code)
if (idx !== null) {
document.querySelector(`a.gts-tag[data-tag-idx="${idx}"]`).click()
event.preventDefault()
}
}
}, true)
window.addEventListener('keyup', () => {
if (showIndicess) {
hide_indices()
}
}, true)
}
windowEvents.push(`hotkey-${type}`)
}
// initialiser
function init() {
const modal = document.querySelector('#gts-selector')
if (modal) {
modal.remove()
}
currentUploadCategory = get_current_upload_category()
allCurrentCategoryTags = categoryKeys[currentUploadCategory].flatMap(c => categoryDict[c])
if (isGroupPage) {
const groupTagEls = Array.from(document.querySelectorAll("a[href^='torrents.php?taglist=']"))
const unlistedTagEls = groupTagEls
.filter(tag => !allCurrentCategoryTags.includes(tag.textContent) && !specialTags.some(t => t === tag.textContent))
for (const groupTagEl of groupTagEls) {
if (unlistedTagEls.includes(groupTagEl)) {
groupTagEl.classList.add('gts-unlisted-tag')
}
}
}
tagBox = document.getElementById('tags') || document.querySelector('input[name=tags]')
tagBox.setAttribute('onfocus', 'this.value = this.value')
insert_modal()
insert_preset_button()
if (!windowEvents.includes('click')) {
window.addEventListener('click', (event) => {
if (!check_gts_element(event.target)) {
setTimeout(() => {
if (!check_gts_element(document.activeElement)) {
hide_gts()
}
}, 50)
}
}, true)
windowEvents.push('click')
}
if (!windowEvents.includes('esc')) {
window.addEventListener('keyup', (event) => {
if (event.code === 'Escape') {
if (check_gts_active()) {
hide_gts()
}
}
}, true)
windowEvents.push('esc')
}
tagBox.addEventListener('focus', show_gts)
tagBox.addEventListener('click', show_gts)
tagBox.addEventListener('keyup', (event) => {
if (event.code !== 'Escape') {
draw_currenttagsarea()
}
})
register_hotkeys('favorite')
register_hotkeys('preset')
register_hotkeys('show_indices')
draw_currenttagsarea()
// watch for value change in the tagBox
observe_element(tagBox, 'value', (_) => {
draw_currenttagsarea()
})
}
if (isUploadPage) {
const observerTarget = document.querySelector('#dynamic_form')
let observer = new MutationObserver(init)
const observerConfig = {childList: true, attributes: false, subtree: false}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init)
observer.observe(observerTarget, observerConfig)
} else {
init()
observer.observe(observerTarget, observerConfig)
}
} else {
init()
if (isSearchPage || isRequestPage) {
document.querySelector('.cat_list').addEventListener('change', e => {
if (e.target.checked) {
init()
}
})
}
else if (isCreateRequestPage) { // it doesn't use dynamic form
init()
document.getElementById('categories').addEventListener('change', () => {
init()
})
}
}
} else {
let hotkeys = (GM_getValue('gts_hotkeys')) || defaultHotkeys
let hotkeyPrefixes = (GM_getValue('gts_hotkey_prefixes')) || defaulthotkeyPrefixes
GM_addStyle(`
#gts-save-settings {
min-width: 200px;
}
.gts-hotkey-grid {
display: grid;
column-gap: 1em;
grid-template-columns: repeat(2, fit-content(400px)) 1fr;
}
.gts-hotkey-grid h1 {
font-size: 1.1em;
}
.gts-hotkey-col div {
margin-bottom: 0.25em;
}
`)
async function init() {
let colhead = document.createElement('tr')
colhead.classList.add('colhead_dark')
colhead.innerHTML = '
GGn Tag Selector'
const lastTr = document.querySelector('#userform > table > tbody > tr:last-child')
lastTr.before(colhead)
let hotkeyTr = document.createElement('tr')
let html = `
Hotkeys
`
for (const [type, cHotkeys] of Object.entries(hotkeys)) {
html += `
${titlecase(type)}
`
for (const [idx, hotkey] of cHotkeys.entries()) {
html += `
${idx + 1}.
`
}
html += `
`
}
html += `
Index peeker
Hold to display indices of the filtered results (modifier keys/their combinations only).
Use the key along with a digit (1-9) to add the tag according to the index.
Note that peeking/adding by index will not work if the filter query is empty.
How to set combos/keys
To set a combo, use the keys joined by the plus sign. For example, Ctrl + Shift + 1 is ctrl + shift + digit1