1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-05-09 05:34:05 +02:00

Refactor notification bootstrapping

This commit is contained in:
chylex 2021-12-23 10:29:10 +01:00
parent ceae748503
commit 6a421292b3
Signed by: chylex
GPG Key ID: 4DE42C8F19A80548
23 changed files with 214 additions and 248 deletions

View File

@ -46,9 +46,18 @@ public static bool RunFile(IFrame frame, string file) {
}
public static void RunBootstrap(IFrame frame, string moduleNamespace) {
string script = GetBootstrapScript(moduleNamespace, includeStylesheets: true);
if (script != null) {
RunScript(frame, script, "bootstrap");
}
}
public static string GetBootstrapScript(string moduleNamespace, bool includeStylesheets) {
string script = Program.Resources.Load("bootstrap.js");
if (script == null) {
return;
return null;
}
string path = Path.Combine(Program.ResourcesPath, moduleNamespace);
@ -62,7 +71,7 @@ public static void RunBootstrap(IFrame frame, string moduleNamespace) {
var targetList = ext switch {
".js" => moduleNames,
".css" => stylesheetNames,
".css" => includeStylesheets ? stylesheetNames : null,
_ => null
};
@ -73,7 +82,7 @@ public static void RunBootstrap(IFrame frame, string moduleNamespace) {
script = script.Replace("{{modules}}", string.Join("|", moduleNames));
script = script.Replace("{{stylesheets}}", string.Join("|", stylesheetNames));
RunScript(frame, script, "bootstrap");
return script;
}
}
}

View File

