diff --git a/package.json b/package.json index ba9d731..b004f8b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cobalt", "description": "save what you love", - "version": "3.6.3", + "version": "3.7", "author": "wukko", "exports": "./src/cobalt.js", "type": "module", diff --git a/src/front/cobalt.css b/src/front/cobalt.css index 9ce5ca1..211211c 100644 --- a/src/front/cobalt.css +++ b/src/front/cobalt.css @@ -66,7 +66,7 @@ a { ::placeholder { color: var(--accent-unhover-2); } -::-webkit-scrollbar { +.switches::-webkit-scrollbar, #popup-content::-webkit-scrollbar { display: none; } :focus-visible { @@ -128,7 +128,7 @@ button:active, cursor: pointer; transform: scale(0.95) } -.imagepicker-image:active { +.picker-image:active { cursor: pointer; transform: scale(0.95) } @@ -422,7 +422,7 @@ input[type="checkbox"] { margin-top: 0.5rem; } .explanation { - padding-top: 1rem; + margin-top: 1rem; width: 100%; font-size: 0.8rem; text-align: left; @@ -503,29 +503,54 @@ input[type="checkbox"] { .button:active .tooltip { display: none; } -.imagepicker-image { +.picker-image { object-fit: cover; width: inherit; height: inherit; } -.imagepicker-image-container { +.picker-image-container { width: 8rem; height: 8rem; margin-bottom: 1rem; background-color: var(--accent-button-bg); - text-align: center; - letter-spacing: -0.2rem; - line-height: 8rem; } -#imagepicker-holder { +.picker-various-container { + height: 20rem; + width: 25rem; + margin-bottom: 1rem; + background-color: var(--accent-button-bg); + position: relative; +} +#picker-holder { display: flex; justify-content: space-between; flex-wrap: wrap; align-content: space-around; } -#popup-imagePicker .explanation { - padding-top: 0!important; - padding-bottom: 1rem; +#picker-holder.various { + justify-content: left; + flex-wrap: unset; + overflow-x: scroll; + gap: 2rem; +} +.imageBlock { + height: 100%; + width: 100%; + position: absolute; + z-index: 9999; +} +.picker-element-name { + position: absolute; + background: var(--background); + color: var(--accent); + padding: 0.3rem 0.6rem; + font-size: 0.8rem; + opacity: 0.7; + margin: 0.4rem; +} +#popup-picker .explanation { + margin-top: 0!important; + margin-bottom: 1rem; } .popup-tabs { padding-top: 0.5rem; @@ -592,7 +617,7 @@ input[type="checkbox"] { width: 60%; } } -@media screen and (max-height: 850px) { +@media screen and (max-height: 605px) { .popup { height: 80% } @@ -611,7 +636,7 @@ input[type="checkbox"] { } @media screen and (max-width: 475px) { .tab { - font-size: 0; + font-size: 0!important; } .tab .emoji { margin-right: 0; @@ -621,17 +646,49 @@ input[type="checkbox"] { } } @media screen and (max-width: 320px) { + #popup-title { + font-size: 1.3rem; + line-height: 2rem; + } .footer-button { - font-size: 0; + font-size: 0!important; } - #cobalt-main-box #bottom button { - font-size: 0; + .switch, .checkbox, .category-title, .subtitle, #popup-desc { + font-size: .75rem; } - .footer-button .emoji, #cobalt-main-box #bottom button .emoji { + .explanation { + font-size: .77rem; + margin-top: 0.8rem; + } + #popup-desc { + line-height: 1.4rem; + } + .changelog-subtitle, #popup-subtitle { + font-size: 0.9rem!important; + } + .category-title { + margin-bottom: 0.8rem; + } + .footer-button .emoji { margin-right: 0; } } @media screen and (max-width: 949px) { + #picker-holder::-webkit-scrollbar { + display: none; + } + #picker-holder.various { + flex-wrap: wrap; + align-content: left; + gap: 0; + overflow-x: hidden; + overflow-y: scroll; + } + .picker-various-container { + width: 100%; + height: 20rem; + max-width: 100%; + } #cobalt-main-box #bottom { flex-direction: column; } @@ -649,7 +706,7 @@ input[type="checkbox"] { .footer-pair .footer-button { width: 100%!important; } - .imagepicker-image-container { + .picker-image-container { height: 7rem; width: 7rem; line-height: 7rem; @@ -687,7 +744,7 @@ input[type="checkbox"] { .popup-title { line-height: inherit; } - .imagepicker-image-container { + .picker-image-container { line-height: 6rem; height: 6rem; width: 6rem; diff --git a/src/front/cobalt.js b/src/front/cobalt.js index 6d0a784..7f49d42 100644 --- a/src/front/cobalt.js +++ b/src/front/cobalt.js @@ -1,5 +1,5 @@ let isIOS = navigator.userAgent.toLowerCase().match("iphone os"); -let version = 12; +let version = 13; let regex = new RegExp(/https:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/); let notification = `
` @@ -141,8 +141,8 @@ function hideAllPopups() { for (let i = 0; i < filter.length; i++) { filter[i].style.visibility = "hidden"; } - eid("imagepicker-holder").innerHTML = ''; - eid("imagepicker-download").href = '/'; + eid("picker-holder").innerHTML = ''; + eid("picker-download").href = '/'; eid("popup-backdrop").style.visibility = "hidden"; } function popup(type, action, text) { @@ -163,19 +163,44 @@ function popup(type, action, text) { eid("pd-download").href = text; eid("pd-copy").setAttribute("onClick", `copy('pd-copy', '${text}')`); break; - case "imagePicker": - eid("imagepicker-download").href = text.url; - for (let i in text.images) { - eid("imagepicker-holder").innerHTML += `
` + case "picker": + switch (text.type) { + case "images": + eid("picker-title").innerHTML = loc.pickerImages; + eid("picker-subtitle").innerHTML = loc.pickerImagesExpl; + if (!eid("popup-picker").classList.contains("scrollable")) eid("popup-picker").classList.add("scrollable"); + if (eid("picker-holder").classList.contains("various")) eid("picker-holder").classList.remove("various"); + eid("picker-download").href = text.audio; + eid("picker-download").style.visibility = "visible" + for (let i in text.arr) { + eid("picker-holder").innerHTML += `` + } + break; + default: + eid("picker-title").innerHTML = loc.pickerDefault; + eid("picker-subtitle").innerHTML = loc.pickerDefaultExpl; + if (eid("popup-picker").classList.contains("scrollable")) eid("popup-picker").classList.remove("scrollable"); + if (!eid("picker-holder").classList.contains("various")) eid("picker-holder").classList.add("various"); + for (let i in text.arr) { + let s = text.arr[i], item; + switch (s.type) { + case "video": + item = `
VIDEO ${Number(i)+1}
` + break; + } + eid("picker-holder").innerHTML += item + } + eid("picker-download").style.visibility = "hidden"; + break; } break; default: break; } } else { - if (type == "imagePicker") { - eid("imagepicker-download").href = '/'; - eid("imagepicker-holder").innerHTML = '' + if (type == "picker") { + eid("picker-download").href = '/'; + eid("picker-holder").innerHTML = '' } } eid("popup-backdrop").style.visibility = vis(action); @@ -261,6 +286,21 @@ function loadSettings() { changeSwitcher(i, sGet(i)) } } +function changeButton(type, text) { + switch (type) { + case 0: //error + eid("url-input-area").disabled = false + eid("url-clear").style.display = "block"; + changeDownloadButton(2, '!!') + popup("error", 1, text); + break; + case 1: //enable back + changeDownloadButton(1, '>>'); + eid("url-clear").style.display = "block"; + eid("url-input-area").disabled = false + break; + } +} async function pasteClipboard() { let t = await navigator.clipboard.readText(); if (regex.test(t)) { @@ -291,64 +331,52 @@ async function download(url) { if (j.url) { switch (j.status) { case "redirect": - changeDownloadButton(2, '>>>') - setTimeout(() => { - changeDownloadButton(1, '>>'); - eid("url-clear").style.display = "block"; - eid("url-input-area").disabled = false - }, 3000) - if (sGet("downloadPopup") == "true") { - popup('download', 1, j.url) + changeDownloadButton(2, '>>>'); + setTimeout(() => { changeButton(1); }, 3000); + sGet("downloadPopup") == "true" ? popup('download', 1, j.url) : window.open(j.url, '_blank'); + break; + case "picker": + if (j.audio && j.url) { + changeDownloadButton(2, '?..') + fetch(`${j.audio}&p=1&origin=front`).then(async (res) => { + let jp = await res.json(); + if (jp.status === "continue") { + changeDownloadButton(2, '>>>'); + popup('picker', 1, { audio: j.audio, arr: j.url, type: j.pickerType }); + setTimeout(() => { changeButton(1) }, 5000); + } else { + changeButton(0, jp.text); + } + }).catch((error) => internetError()); + } else if (j.url) { + changeDownloadButton(2, '>>>'); + popup('picker', 1, { arr: j.url, type: j.pickerType }); + setTimeout(() => { changeButton(1) }, 5000); } else { - window.open(j.url, '_blank'); + changeButton(0, loc.noURLReturned); } break; - case "images": case "stream": changeDownloadButton(2, '?..') fetch(`${j.url}&p=1&origin=front`).then(async (res) => { let jp = await res.json(); - if (jp.status == "continue") { - changeDownloadButton(2, '>>>') - if (j.status === "images") { - popup('imagePicker', 1, { - url: j.url, - images: j.images - }) - } else { - window.location.href = j.url - } - setTimeout(() => { - changeDownloadButton(1, '>>'); - eid("url-clear").style.display = "block"; - eid("url-input-area").disabled = false - }, 5000) + if (jp.status === "continue") { + changeDownloadButton(2, '>>>'); window.location.href = j.url; + setTimeout(() => { changeButton(1) }, 5000); } else { - eid("url-input-area").disabled = false - changeDownloadButton(2, '!!'); - eid("url-clear").style.display = "block"; - popup("error", 1, jp.text); + changeButton(0, jp.text); } }).catch((error) => internetError()); break; default: - eid("url-input-area").disabled = false - changeDownloadButton(2, '!!'); - eid("url-clear").style.display = "block"; - popup("error", 1, loc.unknownStatus); + changeButton(0, loc.unknownStatus); break; } } else { - eid("url-input-area").disabled = false - eid("url-clear").style.display = "block"; - changeDownloadButton(2, '!!') - popup("error", 1, loc.noURLReturned); + changeButton(0, loc.noURLReturned); } } else { - eid("url-input-area").disabled = false - eid("url-clear").style.display = "block"; - changeDownloadButton(2, '!!') - popup("error", 1, j.text); + changeButton(0, j.text); } }).catch((error) => internetError()); } diff --git a/src/localization/languages/en.json b/src/localization/languages/en.json index 08c8a81..de46a19 100644 --- a/src/localization/languages/en.json +++ b/src/localization/languages/en.json @@ -1,7 +1,7 @@ { "name": "english", "substrings": { - "ContactLink": "contact the maintainer/a>" + "ContactLink": "contact the maintainer" }, "strings": { "LinkInput": "paste the link here", @@ -61,7 +61,7 @@ "LinkGitHubChanges": ">> see previous commits and contribute on github", "LinkDonateContact": ">> let me know if currency you want to donate isn't listed", "NoScriptMessage": "{appName} uses javascript for api requests and interactive interface. you have to allow javascript to use this site. i don't have any ads or trackers, pinky promise.", - "DownloadPopupDescriptionIOS": "because you have an ios device, you have to press and hold the download button and then select \"download video\" in appeared popup to save the video. this will be required for as long as apple forces safari webview upon all browser developers on ios.", + "DownloadPopupDescriptionIOS": "on ios devices, you have to press and hold the download button, hide the video preview, and then select \"download linked file\" in appeared popup to save the video. this will be required for as long as apple forces safari webview upon all browser developers on ios.", "DownloadPopupDescription": "download button opens a new tab with requested file. you can disable this popup in settings.", "DownloadPopupWayToSave": "pick a way to save", "ClickToCopy": "press to copy", @@ -101,6 +101,10 @@ "Miscellaneous": "miscellaneous", "ModeToggleAuto": "auto mode", "ModeToggleAudio": "audio mode", - "SettingsDisableNotifications": "hide notification dots" + "SettingsDisableNotifications": "hide notification dots", + "MediaPickerTitle": "pick what to save", + "MediaPickerExplanationPC": "click or right click to download what you want.", + "MediaPickerExplanationPhone": "press or press and hold to download what you want.", + "MediaPickerExplanationPhoneIOS": "press and hold, hide the preview, and then select \"download linked file\" to save." } } diff --git a/src/localization/languages/ru.json b/src/localization/languages/ru.json index 33cf6fe..7553c6c 100644 --- a/src/localization/languages/ru.json +++ b/src/localization/languages/ru.json @@ -62,7 +62,7 @@ "LinkGitHubChanges": ">> смотри предыдущие изменения на гитхабе", "LinkDonateContact": ">> напиши мне, если в этом списке нет подходящей валюты", "NoScriptMessage": "{appName} использует javascript для обработки ссылок и интерактивного интерфейса. ты должен разрешить использование javascript, чтобы пользоваться сайтом. тут нет никаких трекеров или рекламы, обещаю.", - "DownloadPopupDescriptionIOS": "так как у тебя устройство на ios, тебе нужно зажать кнопку \"скачать\" и выбрать что-то похожее на \"сохранить в галерею\" в появившемся окне.", + "DownloadPopupDescriptionIOS": "так как у тебя устройство на ios, тебе нужно зажать кнопку \"скачать\", затем скрыть превью видео и выбрать что-то похожее на \"сохранить файл\" в появившемся окне.", "DownloadPopupDescription": "кнопка скачивания открывает новое окно с файлом. ты можешь отключить выбор метода сохранения файла в настройках.", "DownloadPopupWayToSave": "выбери, как сохранить", "ClickToCopy": "нажми, чтобы скопировать", @@ -104,6 +104,10 @@ "Miscellaneous": "разное", "ModeToggleAuto": "авто режим", "ModeToggleAudio": "аудио режим", - "SettingsDisableNotifications": "скрыть значки уведомлений" + "SettingsDisableNotifications": "скрыть значки уведомлений", + "MediaPickerTitle": "выбери, что сохранить", + "MediaPickerExplanationPC": "кликни, чтобы скачать. также можно скачать через контекстное меню правой кнопки мыши.", + "MediaPickerExplanationPhone": "нажми, или нажми и удерживай, чтобы скачать.", + "MediaPickerExplanationPhoneIOS": "нажми и удерживай, затем скрой превью, и наконец выбери что-то похожее на \"сохранить файл\", чтобы скачать." } } diff --git a/src/modules/api.js b/src/modules/api.js index ffa1cf0..7a63713 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -5,7 +5,7 @@ import { services as patterns } from "./config.js"; import { cleanURL, apiJSON } from "./sub/utils.js"; import { errorUnsupported } from "./sub/errors.js"; import loc from "../localization/manager.js"; -import match from "./match.js"; +import match from "./processing/match.js"; export async function getJSON(originalURL, lang, obj) { try { diff --git a/src/modules/changelog/changelog.json b/src/modules/changelog/changelog.json index 14d2811..fc6ca1c 100644 --- a/src/modules/changelog/changelog.json +++ b/src/modules/changelog/changelog.json @@ -1,9 +1,12 @@ { "current": { - "title": "less disturbance (3.6.2 + 3.6.3)", - "content": "changelog popup no longer annoys you after a major update! this action has been replaced with a notification dot. if you see a red dot, then there's something new.\n\nyour old setting that disabled the changelog popup now applies to notifications.\n\nnew users will see a notification dot instead of an about popup, too. this was mostly done to prevent complications if your browser is set up to clean local storage when you close it.\n\nother changes:\n- popups are now a bit wider, just so more content fits at once.\n- better interface scaling.\n- code is a bit cleaner now.\n- changed twitter api endpoint. there should no longer be any rate limits." + "title": "support for multi media tweets is here! (3.7)", + "content": "{appName} now lets you save any of the videos or gifs in a tweet. even if there are many of them.\n\nsimply paste a link like you'd usually do and {appName} will ask what exactly you want to save.\n\nFIREFOX USERS: if you have strict tracking protection on, you might wanna turn it off for {appName}, or else twitter video previews won't load. firefox filters out twitter image cdn as if it was a tracker, which it's not. it's a false-positive.\n\nhowever, you can leave it on if you're fine with blank squares and video numbers. i have thought of that in prior, you're welcome.\n\nother changes:\n- repurposed ex tiktok-only image picker to be dynamic and adapt depending on content to pick. that's exactly how twitter multi media downloads work.\n- cobalt is now properly viewable on phones with tiny screens, such as first gen iphone se.\n- scrollbars now should be visible only where they're needed.\n- brought back proper twitter api, because other one doesn't have multi media stuff (at least yet).\n- cleaned up some internal files, including main frontend js file.\n- reorganized some files in project directory, now you won't get lost when contributing or just looking through cobalt's code." }, "history": [{ + "title": "less disturbance (3.6.2 + 3.6.3)", + "content": "changelog popup no longer annoys you after a major update! this action has been replaced with a notification dot. if you see a red dot, then there's something new.\n\nyour old setting that disabled the changelog popup now applies to notifications.\n\nnew users will see a notification dot instead of an about popup, too. this was mostly done to prevent complications if your browser is set up to clean local storage when you close it.\n\nother changes:\n- popups are now a bit wider, just so more content fits at once.\n- better interface scaling.\n- code is a bit cleaner now.\n- changed twitter api endpoint. there should no longer be any rate limits." + }, { "title": "improvements all around! (3.6)", "content": "- download mode switcher is moving places, it's now right next to link input area.\n- smart mode has been renamed to auto mode, because this name is easier to understand.\n- all spacings in ui have been evened out. no more eye strain.\n- added support for twitter /video/1 links\n- clipboard button exception has been redone to prepare for adoption of readtext clipboard api in firefox.\n- cobalt is now using different tiktok api endpoint, because previous one got killed, just like the one before.\n- \"other\" settings tab has been cleaned up." }, { diff --git a/src/modules/config.js b/src/modules/config.js index bd09394..f40e170 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -1,7 +1,7 @@ import loadJson from "./sub/loadJSON.js"; const config = loadJson("./src/config.json"); const packageJson = loadJson("./package.json"); -const servicesConfigJson = loadJson("./src/modules/servicesConfig.json"); +const servicesConfigJson = loadJson("./src/modules/processing/servicesConfig.json"); export const services = servicesConfigJson.config, diff --git a/src/modules/pageRender/elements.js b/src/modules/pageRender/elements.js index f1c263d..1b133f1 100644 --- a/src/modules/pageRender/elements.js +++ b/src/modules/pageRender/elements.js @@ -44,11 +44,13 @@ export function popup(obj) { if (Array.isArray(obj.body)) { body = `` for (let i = 0; i < obj.body.length; i++) { - classes = obj.body[i]["classes"] ? obj.body[i]["classes"] : [] - if (i != obj.body.length - 1 && !obj.body[i]["nopadding"]) { - classes.push("desc-padding") + if (obj.body[i]["text"].length > 0) { + classes = obj.body[i]["classes"] ? obj.body[i]["classes"] : [] + if (i != obj.body.length - 1 && !obj.body[i]["nopadding"]) { + classes.push("desc-padding") + } + body += obj.body[i]["raw"] ? obj.body[i]["text"] : `` } - body += obj.body[i]["raw"] ? obj.body[i]["text"] : `` } } return ` diff --git a/src/modules/pageRender/page.js b/src/modules/pageRender/page.js index ee70aa9..db81814 100644 --- a/src/modules/pageRender/page.js +++ b/src/modules/pageRender/page.js @@ -84,8 +84,7 @@ export default function(obj) { }, { text: `${loc(obj.lang, 'AboutSupportedServices')} ${enabledServices}.` }, { - text: obj.lang != "ru" ? `` : "", - raw: true + text: obj.lang != "ru" ? loc(obj.lang, 'FollowTwitter') : "" }, { text: backdropLink(repo, loc(obj.lang, 'LinkGitHubIssues')), classes: ["bottom-link"] @@ -271,14 +270,14 @@ export default function(obj) { }) })} ${popupWithBottomButtons({ - name: "imagePicker", + name: "picker", closeAria: loc(obj.lang, 'AccessibilityClosePopup'), header: { - title: loc(obj.lang, 'ImagePickerTitle'), - explanation: isMobile ? loc(obj.lang, 'ImagePickerExplanationPhone') : loc(obj.lang, 'ImagePickerExplanationPC') + title: `
`, + explanation: `
`, }, - buttons: [`${loc(obj.lang, 'ImagePickerDownloadAudio')}`], - content: '
' + buttons: [`${loc(obj.lang, 'ImagePickerDownloadAudio')}`], + content: '
' })} ${popup({ name: "error", @@ -293,19 +292,6 @@ export default function(obj) { }, body: `
` })} - ${popup({ - name: "info", - standalone: true, - buttonOnly: true, - emoji: emoji("✨", 48, 1), - classes: ["small"], - buttonText: loc(obj.lang, 'ErrorPopupCloseButton'), - header: { - closeAria: loc(obj.lang, 'AccessibilityClosePopup'), - title: "popup title" - }, - body: `` - })}