aboutsummaryrefslogtreecommitdiff
path: root/app/src/web/ts
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2019-05-01 16:05:19 -0700
committerAllan Wang <me@allanwang.ca>2019-05-01 16:05:19 -0700
commit58f4f9298b09081b3c937227828824849057a835 (patch)
tree18d2bf44637b52c9f68817253fc37d4ca699daef /app/src/web/ts
parent576cc1a451a16f2d82ee1e41e83c420a85ded47e (diff)
parentda93672c2ed6b54e0e7119a6b55715185112df3e (diff)
downloadfrost-58f4f9298b09081b3c937227828824849057a835.tar.gz
frost-58f4f9298b09081b3c937227828824849057a835.tar.bz2
frost-58f4f9298b09081b3c937227828824849057a835.zip
Merge dev
Diffstat (limited to 'app/src/web/ts')
-rw-r--r--app/src/web/ts/click_a.ts57
-rw-r--r--app/src/web/ts/click_debugger.ts15
-rw-r--r--app/src/web/ts/context_a.ts125
-rw-r--r--app/src/web/ts/document_watcher.ts27
-rw-r--r--app/src/web/ts/header_badges.ts7
-rw-r--r--app/src/web/ts/media.ts47
-rw-r--r--app/src/web/ts/menu.ts59
-rw-r--r--app/src/web/ts/notif_msg.ts25
-rw-r--r--app/src/web/ts/textarea_listener.ts31
9 files changed, 393 insertions, 0 deletions
diff --git a/app/src/web/ts/click_a.ts b/app/src/web/ts/click_a.ts
new file mode 100644
index 00000000..5023610e
--- /dev/null
+++ b/app/src/web/ts/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/ts/click_debugger.ts b/app/src/web/ts/click_debugger.ts
new file mode 100644
index 00000000..088271fa
--- /dev/null
+++ b/app/src/web/ts/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/ts/context_a.ts b/app/src/web/ts/context_a.ts
new file mode 100644
index 00000000..5eec7611
--- /dev/null
+++ b/app/src/web/ts/context_a.ts
@@ -0,0 +1,125 @@
+/**
+ * Context menu for links
+ * Largely mimics click_a.js
+ */
+
+(function () {
+ let longClick = false;
+
+ /**
+ * Given event and target, return true if handled and false otherwise.
+ */
+ type EventHandler = (e: Event, target: HTMLElement) => Boolean
+
+ const _frostCopyComment: EventHandler = (e, target) => {
+ if (!target.hasAttribute('data-commentid')) {
+ return false;
+ }
+ const text = target.innerText;
+ console.log(`Copy comment ${text}`);
+ Frost.contextMenu(null, text);
+ return true;
+ };
+
+ /**
+ * Posts should click a tag, with two parents up being div.story_body_container
+ */
+ const _frostCopyPost: EventHandler = (e, target) => {
+ if (target.tagName !== 'A') {
+ return false;
+ }
+ const parent1 = target.parentElement;
+ if (!parent1 || parent1.tagName !== 'DIV') {
+ return false;
+ }
+ const parent2 = parent1.parentElement;
+ if (!parent2 || !parent2.classList.contains('story_body_container')) {
+ return false;
+ }
+ const url = target.getAttribute('href');
+ const text = parent1.innerText;
+ console.log(`Copy post ${url} ${text}`);
+ Frost.contextMenu(url, text);
+ return true;
+ };
+
+ const _getImageStyleUrl = (el: Element): string | null => {
+ const img = el.querySelector("[style*=\"background-image: url(\"]");
+ if (!img) {
+ return null
+ }
+ return (<String>window.getComputedStyle(img, null).backgroundImage).trim().slice(4, -1);
+ };
+
+ /**
+ * Opens image activity for posts with just one image
+ */
+ const _frostImage: EventHandler = (e, target) => {
+ let element: Element = target;
+ // Notifications are two layers under
+ for (let i = 0; i < 2; i++) {
+ if (element.tagName !== 'A') {
+ element = <Element>element.parentElement;
+ } else {
+ break
+ }
+ }
+ if (element.tagName !== 'A') {
+ return false;
+ }
+ const url = element.getAttribute('href');
+ if (!url || url === '#') {
+ return false;
+ }
+ const text = (<HTMLElement>element.parentElement).innerText;
+ // Check if image item exists, first in children and then in parent
+ const imageUrl = _getImageStyleUrl(element) || _getImageStyleUrl(<Element>element.parentElement);
+ if (imageUrl) {
+ console.log(`Context image: ${imageUrl}`);
+ Frost.loadImage(imageUrl, text);
+ return true;
+ }
+ // 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);
+ return true;
+ }
+ console.log(`Context content ${url} ${text}`);
+ Frost.contextMenu(url, text);
+ return true;
+ };
+
+ const handlers = [_frostImage, _frostCopyComment, _frostCopyPost];
+
+ 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 HTMLElement)) {
+ console.log("No element found");
+ return
+ }
+ for (const h of handlers) {
+ if (h(e, target)) {
+ e.stopPropagation();
+ e.preventDefault();
+ return
+ }
+ }
+ };
+
+ document.addEventListener('contextmenu', _frostAContext, true);
+ document.addEventListener('touchend', () => {
+ if (longClick) {
+ Frost.longClick(false);
+ longClick = false
+ }
+ }, true);
+}).call(undefined);
diff --git a/app/src/web/ts/document_watcher.ts b/app/src/web/ts/document_watcher.ts
new file mode 100644
index 00000000..e671149c
--- /dev/null
+++ b/app/src/web/ts/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/ts/header_badges.ts b/app/src/web/ts/header_badges.ts
new file mode 100644
index 00000000..473749f2
--- /dev/null
+++ b/app/src/web/ts/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/ts/media.ts b/app/src/web/ts/media.ts
new file mode 100644
index 00000000..5b9b1a54
--- /dev/null
+++ b/app/src/web/ts/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/ts/menu.ts b/app/src/web/ts/menu.ts
new file mode 100644
index 00000000..6f9dbf16
--- /dev/null
+++ b/app/src/web/ts/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/ts/notif_msg.ts b/app/src/web/ts/notif_msg.ts
new file mode 100644
index 00000000..b7ce7a19
--- /dev/null
+++ b/app/src/web/ts/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/ts/textarea_listener.ts b/app/src/web/ts/textarea_listener.ts
new file mode 100644
index 00000000..062f5bf6
--- /dev/null
+++ b/app/src/web/ts/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);