1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-04-22 09:15:48 +02:00

Move the official guide directly into the app

This commit is contained in:
chylex 2021-12-24 11:09:02 +01:00
parent cff93dcc97
commit 3a89a28f8b
Signed by: chylex
GPG Key ID: 4DE42C8F19A80548
11 changed files with 94 additions and 153 deletions

View File

@ -1,10 +1,8 @@
using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
using TweetDuck.Browser;
using TweetDuck.Browser.Adapters;
using TweetDuck.Browser.Data;
using TweetDuck.Browser.Handling;
using TweetDuck.Browser.Handling.General;
@ -14,32 +12,12 @@
namespace TweetDuck.Dialogs {
sealed partial class FormGuide : Form, FormManager.IAppDialog {
private const string GuideUrl = "https://tweetduck.chylex.com/guide/v2/";
private const string GuidePathRegex = @"^guide(?:/v\d+)?(?:/(#.*))?";
private const string GuideUrl = @"td://guide/index.html";
private static readonly ResourceLink DummyPage = new ResourceLink("http://td/dummy", ResourceHandlers.ForString(string.Empty));
public static bool CheckGuideUrl(string url, out string hash) {
if (!url.Contains("//tweetduck.chylex.com/guide")) {
hash = null;
return false;
}
string path = url.Substring(url.IndexOf("/guide") + 1);
Match match = Regex.Match(path, GuidePathRegex);
if (match.Success) {
hash = match.Groups[1].Value;
return true;
}
else {
hash = null;
return false;
}
}
public static void Show(string hash = null) {
string url = GuideUrl + (hash ?? string.Empty);
string url = GuideUrl + (string.IsNullOrEmpty(hash) ? string.Empty : "#" + hash);
FormGuide guide = FormManager.TryFind<FormGuide>();
if (guide == null) {
@ -81,7 +59,6 @@ private FormGuide(string url, FormBrowser owner) {
};
browser.LoadingStateChanged += browser_LoadingStateChanged;
browser.FrameLoadEnd += browser_FrameLoadEnd;
browser.BrowserSettings.BackgroundColor = (uint) BackColor.ToArgb();
browser.Dock = DockStyle.None;
@ -123,9 +100,5 @@ private void browser_LoadingStateChanged(object sender, LoadingStateChangedEvent
}
}
}
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e) {
CefScriptExecutor.RunScript(e.Frame, "Array.prototype.forEach.call(document.getElementsByTagName('A'), ele => ele.addEventListener('click', e => { e.preventDefault(); window.open(ele.getAttribute('href')); }))", "gen:links");
}
}
}

View File

