diff options
Diffstat (limited to 'app')
85 files changed, 872 insertions, 845 deletions
diff --git a/app/build.gradle b/app/build.gradle index 5fc249bf..4025568a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -129,6 +129,7 @@ android { main.java.srcDirs += 'src/main/kotlin' test.java.srcDirs += 'src/test/kotlin' androidTest.java.srcDirs += 'src/androidTest/kotlin' + main.assets.srcDirs += ['src/web/assets'] } packagingOptions { diff --git a/app/src/main/assets/.babelrc b/app/src/main/assets/.babelrc deleted file mode 100644 index 7302f727..00000000 --- a/app/src/main/assets/.babelrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "presets": [ - ["env",{ - "targets": { - "browsers": ["android >= 36", "chrome >= 51"] - } - }] - ] -}
\ No newline at end of file diff --git a/app/src/main/assets/.gitignore b/app/src/main/assets/.gitignore deleted file mode 100644 index f195f4ab..00000000 --- a/app/src/main/assets/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.idea/ -node_modules/ -.sass-cache/ -package-lock.json
\ No newline at end of file diff --git a/app/src/main/assets/js/click_a.coffee b/app/src/main/assets/js/click_a.coffee deleted file mode 100644 index e032b4ad..00000000 --- a/app/src/main/assets/js/click_a.coffee +++ /dev/null @@ -1,48 +0,0 @@ -prevented = false - -_frostAClick = (e) -> - - ### - # Commonality; check for valid target - ### - element = e.target or e.srcElement - if element.tagName != "A" - element = element.parentNode - # Notifications is two layers under - if element.tagName != "A" - element = element.parentNode - if element.tagName == "A" - if !prevented - url = element.getAttribute("href") - console.log "Click Intercept #{url}" - # if frost is injected, check if loading the url through an overlay works - if Frost?.loadUrl(url) == true - e.stopPropagation() - e.preventDefault() - else - console.log "Click Intercept Prevented" - return - -### -# On top of the click event, we must stop it for long presses -# Since that will conflict with the context menu -# Note that we only override it on conditions where the context menu -# Will occur -### - -_frostPreventClick = -> - console.log "Click prevented" - prevented = true - return - -document.addEventListener "click", _frostAClick, true -clickTimeout = undefined -document.addEventListener "touchstart", ((e) -> - clickTimeout = setTimeout(_frostPreventClick, 400) - return -), true -document.addEventListener "touchend", ((e) -> - prevented = false - clearTimeout clickTimeout - return -), true diff --git a/app/src/main/assets/js/click_a.js b/app/src/main/assets/js/click_a.js deleted file mode 100644 index e3ea7f31..00000000 --- a/app/src/main/assets/js/click_a.js +++ /dev/null @@ -1,60 +0,0 @@ -"use strict"; - -(function () { - - /* - * On top of the click event, we must stop it for long presses - * Since that will conflict with the context menu - * Note that we only override it on conditions where the context menu - * Will occur - */ - var _frostAClick, _frostPreventClick, clickTimeout, prevented; - - prevented = false; - - _frostAClick = function _frostAClick(e) { - /* - * Commonality; check for valid target - */ - var element, url; - element = e.target || e.srcElement; - if (element.tagName !== "A") { - element = element.parentNode; - } - // Notifications is two layers under - if (element.tagName !== "A") { - element = element.parentNode; - } - if (element.tagName === "A") { - if (!prevented) { - url = element.getAttribute("href"); - console.log("Click Intercept " + url); - // if frost is injected, check if loading the url through an overlay works - if ((typeof Frost !== "undefined" && Frost !== null ? Frost.loadUrl(url) : void 0) === true) { - e.stopPropagation(); - e.preventDefault(); - } - } else { - console.log("Click Intercept Prevented"); - } - } - }; - - _frostPreventClick = function _frostPreventClick() { - console.log("Click prevented"); - prevented = true; - }; - - document.addEventListener("click", _frostAClick, true); - - clickTimeout = void 0; - - document.addEventListener("touchstart", function (e) { - clickTimeout = setTimeout(_frostPreventClick, 400); - }, true); - - document.addEventListener("touchend", function (e) { - prevented = false; - clearTimeout(clickTimeout); - }, true); -}).call(undefined);
\ No newline at end of file diff --git a/app/src/main/assets/js/click_debugger.coffee b/app/src/main/assets/js/click_debugger.coffee deleted file mode 100644 index 057bb207..00000000 --- a/app/src/main/assets/js/click_debugger.coffee +++ /dev/null @@ -1,14 +0,0 @@ -# for desktop only - -_frostAContext = (e) -> - - ### - # Commonality; check for valid target - ### - element = e.target or e.currentTarget or e.srcElement - if !element - return - console.log "Clicked element: #{element.tagName} #{element.className}" - return - -document.addEventListener 'contextmenu', _frostAContext, true diff --git a/app/src/main/assets/js/click_debugger.js b/app/src/main/assets/js/click_debugger.js deleted file mode 100644 index 71db586a..00000000 --- a/app/src/main/assets/js/click_debugger.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -(function () { - // for desktop only - var _frostAContext; - - _frostAContext = function _frostAContext(e) { - /* - * Commonality; check for valid target - */ - var element; - element = e.target || e.currentTarget || e.srcElement; - if (!element) { - return; - } - console.log('Clicked element: ' + element.tagName + ' ' + element.className); - }; - - document.addEventListener('contextmenu', _frostAContext, true); -}).call(undefined);
\ No newline at end of file diff --git a/app/src/main/assets/js/context_a.coffee b/app/src/main/assets/js/context_a.coffee deleted file mode 100644 index 0dca1b7f..00000000 --- a/app/src/main/assets/js/context_a.coffee +++ /dev/null @@ -1,59 +0,0 @@ -# context menu for links -# largely mimics click_a.js -# we will also bind a listener here to notify the activity not to deal with viewpager scrolls -longClick = false - -_frostAContext = (e) -> - Frost?.longClick true - longClick = true - - ### - # Commonality; check for valid target - ### - - element = e.target or e.currentTarget or e.srcElement - if !element - return - if element.tagName != "A" - element = element.parentNode - #Notifications is two layers under - if element.tagName != "A" - element = element.parentNode - if element.tagName == "A" and element.getAttribute("href") != "#" - url = element.getAttribute("href") - if !url - return - text = element.parentNode.innerText - # check if image item exists, first in children and then in parent - image = element.querySelector("[style*=\"background-image: url(\"]") - if !image - image = element.parentNode.querySelector("[style*=\"background-image: url(\"]") - if image - imageUrl = window.getComputedStyle(image, null).backgroundImage.trim().slice(4, -1) - console.log "Context image: #{imageUrl}" - Frost?.loadImage imageUrl, text - e.stopPropagation() - e.preventDefault() - return - # check if true img exists - img = element.querySelector("img[src*=scontent]") - if img - imgUrl = img.src - console.log "Context img #{imgUrl}" - Frost?.loadImage imgUrl, text - e.stopPropagation() - e.preventDefault() - return - console.log "Context Content #{url} #{text}" - Frost?.contextMenu url, text - e.stopPropagation() - e.preventDefault() - return - -document.addEventListener "contextmenu", _frostAContext, true -document.addEventListener "touchend", ((e) -> - if longClick - Frost?.longClick false - longClick = false - return -), true diff --git a/app/src/main/assets/js/context_a.js b/app/src/main/assets/js/context_a.js deleted file mode 100644 index b39a6542..00000000 --- a/app/src/main/assets/js/context_a.js +++ /dev/null @@ -1,83 +0,0 @@ -"use strict"; - -(function () { - // context menu for links - // largely mimics click_a.js - // we will also bind a listener here to notify the activity not to deal with viewpager scrolls - var _frostAContext, longClick; - - longClick = false; - - _frostAContext = function _frostAContext(e) { - /* - * Commonality; check for valid target - */ - var element, image, imageUrl, img, imgUrl, text, url; - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.longClick(true); - } - longClick = true; - element = e.target || e.currentTarget || e.srcElement; - if (!element) { - return; - } - if (element.tagName !== "A") { - element = element.parentNode; - } - //Notifications is two layers under - if (element.tagName !== "A") { - element = element.parentNode; - } - if (element.tagName === "A" && element.getAttribute("href") !== "#") { - url = element.getAttribute("href"); - if (!url) { - return; - } - text = element.parentNode.innerText; - // check if image item exists, first in children and then in parent - image = element.querySelector("[style*=\"background-image: url(\"]"); - if (!image) { - image = element.parentNode.querySelector("[style*=\"background-image: url(\"]"); - } - if (image) { - imageUrl = window.getComputedStyle(image, null).backgroundImage.trim().slice(4, -1); - console.log("Context image: " + imageUrl); - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.loadImage(imageUrl, text); - } - e.stopPropagation(); - e.preventDefault(); - return; - } - // check if true img exists - img = element.querySelector("img[src*=scontent]"); - if (img) { - imgUrl = img.src; - console.log("Context img " + imgUrl); - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.loadImage(imgUrl, text); - } - e.stopPropagation(); - e.preventDefault(); - return; - } - console.log("Context Content " + url + " " + text); - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.contextMenu(url, text); - } - e.stopPropagation(); - e.preventDefault(); - } - }; - - document.addEventListener("contextmenu", _frostAContext, true); - - document.addEventListener("touchend", function (e) { - if (longClick) { - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.longClick(false); - } - longClick = false; - } - }, true); -}).call(undefined);
\ No newline at end of file diff --git a/app/src/main/assets/js/document_watcher.coffee b/app/src/main/assets/js/document_watcher.coffee deleted file mode 100644 index 11cf7d53..00000000 --- a/app/src/main/assets/js/document_watcher.coffee +++ /dev/null @@ -1,24 +0,0 @@ -# emit key once half the viewport is covered - -isReady = -> - if not (document?.body?) - return false - return document.body.scrollHeight > innerHeight + 100 - -if isReady() - console.log("Already ready") - Frost?.isReady() - return - -console.log("Injected document watcher") - -observer = new MutationObserver(() -> - if isReady() - observer.disconnect() - Frost?.isReady() - console.log("Documented surpassed height in #{performance.now()}") -) - -observer.observe document, - childList: true - subtree: true
\ No newline at end of file diff --git a/app/src/main/assets/js/document_watcher.js b/app/src/main/assets/js/document_watcher.js deleted file mode 100644 index 4613dc87..00000000 --- a/app/src/main/assets/js/document_watcher.js +++ /dev/null @@ -1,38 +0,0 @@ -"use strict"; - -(function () { - // emit key once half the viewport is covered - var isReady, observer; - - isReady = function isReady() { - if (!((typeof document !== "undefined" && document !== null ? document.body : void 0) != null)) { - return false; - } - return document.body.scrollHeight > innerHeight + 100; - }; - - if (isReady()) { - console.log("Already ready"); - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.isReady(); - } - return; - } - - console.log("Injected document watcher"); - - observer = new MutationObserver(function () { - if (isReady()) { - observer.disconnect(); - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.isReady(); - } - return console.log("Documented surpassed height in " + performance.now()); - } - }); - - observer.observe(document, { - childList: true, - subtree: true - }); -}).call(undefined);
\ No newline at end of file diff --git a/app/src/main/assets/js/header_badges.coffee b/app/src/main/assets/js/header_badges.coffee deleted file mode 100644 index e9702751..00000000 --- a/app/src/main/assets/js/header_badges.coffee +++ /dev/null @@ -1,4 +0,0 @@ -# bases the header contents if it exists -header = document.getElementById("mJewelNav") -if header != null - Frost?.handleHeader header.outerHTML diff --git a/app/src/main/assets/js/header_badges.js b/app/src/main/assets/js/header_badges.js deleted file mode 100644 index 13447229..00000000 --- a/app/src/main/assets/js/header_badges.js +++ /dev/null @@ -1,14 +0,0 @@ -"use strict"; - -(function () { - // bases the header contents if it exists - var header; - - header = document.getElementById("mJewelNav"); - - if (header !== null) { - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.handleHeader(header.outerHTML); - } - } -}).call(undefined);
\ No newline at end of file diff --git a/app/src/main/assets/js/header_hider.coffee b/app/src/main/assets/js/header_hider.coffee deleted file mode 100644 index 40510c79..00000000 --- a/app/src/main/assets/js/header_hider.coffee +++ /dev/null @@ -1,11 +0,0 @@ -header = document.querySelector('#header') - -if !header - return - -jewel = header.querySelector('#mJewelNav') - -if !jewel - return - -header.style.display = 'none'
\ No newline at end of file diff --git a/app/src/main/assets/js/header_hider.js b/app/src/main/assets/js/header_hider.js deleted file mode 100644 index f29887ee..00000000 --- a/app/src/main/assets/js/header_hider.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -(function () { - var header, jewel; - - header = document.querySelector('#header'); - - if (!header) { - return; - } - - jewel = header.querySelector('#mJewelNav'); - - if (!jewel) { - return; - } - - header.style.display = 'none'; -}).call(undefined);
\ No newline at end of file diff --git a/app/src/main/assets/js/media.coffee b/app/src/main/assets/js/media.coffee deleted file mode 100644 index e9b20ec8..00000000 --- a/app/src/main/assets/js/media.coffee +++ /dev/null @@ -1,29 +0,0 @@ -# we will handle media events -_frostMediaClick = (e) -> - element = e.target or e.srcElement - if !element?.dataset.sigil?.toLowerCase().includes("inlinevideo") - return - - i = 0 - while !element.hasAttribute("data-store") - if ++i > 2 - return - element = element.parentNode - - try - dataStore = JSON.parse(element.dataset.store) - catch e - return - - url = dataStore.src - - if !url || !url.startsWith("http") - return - - console.log "Inline video #{url}" - if Frost?.loadVideo url, dataStore.animatedGifVideo - e.stopPropagation() - e.preventDefault() - return - -document.addEventListener "click", _frostMediaClick, true diff --git a/app/src/main/assets/js/media.js b/app/src/main/assets/js/media.js deleted file mode 100644 index e8bf8a72..00000000 --- a/app/src/main/assets/js/media.js +++ /dev/null @@ -1,38 +0,0 @@ -// Generated by CoffeeScript 2.3.2 -(function() { - // we will handle media events - var _frostMediaClick; - - _frostMediaClick = function(e) { - var dataStore, element, i, ref, url; - element = e.target || e.srcElement; - if (!(element != null ? (ref = element.dataset.sigil) != null ? ref.toLowerCase().includes("inlinevideo") : void 0 : void 0)) { - return; - } - i = 0; - while (!element.hasAttribute("data-store")) { - if (++i > 2) { - return; - } - element = element.parentNode; - } - try { - dataStore = JSON.parse(element.dataset.store); - } catch (error) { - e = error; - return; - } - url = dataStore.src; - if (!url || !url.startsWith("http")) { - return; - } - console.log(`Inline video ${url}`); - if (typeof Frost !== "undefined" && Frost !== null ? Frost.loadVideo(url, dataStore.animatedGifVideo) : void 0) { - e.stopPropagation(); - e.preventDefault(); - } - }; - - document.addEventListener("click", _frostMediaClick, true); - -}).call(this); diff --git a/app/src/main/assets/js/menu.coffee b/app/src/main/assets/js/menu.coffee deleted file mode 100644 index ebc7a879..00000000 --- a/app/src/main/assets/js/menu.coffee +++ /dev/null @@ -1,52 +0,0 @@ -# click menu and move contents to main view -viewport = document.querySelector("#viewport") -root = document.querySelector("#root") -menuA = document.querySelector("#bookmarks_jewel").querySelector("a") -if !viewport - console.log "Menu.js: viewport is null" - Frost?.emit 0 - return -if !root - console.log "Menu.js: root is null" - Frost?.emit 0 - return -if !menuA - console.log "Menu.js: jewel is null" - Frost?.emit 0 - return - -y = new MutationObserver(() -> - viewport.removeAttribute "style" - root.removeAttribute "style" - return -) - -y.observe viewport, attributes: true -y.observe root, attributes: true - -x = new MutationObserver(() -> - menu = document.querySelector(".mSideMenu") - if menu != null - x.disconnect() - console.log "Found side menu" - while root.firstChild - root.removeChild root.firstChild - while menu.childNodes.length - console.log "append" - viewport.appendChild menu.childNodes[0] - Frost?.emit 0 - setTimeout (-> - y.disconnect() - console.log "Unhook styler" - return - ), 500 - return -) -jewel = document.querySelector("#mJewelNav") -if !jewel - console.log "Menu.js: jewel is null" -x.observe jewel, - childList: true - subtree: true - -menuA.click() diff --git a/app/src/main/assets/js/menu.js b/app/src/main/assets/js/menu.js deleted file mode 100644 index 5464865c..00000000 --- a/app/src/main/assets/js/menu.js +++ /dev/null @@ -1,85 +0,0 @@ -// Generated by CoffeeScript 2.3.2 -(function() { - // click menu and move contents to main view - var jewel, menuA, root, viewport, x, y; - - viewport = document.querySelector("#viewport"); - - root = document.querySelector("#root"); - - menuA = document.querySelector("#bookmarks_jewel").querySelector("a"); - - if (!viewport) { - console.log("Menu.js: viewport is null"); - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.emit(0); - } - return; - } - - if (!root) { - console.log("Menu.js: root is null"); - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.emit(0); - } - return; - } - - if (!menuA) { - console.log("Menu.js: jewel is null"); - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.emit(0); - } - return; - } - - y = new MutationObserver(function() { - viewport.removeAttribute("style"); - root.removeAttribute("style"); - }); - - y.observe(viewport, { - attributes: true - }); - - y.observe(root, { - attributes: true - }); - - x = new MutationObserver(function() { - var menu; - menu = document.querySelector(".mSideMenu"); - if (menu !== null) { - x.disconnect(); - console.log("Found side menu"); - while (root.firstChild) { - root.removeChild(root.firstChild); - } - while (menu.childNodes.length) { - console.log("append"); - viewport.appendChild(menu.childNodes[0]); - } - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.emit(0); - } - setTimeout((function() { - y.disconnect(); - console.log("Unhook styler"); - }), 500); - } - }); - - jewel = document.querySelector("#mJewelNav"); - - if (!jewel) { - console.log("Menu.js: jewel is null"); - } - - x.observe(jewel, { - childList: true, - subtree: true - }); - - menuA.click(); - -}).call(this); diff --git a/app/src/main/assets/js/menu_debug.coffee b/app/src/main/assets/js/menu_debug.coffee deleted file mode 100644 index 54b265f4..00000000 --- a/app/src/main/assets/js/menu_debug.coffee +++ /dev/null @@ -1,42 +0,0 @@ -# click menu and move contents to main view -viewport = document.querySelector("#viewport") -root = document.querySelector("#root") -if !viewport - console.log "Menu.js: viewport is null" -if !root - console.log "Menu.js: root is null" -y = new MutationObserver((mutations) -> - viewport.removeAttribute "style" - root.removeAttribute "style" - return -) -y.observe viewport, attributes: true -y.observe root, attributes: true -x = new MutationObserver((mutations) -> - menu = document.querySelector(".mSideMenu") - if menu != null - x.disconnect() - console.log "Found side menu" - while root.firstChild - root.removeChild root.firstChild - while menu.childNodes.length - console.log "append" - viewport.appendChild menu.childNodes[0] - Frost?.handleHtml viewport.outerHTML - setTimeout (-> - y.disconnect() - console.log "Unhook styler" - return - ), 500 - return -) -jewel = document.querySelector("#mJewelNav") -if !jewel - console.log "Menu.js: jewel is null" -x.observe jewel, - childList: true - subtree: true -menuA = document.querySelector("#bookmarks_jewel").querySelector("a") -if !menuA - console.log "Menu.js: jewel is null" -menuA.click() diff --git a/app/src/main/assets/js/menu_debug.js b/app/src/main/assets/js/menu_debug.js deleted file mode 100644 index 7ecbf276..00000000 --- a/app/src/main/assets/js/menu_debug.js +++ /dev/null @@ -1,73 +0,0 @@ -"use strict"; - -(function () { - // click menu and move contents to main view - var jewel, menuA, root, viewport, x, y; - - viewport = document.querySelector("#viewport"); - - root = document.querySelector("#root"); - - if (!viewport) { - console.log("Menu.js: viewport is null"); - } - - if (!root) { - console.log("Menu.js: root is null"); - } - - y = new MutationObserver(function (mutations) { - viewport.removeAttribute("style"); - root.removeAttribute("style"); - }); - - y.observe(viewport, { - attributes: true - }); - - y.observe(root, { - attributes: true - }); - - x = new MutationObserver(function (mutations) { - var menu; - menu = document.querySelector(".mSideMenu"); - if (menu !== null) { - x.disconnect(); - console.log("Found side menu"); - while (root.firstChild) { - root.removeChild(root.firstChild); - } - while (menu.childNodes.length) { - console.log("append"); - viewport.appendChild(menu.childNodes[0]); - } - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.handleHtml(viewport.outerHTML); - } - setTimeout(function () { - y.disconnect(); - console.log("Unhook styler"); - }, 500); - } - }); - - jewel = document.querySelector("#mJewelNav"); - - if (!jewel) { - console.log("Menu.js: jewel is null"); - } - - x.observe(jewel, { - childList: true, - subtree: true - }); - - menuA = document.querySelector("#bookmarks_jewel").querySelector("a"); - - if (!menuA) { - console.log("Menu.js: jewel is null"); - } - - menuA.click(); -}).call(undefined);
\ No newline at end of file diff --git a/app/src/main/assets/js/notif_msg.coffee b/app/src/main/assets/js/notif_msg.coffee deleted file mode 100644 index 1c3f8e38..00000000 --- a/app/src/main/assets/js/notif_msg.coffee +++ /dev/null @@ -1,22 +0,0 @@ -# binds callbacks to an invisible webview to take in the search events -finished = false -x = new MutationObserver((mutations) -> - _f_thread = document.querySelector("#threadlist_rows") - if !_f_thread - return - console.log "Found message threads #{_f_thread.outerHTML}" - Frost?.handleHtml _f_thread.outerHTML - finished = true - x.disconnect() - return -) -x.observe document, - childList: true - subtree: true -setTimeout (-> - if !finished - finished = true - console.log "Message thread timeout cancellation" - Frost?.handleHtml "" - return -), 20000 diff --git a/app/src/main/assets/js/notif_msg.js b/app/src/main/assets/js/notif_msg.js deleted file mode 100644 index 134ad4f0..00000000 --- a/app/src/main/assets/js/notif_msg.js +++ /dev/null @@ -1,37 +0,0 @@ -"use strict"; - -(function () { - // binds callbacks to an invisible webview to take in the search events - var finished, x; - - finished = false; - - x = new MutationObserver(function (mutations) { - var _f_thread; - _f_thread = document.querySelector("#threadlist_rows"); - if (!_f_thread) { - return; - } - console.log("Found message threads " + _f_thread.outerHTML); - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.handleHtml(_f_thread.outerHTML); - } - finished = true; - x.disconnect(); - }); - - x.observe(document, { - childList: true, - subtree: true - }); - - setTimeout(function () { - if (!finished) { - finished = true; - console.log("Message thread timeout cancellation"); - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.handleHtml(""); - } - } - }, 20000); -}).call(undefined);
\ No newline at end of file diff --git a/app/src/main/assets/js/textarea_listener.coffee b/app/src/main/assets/js/textarea_listener.coffee deleted file mode 100644 index 950f663e..00000000 --- a/app/src/main/assets/js/textarea_listener.coffee +++ /dev/null @@ -1,22 +0,0 @@ -# focus listener for textareas -# since swipe to refresh is quite sensitive, we will disable it -# when we detect a user typing -# note that this extends passed having a keyboard opened, -# as a user may still be reviewing his/her post -# swiping should automatically be reset on refresh - -_frostFocus = (e) -> - element = e.target or e.srcElement - console.log "Frost focus", element.tagName - if element.tagName == "TEXTAREA" - Frost?.disableSwipeRefresh true - return - -_frostBlur = (e) -> - element = e.target or e.srcElement - console.log "Frost blur", element.tagName - Frost?.disableSwipeRefresh false - return - -document.addEventListener "focus", _frostFocus, true -document.addEventListener "blur", _frostBlur, true diff --git a/app/src/main/assets/js/textarea_listener.js b/app/src/main/assets/js/textarea_listener.js deleted file mode 100644 index 41d77159..00000000 --- a/app/src/main/assets/js/textarea_listener.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict"; - -(function () { - // focus listener for textareas - // since swipe to refresh is quite sensitive, we will disable it - // when we detect a user typing - // note that this extends passed having a keyboard opened, - // as a user may still be reviewing his/her post - // swiping should automatically be reset on refresh - var _frostBlur, _frostFocus; - - _frostFocus = function _frostFocus(e) { - var element; - element = e.target || e.srcElement; - console.log("Frost focus", element.tagName); - if (element.tagName === "TEXTAREA") { - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.disableSwipeRefresh(true); - } - } - }; - - _frostBlur = function _frostBlur(e) { - var element; - element = e.target || e.srcElement; - console.log("Frost blur", element.tagName); - if (typeof Frost !== "undefined" && Frost !== null) { - Frost.disableSwipeRefresh(false); - } - }; - - document.addEventListener("focus", _frostFocus, true); - - document.addEventListener("blur", _frostBlur, true); -}).call(undefined);
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt index 0caeda1a..a466feec 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt @@ -19,6 +19,7 @@ package com.pitchedapps.frost.injectors import android.content.Context import android.graphics.Color import android.webkit.WebView +import androidx.annotation.VisibleForTesting import ca.allanwang.kau.kotlin.lazyContext import ca.allanwang.kau.utils.adjustAlpha import ca.allanwang.kau.utils.colorToBackground @@ -43,7 +44,8 @@ enum class CssAssets(val folder: String = THEME_FOLDER) : InjectorContract { MATERIAL_LIGHT, MATERIAL_DARK, MATERIAL_AMOLED, MATERIAL_GLASS, CUSTOM, ROUND_ICONS("components") ; - private val file = "${name.toLowerCase(Locale.CANADA)}.css" + @VisibleForTesting + internal val file = "${name.toLowerCase(Locale.CANADA)}.css" /** * Note that while this can be loaded from any thread, it is typically done through [load] diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt index 4b1bde43..e0be7977 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt @@ -18,6 +18,7 @@ package com.pitchedapps.frost.injectors import android.content.Context import android.webkit.WebView +import androidx.annotation.VisibleForTesting import ca.allanwang.kau.kotlin.lazyContext import com.pitchedapps.frost.utils.L import kotlinx.coroutines.Dispatchers @@ -32,11 +33,12 @@ import java.util.Locale * The enum name must match the css file name */ enum class JsAssets : InjectorContract { - MENU, MENU_DEBUG, CLICK_A, CONTEXT_A, MEDIA, HEADER_BADGES, HEADER_HIDER, TEXTAREA_LISTENER, NOTIF_MSG, + MENU, CLICK_A, CONTEXT_A, MEDIA, HEADER_BADGES, HEADER_HIDER, TEXTAREA_LISTENER, NOTIF_MSG, DOCUMENT_WATCHER ; - private val file = "${name.toLowerCase(Locale.CANADA)}.js" + @VisibleForTesting + internal val file = "${name.toLowerCase(Locale.CANADA)}.js" private val injector = lazyContext { try { val content = it.assets.open("js/$file").bufferedReader().use(BufferedReader::readText) diff --git a/app/src/test/kotlin/com/pitchedapps/frost/injectors/CssAssetsTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/injectors/CssAssetsTest.kt new file mode 100644 index 00000000..0613d28e --- /dev/null +++ b/app/src/test/kotlin/com/pitchedapps/frost/injectors/CssAssetsTest.kt @@ -0,0 +1,16 @@ +package com.pitchedapps.frost.injectors + +import java.io.File +import kotlin.test.Test +import kotlin.test.assertTrue + +class CssAssetsTest { + + @Test + fun verifyAssetsExist() { + CssAssets.values().forEach { asset -> + val file = File("src/web/assets/css/${asset.folder}/${asset.file}").absoluteFile + assertTrue(file.exists(), "${asset.name} not found at ${file.path}") + } + } +}
\ No newline at end of file diff --git a/app/src/test/kotlin/com/pitchedapps/frost/injectors/JsAssetsTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/injectors/JsAssetsTest.kt new file mode 100644 index 00000000..eab62b27 --- /dev/null +++ b/app/src/test/kotlin/com/pitchedapps/frost/injectors/JsAssetsTest.kt @@ -0,0 +1,16 @@ +package com.pitchedapps.frost.injectors + +import java.io.File +import kotlin.test.Test +import kotlin.test.assertTrue + +class JsAssetsTest { + + @Test + fun verifyAssetsExist() { + JsAssets.values().forEach { asset -> + val file = File("src/web/assets/js/${asset.file}").absoluteFile + assertTrue(file.exists(), "${asset.name} not found at ${file.path}") + } + } +}
\ No newline at end of file diff --git a/app/src/web/.gitignore b/app/src/web/.gitignore new file mode 100644 index 00000000..76a547ef --- /dev/null +++ b/app/src/web/.gitignore @@ -0,0 +1,25 @@ +node_modules/ +.sass-cache/ +package-lock.json + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml diff --git a/app/src/web/.idea/compiler.xml b/app/src/web/.idea/compiler.xml new file mode 100644 index 00000000..1a2fb332 --- /dev/null +++ b/app/src/web/.idea/compiler.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="TypeScriptCompiler"> + <option name="recompileOnChanges" value="true" /> + </component> +</project>
\ No newline at end of file diff --git a/app/src/web/.idea/encodings.xml b/app/src/web/.idea/encodings.xml new file mode 100644 index 00000000..15a15b21 --- /dev/null +++ b/app/src/web/.idea/encodings.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="Encoding" addBOMForNewFiles="with NO BOM" /> +</project>
\ No newline at end of file diff --git a/app/src/web/.idea/misc.xml b/app/src/web/.idea/misc.xml new file mode 100644 index 00000000..28a804d8 --- /dev/null +++ b/app/src/web/.idea/misc.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="JavaScriptSettings"> + <option name="languageLevel" value="ES6" /> + </component> +</project>
\ No newline at end of file diff --git a/app/src/web/.idea/modules.xml b/app/src/web/.idea/modules.xml new file mode 100644 index 00000000..e2d63b96 --- /dev/null +++ b/app/src/web/.idea/modules.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/.idea/assets.iml" filepath="$PROJECT_DIR$/.idea/assets.iml" /> + </modules> + </component> +</project>
\ No newline at end of file diff --git a/app/src/web/.idea/vcs.xml b/app/src/web/.idea/vcs.xml new file mode 100644 index 00000000..c2365ab1 --- /dev/null +++ b/app/src/web/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="$PROJECT_DIR$/../../.." vcs="Git" /> + </component> +</project>
\ No newline at end of file diff --git a/app/src/web/.idea/watcherTasks.xml b/app/src/web/.idea/watcherTasks.xml new file mode 100644 index 00000000..32d1e6f4 --- /dev/null +++ b/app/src/web/.idea/watcherTasks.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectTasksOptions"> + <TaskOptions isEnabled="true"> + <option name="arguments" value="--no-source-map --update $FileName$:$FileNameWithoutExtension$.css" /> + <option name="checkSyntaxErrors" value="true" /> + <option name="description" /> + <option name="exitCodeBehavior" value="ERROR" /> + <option name="fileExtension" value="scss" /> + <option name="immediateSync" value="true" /> + <option name="name" value="SCSS" /> + <option name="output" value="$FileNameWithoutExtension$.css" /> + <option name="outputFilters"> + <array /> + </option> + <option name="outputFromStdout" value="false" /> + <option name="program" value="sass" /> + <option name="runOnExternalChanges" value="true" /> + <option name="scopeName" value="Project Files" /> + <option name="trackOnlyRoot" value="true" /> + <option name="workingDir" value="$FileDir$" /> + <envs /> + </TaskOptions> + </component> +</project>
\ No newline at end of file diff --git a/app/src/web/README.md b/app/src/web/README.md new file mode 100644 index 00000000..29981033 --- /dev/null +++ b/app/src/web/README.md @@ -0,0 +1,4 @@ +# Frost for Facebook Assets + +This is the root project for the assets, which is primarily css and js. +The assets that will be added to Android are within the `assets` folder. diff --git a/app/src/main/assets/adblock.txt b/app/src/web/assets/adblock.txt index a35d95c8..a35d95c8 100644 --- a/app/src/main/assets/adblock.txt +++ b/app/src/web/assets/adblock.txt diff --git a/app/src/main/assets/css/components/round_icons.css b/app/src/web/assets/css/components/round_icons.css index c765d2ab..c765d2ab 100644 --- a/app/src/main/assets/css/components/round_icons.css +++ b/app/src/web/assets/css/components/round_icons.css diff --git a/app/src/main/assets/css/components/round_icons.scss b/app/src/web/assets/css/components/round_icons.scss index c00fe1bf..c00fe1bf 100644 --- a/app/src/main/assets/css/components/round_icons.scss +++ b/app/src/web/assets/css/components/round_icons.scss diff --git a/app/src/main/assets/css/core/_base.scss b/app/src/web/assets/css/core/_base.scss index 472319fe..472319fe 100644 --- a/app/src/main/assets/css/core/_base.scss +++ b/app/src/web/assets/css/core/_base.scss diff --git a/app/src/main/assets/css/core/_colors.scss b/app/src/web/assets/css/core/_colors.scss index 1411a857..1411a857 100644 --- a/app/src/main/assets/css/core/_colors.scss +++ b/app/src/web/assets/css/core/_colors.scss diff --git a/app/src/main/assets/css/core/_core_bg.scss b/app/src/web/assets/css/core/_core_bg.scss index 21c20bcc..21c20bcc 100644 --- a/app/src/main/assets/css/core/_core_bg.scss +++ b/app/src/web/assets/css/core/_core_bg.scss diff --git a/app/src/main/assets/css/core/_core_border.scss b/app/src/web/assets/css/core/_core_border.scss index c366bc14..c366bc14 100644 --- a/app/src/main/assets/css/core/_core_border.scss +++ b/app/src/web/assets/css/core/_core_border.scss diff --git a/app/src/main/assets/css/core/_core_messenger.scss b/app/src/web/assets/css/core/_core_messenger.scss index 608fc23d..608fc23d 100644 --- a/app/src/main/assets/css/core/_core_messenger.scss +++ b/app/src/web/assets/css/core/_core_messenger.scss diff --git a/app/src/main/assets/css/core/_core_text.scss b/app/src/web/assets/css/core/_core_text.scss index 154cee84..154cee84 100644 --- a/app/src/main/assets/css/core/_core_text.scss +++ b/app/src/web/assets/css/core/_core_text.scss diff --git a/app/src/main/assets/css/core/_main.scss b/app/src/web/assets/css/core/_main.scss index 3e972f93..3e972f93 100644 --- a/app/src/main/assets/css/core/_main.scss +++ b/app/src/web/assets/css/core/_main.scss diff --git a/app/src/main/assets/css/core/_svg.scss b/app/src/web/assets/css/core/_svg.scss index 8c714438..8c714438 100644 --- a/app/src/main/assets/css/core/_svg.scss +++ b/app/src/web/assets/css/core/_svg.scss diff --git a/app/src/main/assets/css/core/core.css b/app/src/web/assets/css/core/core.css index 1d48fa35..1d48fa35 100644 --- a/app/src/main/assets/css/core/core.css +++ b/app/src/web/assets/css/core/core.css diff --git a/app/src/main/assets/css/core/core.scss b/app/src/web/assets/css/core/core.scss index 38086529..38086529 100644 --- a/app/src/main/assets/css/core/core.scss +++ b/app/src/web/assets/css/core/core.scss diff --git a/app/src/main/assets/css/themes/.gitignore b/app/src/web/assets/css/themes/.gitignore index 01d06441..01d06441 100644 --- a/app/src/main/assets/css/themes/.gitignore +++ b/app/src/web/assets/css/themes/.gitignore diff --git a/app/src/main/assets/css/themes/custom.css b/app/src/web/assets/css/themes/custom.css index e38c6de0..e38c6de0 100644 --- a/app/src/main/assets/css/themes/custom.css +++ b/app/src/web/assets/css/themes/custom.css diff --git a/app/src/main/assets/css/themes/custom.scss b/app/src/web/assets/css/themes/custom.scss index 50c029fb..50c029fb 100644 --- a/app/src/main/assets/css/themes/custom.scss +++ b/app/src/web/assets/css/themes/custom.scss diff --git a/app/src/main/assets/css/themes/material_amoled.css b/app/src/web/assets/css/themes/material_amoled.css index c821003e..c821003e 100644 --- a/app/src/main/assets/css/themes/material_amoled.css +++ b/app/src/web/assets/css/themes/material_amoled.css diff --git a/app/src/main/assets/css/themes/material_amoled.scss b/app/src/web/assets/css/themes/material_amoled.scss index 19190126..19190126 100644 --- a/app/src/main/assets/css/themes/material_amoled.scss +++ b/app/src/web/assets/css/themes/material_amoled.scss diff --git a/app/src/main/assets/css/themes/material_dark.css b/app/src/web/assets/css/themes/material_dark.css index 0dc739eb..0dc739eb 100644 --- a/app/src/main/assets/css/themes/material_dark.css +++ b/app/src/web/assets/css/themes/material_dark.css diff --git a/app/src/main/assets/css/themes/material_dark.scss b/app/src/web/assets/css/themes/material_dark.scss index 18b8b461..18b8b461 100644 --- a/app/src/main/assets/css/themes/material_dark.scss +++ b/app/src/web/assets/css/themes/material_dark.scss diff --git a/app/src/main/assets/css/themes/material_glass.css b/app/src/web/assets/css/themes/material_glass.css index 3bf9530f..3bf9530f 100644 --- a/app/src/main/assets/css/themes/material_glass.css +++ b/app/src/web/assets/css/themes/material_glass.css diff --git a/app/src/main/assets/css/themes/material_glass.scss b/app/src/web/assets/css/themes/material_glass.scss index 0c61a38c..0c61a38c 100644 --- a/app/src/main/assets/css/themes/material_glass.scss +++ b/app/src/web/assets/css/themes/material_glass.scss diff --git a/app/src/main/assets/css/themes/material_light.css b/app/src/web/assets/css/themes/material_light.css index c00dd12f..c00dd12f 100644 --- a/app/src/main/assets/css/themes/material_light.css +++ b/app/src/web/assets/css/themes/material_light.css diff --git a/app/src/main/assets/css/themes/material_light.scss b/app/src/web/assets/css/themes/material_light.scss index 7ec58463..7ec58463 100644 --- a/app/src/main/assets/css/themes/material_light.scss +++ b/app/src/web/assets/css/themes/material_light.scss diff --git a/app/src/web/assets/js/click_a.js b/app/src/web/assets/js/click_a.js new file mode 100644 index 00000000..7faafc15 --- /dev/null +++ b/app/src/web/assets/js/click_a.js @@ -0,0 +1,55 @@ +"use strict"; +(function () { + var prevented = false; + var _frostAClick = function (e) { + // check for valid target + var target = e.target || e.currentTarget || e.srcElement; + if (!(target instanceof Element)) { + console.log("No element found"); + return; + } + var element = target; + // Notifications are two layers under + for (var i = 0; i < 2; i++) { + if (element.tagName !== 'A') { + element = element.parentElement; + } + } + if (element.tagName === 'A') { + if (!prevented) { + var url = element.getAttribute('href'); + if (!url || url === '#') { + return; + } + console.log("Click intercept " + url); + // If Frost is injected, check if loading the url through an overlay works + if (Frost.loadUrl(url)) { + e.stopPropagation(); + e.preventDefault(); + } + } + else { + console.log("Click intercept prevented"); + } + } + }; + /* + * On top of the click event, we must stop it for long presses + * Since that will conflict with the context menu + * Note that we only override it on conditions where the context menu + * Will occur + */ + var _frostPreventClick = function () { + console.log("Click _frostPrevented"); + prevented = true; + }; + document.addEventListener('click', _frostAClick, true); + var clickTimeout = undefined; + document.addEventListener('touchstart', function () { + clickTimeout = setTimeout(_frostPreventClick, 400); + }, true); + document.addEventListener('touchend', function () { + prevented = false; + clearTimeout(clickTimeout); + }, true); +}).call(undefined); diff --git a/app/src/web/assets/js/click_a.ts b/app/src/web/assets/js/click_a.ts new file mode 100644 index 00000000..5023610e --- /dev/null +++ b/app/src/web/assets/js/click_a.ts @@ -0,0 +1,57 @@ +(function () { + let prevented = false; + + const _frostAClick = (e: Event) => { + // check for valid target + const target = e.target || e.currentTarget || e.srcElement; + if (!(target instanceof Element)) { + console.log("No element found"); + return + } + let element: Element = target; + // Notifications are two layers under + for (let i = 0; i < 2; i++) { + if (element.tagName !== 'A') { + element = <Element>element.parentElement; + } + } + if (element.tagName === 'A') { + if (!prevented) { + const url = element.getAttribute('href'); + if (!url || url === '#') { + return + } + console.log(`Click intercept ${url}`); + // If Frost is injected, check if loading the url through an overlay works + if (Frost.loadUrl(url)) { + e.stopPropagation(); + e.preventDefault(); + } + } else { + console.log("Click intercept prevented") + } + } + }; + + /* + * On top of the click event, we must stop it for long presses + * Since that will conflict with the context menu + * Note that we only override it on conditions where the context menu + * Will occur + */ + const _frostPreventClick = () => { + console.log("Click _frostPrevented"); + prevented = true; + }; + + document.addEventListener('click', _frostAClick, true); + let clickTimeout: number | undefined = undefined; + document.addEventListener('touchstart', () => { + clickTimeout = setTimeout(_frostPreventClick, 400); + }, true); + document.addEventListener('touchend', () => { + prevented = false; + clearTimeout(clickTimeout) + }, true); +}).call(undefined); + diff --git a/app/src/web/assets/js/click_debugger.js b/app/src/web/assets/js/click_debugger.js new file mode 100644 index 00000000..aab4572d --- /dev/null +++ b/app/src/web/assets/js/click_debugger.js @@ -0,0 +1,14 @@ +"use strict"; +// For desktop only +(function () { + var _frostAContext = function (e) { + // Commonality; check for valid target + var element = e.target || e.currentTarget || e.srcElement; + if (!(element instanceof Element)) { + console.log("No element found"); + return; + } + console.log("Clicked element " + element.tagName + " " + element.className); + }; + document.addEventListener('contextmenu', _frostAContext, true); +}).call(undefined); diff --git a/app/src/web/assets/js/click_debugger.ts b/app/src/web/assets/js/click_debugger.ts new file mode 100644 index 00000000..088271fa --- /dev/null +++ b/app/src/web/assets/js/click_debugger.ts @@ -0,0 +1,15 @@ +// For desktop only + +(function () { + const _frostAContext = (e: Event) => { + // Commonality; check for valid target + const element = e.target || e.currentTarget || e.srcElement; + if (!(element instanceof Element)) { + console.log("No element found"); + return + } + console.log(`Clicked element ${element.tagName} ${element.className}`); + }; + + document.addEventListener('contextmenu', _frostAContext, true); +}).call(undefined); diff --git a/app/src/web/assets/js/context_a.js b/app/src/web/assets/js/context_a.js new file mode 100644 index 00000000..7e636cea --- /dev/null +++ b/app/src/web/assets/js/context_a.js @@ -0,0 +1,68 @@ +"use strict"; +/** + * Context menu for links + * Largely mimics click_a.js + */ +(function () { + var longClick = false; + var _frostAContext = function (e) { + Frost.longClick(true); + longClick = true; + /* + * Commonality; check for valid target + */ + var target = e.target || e.currentTarget || e.srcElement; + if (!(target instanceof Element)) { + console.log("No element found"); + return; + } + var element = target; + // Notifications are two layers under + for (var i = 0; i < 2; i++) { + if (element.tagName != 'A') { + element = element.parentElement; + } + } + if (element.tagName == 'A') { + var url = element.getAttribute('href'); + if (!url || url == '#') { + return; + } + var text = element.parentElement.innerText; + // Check if image item exists, first in children and then in parent + var image = element.querySelector("[style*=\"background-image: url(\"]"); + if (!image) { + image = element.parentElement.querySelector("[style*=\"background-image: url(\"]"); + } + if (image) { + var imageUrl = window.getComputedStyle(image, null).backgroundImage.trim().slice(4, -1); + console.log("Context image: " + imageUrl); + Frost.loadImage(imageUrl, text); + e.stopPropagation(); + e.preventDefault(); + return; + } + // Check if true img exists + var img = element.querySelector("img[src*=scontent]"); + if (img instanceof HTMLMediaElement) { + var imgUrl = img.src; + console.log("Context img: " + imgUrl); + Frost.loadImage(imgUrl, text); + e.stopPropagation(); + e.preventDefault(); + return; + } + console.log("Context content " + url + " " + text); + Frost.contextMenu(url, text); + e.stopPropagation(); + e.preventDefault(); + } + }; + document.addEventListener('contextmenu', _frostAContext, true); + document.addEventListener('touchend', function () { + if (longClick) { + Frost.longClick(false); + longClick = false; + } + }, true); +}).call(undefined); diff --git a/app/src/web/assets/js/context_a.ts b/app/src/web/assets/js/context_a.ts new file mode 100644 index 00000000..16ed33a9 --- /dev/null +++ b/app/src/web/assets/js/context_a.ts @@ -0,0 +1,69 @@ +/** + * Context menu for links + * Largely mimics click_a.js + */ +(function () { + let longClick = false; + const _frostAContext = (e: Event) => { + Frost.longClick(true); + longClick = true; + + /* + * Commonality; check for valid target + */ + const target = e.target || e.currentTarget || e.srcElement; + if (!(target instanceof Element)) { + console.log("No element found"); + return + } + let element: Element = target; + // Notifications are two layers under + for (let i = 0; i < 2; i++) { + if (element.tagName != 'A') { + element = <Element>element.parentElement; + } + } + if (element.tagName == 'A') { + const url = element.getAttribute('href'); + if (!url || url == '#') { + return + } + const text = (<HTMLElement>element.parentElement).innerText; + // Check if image item exists, first in children and then in parent + let image = element.querySelector("[style*=\"background-image: url(\"]"); + if (!image) { + image = (<Element>element.parentElement).querySelector("[style*=\"background-image: url(\"]") + } + if (image) { + const imageUrl = (<String>window.getComputedStyle(image, null).backgroundImage).trim().slice(4, -1); + console.log(`Context image: ${imageUrl}`); + Frost.loadImage(imageUrl, text); + e.stopPropagation(); + e.preventDefault(); + return + } + // Check if true img exists + const img = element.querySelector("img[src*=scontent]"); + if (img instanceof HTMLMediaElement) { + const imgUrl = img.src; + console.log(`Context img: ${imgUrl}`); + Frost.loadImage(imgUrl, text); + e.stopPropagation(); + e.preventDefault(); + return + } + console.log(`Context content ${url} ${text}`); + Frost.contextMenu(url, text); + e.stopPropagation(); + e.preventDefault(); + } + }; + + document.addEventListener('contextmenu', _frostAContext, true); + document.addEventListener('touchend', () => { + if (longClick) { + Frost.longClick(false); + longClick = false + } + }, true); +}).call(undefined); diff --git a/app/src/web/assets/js/document_watcher.js b/app/src/web/assets/js/document_watcher.js new file mode 100644 index 00000000..f3c4ab25 --- /dev/null +++ b/app/src/web/assets/js/document_watcher.js @@ -0,0 +1,24 @@ +"use strict"; +// Emit key once half the viewport is covered +(function () { + var isReady = function () { + return document.body.scrollHeight > innerHeight + 100; + }; + if (isReady()) { + console.log('Already ready'); + Frost.isReady(); + return; + } + console.log('Injected document watcher'); + var observer = new MutationObserver(function () { + if (isReady()) { + observer.disconnect(); + Frost.isReady(); + console.log("Documented surpassed height in " + performance.now()); + } + }); + observer.observe(document, { + childList: true, + subtree: true + }); +}).call(undefined); diff --git a/app/src/web/assets/js/document_watcher.ts b/app/src/web/assets/js/document_watcher.ts new file mode 100644 index 00000000..e671149c --- /dev/null +++ b/app/src/web/assets/js/document_watcher.ts @@ -0,0 +1,27 @@ +// Emit key once half the viewport is covered +(function () { + const isReady = () => { + return document.body.scrollHeight > innerHeight + 100 + }; + + if (isReady()) { + console.log('Already ready'); + Frost.isReady(); + return + } + + console.log('Injected document watcher'); + + const observer = new MutationObserver(() => { + if (isReady()) { + observer.disconnect(); + Frost.isReady(); + console.log(`Documented surpassed height in ${performance.now()}`); + } + }); + + observer.observe(document, { + childList: true, + subtree: true + }) +}).call(undefined); diff --git a/app/src/web/assets/js/header_badges.js b/app/src/web/assets/js/header_badges.js new file mode 100644 index 00000000..daaf540a --- /dev/null +++ b/app/src/web/assets/js/header_badges.js @@ -0,0 +1,8 @@ +"use strict"; +// Fetches the header contents if it exists +(function () { + var header = document.getElementById('mJewelNav'); + if (header) { + Frost.handleHeader(header.outerHTML); + } +}).call(undefined); diff --git a/app/src/web/assets/js/header_badges.ts b/app/src/web/assets/js/header_badges.ts new file mode 100644 index 00000000..473749f2 --- /dev/null +++ b/app/src/web/assets/js/header_badges.ts @@ -0,0 +1,7 @@ +// Fetches the header contents if it exists +(function() { + const header = document.getElementById('mJewelNav'); + if (header) { + Frost.handleHeader(header.outerHTML); + } +}).call(undefined); diff --git a/app/src/web/assets/js/header_hider.js b/app/src/web/assets/js/header_hider.js new file mode 100644 index 00000000..faa9f66d --- /dev/null +++ b/app/src/web/assets/js/header_hider.js @@ -0,0 +1,12 @@ +"use strict"; +(function () { + var header = document.querySelector('#header'); + if (!header) { + return; + } + var jewel = header.querySelector('#mJewelNav'); + if (!jewel) { + return; + } + header.style.display = 'none'; +}).call(undefined); diff --git a/app/src/web/assets/js/header_hider.ts b/app/src/web/assets/js/header_hider.ts new file mode 100644 index 00000000..1a8f27f2 --- /dev/null +++ b/app/src/web/assets/js/header_hider.ts @@ -0,0 +1,17 @@ +(function () { + const header = document.querySelector('#header'); + + if (!header) { + return + } + + const jewel = header.querySelector('#mJewelNav'); + + if (!jewel) { + return + } + + (<HTMLElement>header).style.display = 'none' +}).call(undefined); + + diff --git a/app/src/web/assets/js/media.js b/app/src/web/assets/js/media.js new file mode 100644 index 00000000..571168d6 --- /dev/null +++ b/app/src/web/assets/js/media.js @@ -0,0 +1,43 @@ +"use strict"; +// Handles media events +(function () { + var _frostMediaClick = function (e) { + var target = e.target || e.srcElement; + if (!(target instanceof HTMLElement)) { + return; + } + var element = target; + var dataset = element.dataset; + if (!dataset || !dataset.sigil || dataset.sigil.toLowerCase().indexOf('inlinevideo') == -1) { + return; + } + var i = 0; + while (!element.hasAttribute('data-store')) { + if (++i > 2) { + return; + } + element = element.parentNode; + } + var store = element.dataset.store; + if (!store) { + return; + } + var dataStore; + try { + dataStore = JSON.parse(store); + } + catch (e) { + return; + } + var url = dataStore.src; + // !startsWith; see https://stackoverflow.com/a/36876507/4407321 + if (!url || url.lastIndexOf('http', 0) !== 0) { + return; + } + console.log("Inline video " + url); + if (Frost.loadVideo(url, dataStore.animatedGifVideo || false)) { + e.stopPropagation(); + } + }; + document.addEventListener('click', _frostMediaClick, true); +}).call(undefined); diff --git a/app/src/web/assets/js/media.ts b/app/src/web/assets/js/media.ts new file mode 100644 index 00000000..5b9b1a54 --- /dev/null +++ b/app/src/web/assets/js/media.ts @@ -0,0 +1,47 @@ +// Handles media events +(function () { + const _frostMediaClick = (e: Event) => { + const target = e.target || e.srcElement; + if (!(target instanceof HTMLElement)) { + return + } + let element: HTMLElement = target; + const dataset = element.dataset; + if (!dataset || !dataset.sigil || dataset.sigil.toLowerCase().indexOf('inlinevideo') == -1) { + return + } + let i = 0; + while (!element.hasAttribute('data-store')) { + if (++i > 2) { + return + } + element = <HTMLElement>element.parentNode; + } + const store = element.dataset.store; + if (!store) { + return + } + + let dataStore; + + try { + dataStore = JSON.parse(store) + } catch (e) { + return + } + + const url = dataStore.src; + + // !startsWith; see https://stackoverflow.com/a/36876507/4407321 + if (!url || url.lastIndexOf('http', 0) !== 0) { + return + } + + console.log(`Inline video ${url}`); + if (Frost.loadVideo(url, dataStore.animatedGifVideo || false)) { + e.stopPropagation() + } + }; + + document.addEventListener('click', _frostMediaClick, true); +}).call(undefined); diff --git a/app/src/web/assets/js/menu.js b/app/src/web/assets/js/menu.js new file mode 100644 index 00000000..c30e93cf --- /dev/null +++ b/app/src/web/assets/js/menu.js @@ -0,0 +1,57 @@ +"use strict"; +// Click menu and move contents to main view +(function () { + var viewport = document.querySelector("#viewport"); + var root = document.querySelector("#root"); + var bookmarkJewel = document.querySelector("#bookmarks_jewel"); + if (!viewport || !root || !bookmarkJewel) { + console.log('Menu.js: main elements not found'); + Frost.emit(0); + return; + } + var menuA = bookmarkJewel.querySelector("a"); + if (!menuA) { + console.log('Menu.js: menu links not found'); + Frost.emit(0); + return; + } + var jewel = document.querySelector('#mJewelNav'); + if (!jewel) { + console.log('Menu.js: jewel is null'); + return; + } + var y = new MutationObserver(function () { + viewport.removeAttribute('style'); + root.removeAttribute('style'); + }); + y.observe(viewport, { + attributes: true + }); + y.observe(root, { + attributes: true + }); + var x = new MutationObserver(function () { + var menu = document.querySelector('.mSideMenu'); + if (menu) { + x.disconnect(); + console.log("Found side menu"); + // Transfer elements + while (root.firstChild) { + root.removeChild(root.firstChild); + } + while (menu.childNodes.length) { + viewport.appendChild(menu.childNodes[0]); + } + Frost.emit(0); + setTimeout(function () { + y.disconnect(); + console.log('Unhook styler'); + }, 500); + } + }); + x.observe(jewel, { + childList: true, + subtree: true + }); + menuA.click(); +}).call(undefined); diff --git a/app/src/web/assets/js/menu.ts b/app/src/web/assets/js/menu.ts new file mode 100644 index 00000000..6f9dbf16 --- /dev/null +++ b/app/src/web/assets/js/menu.ts @@ -0,0 +1,59 @@ +// Click menu and move contents to main view +(function () { + const viewport = document.querySelector("#viewport"); + const root = document.querySelector("#root"); + const bookmarkJewel = document.querySelector("#bookmarks_jewel"); + if (!viewport || !root || !bookmarkJewel) { + console.log('Menu.js: main elements not found'); + Frost.emit(0); + return + } + const menuA = bookmarkJewel.querySelector("a"); + if (!menuA) { + console.log('Menu.js: menu links not found'); + Frost.emit(0); + return + } + const jewel = document.querySelector('#mJewelNav'); + if (!jewel) { + console.log('Menu.js: jewel is null'); + return + } + + const y = new MutationObserver(() => { + viewport.removeAttribute('style'); + root.removeAttribute('style'); + }); + + y.observe(viewport, { + attributes: true + }); + y.observe(root, { + attributes: true + }); + + const x = new MutationObserver(() => { + const menu = document.querySelector('.mSideMenu'); + if (menu) { + x.disconnect(); + console.log("Found side menu"); + // Transfer elements + while (root.firstChild) { + root.removeChild(root.firstChild); + } + while (menu.childNodes.length) { + viewport.appendChild(menu.childNodes[0]); + } + Frost.emit(0); + setTimeout(() => { + y.disconnect(); + console.log('Unhook styler'); + }, 500); + } + }); + x.observe(jewel, { + childList: true, + subtree: true + }); + menuA.click(); +}).call(undefined); diff --git a/app/src/web/assets/js/notif_msg.js b/app/src/web/assets/js/notif_msg.js new file mode 100644 index 00000000..20a89e88 --- /dev/null +++ b/app/src/web/assets/js/notif_msg.js @@ -0,0 +1,26 @@ +"use strict"; +// Binds callback to an invisible webview to take in the search events +(function () { + var finished = false; + var x = new MutationObserver(function () { + var _f_thread = document.querySelector('#threadlist_rows'); + if (!_f_thread) { + return; + } + console.log("Found message threads " + _f_thread.outerHTML); + Frost.handleHtml(_f_thread.outerHTML); + finished = true; + x.disconnect(); + }); + x.observe(document, { + childList: true, + subtree: true + }); + setTimeout(function () { + if (!finished) { + finished = true; + console.log('Message thread timeout cancellation'); + Frost.handleHtml(""); + } + }, 20000); +}).call(undefined); diff --git a/app/src/web/assets/js/notif_msg.ts b/app/src/web/assets/js/notif_msg.ts new file mode 100644 index 00000000..b7ce7a19 --- /dev/null +++ b/app/src/web/assets/js/notif_msg.ts @@ -0,0 +1,25 @@ +// Binds callback to an invisible webview to take in the search events +(function () { + let finished = false; + const x = new MutationObserver(() => { + const _f_thread = document.querySelector('#threadlist_rows'); + if (!_f_thread) { + return + } + console.log(`Found message threads ${_f_thread.outerHTML}`); + Frost.handleHtml(_f_thread.outerHTML); + finished = true; + x.disconnect(); + }); + x.observe(document, { + childList: true, + subtree: true + }); + setTimeout(() => { + if (!finished) { + finished = true; + console.log('Message thread timeout cancellation'); + Frost.handleHtml("") + } + }, 20000); +}).call(undefined); diff --git a/app/src/web/assets/js/textarea_listener.js b/app/src/web/assets/js/textarea_listener.js new file mode 100644 index 00000000..9a8783c1 --- /dev/null +++ b/app/src/web/assets/js/textarea_listener.js @@ -0,0 +1,31 @@ +"use strict"; +/* + * focus listener for textareas + * since swipe to refresh is quite sensitive, we will disable it + * when we detect a user typing + * note that this extends passed having a keyboard opened, + * as a user may still be reviewing his/her post + * swiping should automatically be reset on refresh + */ +(function () { + var _frostFocus = function (e) { + var element = e.target || e.srcElement; + if (!(element instanceof Element)) { + return; + } + console.log("FrostJSI focus, " + element.tagName); + if (element.tagName == 'TEXTAREA') { + Frost.disableSwipeRefresh(true); + } + }; + var _frostBlur = function (e) { + var element = e.target || e.srcElement; + if (!(element instanceof Element)) { + return; + } + console.log("FrostJSI blur, " + element.tagName); + Frost.disableSwipeRefresh(false); + }; + document.addEventListener("focus", _frostFocus, true); + document.addEventListener("blur", _frostBlur, true); +}).call(undefined); diff --git a/app/src/web/assets/js/textarea_listener.ts b/app/src/web/assets/js/textarea_listener.ts new file mode 100644 index 00000000..9d5fd388 --- /dev/null +++ b/app/src/web/assets/js/textarea_listener.ts @@ -0,0 +1,31 @@ +/* + * focus listener for textareas + * since swipe to refresh is quite sensitive, we will disable it + * when we detect a user typing + * note that this extends passed having a keyboard opened, + * as a user may still be reviewing his/her post + * swiping should automatically be reset on refresh + */ +(function () { + const _frostFocus = (e: Event) => { + const element = e.target || e.srcElement; + if (!(element instanceof Element)) { + return + } + console.log(`FrostJSI focus, ${element.tagName}`); + if (element.tagName == 'TEXTAREA') { + Frost.disableSwipeRefresh(true); + } + }; + + const _frostBlur = (e: Event) => { + const element = e.target || e.srcElement; + if (!(element instanceof Element)) { + return + } + console.log(`FrostJSI blur, ${element.tagName}`); + Frost.disableSwipeRefresh(false); + }; + document.addEventListener("focus", _frostFocus, true); + document.addEventListener("blur", _frostBlur, true); +}).call(undefined); diff --git a/app/src/main/assets/pgl.yoyo.org.txt b/app/src/web/assets/pgl.yoyo.org.txt index 63d6fa41..63d6fa41 100644 --- a/app/src/main/assets/pgl.yoyo.org.txt +++ b/app/src/web/assets/pgl.yoyo.org.txt diff --git a/app/src/web/assets/typings/frost.d.ts b/app/src/web/assets/typings/frost.d.ts new file mode 100644 index 00000000..a3591f66 --- /dev/null +++ b/app/src/web/assets/typings/frost.d.ts @@ -0,0 +1,27 @@ +declare interface FrostJSI { + loadUrl(url: string | null): boolean + + loadVideo(url: string | null, isGif: boolean): boolean + + reloadBaseUrl(animate: boolean) + + contextMenu(url: string, text: string | null) + + longClick(start: boolean) + + disableSwipeRefresh(disable: boolean) + + loadLogin() + + loadImage(imageUrl: string, text: string | null) + + emit(flag: number) + + isReady() + + handleHtml(html: string | null) + + handleHeader(html: string | null) +} + +declare var Frost: FrostJSI; diff --git a/app/src/web/package.json b/app/src/web/package.json new file mode 100644 index 00000000..c80696b3 --- /dev/null +++ b/app/src/web/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "typescript": "^3.3.1" + } +} diff --git a/app/src/web/tsconfig.json b/app/src/web/tsconfig.json new file mode 100644 index 00000000..711fdcbb --- /dev/null +++ b/app/src/web/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es3", + "allowJs": true, + "skipLibCheck": true, +// "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", +// "resolveJsonModule": true, + "isolatedModules": false, +// "noEmit": true, + // Extras + "strictNullChecks": true, + "noImplicitAny": true, + "allowUnreachableCode": true, + "allowUnusedLabels": true + }, + "include": [ + "assets/js", "assets/typings" + ] +} |