aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/build.gradle1
-rw-r--r--app/src/main/assets/.babelrc9
-rw-r--r--app/src/main/assets/.gitignore4
-rw-r--r--app/src/main/assets/js/click_a.coffee48
-rw-r--r--app/src/main/assets/js/click_a.js60
-rw-r--r--app/src/main/assets/js/click_debugger.coffee14
-rw-r--r--app/src/main/assets/js/click_debugger.js20
-rw-r--r--app/src/main/assets/js/context_a.coffee59
-rw-r--r--app/src/main/assets/js/context_a.js83
-rw-r--r--app/src/main/assets/js/document_watcher.coffee24
-rw-r--r--app/src/main/assets/js/document_watcher.js38
-rw-r--r--app/src/main/assets/js/header_badges.coffee4
-rw-r--r--app/src/main/assets/js/header_badges.js14
-rw-r--r--app/src/main/assets/js/header_hider.coffee11
-rw-r--r--app/src/main/assets/js/header_hider.js19
-rw-r--r--app/src/main/assets/js/media.coffee29
-rw-r--r--app/src/main/assets/js/media.js38
-rw-r--r--app/src/main/assets/js/menu.coffee52
-rw-r--r--app/src/main/assets/js/menu.js85
-rw-r--r--app/src/main/assets/js/menu_debug.coffee42
-rw-r--r--app/src/main/assets/js/menu_debug.js73
-rw-r--r--app/src/main/assets/js/notif_msg.coffee22
-rw-r--r--app/src/main/assets/js/notif_msg.js37
-rw-r--r--app/src/main/assets/js/textarea_listener.coffee22
-rw-r--r--app/src/main/assets/js/textarea_listener.js35
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt4
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt6
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/injectors/CssAssetsTest.kt16
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/injectors/JsAssetsTest.kt16
-rw-r--r--app/src/web/.gitignore25
-rw-r--r--app/src/web/.idea/compiler.xml6
-rw-r--r--app/src/web/.idea/encodings.xml4
-rw-r--r--app/src/web/.idea/misc.xml6
-rw-r--r--app/src/web/.idea/modules.xml8
-rw-r--r--app/src/web/.idea/vcs.xml6
-rw-r--r--app/src/web/.idea/watcherTasks.xml25
-rw-r--r--app/src/web/README.md4
-rw-r--r--app/src/web/assets/adblock.txt (renamed from app/src/main/assets/adblock.txt)0
-rw-r--r--app/src/web/assets/css/components/round_icons.css (renamed from app/src/main/assets/css/components/round_icons.css)0
-rw-r--r--app/src/web/assets/css/components/round_icons.scss (renamed from app/src/main/assets/css/components/round_icons.scss)0
-rw-r--r--app/src/web/assets/css/core/_base.scss (renamed from app/src/main/assets/css/core/_base.scss)0
-rw-r--r--app/src/web/assets/css/core/_colors.scss (renamed from app/src/main/assets/css/core/_colors.scss)0
-rw-r--r--app/src/web/assets/css/core/_core_bg.scss (renamed from app/src/main/assets/css/core/_core_bg.scss)0
-rw-r--r--app/src/web/assets/css/core/_core_border.scss (renamed from app/src/main/assets/css/core/_core_border.scss)0
-rw-r--r--app/src/web/assets/css/core/_core_messenger.scss (renamed from app/src/main/assets/css/core/_core_messenger.scss)0
-rw-r--r--app/src/web/assets/css/core/_core_text.scss (renamed from app/src/main/assets/css/core/_core_text.scss)0
-rw-r--r--app/src/web/assets/css/core/_main.scss (renamed from app/src/main/assets/css/core/_main.scss)0
-rw-r--r--app/src/web/assets/css/core/_svg.scss (renamed from app/src/main/assets/css/core/_svg.scss)0
-rw-r--r--app/src/web/assets/css/core/core.css (renamed from app/src/main/assets/css/core/core.css)0
-rw-r--r--app/src/web/assets/css/core/core.scss (renamed from app/src/main/assets/css/core/core.scss)0
-rw-r--r--app/src/web/assets/css/themes/.gitignore (renamed from app/src/main/assets/css/themes/.gitignore)0
-rw-r--r--app/src/web/assets/css/themes/custom.css (renamed from app/src/main/assets/css/themes/custom.css)0
-rw-r--r--app/src/web/assets/css/themes/custom.scss (renamed from app/src/main/assets/css/themes/custom.scss)0
-rw-r--r--app/src/web/assets/css/themes/material_amoled.css (renamed from app/src/main/assets/css/themes/material_amoled.css)0
-rw-r--r--app/src/web/assets/css/themes/material_amoled.scss (renamed from app/src/main/assets/css/themes/material_amoled.scss)0
-rw-r--r--app/src/web/assets/css/themes/material_dark.css (renamed from app/src/main/assets/css/themes/material_dark.css)0
-rw-r--r--app/src/web/assets/css/themes/material_dark.scss (renamed from app/src/main/assets/css/themes/material_dark.scss)0
-rw-r--r--app/src/web/assets/css/themes/material_glass.css (renamed from app/src/main/assets/css/themes/material_glass.css)0
-rw-r--r--app/src/web/assets/css/themes/material_glass.scss (renamed from app/src/main/assets/css/themes/material_glass.scss)0
-rw-r--r--app/src/web/assets/css/themes/material_light.css (renamed from app/src/main/assets/css/themes/material_light.css)0
-rw-r--r--app/src/web/assets/css/themes/material_light.scss (renamed from app/src/main/assets/css/themes/material_light.scss)0
-rw-r--r--app/src/web/assets/js/click_a.js55
-rw-r--r--app/src/web/assets/js/click_a.ts57
-rw-r--r--app/src/web/assets/js/click_debugger.js14
-rw-r--r--app/src/web/assets/js/click_debugger.ts15
-rw-r--r--app/src/web/assets/js/context_a.js68
-rw-r--r--app/src/web/assets/js/context_a.ts69
-rw-r--r--app/src/web/assets/js/document_watcher.js24
-rw-r--r--app/src/web/assets/js/document_watcher.ts27
-rw-r--r--app/src/web/assets/js/header_badges.js8
-rw-r--r--app/src/web/assets/js/header_badges.ts7
-rw-r--r--app/src/web/assets/js/header_hider.js12
-rw-r--r--app/src/web/assets/js/header_hider.ts17
-rw-r--r--app/src/web/assets/js/media.js43
-rw-r--r--app/src/web/assets/js/media.ts47
-rw-r--r--app/src/web/assets/js/menu.js57
-rw-r--r--app/src/web/assets/js/menu.ts59
-rw-r--r--app/src/web/assets/js/notif_msg.js26
-rw-r--r--app/src/web/assets/js/notif_msg.ts25
-rw-r--r--app/src/web/assets/js/textarea_listener.js31
-rw-r--r--app/src/web/assets/js/textarea_listener.ts31
-rw-r--r--app/src/web/assets/pgl.yoyo.org.txt (renamed from app/src/main/assets/pgl.yoyo.org.txt)0
-rw-r--r--app/src/web/assets/typings/frost.d.ts27
-rw-r--r--app/src/web/package.json5
-rw-r--r--app/src/web/tsconfig.json24
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"
+ ]
+}