@ -33,6 +33,7 @@ static class Program {
public static readonly string ResourcesPath = Path.Combine(ProgramPath, "resources");
public static readonly string PluginPath = Path.Combine(ProgramPath, "plugins");
public static readonly string GuidePath = Path.Combine(ProgramPath, "guide");
public static readonly string StoragePath = IsPortable ? Path.Combine(ProgramPath, "portable", "storage") : GetDataStoragePath();

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>TweetDuck - Guide</title>
@ -11,8 +11,6 @@
<link rel="icon" href="img/icon.ico">
<link rel="stylesheet" href="style.css" type="text/css">
<script type="text/javascript" src="script.js" async></script>
</head>
<body>
<div id="guide">
@ -256,7 +254,7 @@
<li>Select items you want to save in your profile (note that <em>Plugin Data</em> includes data from official plugins, such as those that let you customize the website or create tweet templates)</li>
<li>Click <em>Export Profile</em></li>
</ol>
<img src="img/options-manage.png" alt="" style="margin-right:6px">
<img src="img/options-manage.png" alt="" style="margin-right:6px;">
<img src="img/options-manage-export.png" alt="">
<p>You can save your profile into a cloud storage (Dropbox, Google Drive, etc.) or an external drive, for example. When you want to restore it, follow the same steps but select <em>Import profile</em> and then select the file instead.</p>
<p>When importing a profile, you will be again able to pick which items you want to restore. You can for example export a full profile including your login session, but then only import program options and plugin data if you want to login to a different account.</p>
@ -272,7 +270,7 @@
<li>Select items you want reset (note that <em>Plugin Data</em> includes data from official plugins, such as those that let you customize the website or create tweet templates)</li>
<li>Click <em>Restore Defaults</em></li>
</ol>
<img src="img/options-manage.png" alt="" style="margin-right:6px">
<img src="img/options-manage.png" alt="" style="margin-right:6px;">
<img src="img/options-manage-reset.png" alt="">
</div>
</details>
@ -349,7 +347,7 @@
</ol>
<p>After you enable the plugin, it will use your preferred account for all replies by default. If that's your intention, you can simply enable the plugin and leave it, otherwise continue reading:</p>
<ol>
<li>Click <em>Configure</em> next to the plugin to open a folder with the configuration file
<li>Click <em>Configure</em> next to the plugin to open a folder with the configuration file</li>
<li>Open <em>configuration.js</em> in a text editor that can edit and save JavaScript or any pure text files, therefore office suits or WordPad are not suitable; if you don't have any specific editor, use Notepad.</li>
<li>The configuration file includes very detailed instructions &ndash; you can use one of the <em>presets</em>, a <em>specific account</em> for all replies, or use JavaScript to <em>fully customize</em> the reply behavior</li>
</ol>
@ -400,5 +398,7 @@
</section>
</div>
<script type="text/javascript" src="script.js"></script>
</body>
</html>

View File

@ -1,94 +1,36 @@
var init = function(){
if (!("open" in document.getElementsByTagName("details")[0])){
var elements = document.getElementsByTagName("details");
var onClick = function(e){
var summary = e.target;
var parent = e.target.parentElement;
var contents = e.target.nextElementSibling;
if (parent.hasAttribute("open")){
parent.removeAttribute("open");
summary.setAttribute("aria-expanded", "false");
contents.setAttribute("aria-hidden", "true");
contents.style.display = "none";
}
else{
parent.setAttribute("open", "");
summary.setAttribute("aria-expanded", "true");
contents.setAttribute("aria-hidden", "false");
contents.style.display = "block";
}
};
var onKey = function(e){
if (e.keyCode === 13 || e.keyCode === 32){
onClick(e);
}
};
for(var index = 0; index < elements.length; index++){
var ele = elements[index];
if (ele.childElementCount === 2){
var summary = ele.children[0];
var contents = ele.children[1];
ele.style.display = "block";
ele.setAttribute("role", "group");
summary.setAttribute("role", "button");
summary.setAttribute("aria-expanded", "false");
summary.setAttribute("tabindex", 0);
summary.addEventListener("click", onClick);
summary.addEventListener("keydown", onKey);
contents.setAttribute("aria-hidden", "true");
contents.style.display = "none";
}
}
}
else if ("WebkitAppearance" in document.documentElement.style){
var elements = document.getElementsByTagName("summary");
var onMouseDown = function(e){
e.target.classList.add("webkit-workaround");
};
var onMouseUp = function(e){
e.target.classList.remove("webkit-workaround");
e.target.blur();
};
for(var index = 0; index < elements.length; index++){
elements[index].addEventListener("mousedown", onMouseDown);
elements[index].addEventListener("mouseup", onMouseUp);
}
}
if (location.hash.length > 1){
var element = document.getElementById(location.hash.substring(1));
if (element && element.tagName === "SUMMARY"){
element.click();
element.blur();
element.scrollIntoView(true);
if (window.innerWidth === 0){
var ffs = function(){
element.scrollIntoView(true);
window.removeEventListener("resize", ffs);
};
window.addEventListener("resize", ffs);
}
}
}
};
for (const summary of document.getElementsByTagName("summary")) {
summary.addEventListener("mousedown", function(e) {
e.target.classList.add("webkit-workaround");
});
summary.addEventListener("mouseup", function(e) {
e.target.classList.remove("webkit-workaround");
e.target.blur();
});
}
if (document.readyState !== "loading"){
init();
for (const link of document.getElementsByTagName("A")) {
link.addEventListener("click", function(e) {
e.preventDefault();
window.open(link.getAttribute("href"));
});
}
else{
document.addEventListener("DOMContentLoaded", init);
if (location.hash.length > 1) {
const element = document.getElementById(location.hash.substring(1));
if (element?.tagName === "SUMMARY") {
element.click();
element.blur();
element.scrollIntoView(true);
if (window.innerWidth === 0) {
const ffs = function() {
element.scrollIntoView(true);
window.removeEventListener("resize", ffs);
};
window.addEventListener("resize", ffs);
}
}
}

View File

@ -23,7 +23,7 @@ body {
flex-direction: column;
}
@media(max-width: 408px) {
@media (max-width: 408px) {
#guide section {
min-width: calc(100vw - 48px);
}

View File

@ -47,9 +47,10 @@ let main (argv: string[]) =
printfn "TweetDuck version %s" version
printfn "--------------------------"
let guideDir = targetDir +/ "guide"
let localesDir = targetDir +/ "locales"
let resourcesDir = targetDir +/ "resources"
let pluginsDir = targetDir +/ "plugins"
let resourcesDir = targetDir +/ "resources"
// Functions (File Management)
@ -120,6 +121,7 @@ let main (argv: string[]) =
copyFile (projectDir +/ "bld/Resources/LICENSES.txt") (targetDir +/ "LICENSES.txt")
copyDirectoryContents (projectDir +/ "Resources/Guide") guideDir
copyDirectoryContents (projectDir +/ "Resources/Content") resourcesDir
createDirectory (pluginsDir +/ "official")

View File

@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Net;
using CefSharp;
using TweetLib.Core.Browser;
@ -9,8 +8,6 @@ namespace TweetDuck.Resources {
public class ResourceSchemeFactory : ISchemeHandlerFactory {
public const string Name = "td";
private static readonly string RootPath = Path.Combine(Program.ResourcesPath);
private readonly IResourceProvider<IResourceHandler> resourceProvider;
public ResourceSchemeFactory(IResourceProvider<IResourceHandler> resourceProvider) {
@ -22,12 +19,18 @@ public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName
return null;
}
if (uri.Authority != "resources") {
return null;
string rootPath = uri.Authority switch {
"resources" => Program.ResourcesPath,
"guide" => Program.GuidePath,
_ => null
};
if (rootPath == null) {
return resourceProvider.Status(HttpStatusCode.NotFound, "Invalid URL.");
}
string filePath = FileUtils.ResolveRelativePathSafely(RootPath, uri.AbsolutePath.TrimStart('/'));
return filePath.Length == 0 ? resourceProvider.Status(HttpStatusCode.Forbidden, "File path has to be relative to the resources root folder.") : resourceProvider.File(filePath);
string filePath = FileUtils.ResolveRelativePathSafely(rootPath, uri.AbsolutePath.TrimStart('/'));
return filePath.Length == 0 ? resourceProvider.Status(HttpStatusCode.Forbidden, "File path has to be relative to the root folder.") : resourceProvider.File(filePath);
}
}
}

View File

@ -467,14 +467,35 @@
<Content Include="Resources\Content\tweetdeck\tweetdeck.css" />
<Content Include="Resources\Content\update\update.js" />
<Content Include="Resources\Content\update\update.css" />
<Content Include="Resources\Guide\img\app-menu.png" />
<Content Include="Resources\Guide\img\column-clear-header.png" />
<Content Include="Resources\Guide\img\column-clear-preferences.png" />
<Content Include="Resources\Guide\img\column-preferences.png" />
<Content Include="Resources\Guide\img\icon.ico" />
<Content Include="Resources\Guide\img\new-tweet-emoji.png" />
<Content Include="Resources\Guide\img\new-tweet-pin.png" />
<Content Include="Resources\Guide\img\new-tweet-template-advanced.png" />
<Content Include="Resources\Guide\img\new-tweet-template-basic.png" />
<Content Include="Resources\Guide\img\options-manage-export.png" />
<Content Include="Resources\Guide\img\options-manage-reset.png" />
<Content Include="Resources\Guide\img\options-manage.png" />
<Content Include="Resources\Guide\img\options-notifications-location.png" />
<Content Include="Resources\Guide\img\options-notifications-size.png" />
<Content Include="Resources\Guide\img\options-sounds.png" />
<Content Include="Resources\Guide\img\settings-dropdown.png" />
<Content Include="Resources\Guide\img\settings-editdesign.png" />
<Content Include="Resources\Guide\index.html" />
<Content Include="Resources\Guide\script.js" />
<Content Include="Resources\Guide\style.css" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>rmdir "$(ProjectDir)bin\Debug"
rmdir "$(ProjectDir)bin\Release"
rmdir "$(TargetDir)resources" /S /Q
rmdir "$(TargetDir)guide" /S /Q
rmdir "$(TargetDir)plugins" /S /Q
rmdir "$(TargetDir)resources" /S /Q
"$(ProjectDir)bld\post_build.exe" "$(TargetDir)\" "$(ProjectDir)\" "$(ConfigurationName)"
</PostBuildEvent>

View File

@ -115,24 +115,19 @@ public static void OpenExternalBrowser(string url) {
switch (TwitterUrls.Check(url)) {
case TwitterUrls.UrlType.Fine:
if (FormGuide.CheckGuideUrl(url, out string hash)) {
FormGuide.Show(hash);
string browserPath = Config.BrowserPath;
if (browserPath == null || !File.Exists(browserPath)) {
App.SystemHandler.OpenAssociatedProgram(url);
}
else {
string browserPath = Config.BrowserPath;
string quotedUrl = '"' + url + '"';
string browserArgs = Config.BrowserPathArgs == null ? quotedUrl : Config.BrowserPathArgs + ' ' + quotedUrl;
if (browserPath == null || !File.Exists(browserPath)) {
App.SystemHandler.OpenAssociatedProgram(url);
}
else {
string quotedUrl = '"' + url + '"';
string browserArgs = Config.BrowserPathArgs == null ? quotedUrl : Config.BrowserPathArgs + ' ' + quotedUrl;
try {
using (Process.Start(browserPath, browserArgs)) {}
} catch (Exception e) {
Program.Reporter.HandleException("Error Opening Browser", "Could not open the browser.", true, e);
}
try {
using (Process.Start(browserPath, browserArgs)) {}
} catch (Exception e) {
Program.Reporter.HandleException("Error Opening Browser", "Could not open the browser.", true, e);
}
}

View File

@ -55,8 +55,9 @@ Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChang
[UninstallDelete]
Type: files; Name: "{app}\*.*"
Type: filesandordirs; Name: "{app}\resources"
Type: filesandordirs; Name: "{app}\guide"
Type: filesandordirs; Name: "{app}\locales"
Type: filesandordirs; Name: "{app}\resources"
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache"
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"

View File

@ -46,6 +46,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\bin\x86\Release\TweetDuck.*"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\bin\x86\Release\TweetLib.*"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\bin\x86\Release\guide\*.*"; DestDir: "{app}\guide"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\bin\x86\Release\resources\*.*"; DestDir: "{app}\resources"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\bin\x86\Release\plugins\*.*"; DestDir: "{app}\plugins"; Flags: ignoreversion recursesubdirs createallsubdirs
@ -57,6 +58,7 @@ Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChang
[UninstallDelete]
Type: files; Name: "{app}\*.*"
Type: filesandordirs; Name: "{app}\guide"
Type: filesandordirs; Name: "{app}\locales"
Type: filesandordirs; Name: "{app}\resources"
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache"
@ -71,9 +73,10 @@ Type: files; Name: "{app}\cef_100_percent.pak"
Type: files; Name: "{app}\cef_200_percent.pak"
Type: files; Name: "{app}\cef_extensions.pak"
Type: files; Name: "{app}\devtools_resources.pak"
Type: filesandordirs; Name: "{app}\guide"
Type: filesandordirs; Name: "{app}\plugins\official"
Type: filesandordirs; Name: "{app}\resources"
Type: filesandordirs; Name: "{app}\scripts"
Type: filesandordirs; Name: "{app}\plugins\official"
[Code]
function TDIsUninstallable: Boolean; forward;