mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-05-22 08:34:05 +02:00
Refactor plugin bootstrapping
This commit is contained in:
parent
e7479ef9e3
commit
008de87e55
Browser
Adapters
Notification
Resources
TweetDuck.csprojlib/TweetLib.Core
Browser
Features
Notifications
Plugins
@ -26,6 +26,11 @@ public bool RunFile(string file) {
|
|||||||
return RunFile(frame, file);
|
return RunFile(frame, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RunBootstrap(string moduleNamespace) {
|
||||||
|
using IFrame frame = browser.GetMainFrame();
|
||||||
|
RunBootstrap(frame, moduleNamespace);
|
||||||
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
public static void RunScript(IFrame frame, string script, string identifier) {
|
public static void RunScript(IFrame frame, string script, string identifier) {
|
||||||
|
@ -2,13 +2,11 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Browser.Adapters;
|
|
||||||
using TweetDuck.Browser.Bridge;
|
using TweetDuck.Browser.Bridge;
|
||||||
using TweetDuck.Browser.Handling;
|
using TweetDuck.Browser.Handling;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Plugins;
|
using TweetDuck.Plugins;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
using TweetLib.Core.Data;
|
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetLib.Core.Features.Notifications;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
using TweetLib.Core.Features.Plugins.Enums;
|
using TweetLib.Core.Features.Plugins.Enums;
|
||||||
@ -74,7 +72,6 @@ protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, b
|
|||||||
browser.RegisterJsBridge("$TD", new TweetDeckBridge.Notification(owner, this));
|
browser.RegisterJsBridge("$TD", new TweetDeckBridge.Notification(owner, this));
|
||||||
|
|
||||||
browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||||
browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
|
||||||
|
|
||||||
plugins.Register(PluginEnvironment.Notification, new PluginDispatcher(browser, url => TwitterUrls.IsTweetDeck(url) && url != BlankURL));
|
plugins.Register(PluginEnvironment.Notification, new PluginDispatcher(browser, url => TwitterUrls.IsTweetDeck(url) && url != BlankURL));
|
||||||
|
|
||||||
@ -168,15 +165,6 @@ private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEvent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e) {
|
|
||||||
IFrame frame = e.Frame;
|
|
||||||
|
|
||||||
if (frame.IsMain && browser.Address != BlankURL) {
|
|
||||||
frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
|
|
||||||
CefScriptExecutor.RunFile(frame, "notification.js");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void timerDisplayDelay_Tick(object sender, EventArgs e) {
|
private void timerDisplayDelay_Tick(object sender, EventArgs e) {
|
||||||
OnNotificationReady();
|
OnNotificationReady();
|
||||||
timerDisplayDelay.Stop();
|
timerDisplayDelay.Stop();
|
||||||
@ -248,13 +236,10 @@ public override void ResumeNotification() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetTweetHTML(DesktopNotification tweet) {
|
protected override string GetTweetHTML(DesktopNotification tweet) {
|
||||||
string html = tweet.GenerateHtml(BodyClasses, HeadLayout, Config.CustomNotificationCSS);
|
return tweet.GenerateHtml(BodyClasses, HeadLayout, Config.CustomNotificationCSS, plugins.NotificationInjections, new string[] {
|
||||||
|
PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification),
|
||||||
foreach (InjectedHTML injection in plugins.NotificationInjections) {
|
Program.Resources.Load("notification.js")
|
||||||
html = injection.InjectInto(html);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadTweet(DesktopNotification tweet) {
|
protected override void LoadTweet(DesktopNotification tweet) {
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Dialogs;
|
using TweetDuck.Dialogs;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
using TweetLib.Core.Data;
|
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetLib.Core.Features.Notifications;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
|
||||||
@ -46,13 +45,7 @@ public FormNotificationScreenshotable(Action callback, FormBrowser owner, Plugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetTweetHTML(DesktopNotification tweet) {
|
protected override string GetTweetHTML(DesktopNotification tweet) {
|
||||||
string html = tweet.GenerateHtml("td-screenshot", HeadLayout, Config.CustomNotificationCSS);
|
return tweet.GenerateHtml("td-screenshot", HeadLayout, Config.CustomNotificationCSS, plugins.NotificationInjections, Array.Empty<string>());
|
||||||
|
|
||||||
foreach (InjectedHTML injection in plugins.NotificationInjections) {
|
|
||||||
html = injection.InjectInto(html);
|
|
||||||
}
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetScreenshotHeight(int browserHeight) {
|
private void SetScreenshotHeight(int browserHeight) {
|
||||||
|
@ -58,6 +58,7 @@ import skip_pre_login_page from "./tweetdeck/skip_pre_login_page.js";
|
|||||||
import update from "./update/update.js";
|
import update from "./update/update.js";
|
||||||
|
|
||||||
const globalFunctions = [
|
const globalFunctions = [
|
||||||
|
window.PluginBase,
|
||||||
window.TDGF_applyROT13,
|
window.TDGF_applyROT13,
|
||||||
window.TDGF_getColumnName,
|
window.TDGF_getColumnName,
|
||||||
window.TDGF_injectMustache,
|
window.TDGF_injectMustache,
|
||||||
@ -74,6 +75,15 @@ const globalFunctions = [
|
|||||||
window.TDGF_setSoundNotificationData,
|
window.TDGF_setSoundNotificationData,
|
||||||
window.TDGF_showTweetDetail,
|
window.TDGF_showTweetDetail,
|
||||||
window.TDGF_triggerScreenshot,
|
window.TDGF_triggerScreenshot,
|
||||||
|
window.TDPF_configurePlugin,
|
||||||
|
window.TDPF_createCustomStyle,
|
||||||
|
window.TDPF_createStorage,
|
||||||
|
window.TDPF_loadConfigurationFile,
|
||||||
|
window.TDPF_playVideo,
|
||||||
|
window.TDPF_registerPropertyUpdateCallback,
|
||||||
|
window.TDPF_requestReload,
|
||||||
|
window.TDPF_setPluginState,
|
||||||
window.TDUF_displayNotification,
|
window.TDUF_displayNotification,
|
||||||
|
window.TD_PLUGINS_INSTALL,
|
||||||
window.jQuery,
|
window.jQuery,
|
||||||
];
|
];
|
||||||
|
@ -10,6 +10,7 @@ if (!("$TDX" in window)) {
|
|||||||
* @typedef TD_Bridge
|
* @typedef TD_Bridge
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*
|
*
|
||||||
|
* @property {function(type: "error"|"warning"|"info"|"", contents: string)} alert
|
||||||
* @property {function(message: string)} crashDebug
|
* @property {function(message: string)} crashDebug
|
||||||
* @property {function(tooltip: string|null)} displayTooltip
|
* @property {function(tooltip: string|null)} displayTooltip
|
||||||
* @property {function} fixClipboard
|
* @property {function} fixClipboard
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadModules().then(([ successes, total ]) => {
|
loadModules().then(([ successes, total ]) => {
|
||||||
if ("$TD" in window) {
|
if ("$TD" in window && "notifyModulesLoaded" in window.$TD) {
|
||||||
notifyModulesLoaded(window.$TD);
|
notifyModulesLoaded(window.$TD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
211
Resources/Content/plugins/base.js
Normal file
211
Resources/Content/plugins/base.js
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import { $TD } from "../api/bridge.js";
|
||||||
|
|
||||||
|
if (!("$TDP" in window)) {
|
||||||
|
throw "Missing $TDP in global scope";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Object}
|
||||||
|
* @property {function(token: number, path: string): Promise<boolean>} checkFileExists
|
||||||
|
* @property {function(token: number, path: string, cache: boolean): Promise<string>} readFile
|
||||||
|
* @property {function(token: number, path: string): Promise<string>} readFileRoot
|
||||||
|
* @property {function(token: number)} setConfigurable
|
||||||
|
* @property {function(token: number, path: string, contents: string): Promise<string>} writeFile
|
||||||
|
*/
|
||||||
|
export const $TDP = window.$TDP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef TweetDuckPlugin
|
||||||
|
* @type {Object}
|
||||||
|
*
|
||||||
|
* @property {string} $id
|
||||||
|
* @property {number} $token
|
||||||
|
* @property {Storage} [$storage]
|
||||||
|
* @property {function} [configure]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that a plugin object contains a token.
|
||||||
|
* @param {TweetDuckPlugin} pluginObject
|
||||||
|
*/
|
||||||
|
export function validatePluginObject(pluginObject) {
|
||||||
|
if (!("$token" in pluginObject)) {
|
||||||
|
throw "Invalid plugin object.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a simple JavaScript object as configuration.
|
||||||
|
* @param {TweetDuckPlugin} pluginObject
|
||||||
|
* @param {string} fileNameUser
|
||||||
|
* @param {string|null} fileNameDefault
|
||||||
|
* @param {function(Object)} onSuccess
|
||||||
|
* @param {function(Error)} onFailure
|
||||||
|
*/
|
||||||
|
export function loadConfigurationFile(pluginObject, fileNameUser, fileNameDefault, onSuccess, onFailure) {
|
||||||
|
validatePluginObject(pluginObject);
|
||||||
|
|
||||||
|
const identifier = pluginObject.$id;
|
||||||
|
const token = pluginObject.$token;
|
||||||
|
|
||||||
|
$TDP.checkFileExists(token, fileNameUser).then(exists => {
|
||||||
|
/** @type string|null */
|
||||||
|
const fileName = exists ? fileNameUser : fileNameDefault;
|
||||||
|
|
||||||
|
if (fileName === null) {
|
||||||
|
onSuccess && onSuccess({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(exists ? $TDP.readFile(token, fileName, true) : $TDP.readFileRoot(token, fileName)).then(contents => {
|
||||||
|
let obj;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// noinspection DynamicallyGeneratedCodeJS
|
||||||
|
obj = eval("(" + contents + ")");
|
||||||
|
} catch (err) {
|
||||||
|
if (!(onFailure && onFailure(err))) {
|
||||||
|
$TD.alert("warning", "Problem loading '" + fileName + "' file for '" + identifier + "' plugin, the JavaScript syntax is invalid: " + err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess && onSuccess(obj);
|
||||||
|
}).catch(err => {
|
||||||
|
if (!(onFailure && onFailure(err))) {
|
||||||
|
$TD.alert("warning", "Problem loading '" + fileName + "' file for '" + identifier + "' plugin: " + err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
if (!(onFailure && onFailure(err))) {
|
||||||
|
$TD.alert("warning", "Problem checking '" + fileNameUser + "' file for '" + identifier + "' plugin: " + err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns an object for managing a custom stylesheet.
|
||||||
|
* @param {TweetDuckPlugin} pluginObject
|
||||||
|
* @returns {{insert: (function(string): number), remove: (function(): void), element: HTMLStyleElement}}
|
||||||
|
*/
|
||||||
|
export function createCustomStyle(pluginObject) {
|
||||||
|
validatePluginObject(pluginObject);
|
||||||
|
|
||||||
|
const element = document.createElement("style");
|
||||||
|
element.id = "plugin-" + pluginObject.$id + "-" + Math.random().toString(36).substring(2, 7);
|
||||||
|
document.head.appendChild(element);
|
||||||
|
|
||||||
|
return {
|
||||||
|
insert: (rule) => element.sheet.insertRule(rule, 0),
|
||||||
|
remove: () => element.remove(),
|
||||||
|
element
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a function that mimics a Storage object that will be saved in the plugin.
|
||||||
|
* @param {TweetDuckPlugin} pluginObject
|
||||||
|
* @param {function(Storage)} onReady
|
||||||
|
*/
|
||||||
|
export function createStorage(pluginObject, onReady) {
|
||||||
|
validatePluginObject(pluginObject);
|
||||||
|
|
||||||
|
if ("$storage" in pluginObject) {
|
||||||
|
if (pluginObject.$storage !== null) { // set to null while the file is still loading
|
||||||
|
onReady(pluginObject.$storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Storage {
|
||||||
|
get length() {
|
||||||
|
return Object.keys(this).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
key(index) {
|
||||||
|
return Object.keys(this)[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
getItem(key) {
|
||||||
|
return this[key] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setItem(key, value) {
|
||||||
|
this[key] = value;
|
||||||
|
updateFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItem(key) {
|
||||||
|
delete this[key];
|
||||||
|
updateFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
for (const key of Object.keys(this)) {
|
||||||
|
delete this[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
replace(obj, silent) {
|
||||||
|
for (const key of Object.keys(this)) {
|
||||||
|
delete this[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in obj) {
|
||||||
|
this[key] = obj[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!silent) {
|
||||||
|
updateFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// noinspection JSUnusedLocalSymbols,JSUnusedGlobalSymbols
|
||||||
|
const handler = {
|
||||||
|
get(obj, prop, receiver) {
|
||||||
|
const value = obj[prop];
|
||||||
|
return typeof value === "function" ? value.bind(obj) : value;
|
||||||
|
},
|
||||||
|
|
||||||
|
set(obj, prop, value) {
|
||||||
|
obj.setItem(prop, value);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteProperty(obj, prop) {
|
||||||
|
obj.removeItem(prop);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
enumerate(obj) {
|
||||||
|
return Object.keys(obj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const storage = new Proxy(new Storage(), handler);
|
||||||
|
let delay = -1;
|
||||||
|
|
||||||
|
const updateFile = function() {
|
||||||
|
window.clearTimeout(delay);
|
||||||
|
|
||||||
|
delay = window.setTimeout(function() {
|
||||||
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
$TDP.writeFile(pluginObject.$token, ".storage", JSON.stringify(storage));
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
pluginObject.$storage = null;
|
||||||
|
|
||||||
|
loadConfigurationFile(pluginObject, ".storage", null, function(obj) {
|
||||||
|
storage.replace(obj, true);
|
||||||
|
onReady(pluginObject.$storage = storage);
|
||||||
|
}, function() {
|
||||||
|
onReady(pluginObject.$storage = storage);
|
||||||
|
});
|
||||||
|
}
|
35
Resources/Content/plugins/notification/plugins.js
Normal file
35
Resources/Content/plugins/notification/plugins.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import setup from "../setup.js";
|
||||||
|
|
||||||
|
export default function() {
|
||||||
|
window.PluginBase = class {
|
||||||
|
constructor() {}
|
||||||
|
run() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
window.TD_PLUGINS = new class {
|
||||||
|
constructor() {
|
||||||
|
this.waitingForModules = [];
|
||||||
|
this.areModulesLoaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
install(plugin) {
|
||||||
|
if (this.areModulesLoaded) {
|
||||||
|
plugin.obj.run();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.waitingForModules.push(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onModulesLoaded(namespace) {
|
||||||
|
if (namespace === "plugins/notification") {
|
||||||
|
this.waitingForModules.forEach(plugin => plugin.obj.run());
|
||||||
|
this.waitingForModules = [];
|
||||||
|
this.areModulesLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setup();
|
||||||
|
};
|
22
Resources/Content/plugins/setup.js
Normal file
22
Resources/Content/plugins/setup.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { createCustomStyle, createStorage, loadConfigurationFile } from "./base.js";
|
||||||
|
|
||||||
|
export default function() {
|
||||||
|
window.TDPF_loadConfigurationFile = loadConfigurationFile;
|
||||||
|
window.TDPF_createCustomStyle = createCustomStyle;
|
||||||
|
window.TDPF_createStorage = createStorage;
|
||||||
|
|
||||||
|
if ("TD_PLUGINS_SETUP" in window) {
|
||||||
|
for (const callback of window.TD_PLUGINS_SETUP) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
delete window["TD_PLUGINS_SETUP"];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the deferred installation function from PluginScriptGenerator with one that performs the installation function immediately,
|
||||||
|
* since at this point we are all set up and aren't checking TD_PLUGINS_SETUP anymore.
|
||||||
|
* @param {function} f
|
||||||
|
*/
|
||||||
|
window.TD_PLUGINS_INSTALL = f => f();
|
||||||
|
};
|
162
Resources/Content/plugins/tweetdeck/plugins.js
Normal file
162
Resources/Content/plugins/tweetdeck/plugins.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import { isAppReady } from "../../api/ready.js";
|
||||||
|
import { ensurePropertyExists } from "../../api/utils.js";
|
||||||
|
import { $TDP } from "../base.js";
|
||||||
|
import setup from "../setup.js";
|
||||||
|
|
||||||
|
export default function() {
|
||||||
|
ensurePropertyExists(window, "TD_PLUGINS_DISABLE");
|
||||||
|
|
||||||
|
window.PluginBase = class {
|
||||||
|
/**
|
||||||
|
* @param {{ [requiresPageReload]: boolean }} pluginSettings
|
||||||
|
*/
|
||||||
|
constructor(pluginSettings) {
|
||||||
|
this.$requiresReload = !!(pluginSettings && pluginSettings.requiresPageReload);
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled() {}
|
||||||
|
ready() {}
|
||||||
|
disabled() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
window.TD_PLUGINS = new class {
|
||||||
|
constructor() {
|
||||||
|
this.installed = [];
|
||||||
|
this.disabled = window["TD_PLUGINS_DISABLE"];
|
||||||
|
this.waitingForModules = [];
|
||||||
|
this.waitingForReady = [];
|
||||||
|
this.areModulesLoaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDisabled(plugin) {
|
||||||
|
return this.disabled.includes(plugin.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
findObject(identifier) {
|
||||||
|
return this.installed.find(plugin => plugin.id === identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
install(plugin) {
|
||||||
|
this.installed.push(plugin);
|
||||||
|
|
||||||
|
if (typeof plugin.obj.configure === "function") {
|
||||||
|
$TDP.setConfigurable(plugin.obj.$token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isDisabled(plugin)) {
|
||||||
|
this.runWhenModulesLoaded(plugin);
|
||||||
|
this.runWhenReady(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runWhenModulesLoaded(plugin) {
|
||||||
|
if (this.areModulesLoaded) {
|
||||||
|
plugin.obj.enabled();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.waitingForModules.push(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runWhenReady(plugin) {
|
||||||
|
if (isAppReady()) {
|
||||||
|
plugin.obj.ready();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.waitingForReady.push(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(plugin, enable) {
|
||||||
|
const reloading = plugin.obj.$requiresReload;
|
||||||
|
|
||||||
|
if (enable && this.isDisabled(plugin)) {
|
||||||
|
if (reloading) {
|
||||||
|
window.TDPF_requestReload();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.disabled.splice(this.disabled.indexOf(plugin.id), 1);
|
||||||
|
plugin.obj.enabled();
|
||||||
|
this.runWhenReady(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!enable && !this.isDisabled(plugin)) {
|
||||||
|
if (reloading) {
|
||||||
|
window.TDPF_requestReload();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.disabled.push(plugin.id);
|
||||||
|
plugin.obj.disabled();
|
||||||
|
|
||||||
|
for (const key of Object.keys(plugin.obj)) {
|
||||||
|
if (key[0] !== "$") {
|
||||||
|
delete plugin.obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onModulesLoaded(namespace) {
|
||||||
|
if (namespace === "tweetdeck") {
|
||||||
|
window.TDPF_getColumnName = window.TDGF_getColumnName;
|
||||||
|
window.TDPF_reloadColumns = window.TDGF_reloadColumns;
|
||||||
|
window.TDPF_prioritizeNewestEvent = window.TDGF_prioritizeNewestEvent;
|
||||||
|
window.TDPF_injectMustache = window.TDGF_injectMustache;
|
||||||
|
window.TDPF_registerPropertyUpdateCallback = window.TDGF_registerPropertyUpdateCallback;
|
||||||
|
window.TDPF_playVideo = function(urlOrObject, username) {
|
||||||
|
if (typeof urlOrObject === "string") {
|
||||||
|
window.TDGF_playVideo(urlOrObject, null, username);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// noinspection JSUnresolvedVariable
|
||||||
|
window.TDGF_playVideo(urlOrObject.videoUrl, urlOrObject.tweetUrl, urlOrObject.username);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.waitingForModules.forEach(plugin => plugin.obj.enabled());
|
||||||
|
this.waitingForModules = [];
|
||||||
|
this.areModulesLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onReady() {
|
||||||
|
this.waitingForReady.forEach(plugin => plugin.obj.ready());
|
||||||
|
this.waitingForReady = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes plugin enabled state.
|
||||||
|
* @param {string} identifier
|
||||||
|
* @param {boolean} enable
|
||||||
|
*/
|
||||||
|
window.TDPF_setPluginState = function(identifier, enable) {
|
||||||
|
window.TD_PLUGINS.setState(window.TD_PLUGINS.findObject(identifier), enable);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers configure() function on a plugin object.
|
||||||
|
* @param {string} identifier
|
||||||
|
*/
|
||||||
|
window.TDPF_configurePlugin = function(identifier) {
|
||||||
|
window.TD_PLUGINS.findObject(identifier).obj.configure();
|
||||||
|
};
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
let isReloading = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads the page.
|
||||||
|
*/
|
||||||
|
window.TDPF_requestReload = function() {
|
||||||
|
if (!isReloading) {
|
||||||
|
window.setTimeout(window.TDGF_reload, 1);
|
||||||
|
isReloading = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
setup();
|
||||||
|
};
|
@ -52,7 +52,6 @@ let main (argv: string[]) =
|
|||||||
let scriptsDir = targetDir +/ "scripts"
|
let scriptsDir = targetDir +/ "scripts"
|
||||||
let resourcesDir = targetDir +/ "resources"
|
let resourcesDir = targetDir +/ "resources"
|
||||||
let pluginsDir = targetDir +/ "plugins"
|
let pluginsDir = targetDir +/ "plugins"
|
||||||
let importsDir = scriptsDir +/ "imports"
|
|
||||||
|
|
||||||
// Functions (Strings)
|
// Functions (Strings)
|
||||||
|
|
||||||
@ -99,7 +98,7 @@ let main (argv: string[]) =
|
|||||||
// Functions (File Processing)
|
// Functions (File Processing)
|
||||||
|
|
||||||
let byPattern path pattern =
|
let byPattern path pattern =
|
||||||
Directory.EnumerateFiles(path, pattern, SearchOption.AllDirectories) |> Seq.filter (fun (file: string) -> not (file.Contains(importsDir)))
|
Directory.EnumerateFiles(path, pattern, SearchOption.AllDirectories)
|
||||||
|
|
||||||
let exceptEndingWith (name: string) =
|
let exceptEndingWith (name: string) =
|
||||||
Seq.filter (fun (file: string) -> not (file.EndsWith(name)))
|
Seq.filter (fun (file: string) -> not (file.EndsWith(name)))
|
||||||
@ -123,7 +122,7 @@ let main (argv: string[]) =
|
|||||||
|
|
||||||
let writeFile (fullPath: string) (lines: string seq) =
|
let writeFile (fullPath: string) (lines: string seq) =
|
||||||
let relativePath = fullPath.[(targetDir.Length)..]
|
let relativePath = fullPath.[(targetDir.Length)..]
|
||||||
let includeVersion = relativePath.StartsWith(@"scripts\") && not (relativePath.StartsWith(@"scripts\imports\"))
|
let includeVersion = relativePath.StartsWith(@"scripts\")
|
||||||
let finalLines = if includeVersion then seq { yield "#" + version; yield! lines } else lines
|
let finalLines = if includeVersion then seq { yield "#" + version; yield! lines } else lines
|
||||||
|
|
||||||
File.WriteAllLines(fullPath, finalLines |> Seq.toArray)
|
File.WriteAllLines(fullPath, finalLines |> Seq.toArray)
|
||||||
@ -133,13 +132,6 @@ let main (argv: string[]) =
|
|||||||
let rec processFileContents file =
|
let rec processFileContents file =
|
||||||
readFile file
|
readFile file
|
||||||
|> extProcessors.[Path.GetExtension(file)]
|
|> extProcessors.[Path.GetExtension(file)]
|
||||||
|> Seq.map (fun line ->
|
|
||||||
Regex.Replace(line, @"#import ""(.*?)""", (fun matchInfo ->
|
|
||||||
processFileContents(importsDir +/ matchInfo.Groups.[1].Value.Trim())
|
|
||||||
|> collapseLines (Environment.NewLine)
|
|
||||||
|> (fun contents -> contents.TrimEnd())
|
|
||||||
))
|
|
||||||
)
|
|
||||||
|
|
||||||
iterateFiles files (fun file ->
|
iterateFiles files (fun file ->
|
||||||
processFileContents file
|
processFileContents file
|
||||||
@ -221,10 +213,6 @@ let main (argv: string[]) =
|
|||||||
processFiles (byPattern targetDir "*.html") fileProcessors
|
processFiles (byPattern targetDir "*.html") fileProcessors
|
||||||
processFiles (byPattern pluginsDir "*.meta") fileProcessors
|
processFiles (byPattern pluginsDir "*.meta") fileProcessors
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
|
|
||||||
Directory.Delete(importsDir, true)
|
|
||||||
|
|
||||||
// Finished
|
// Finished
|
||||||
|
|
||||||
sw.Stop()
|
sw.Stop()
|
||||||
|
@ -1,172 +0,0 @@
|
|||||||
(function(){
|
|
||||||
if (!("$TDP" in window)){
|
|
||||||
console.error("Missing $TDP");
|
|
||||||
}
|
|
||||||
|
|
||||||
const validatePluginObject = function(pluginObject){
|
|
||||||
if (!("$token" in pluginObject)){
|
|
||||||
throw "Invalid plugin object.";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Block: Setup a simple JavaScript object configuration loader.
|
|
||||||
//
|
|
||||||
window.TDPF_loadConfigurationFile = function(pluginObject, fileNameUser, fileNameDefault, onSuccess, onFailure){
|
|
||||||
validatePluginObject(pluginObject);
|
|
||||||
|
|
||||||
let identifier = pluginObject.$id;
|
|
||||||
let token = pluginObject.$token;
|
|
||||||
|
|
||||||
$TDP.checkFileExists(token, fileNameUser).then(exists => {
|
|
||||||
let fileName = exists ? fileNameUser : fileNameDefault;
|
|
||||||
|
|
||||||
if (fileName === null){
|
|
||||||
onSuccess && onSuccess({});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
(exists ? $TDP.readFile(token, fileName, true) : $TDP.readFileRoot(token, fileName)).then(contents => {
|
|
||||||
let obj;
|
|
||||||
|
|
||||||
try{
|
|
||||||
obj = eval("(" + contents + ")");
|
|
||||||
}catch(err){
|
|
||||||
if (!(onFailure && onFailure(err))){
|
|
||||||
$TD.alert("warning", "Problem loading '" + fileName + "' file for '" + identifier + "' plugin, the JavaScript syntax is invalid: " + err.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
onSuccess && onSuccess(obj);
|
|
||||||
}).catch(err => {
|
|
||||||
if (!(onFailure && onFailure(err))){
|
|
||||||
$TD.alert("warning", "Problem loading '" + fileName + "' file for '" + identifier + "' plugin: " + err.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).catch(err => {
|
|
||||||
if (!(onFailure && onFailure(err))){
|
|
||||||
$TD.alert("warning", "Problem checking '" + fileNameUser + "' file for '" + identifier + "' plugin: " + err.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Block: Setup a function to add/remove custom CSS.
|
|
||||||
//
|
|
||||||
window.TDPF_createCustomStyle = function(pluginObject){
|
|
||||||
validatePluginObject(pluginObject);
|
|
||||||
|
|
||||||
let element = document.createElement("style");
|
|
||||||
element.id = "plugin-" + pluginObject.$id + "-"+Math.random().toString(36).substring(2, 7);
|
|
||||||
document.head.appendChild(element);
|
|
||||||
|
|
||||||
return {
|
|
||||||
insert: (rule) => element.sheet.insertRule(rule, 0),
|
|
||||||
remove: () => element.remove(),
|
|
||||||
element: element
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Block: Setup a function to mimic a Storage object that will be saved in the plugin.
|
|
||||||
//
|
|
||||||
window.TDPF_createStorage = function(pluginObject, onReady){
|
|
||||||
validatePluginObject(pluginObject);
|
|
||||||
|
|
||||||
if ("$storage" in pluginObject){
|
|
||||||
if (pluginObject.$storage !== null){ // set to null while the file is still loading
|
|
||||||
onReady(pluginObject.$storage);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Storage{
|
|
||||||
get length(){
|
|
||||||
return Object.keys(this).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
key(index){
|
|
||||||
return Object.keys(this)[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
getItem(key){
|
|
||||||
return this[key] || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
setItem(key, value){
|
|
||||||
this[key] = value;
|
|
||||||
updateFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeItem(key){
|
|
||||||
delete this[key];
|
|
||||||
updateFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
clear(){
|
|
||||||
for(key of Object.keys(this)){
|
|
||||||
delete this[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
replace(obj, silent){
|
|
||||||
for(let key of Object.keys(this)){
|
|
||||||
delete this[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
for(let key in obj){
|
|
||||||
this[key] = obj[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!silent){
|
|
||||||
updateFile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var storage = new Proxy(new Storage(), {
|
|
||||||
get: function(obj, prop, receiver){
|
|
||||||
const value = obj[prop];
|
|
||||||
return typeof value === "function" ? value.bind(obj) : value;
|
|
||||||
},
|
|
||||||
|
|
||||||
set: function(obj, prop, value){
|
|
||||||
obj.setItem(prop, value);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteProperty: function(obj, prop){
|
|
||||||
obj.removeItem(prop);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
enumerate: function(obj){
|
|
||||||
return Object.keys(obj);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var delay = -1;
|
|
||||||
|
|
||||||
const updateFile = function(){
|
|
||||||
window.clearTimeout(delay);
|
|
||||||
|
|
||||||
delay = window.setTimeout(function(){
|
|
||||||
$TDP.writeFile(pluginObject.$token, ".storage", JSON.stringify(storage));
|
|
||||||
}, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
pluginObject.$storage = null;
|
|
||||||
|
|
||||||
window.TDPF_loadConfigurationFile(pluginObject, ".storage", null, function(obj){
|
|
||||||
storage.replace(obj, true);
|
|
||||||
onReady(pluginObject.$storage = storage);
|
|
||||||
}, function(){
|
|
||||||
onReady(pluginObject.$storage = storage);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
})();
|
|
@ -1,154 +0,0 @@
|
|||||||
(function(){
|
|
||||||
//
|
|
||||||
// Class: Abstract plugin base class.
|
|
||||||
//
|
|
||||||
window.PluginBase = class{
|
|
||||||
constructor(pluginSettings){
|
|
||||||
this.$requiresReload = !!(pluginSettings && pluginSettings.requiresPageReload);
|
|
||||||
}
|
|
||||||
|
|
||||||
enabled(){}
|
|
||||||
ready(){}
|
|
||||||
disabled(){}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Variable: Main object for containing and managing plugins.
|
|
||||||
//
|
|
||||||
window.TD_PLUGINS = new class{
|
|
||||||
constructor(){
|
|
||||||
this.installed = [];
|
|
||||||
this.disabled = [];
|
|
||||||
this.waitingForFeatures = [];
|
|
||||||
this.waitingForReady = [];
|
|
||||||
this.areFeaturesLoaded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
isDisabled(plugin){
|
|
||||||
return this.disabled.includes(plugin.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
findObject(identifier){
|
|
||||||
return this.installed.find(plugin => plugin.id === identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
install(plugin){
|
|
||||||
this.installed.push(plugin);
|
|
||||||
|
|
||||||
if (typeof plugin.obj.configure === "function"){
|
|
||||||
$TDP.setConfigurable(plugin.obj.$token);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isDisabled(plugin)){
|
|
||||||
this.runWhenModulesLoaded(plugin);
|
|
||||||
this.runWhenReady(plugin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runWhenModulesLoaded(plugin){
|
|
||||||
if (this.areFeaturesLoaded){
|
|
||||||
plugin.obj.enabled();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
this.waitingForFeatures.push(plugin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runWhenReady(plugin){
|
|
||||||
if (TD.ready){
|
|
||||||
plugin.obj.ready();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
this.waitingForReady.push(plugin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(plugin, enable){
|
|
||||||
let reloading = plugin.obj.$requiresReload;
|
|
||||||
|
|
||||||
if (enable && this.isDisabled(plugin)){
|
|
||||||
if (reloading){
|
|
||||||
window.TDPF_requestReload();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
this.disabled.splice(this.disabled.indexOf(plugin.id), 1);
|
|
||||||
plugin.obj.enabled();
|
|
||||||
this.runWhenReady(plugin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!enable && !this.isDisabled(plugin)){
|
|
||||||
if (reloading){
|
|
||||||
window.TDPF_requestReload();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
this.disabled.push(plugin.id);
|
|
||||||
plugin.obj.disabled();
|
|
||||||
|
|
||||||
for(let key of Object.keys(plugin.obj)){
|
|
||||||
if (key[0] !== '$'){
|
|
||||||
delete plugin.obj[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onModulesLoaded(namespace){
|
|
||||||
if (namespace !== "tweetdeck") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.TDPF_getColumnName = window.TDGF_getColumnName;
|
|
||||||
window.TDPF_reloadColumns = window.TDGF_reloadColumns;
|
|
||||||
window.TDPF_prioritizeNewestEvent = window.TDGF_prioritizeNewestEvent;
|
|
||||||
window.TDPF_injectMustache = window.TDGF_injectMustache;
|
|
||||||
window.TDPF_registerPropertyUpdateCallback = window.TDGF_registerPropertyUpdateCallback;
|
|
||||||
window.TDPF_playVideo = function(urlOrObject, username){
|
|
||||||
if (typeof urlOrObject === "string"){
|
|
||||||
window.TDGF_playVideo(urlOrObject, null, username);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
window.TDGF_playVideo(urlOrObject.videoUrl, urlOrObject.tweetUrl, urlOrObject.username);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.waitingForFeatures.forEach(plugin => plugin.obj.enabled());
|
|
||||||
this.waitingForFeatures = [];
|
|
||||||
this.areFeaturesLoaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
onReady(){
|
|
||||||
this.waitingForReady.forEach(plugin => plugin.obj.ready());
|
|
||||||
this.waitingForReady = [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Block: Setup a function to change plugin state.
|
|
||||||
//
|
|
||||||
window.TDPF_setPluginState = function(identifier, enable){
|
|
||||||
window.TD_PLUGINS.setState(window.TD_PLUGINS.findObject(identifier), enable);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Block: Setup a function to trigger plugin configuration.
|
|
||||||
//
|
|
||||||
window.TDPF_configurePlugin = function(identifier){
|
|
||||||
window.TD_PLUGINS.findObject(identifier).obj.configure();
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Block: Setup a function to reload the page.
|
|
||||||
//
|
|
||||||
(function(){
|
|
||||||
let isReloading = false;
|
|
||||||
|
|
||||||
window.TDPF_requestReload = function(){
|
|
||||||
if (!isReloading){
|
|
||||||
window.setTimeout(window.TDGF_reload, 1);
|
|
||||||
isReloading = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
#import "scripts/plugins.base.js"
|
|
||||||
})();
|
|
@ -1,18 +0,0 @@
|
|||||||
//
|
|
||||||
// Class: Abstract plugin base class.
|
|
||||||
//
|
|
||||||
window.PluginBase = class{
|
|
||||||
constructor(){}
|
|
||||||
run(){}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Variable: Main object for containing and managing plugins.
|
|
||||||
//
|
|
||||||
window.TD_PLUGINS = {
|
|
||||||
install: function(plugin){
|
|
||||||
plugin.obj.run();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#import "scripts/plugins.base.js"
|
|
@ -323,6 +323,8 @@
|
|||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
<None Include="Resources\Content\error\error.html" />
|
<None Include="Resources\Content\error\error.html" />
|
||||||
|
<None Include="Resources\Content\plugins\notification\plugins.js" />
|
||||||
|
<None Include="Resources\Content\plugins\tweetdeck\plugins.js" />
|
||||||
<None Include="Resources\Images\avatar.png" />
|
<None Include="Resources\Images\avatar.png" />
|
||||||
<None Include="Resources\Images\icon-muted.ico" />
|
<None Include="Resources\Images\icon-muted.ico" />
|
||||||
<None Include="Resources\Images\icon-small.ico" />
|
<None Include="Resources\Images\icon-small.ico" />
|
||||||
@ -354,11 +356,8 @@
|
|||||||
<None Include="Resources\Plugins\timeline-polls\browser.js" />
|
<None Include="Resources\Plugins\timeline-polls\browser.js" />
|
||||||
<None Include="Resources\PostBuild.fsx" />
|
<None Include="Resources\PostBuild.fsx" />
|
||||||
<None Include="Resources\PostCefUpdate.ps1" />
|
<None Include="Resources\PostCefUpdate.ps1" />
|
||||||
<None Include="Resources\Scripts\imports\scripts\plugins.base.js" />
|
|
||||||
<None Include="Resources\Scripts\notification.js" />
|
<None Include="Resources\Scripts\notification.js" />
|
||||||
<None Include="Resources\Scripts\pages\example.html" />
|
<None Include="Resources\Scripts\pages\example.html" />
|
||||||
<None Include="Resources\Scripts\plugins.browser.js" />
|
|
||||||
<None Include="Resources\Scripts\plugins.notification.js" />
|
|
||||||
<None Include="Resources\Scripts\screenshot.js" />
|
<None Include="Resources\Scripts\screenshot.js" />
|
||||||
<None Include="Resources\Scripts\styles\notification.css" />
|
<None Include="Resources\Scripts\styles\notification.css" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@ -396,6 +395,8 @@
|
|||||||
<Content Include="Resources\Content\login\hide_cookie_bar.js" />
|
<Content Include="Resources\Content\login\hide_cookie_bar.js" />
|
||||||
<Content Include="Resources\Content\login\login.css" />
|
<Content Include="Resources\Content\login\login.css" />
|
||||||
<Content Include="Resources\Content\login\setup_document_attributes.js" />
|
<Content Include="Resources\Content\login\setup_document_attributes.js" />
|
||||||
|
<Content Include="Resources\Content\plugins\base.js" />
|
||||||
|
<Content Include="Resources\Content\plugins\setup.js" />
|
||||||
<Content Include="Resources\Content\tweetdeck\add_tweetduck_to_settings_menu.js" />
|
<Content Include="Resources\Content\tweetdeck\add_tweetduck_to_settings_menu.js" />
|
||||||
<Content Include="Resources\Content\tweetdeck\bypass_t.co_links.js" />
|
<Content Include="Resources\Content\tweetdeck\bypass_t.co_links.js" />
|
||||||
<Content Include="Resources\Content\tweetdeck\clear_search_input.js" />
|
<Content Include="Resources\Content\tweetdeck\clear_search_input.js" />
|
||||||
|
@ -3,5 +3,6 @@ public interface IScriptExecutor {
|
|||||||
void RunFunction(string name, params object[] args);
|
void RunFunction(string name, params object[] args);
|
||||||
void RunScript(string identifier, string script);
|
void RunScript(string identifier, string script);
|
||||||
bool RunFile(string file);
|
bool RunFile(string file);
|
||||||
|
void RunBootstrap(string moduleNamespace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using TweetLib.Core.Data;
|
||||||
|
|
||||||
namespace TweetLib.Core.Features.Notifications {
|
namespace TweetLib.Core.Features.Notifications {
|
||||||
public sealed class DesktopNotification {
|
public sealed class DesktopNotification {
|
||||||
@ -44,13 +46,13 @@ public int GetDisplayDuration(int value) {
|
|||||||
return 2000 + Math.Max(1000, value * characters);
|
return 2000 + Math.Max(1000, value * characters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GenerateHtml(string bodyClasses, string? headLayout, string? customStyles) { // TODO
|
public string GenerateHtml(string bodyClasses, string? headLayout, string? customStyles, IEnumerable<InjectedHTML> injections, string[] scripts) { // TODO
|
||||||
headLayout ??= DefaultHeadLayout;
|
headLayout ??= DefaultHeadLayout;
|
||||||
customStyles ??= string.Empty;
|
customStyles ??= string.Empty;
|
||||||
|
|
||||||
string mainCSS = App.ResourceHandler.Load("styles/notification.css") ?? string.Empty;
|
string mainCSS = App.ResourceHandler.Load("styles/notification.css") ?? string.Empty;
|
||||||
|
|
||||||
StringBuilder build = new StringBuilder(320 + headLayout.Length + mainCSS.Length + customStyles.Length + html.Length);
|
StringBuilder build = new StringBuilder(1000);
|
||||||
build.Append("<!DOCTYPE html>");
|
build.Append("<!DOCTYPE html>");
|
||||||
build.Append(headLayout);
|
build.Append(headLayout);
|
||||||
build.Append("<style type='text/css'>").Append(mainCSS).Append("</style>");
|
build.Append("<style type='text/css'>").Append(mainCSS).Append("</style>");
|
||||||
@ -67,7 +69,31 @@ public string GenerateHtml(string bodyClasses, string? headLayout, string? custo
|
|||||||
|
|
||||||
build.Append("'><div class='column' style='width:100%!important;min-height:100vh!important;height:auto!important;overflow:initial!important;'>");
|
build.Append("'><div class='column' style='width:100%!important;min-height:100vh!important;height:auto!important;overflow:initial!important;'>");
|
||||||
build.Append(html);
|
build.Append(html);
|
||||||
build.Append("</div></body></html>");
|
build.Append("</div>");
|
||||||
|
build.Append("<tweetduck-script-placeholder></body></html>");
|
||||||
|
|
||||||
|
string result = build.ToString();
|
||||||
|
|
||||||
|
foreach (var injection in injections) {
|
||||||
|
result = injection.InjectInto(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Replace("<tweetduck-script-placeholder>", GenerateScripts(scripts));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateScripts(string[] scripts) {
|
||||||
|
if (scripts.Length == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder build = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (string script in scripts) {
|
||||||
|
build.Append("<script type='text/javascript'>");
|
||||||
|
build.Append(script);
|
||||||
|
build.Append("</script>");
|
||||||
|
}
|
||||||
|
|
||||||
return build.ToString();
|
return build.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,14 @@ public static class PluginEnvironments {
|
|||||||
PluginEnvironment.Notification
|
PluginEnvironment.Notification
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static string GetPluginScriptNamespace(this PluginEnvironment environment) {
|
||||||
|
return environment switch {
|
||||||
|
PluginEnvironment.Browser => "tweetdeck",
|
||||||
|
PluginEnvironment.Notification => "notification",
|
||||||
|
_ => throw new InvalidOperationException($"Invalid plugin environment: {environment}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetPluginScriptFile(this PluginEnvironment environment) {
|
public static string GetPluginScriptFile(this PluginEnvironment environment) {
|
||||||
return environment switch {
|
return environment switch {
|
||||||
PluginEnvironment.Browser => "browser.js",
|
PluginEnvironment.Browser => "browser.js",
|
||||||
|
@ -69,10 +69,12 @@ public void Reload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Execute(PluginEnvironment environment, IScriptExecutor executor) {
|
private void Execute(PluginEnvironment environment, IScriptExecutor executor) {
|
||||||
if (!plugins.Any(plugin => plugin.HasEnvironment(environment)) || !executor.RunFile($"plugins.{environment.GetPluginScriptFile()}")) {
|
if (!plugins.Any(plugin => plugin.HasEnvironment(environment))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
executor.RunScript("gen:pluginstall", PluginScriptGenerator.GenerateInstaller());
|
||||||
|
|
||||||
bool includeDisabled = environment == PluginEnvironment.Browser;
|
bool includeDisabled = environment == PluginEnvironment.Browser;
|
||||||
|
|
||||||
if (includeDisabled) {
|
if (includeDisabled) {
|
||||||
@ -100,6 +102,8 @@ private void Execute(PluginEnvironment environment, IScriptExecutor executor) {
|
|||||||
executor.RunScript($"plugin:{plugin}", PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, bridge.GetTokenFromPlugin(plugin), environment));
|
executor.RunScript($"plugin:{plugin}", PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, bridge.GetTokenFromPlugin(plugin), environment));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
executor.RunBootstrap($"plugins/{environment.GetPluginScriptNamespace()}");
|
||||||
|
|
||||||
Executed?.Invoke(this, new PluginErrorEventArgs(errors));
|
Executed?.Invoke(this, new PluginErrorEventArgs(errors));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,11 @@
|
|||||||
namespace TweetLib.Core.Features.Plugins {
|
namespace TweetLib.Core.Features.Plugins {
|
||||||
public static class PluginScriptGenerator {
|
public static class PluginScriptGenerator {
|
||||||
public static string GenerateConfig(IPluginConfig config) {
|
public static string GenerateConfig(IPluginConfig config) {
|
||||||
return "window.TD_PLUGINS.disabled = [" + string.Join(",", config.DisabledPlugins.Select(id => '"' + id + '"')) + "]";
|
return "window.TD_PLUGINS_DISABLE = [" + string.Join(",", config.DisabledPlugins.Select(id => '"' + id + '"')) + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GenerateInstaller() {
|
||||||
|
return @"if (!window.TD_PLUGINS_INSTALL) { window.TD_PLUGINS_SETUP = []; window.TD_PLUGINS_INSTALL = function(f) { window.TD_PLUGINS_SETUP.push(f); }; }";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment) {
|
public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment) {
|
||||||
@ -16,22 +20,24 @@ public static string GeneratePlugin(string pluginIdentifier, string pluginConten
|
|||||||
.Replace("%contents", pluginContents);
|
.Replace("%contents", pluginContents);
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string PluginGen = "(function(%params,$d){let tmp={id:'%id',obj:new class extends PluginBase{%contents}};$d(tmp.obj,'$id',{value:'%id'});$d(tmp.obj,'$token',{value:%token});window.TD_PLUGINS.install(tmp);})(%params,Object.defineProperty);";
|
private const string PluginGen = "window.TD_PLUGINS_INSTALL(function(){" +
|
||||||
|
"return (function(%params,$d){let tmp={id:'%id',obj:new class extends PluginBase{%contents}};$d(tmp.obj,'$id',{value:'%id'});$d(tmp.obj,'$token',{value:%token});window.TD_PLUGINS.install(tmp);})(%params,Object.defineProperty);" +
|
||||||
|
"});";
|
||||||
|
|
||||||
/* PluginGen
|
/* PluginGen
|
||||||
|
|
||||||
(function(%params, $d){
|
(function(%params, $d){
|
||||||
let tmp = {
|
let tmp = {
|
||||||
id: '%id',
|
id: '%id',
|
||||||
obj: new class extends PluginBase{%contents}
|
obj: new class extends PluginBase{%contents}
|
||||||
};
|
};
|
||||||
|
|
||||||
$d(tmp.obj, '$id', { value: '%id' });
|
$d(tmp.obj, '$id', { value: '%id' });
|
||||||
$d(tmp.obj, '$token', { value: %token });
|
$d(tmp.obj, '$token', { value: %token });
|
||||||
|
|
||||||
window.TD_PLUGINS.install(tmp);
|
window.TD_PLUGINS.install(tmp);
|
||||||
})(%params, Object.defineProperty);
|
})(%params, Object.defineProperty);
|
||||||
|
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user