mirror of
https://github.com/AllanWang/Frost-for-Facebook.git
synced 2024-11-09 12:32:30 +01:00
Remove gecko extension and add back old scripts
This commit is contained in:
parent
c3023b0da9
commit
f13b2d2998
@ -42,11 +42,11 @@ internal constructor(
|
||||
theme.inject(view)
|
||||
}
|
||||
|
||||
fun getTheme(): JsInjector {
|
||||
private fun getTheme(): JsInjector {
|
||||
return try {
|
||||
val content =
|
||||
context.assets
|
||||
.open("frostcore/css/facebook/themes/material_glass.css")
|
||||
.open("frost/css/facebook/themes/material_glass.css")
|
||||
.bufferedReader()
|
||||
.use(BufferedReader::readText)
|
||||
JsBuilder().css(content).build()
|
||||
|
@ -50,7 +50,7 @@ enum class JsAssets(private val singleLoad: Boolean = true) : JsInjector {
|
||||
private fun injectorBlocking(context: Context): JsInjector {
|
||||
return try {
|
||||
val content =
|
||||
context.assets.open("frostcore/js/$file").bufferedReader().use(BufferedReader::readText)
|
||||
context.assets.open("frost/js/$file").bufferedReader().use(BufferedReader::readText)
|
||||
JsBuilder().js(content).run { if (singleLoad) single(name) else this }.build()
|
||||
} catch (e: FileNotFoundException) {
|
||||
logger.atWarning().withCause(e).log("JsAssets file not found")
|
||||
|
@ -1,48 +0,0 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "frostcore",
|
||||
"version": "1.0.0",
|
||||
"description": "Core web extension for Frost",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "frost_gecko_core@pitchedapps"
|
||||
}
|
||||
},
|
||||
"background": {
|
||||
"scripts": [
|
||||
"js/background/cookies.js"
|
||||
]
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"js": [
|
||||
"js/frost.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
"*://*.facebook.com/*"
|
||||
],
|
||||
"js": [
|
||||
"js/click_a.js"
|
||||
]
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
"<all_urls>",
|
||||
"activeTab",
|
||||
"contextMenus",
|
||||
"contextualIdentities",
|
||||
"cookies",
|
||||
"history",
|
||||
"management",
|
||||
"tabs",
|
||||
"nativeMessaging",
|
||||
"nativeMessagingFromContent",
|
||||
"geckoViewAddons",
|
||||
"webRequest"
|
||||
]
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"scripts": {
|
||||
"compile": "tsc -p tsconfig.json && sass --no-source-map --style compressed --update scss:assets/frostcore/css",
|
||||
"scss-watch": "sass --no-source-map --style compressed --update scss:assets/frostcore/css --watch"
|
||||
"compile": "tsc -p tsconfig.json && sass --no-source-map --style compressed --update scss:assets/frost/css",
|
||||
"scss-watch": "sass --no-source-map --style compressed --update scss:assets/frost/css --watch"
|
||||
},
|
||||
"license": "MPL-2.0",
|
||||
"repository": {
|
||||
|
43
app-compose/src/web/ts/auto_resize_textarea.ts
Normal file
43
app-compose/src/web/ts/auto_resize_textarea.ts
Normal file
@ -0,0 +1,43 @@
|
||||
// Credits to https://codepen.io/tomhodgins/pen/KgazaE
|
||||
(function () {
|
||||
const classTag = 'frostAutoExpand';
|
||||
const textareas = <NodeListOf<HTMLTextAreaElement>>document.querySelectorAll(`textarea:not(.${classTag})`);
|
||||
|
||||
const dataAttribute = 'data-frost-minHeight';
|
||||
|
||||
const _frostAutoExpand = (el: HTMLElement) => {
|
||||
if (!el.hasAttribute(dataAttribute)) {
|
||||
el.setAttribute(dataAttribute, el.offsetHeight.toString());
|
||||
}
|
||||
// If no height is defined, have min bound to current height;
|
||||
// otherwise we will allow for height decreases in case user deletes text
|
||||
const minHeight = parseInt(el.getAttribute(dataAttribute) ?? '0');
|
||||
|
||||
// Save scroll position prior to height update
|
||||
// See https://stackoverflow.com/a/18262927/4407321
|
||||
const scrollLeft = window.pageXOffset ||
|
||||
(document.documentElement || document.body.parentNode || document.body).scrollLeft;
|
||||
const scrollTop = window.pageYOffset ||
|
||||
(document.documentElement || document.body.parentNode || document.body).scrollTop;
|
||||
|
||||
el.style.height = 'inherit';
|
||||
el.style.height = `${Math.max(el.scrollHeight, minHeight)}px`;
|
||||
|
||||
// Go to original scroll position
|
||||
window.scrollTo(scrollLeft, scrollTop);
|
||||
};
|
||||
function _frostExpandAll() {
|
||||
textareas.forEach(_frostAutoExpand);
|
||||
}
|
||||
textareas.forEach(el => {
|
||||
el.classList.add(classTag)
|
||||
const __frostAutoExpand = () => {
|
||||
_frostAutoExpand(el)
|
||||
};
|
||||
el.addEventListener('paste', __frostAutoExpand)
|
||||
el.addEventListener('input', __frostAutoExpand)
|
||||
el.addEventListener('keyup', __frostAutoExpand)
|
||||
});
|
||||
window.addEventListener('load', _frostExpandAll)
|
||||
window.addEventListener('resize', _frostExpandAll)
|
||||
}).call(undefined);
|
@ -1,44 +0,0 @@
|
||||
async function updateCookies(changeInfo: browser.cookies._OnChangedChangeInfo) {
|
||||
|
||||
const application = "frostBackgroundChannel"
|
||||
|
||||
browser.runtime.sendNativeMessage(application, changeInfo)
|
||||
}
|
||||
|
||||
async function readCookies() {
|
||||
const application = "frostBackgroundChannel"
|
||||
|
||||
browser.runtime.sendNativeMessage(application, 'start cookie fetch')
|
||||
|
||||
// Testing with domains or urls didn't work
|
||||
const cookies = await browser.cookies.getAll({});
|
||||
|
||||
const cookies2 = await browser.cookies.getAll({ storeId: "firefox-container-frost-context-1" })
|
||||
|
||||
const cookieStores = await browser.cookies.getAllCookieStores();
|
||||
|
||||
browser.runtime.sendNativeMessage(application, { name: "cookies", data: cookies.length, stores: cookieStores.map((s) => s.id), data2: cookies2.length, data3: cookies.filter(s => s.storeId != 'firefox-default').length })
|
||||
}
|
||||
|
||||
async function handleMessage(request: any, sender: browser.runtime.MessageSender, sendResponse: (response?: any) => void) {
|
||||
browser.runtime.sendNativeMessage("frostBackgroundChannel", 'pre send')
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
browser.runtime.sendNativeMessage("frostBackgroundChannel", 'post send')
|
||||
|
||||
sendResponse({ received: request, asdf: "asdf" })
|
||||
}
|
||||
|
||||
// Reading cookies with storeId might not be fully supported on Android
|
||||
// https://stackoverflow.com/q/76505000/4407321
|
||||
// Using manifest 3 stopped getAll from working
|
||||
// Reading now always shows storeId as firefox-default
|
||||
// Setting a cookie with a custom container does not seem to work
|
||||
|
||||
// browser.cookies.onChanged.addListener(updateCookies);
|
||||
// browser.tabs.onActivated.addListener(readCookies);
|
||||
// browser.runtime.onStartup.addListener(readCookies);
|
||||
|
||||
// browser.runtime.onMessage.addListener(handleMessage);
|
||||
|
@ -1,7 +1,6 @@
|
||||
(async function () {
|
||||
(function () {
|
||||
let prevented = false;
|
||||
|
||||
|
||||
/**
|
||||
* Go up at most [depth] times, to retrieve a parent matching the provided predicate
|
||||
* If one is found, it is returned immediately.
|
||||
@ -40,29 +39,29 @@
|
||||
/**
|
||||
* Given event and target, return true if handled and false otherwise.
|
||||
*/
|
||||
type EventHandler = (e: Event, target: HTMLElement) => Promise<Boolean>
|
||||
type EventHandler = (e: Event, target: HTMLElement) => Boolean
|
||||
|
||||
const _frostGeneral: EventHandler = async (e, target) => {
|
||||
const _frostGeneral: EventHandler = (e, target) => {
|
||||
// We now disable clicks for the main notification page
|
||||
if (document.getElementById("notifications_list")) {
|
||||
return false
|
||||
}
|
||||
const url = _parentUrl(target, 2);
|
||||
return frost.loadUrl(url);
|
||||
return Frost.loadUrl(url);
|
||||
};
|
||||
|
||||
const _frostLaunchpadClick: EventHandler = async (e, target) => {
|
||||
const _frostLaunchpadClick: EventHandler = (e, target) => {
|
||||
if (!_parentEl(target, 6, (el) => el.id === 'launchpad')) {
|
||||
return false
|
||||
}
|
||||
console.log('Clicked launchpad');
|
||||
const url = _parentUrl(target, 5);
|
||||
return frost.loadUrl(url);
|
||||
return Frost.loadUrl(url);
|
||||
};
|
||||
|
||||
const handlers: EventHandler[] = [_frostLaunchpadClick, _frostGeneral];
|
||||
|
||||
const _frostAClick = async (e: Event) => {
|
||||
const _frostAClick = (e: Event) => {
|
||||
if (prevented) {
|
||||
console.log("Click intercept prevented");
|
||||
return
|
||||
@ -75,9 +74,8 @@
|
||||
console.log("No element found");
|
||||
return
|
||||
}
|
||||
// TODO cannot use await here; copy logic over here
|
||||
for (const h of handlers) {
|
||||
if (await h(e, target)) {
|
||||
if (h(e, target)) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return
|
||||
|
15
app-compose/src/web/ts/click_debugger.ts
Normal file
15
app-compose/src/web/ts/click_debugger.ts
Normal file
@ -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);
|
145
app-compose/src/web/ts/context_a.ts
Normal file
145
app-compose/src/web/ts/context_a.ts
Normal file
@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Context menu for links
|
||||
* Largely mimics click_a.js
|
||||
*/
|
||||
|
||||
(function () {
|
||||
let longClick = false;
|
||||
|
||||
/**
|
||||
* Go up at most [depth] times, to retrieve a parent matching the provided predicate
|
||||
* If one is found, it is returned immediately.
|
||||
* Otherwise, null is returned.
|
||||
*/
|
||||
function _parentEl(el: HTMLElement, depth: number, predicate: (el: HTMLElement) => boolean): HTMLElement | null {
|
||||
for (let i = 0; i < depth + 1; i++) {
|
||||
if (predicate(el)) {
|
||||
return el
|
||||
}
|
||||
const parent = el.parentElement;
|
||||
if (!parent) {
|
||||
return null
|
||||
}
|
||||
el = parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 => {
|
||||
// Emojis and special characters may be images from a span
|
||||
const img = el.querySelector("[style*=\"background-image: url(\"]:not(span)");
|
||||
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) => {
|
||||
const element = _parentEl(target, 2, (el) => el.tagName === 'A');
|
||||
if (!element) {
|
||||
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: EventHandler[] = [_frostImage, _frostCopyComment, _frostCopyPost];
|
||||
|
||||
const _frostAContext = (e: Event) => {
|
||||
Frost.longClick(true);
|
||||
longClick = true;
|
||||
|
||||
/**
|
||||
* Don't handle context events while scrolling
|
||||
*/
|
||||
if (Frost.isScrolling()) {
|
||||
console.log("Skip from scrolling");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
27
app-compose/src/web/ts/document_watcher.ts
Normal file
27
app-compose/src/web/ts/document_watcher.ts
Normal file
@ -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);
|
@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Mobile browsers don't support modules, so I'm creating a shared variable.
|
||||
*
|
||||
* No idea if this is good practice.
|
||||
*/
|
||||
const frost = (function () {
|
||||
const application = "frostChannel"
|
||||
|
||||
async function sendMessage<T>(message: ExtensionModel): Promise<T> {
|
||||
return browser.runtime.sendNativeMessage(application, message)
|
||||
}
|
||||
|
||||
async function loadUrl(url: string | null): Promise<boolean> {
|
||||
if (url == null) return false
|
||||
return sendMessage({ type: "url-click", url: url })
|
||||
}
|
||||
|
||||
return {
|
||||
sendMessage, loadUrl
|
||||
}
|
||||
}).call(undefined);
|
7
app-compose/src/web/ts/header_badges.ts
Normal file
7
app-compose/src/web/ts/header_badges.ts
Normal file
@ -0,0 +1,7 @@
|
||||
// Fetches the header contents if it exists
|
||||
(function() {
|
||||
const header = document.getElementById('header');
|
||||
if (header) {
|
||||
Frost.handleHeader(header.outerHTML);
|
||||
}
|
||||
}).call(undefined);
|
61
app-compose/src/web/ts/horizontal_scrolling.ts
Normal file
61
app-compose/src/web/ts/horizontal_scrolling.ts
Normal file
@ -0,0 +1,61 @@
|
||||
(function () {
|
||||
|
||||
/**
|
||||
* Go up at most [depth] times, to retrieve a parent matching the provided predicate
|
||||
* If one is found, it is returned immediately.
|
||||
* Otherwise, null is returned.
|
||||
*/
|
||||
function _parentEl(el: HTMLElement, depth: number, predicate: (el: HTMLElement) => boolean): HTMLElement | null {
|
||||
for (let i = 0; i < depth + 1; i++) {
|
||||
if (predicate(el)) {
|
||||
return el
|
||||
}
|
||||
const parent = el.parentElement;
|
||||
if (!parent) {
|
||||
return null
|
||||
}
|
||||
el = parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if element can scroll horizontally.
|
||||
* We primarily rely on the overflow-x field.
|
||||
* For performance reasons, we will check scrollWidth first to see if scrolling is a possibility
|
||||
*/
|
||||
function _canScrollHorizontally(el: HTMLElement): boolean {
|
||||
/*
|
||||
* Sometimes the offsetWidth is off by < 10px. We use the multiplier
|
||||
* since the trays are typically more than 2 times greater
|
||||
*/
|
||||
if (el.scrollWidth > el.offsetWidth * 1.2) {
|
||||
return true
|
||||
}
|
||||
const styles = window.getComputedStyle(el);
|
||||
/*
|
||||
* Works well in testing, but on mobile it just shows 'visible'
|
||||
*/
|
||||
return styles.overflowX === 'scroll';
|
||||
}
|
||||
|
||||
const _frostCheckHorizontalScrolling = (e: Event) => {
|
||||
const target = e.target || e.currentTarget || e.srcElement;
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return
|
||||
}
|
||||
const scrollable = _parentEl(target, 5, _canScrollHorizontally) !== null;
|
||||
if (scrollable) {
|
||||
console.log('Pause horizontal scrolling');
|
||||
Frost.allowHorizontalScrolling(false);
|
||||
}
|
||||
};
|
||||
|
||||
const _frostResetHorizontalScrolling = (e: Event) => {
|
||||
Frost.allowHorizontalScrolling(true)
|
||||
};
|
||||
|
||||
document.addEventListener('touchstart', _frostCheckHorizontalScrolling, true);
|
||||
document.addEventListener('touchend', _frostResetHorizontalScrolling, true);
|
||||
}).call(undefined);
|
||||
|
47
app-compose/src/web/ts/media.ts
Normal file
47
app-compose/src/web/ts/media.ts
Normal file
@ -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);
|
25
app-compose/src/web/ts/notif_msg.ts
Normal file
25
app-compose/src/web/ts/notif_msg.ts
Normal file
@ -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);
|
25
app-compose/src/web/ts/scroll_stop.ts
Normal file
25
app-compose/src/web/ts/scroll_stop.ts
Normal file
@ -0,0 +1,25 @@
|
||||
// Listen when scrolling events stop
|
||||
(function () {
|
||||
let scrollTimeout: number | undefined = undefined;
|
||||
let scrolling: boolean = false;
|
||||
|
||||
window.addEventListener('scroll', function (event) {
|
||||
|
||||
if (!scrolling) {
|
||||
Frost.setScrolling(true);
|
||||
scrolling = true;
|
||||
}
|
||||
|
||||
window.clearTimeout(scrollTimeout);
|
||||
|
||||
scrollTimeout = setTimeout(function () {
|
||||
if (scrolling) {
|
||||
Frost.setScrolling(false);
|
||||
scrolling = false;
|
||||
}
|
||||
}, 600);
|
||||
// For our specific use case, we want to release other features pretty far after scrolling stops
|
||||
// For general scrolling use cases, the delta can be much smaller
|
||||
// My assumption for context menus is that the long press is 500ms
|
||||
}, false);
|
||||
}).call(undefined);
|
31
app-compose/src/web/ts/textarea_listener.ts
Normal file
31
app-compose/src/web/ts/textarea_listener.ts
Normal file
@ -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);
|
@ -16,7 +16,7 @@
|
||||
"allowUnreachableCode": true,
|
||||
"allowUnusedLabels": true,
|
||||
"removeComments": true,
|
||||
"outDir": "assets/frostcore/js"
|
||||
"outDir": "assets/frost/js"
|
||||
},
|
||||
"include": [
|
||||
"ts",
|
||||
|
7
app-compose/src/web/typings/browser.d.ts
vendored
7
app-compose/src/web/typings/browser.d.ts
vendored
@ -1,7 +0,0 @@
|
||||
declare namespace browser.runtime {
|
||||
interface Port {
|
||||
postMessage: (message: string) => void;
|
||||
postMessage: (message: ExtensionModel) => void;
|
||||
}
|
||||
function sendNativeMessage(application: string, message: ExtensionModel): Promise<any>;
|
||||
}
|
40
app-compose/src/web/typings/frost.d.ts
vendored
40
app-compose/src/web/typings/frost.d.ts
vendored
@ -1,11 +1,33 @@
|
||||
type TestModel = {
|
||||
type: 'test-model'
|
||||
message: string
|
||||
declare interface FrostJSI {
|
||||
loadUrl(url: string | null): boolean
|
||||
|
||||
loadVideo(url: string | null, isGif: boolean): boolean
|
||||
|
||||
reloadBaseUrl(animate: boolean)
|
||||
|
||||
contextMenu(url: string | null, 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)
|
||||
|
||||
allowHorizontalScrolling(enable: boolean)
|
||||
|
||||
setScrolling(scrolling: boolean)
|
||||
|
||||
isScrolling(): boolean
|
||||
}
|
||||
|
||||
type UrlClickModel = {
|
||||
type: 'url-click'
|
||||
url: string
|
||||
}
|
||||
|
||||
type ExtensionModel = TestModel | UrlClickModel
|
||||
declare var Frost: FrostJSI;
|
||||
|
Loading…
Reference in New Issue
Block a user