@ -32,7 +32,7 @@ protected override FormBorderStyle NotificationBorderStyle {
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false) {
browser.LoadingStateChanged += browser_LoadingStateChanged;
string exampleTweetHTML = Program.Resources.LoadSilent("pages/example.html")?.Replace("{avatar}", AppLogo.Url) ?? string.Empty;
string exampleTweetHTML = Program.Resources.LoadSilent("notification/example/example.html")?.Replace("{avatar}", AppLogo.Url) ?? string.Empty;
#if DEBUG
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");

View File

@ -2,6 +2,7 @@
using System.Drawing;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Browser.Adapters;
using TweetDuck.Browser.Bridge;
using TweetDuck.Browser.Handling;
using TweetDuck.Controls;
@ -238,7 +239,7 @@ public override void ResumeNotification() {
protected override string GetTweetHTML(DesktopNotification tweet) {
return tweet.GenerateHtml(BodyClasses, HeadLayout, Config.CustomNotificationCSS, plugins.NotificationInjections, new string[] {
PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification),
Program.Resources.Load("notification.js")
CefScriptExecutor.GetBootstrapScript("notification", includeStylesheets: false)
});
}

View File

@ -29,7 +29,7 @@ public FormNotificationScreenshotable(Action callback, FormBrowser owner, Plugin
return;
}
string script = Program.Resources.LoadSilent("screenshot.js");
string script = Program.Resources.LoadSilent("notification/screenshot/screenshot.js");
if (script == null) {
this.InvokeAsyncSafe(callback);
@ -37,7 +37,7 @@ public FormNotificationScreenshotable(Action callback, FormBrowser owner, Plugin
}
using IFrame frame = args.Browser.MainFrame;
CefScriptExecutor.RunScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("{frames}", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
CefScriptExecutor.RunScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("1/*FRAMES*/", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
};
SetNotificationSize(realWidth, 1024);

View File

@ -7,6 +7,14 @@
import introduction from "./introduction/introduction.js";
import hide_cookie_bar from "./login/hide_cookie_bar.js";
import setup_document_attributes from "./login/setup_document_attributes.js";
import add_skip_button from "./notification/add_skip_button.js";
import disable_clipboard_formatting_notification from "./notification/disable_clipboard_formatting.js";
import expand_links_or_show_tooltip_notification from "./notification/expand_links_or_show_tooltip.js";
import handle_links from "./notification/handle_links.js";
import handle_show_this_thread_link from "./notification/handle_show_this_thread_link.js";
import reset_scroll_position_on_load from "./notification/reset_scroll_position_on_load.js";
import scroll_smoothly from "./notification/scroll_smoothly.js";
import setup_body_hover_class from "./notification/setup_body_hover_class.js";
import add_tweetduck_to_settings_menu from "./tweetdeck/add_tweetduck_to_settings_menu.js";
import bypass_tco_links from "./tweetdeck/bypass_t.co_links.js";
import clear_search_input from "./tweetdeck/clear_search_input.js";
@ -72,6 +80,7 @@ const globalFunctions = [
window.TDGF_reinjectCustomCSS,
window.TDGF_reload,
window.TDGF_reloadColumns,
window.TDGF_scrollSmoothly,
window.TDGF_setSoundNotificationData,
window.TDGF_showTweetDetail,
window.TDGF_triggerScreenshot,

View File

@ -14,6 +14,7 @@ if (!("$TDX" in window)) {
* @property {function(message: string)} crashDebug
* @property {function(tooltip: string|null)} displayTooltip
* @property {function} fixClipboard
* @property {function} loadNextNotification
* @property {function(fontSize: string, headLayout: string)} loadNotificationLayout
* @property {function(namespace: string)} onModulesLoaded
* @property {function(columnId: string, chirpId: string, columnName: string, tweetHtml: string, tweetCharacters: int, tweetUrl: string, quoteUrl: string)} onTweetPopup
@ -24,6 +25,7 @@ if (!("$TDX" in window)) {
* @property {function(videoUrl: string, tweetUrl: string, username: string, callback: function)} playVideo
* @property {function(tweetUrl: string, quoteUrl: string, chirpAuthors: string, chirpImages: string)} setRightClickedChirp
* @property {function(type: string, url: string)} setRightClickedLink
* @property {function} showTweetDetail
* @property {function(html: string, width: number)} screenshotTweet
* @property {function} stopVideo
*/
@ -39,6 +41,7 @@ if (!("$TDX" in window)) {
* @property {boolean} [muteNotifications]
* @property {boolean} [notificationMediaPreviews]
* @property {boolean} [openSearchInFirstColumn]
* @property {boolean} [skipOnLinkClick]
* @property {string} [translationTarget]
*/

View File

@ -0,0 +1,17 @@
import { $TD } from "../api/bridge.js";
const iconHTML = `
<svg id="td-skip" width="10" height="17" viewBox="0 0 350 600">
<path fill="#888" d="M0,151.656l102.208-102.22l247.777,247.775L102.208,544.986L0,442.758l145.546-145.547">
</svg>`;
/**
* Adds an icon button that skips to the next notification.
*/
export default function() {
document.body.children[0].insertAdjacentHTML("beforeend", iconHTML);
document.getElementById("td-skip").addEventListener("click", function() {
$TD.loadNextNotification();
});
};

View File

@ -0,0 +1,5 @@
import disable_clipboard_formatting from "../tweetdeck/disable_clipboard_formatting.js";
export default function() {
disable_clipboard_formatting();
};

View File

@ -0,0 +1,5 @@
import expand_links_or_show_tooltip from "../tweetdeck/expand_links_or_show_tooltip.js";
export default function() {
expand_links_or_show_tooltip();
};

View File

@ -0,0 +1,39 @@
import { $TD, $TDX } from "../api/bridge.js";
/**
* Adds an event listener to all elements in a collection.
*/
function addEventListener(collection, type, listener) {
for (const ele of collection) {
ele.addEventListener(type, listener);
}
}
/**
* @param {MouseEvent} e
*/
function handleLinkClick(e) {
if (e.button === 0 || e.button === 1) {
const ele = e.currentTarget;
$TD.openBrowser(ele.href);
e.preventDefault();
if ($TDX.skipOnLinkClick) {
const parentClasses = ele.parentNode.classList;
if (parentClasses.contains("js-tweet-text") || parentClasses.contains("js-quoted-tweet-text") || parentClasses.contains("js-timestamp")) {
$TD.loadNextNotification();
}
}
}
}
/**
* Bypasses default link open function, and skips notification when opening links if enabled in app configuration.
*/
export default function() {
const links = document.getElementsByTagName("A");
addEventListener(links, "click", handleLinkClick);
addEventListener(links, "auxclick", handleLinkClick);
};

View File

@ -0,0 +1,10 @@
import { $TD } from "../api/bridge.js";
/**
* Handles clicking on the 'Show this thread' link.
*/
export default function() {
document.getElementById("tduck-show-thread")?.addEventListener("click", function(){
$TD.showTweetDetail();
});
};

View File

@ -0,0 +1,6 @@
/**
* Forces the scroll position to reset every time a notification loads.
*/
export default function() {
history.scrollRestoration = "manual";
};

View File

@ -0,0 +1,37 @@
/**
* @typedef TD_Screenshot_Bridge
* @type {Object}
*
* @property {function} triggerScreenshot
* @property {function(number)} setHeight
*/
/**
* @param $TDS
*/
(function($TDS) {
const ele = document.getElementsByTagName("article")[0];
ele.style.width = "{width}px";
ele.style.position = "absolute";
const contentHeight = ele.offsetHeight;
ele.style.position = "static";
const avatar = ele.querySelector(".tweet-avatar");
const avatarBottom = avatar ? avatar.getBoundingClientRect().bottom : 0;
$TDS.setHeight(Math.floor(Math.max(contentHeight, avatarBottom + 9))).then(() => {
let framesLeft = 1/*FRAMES*/; // basic render is done in 1 frame, large media take longer
const onNextFrame = function() {
if (--framesLeft < 0) {
$TDS.triggerScreenshot();
}
else {
requestAnimationFrame(onNextFrame);
}
};
onNextFrame();
});
})(/** @type TD_Screenshot_Bridge */ $TD_NotificationScreenshot);

View File

@ -0,0 +1,35 @@
/**
* Works around broken smooth scrolling in Chromium/CEF.
*/
export default function() {
let targetY = 0;
let delay = -1;
let scrolling = false;
window.TDGF_scrollSmoothly = function(delta) {
targetY += delta;
if (targetY < 0) {
targetY = 0;
}
else if (targetY > document.body.offsetHeight - window.innerHeight) {
targetY = document.body.offsetHeight - window.innerHeight;
}
const prevY = window.scrollY;
window.scrollTo({ top: targetY, left: window.scrollX, behavior: "smooth" });
scrolling = true;
const diff = Math.abs(targetY - prevY);
const time = 420 * (Math.log(diff + 510) - 6);
clearTimeout(delay);
delay = setTimeout(() => scrolling = false, time);
};
window.addEventListener("scroll", function() {
if (!scrolling) {
targetY = window.scrollY;
}
});
};

View File

@ -0,0 +1,12 @@
/**
* Adds a class to the <body> element when hovering the notification.
*/
export default function() {
document.body.addEventListener("mouseenter", function(){
document.body.classList.add("td-hover");
});
document.body.addEventListener("mouseleave", function(){
document.body.classList.remove("td-hover");
});
};

View File

@ -30,7 +30,7 @@ public virtual void OnReloadTriggered() {
public string LoadSilent(string path) => LoadInternal(path, silent: true);
protected virtual string LocateFile(string path) {
return Path.Combine(Program.ScriptPath, path);
return Path.Combine(Program.ResourcesPath, path);
}
private string LoadInternal(string path, bool silent) {
@ -49,27 +49,7 @@ private string LoadInternal(string path, bool silent) {
string resource;
try {
string contents = File.ReadAllText(location, Encoding.UTF8);
int separator;
// first line can be either:
// #<version>\r\n
// #<version>\n
if (contents[0] != '#') {
ShowLoadError(silent ? null : sync, $"File {path} appears to be corrupted, please try reinstalling the app.");
separator = 0;
}
else {
separator = contents.IndexOf('\n');
string fileVersion = contents.Substring(1, separator - 1).TrimEnd();
if (fileVersion != Program.VersionTag) {
ShowLoadError(silent ? null : sync, $"File {path} is made for a different version of TweetDuck ({fileVersion}) and may not function correctly in this version, please try reinstalling the app.");
}
}
resource = contents.Substring(separator).TrimStart();
resource = File.ReadAllText(location, Encoding.UTF8);
} catch (Exception ex) {
ShowLoadError(silent ? null : sync, $"Could not load {path}. The program will continue running with limited functionality.\n\n{ex.Message}");
resource = null;

View File

@ -1,184 +0,0 @@
(function(){
//
// Variable: Collection of all <a> tags.
//
const links = document.getElementsByTagName("A");
//
// Function: Adds an event listener to all elements in the array or collection.
//
const addEventListener = function(collection, type, listener){
for(let ele of collection){
ele.addEventListener(type, listener);
}
};
//
// Block: Hook into links to bypass default open function, and handle skipping notification when opening links.
//
(function(){
const onLinkClick = function(e){
if (e.button === 0 || e.button === 1){
const ele = e.currentTarget;
$TD.openBrowser(ele.href);
e.preventDefault();
if ($TDX.skipOnLinkClick){
const parentClasses = ele.parentNode.classList;
if (parentClasses.contains("js-tweet-text") || parentClasses.contains("js-quoted-tweet-text") || parentClasses.contains("js-timestamp")){
$TD.loadNextNotification();
}
}
}
};
addEventListener(links, "click", onLinkClick);
addEventListener(links, "auxclick", onLinkClick);
})();
//
// Block: Expand shortened links on hover or display tooltip.
//
(function(){
let prevMouseX = -1, prevMouseY = -1;
let tooltipTimer, tooltipDisplayed;
addEventListener(links, "mouseenter", function(e){
const me = e.currentTarget;
const url = me.getAttribute("data-full-url");
return if !url;
const text = me.textContent;
return if text.charCodeAt(text.length-1) !== 8230 && text.charCodeAt(0) !== 8230; // horizontal ellipsis
if ($TDX.expandLinksOnHover){
tooltipTimer = window.setTimeout(function(){
me.setAttribute("td-prev-text", text);
me.innerHTML = url.replace(/^https?:\/\/(www\.)?/, "");
}, 200);
}
else{
tooltipTimer = window.setTimeout(function(){
$TD.displayTooltip(url);
tooltipDisplayed = true;
}, 400);
}
});
addEventListener(links, "mouseleave", function(e){
return if !e.currentTarget.hasAttribute("data-full-url");
if ($TDX.expandLinksOnHover){
const prevText = e.currentTarget.getAttribute("td-prev-text");
if (prevText){
e.currentTarget.innerHTML = prevText;
}
}
window.clearTimeout(tooltipTimer);
if (tooltipDisplayed){
tooltipDisplayed = false;
$TD.displayTooltip(null);
}
});
addEventListener(links, "mousemove", function(e){
if (tooltipDisplayed && (prevMouseX !== e.clientX || prevMouseY !== e.clientY)){
const url = e.currentTarget.getAttribute("data-full-url");
return if !url;
$TD.displayTooltip(url);
prevMouseX = e.clientX;
prevMouseY = e.clientY;
}
});
})();
//
// Block: Work around broken smooth scrolling.
//
(function(){;
let targetY = 0;
let delay = -1;
let scrolling = false;
window.TDGF_scrollSmoothly = function(delta){
targetY += delta;
if (targetY < 0){
targetY = 0;
}
else if (targetY > document.body.offsetHeight - window.innerHeight){
targetY = document.body.offsetHeight - window.innerHeight;
}
const prevY = window.scrollY;
window.scrollTo({ top: targetY, left: window.scrollX, behavior: "smooth" });
scrolling = true;
const diff = Math.abs(targetY - prevY);
const time = 420 * (Math.log(diff + 510) - 6);
clearTimeout(delay);
delay = setTimeout(function(){ scrolling = false; }, time);
};
window.addEventListener("scroll", function(){
if (!scrolling){
targetY = window.scrollY;
}
});
})();
//
// Block: Work around clipboard HTML formatting.
//
document.addEventListener("copy", function(){
window.setTimeout($TD.fixClipboard, 0);
});
//
// Block: Setup a handler for 'Show this thread'.
//
(function(){
const btn = document.getElementById("tduck-show-thread");
return if !btn;
btn.addEventListener("click", function(){
$TD.showTweetDetail();
});
})();
//
// Block: Setup a skip button.
//
document.body.children[0].insertAdjacentHTML("beforeend", `
<svg id="td-skip" width="10" height="17" viewBox="0 0 350 600">
<path fill="#888" d="M0,151.656l102.208-102.22l247.777,247.775L102.208,544.986L0,442.758l145.546-145.547">
</svg>`);
document.getElementById("td-skip").addEventListener("click", function(){
$TD.loadNextNotification();
});
//
// Block: Setup a hover class on body.
//
document.body.addEventListener("mouseenter", function(){
document.body.classList.add("td-hover");
});
document.body.addEventListener("mouseleave", function(){
document.body.classList.remove("td-hover");
});
//
// Block: Force a reset of scroll position on every load.
//
history.scrollRestoration = "manual";
})();

View File

@ -1,26 +0,0 @@
(function($TD){
const ele = document.getElementsByTagName("article")[0];
ele.style.width = "{width}px";
ele.style.position = "absolute";
const contentHeight = ele.offsetHeight;
ele.style.position = "static";
const avatar = ele.querySelector(".tweet-avatar");
const avatarBottom = avatar ? avatar.getBoundingClientRect().bottom : 0;
$TD.setHeight(Math.floor(Math.max(contentHeight, avatarBottom + 9))).then(() => {
let framesLeft = {frames}; // basic render is done in 1 frame, large media take longer
let onNextFrame = function(){
if (--framesLeft < 0){
$TD.triggerScreenshot();
}
else{
requestAnimationFrame(onNextFrame);
}
};
onNextFrame();
});
})($TD_NotificationScreenshot);

View File

@ -323,6 +323,9 @@
<None Include="app.config" />
<None Include="packages.config" />
<None Include="Resources\Content\error\error.html" />
<None Include="Resources\Content\notification\example\example.html" />
<None Include="Resources\Content\notification\notification.css" />
<None Include="Resources\Content\notification\screenshot\screenshot.js" />
<None Include="Resources\Content\plugins\notification\plugins.js" />
<None Include="Resources\Content\plugins\tweetdeck\plugins.js" />
<None Include="Resources\Images\avatar.png" />
@ -356,10 +359,6 @@
<None Include="Resources\Plugins\timeline-polls\browser.js" />
<None Include="Resources\PostBuild.fsx" />
<None Include="Resources\PostCefUpdate.ps1" />
<None Include="Resources\Scripts\notification.js" />
<None Include="Resources\Scripts\pages\example.html" />
<None Include="Resources\Scripts\screenshot.js" />
<None Include="Resources\Scripts\styles\notification.css" />
</ItemGroup>
<ItemGroup>
<Redist Include="$(ProjectDir)bld\Redist\*.*" />
@ -389,12 +388,21 @@
<Content Include="Resources\Content\api\ready.js" />
<Content Include="Resources\Content\api\td.js" />
<Content Include="Resources\Content\api\utils.js" />
<Content Include="Resources\Content\bootstrap.js" />
<Content Include="Resources\Content\introduction\introduction.css" />
<Content Include="Resources\Content\introduction\introduction.js" />
<Content Include="Resources\Content\load.js" />
<Content Include="Resources\Content\login\hide_cookie_bar.js" />
<Content Include="Resources\Content\login\login.css" />
<Content Include="Resources\Content\login\setup_document_attributes.js" />
<Content Include="Resources\Content\notification\add_skip_button.js" />
<Content Include="Resources\Content\notification\disable_clipboard_formatting.js" />
<Content Include="Resources\Content\notification\expand_links_or_show_tooltip.js" />
<Content Include="Resources\Content\notification\handle_links.js" />
<Content Include="Resources\Content\notification\handle_show_this_thread_link.js" />
<Content Include="Resources\Content\notification\reset_scroll_position_on_load.js" />
<Content Include="Resources\Content\notification\scroll_smoothly.js" />
<Content Include="Resources\Content\notification\setup_body_hover_class.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" />
@ -459,7 +467,9 @@
<Content Include="Resources\Content\tweetdeck\tweetdeck.css" />
<Content Include="Resources\Content\update\update.js" />
<Content Include="Resources\Content\update\update.css" />
<Content Include="Resources\Scripts\bootstrap.js" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\Scripts" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>

View File

@ -50,15 +50,13 @@ public string GenerateHtml(string bodyClasses, string? headLayout, string? custo
headLayout ??= DefaultHeadLayout;
customStyles ??= string.Empty;
string mainCSS = App.ResourceHandler.Load("styles/notification.css") ?? string.Empty;
StringBuilder build = new StringBuilder(1000);
build.Append("<!DOCTYPE html>");
build.Append(headLayout);
build.Append("<style type='text/css'>").Append(mainCSS).Append("</style>");
build.Append("<link rel='stylesheet' href='td://resources/notification/notification.css'>");
if (!string.IsNullOrWhiteSpace(customStyles)) {
build.Append("<style type='text/css'>").Append(customStyles).Append("</style>");
build.Append("<style>").Append(customStyles).Append("</style>");
}
build.Append("</head><body class='scroll-styled-v");