mirror of
				https://github.com/smartfrigde/armcord.git
				synced 2024-08-14 23:56:58 +00:00 
			
		
		
		
	A few things (#199)
* 2 new things (Read desc.)
- Cleaned up ASAR packaging, ignoring unneeded files for building
- Moved install location for Windows users ("AppData\Local\Programs" -> "AppData\Local"
* 3 things (Read desc.)
- Updated things related to Hummus (Hummus settings don't save nor load in it's respective settings window yet, idk why)
- Added check for package version (ArmCord's internal version)
- Made check for Kernel mod a bit cleaner, it still uses the same jank method
* 3 things
- Made macOS titlebar more accurate to Discord
- Added "unFocused" class when window isn't focused
- Added option to uninstall Husky hook for Windows users with reminder to run format script before committing
* Resolved a dumb issue
My dumbass not knowing the "echo" command existed smh
* Made "precommit-fix" warning more noticable
* Whoops
* Fixed a CSS bug
Discord updates are gonna hate us, huh?
* 4 things (Formatted)
- Updated coding for getting the current version
- Updated some context menu and tray stuff
- Added current version to the title of the settings window
- Added the ability to restart the app within the settings
* A few things
- Updated tray menu to include the tray icon infront of the ArmCord version
- Updated MacOS titlebar to not be broken in setup
- Polished settings menu a bit
- Polished the Discord tray icon
- Added the Classic Discord icon as tray icon option
			
			
This commit is contained in:
		
							parent
							
								
									0392f176c5
								
							
						
					
					
						commit
						f57fe11769
					
				
					 19 changed files with 238 additions and 150 deletions
				
			
		|  | @ -74,52 +74,56 @@ p { | |||
|     vertical-align: middle; | ||||
|     border-radius: 10px; | ||||
|     background: var(--background-floating); | ||||
|     border: 20px; | ||||
|     padding-left: 20px; | ||||
|     padding-right: 20px; | ||||
|     border-color: var(--background-floating); | ||||
|     border-style: solid; | ||||
| } | ||||
| .saveBar { | ||||
|     position: fixed; | ||||
|     bottom: 20px; | ||||
|     float: left; | ||||
|     left: 0; | ||||
|     font-weight: bold; | ||||
|     z-index: 999; | ||||
|     font-size: 10px; | ||||
| } | ||||
| .restartBar { | ||||
|     position: fixed; | ||||
|     bottom: 20px; | ||||
|     float: right; | ||||
|     right: 0; | ||||
|     font-weight: bold; | ||||
|     z-index: 999; | ||||
|     font-size: 10px; | ||||
| } | ||||
| .saveBar > button { | ||||
|     width: 90px; | ||||
|     transform: translateX(56%); | ||||
| } | ||||
| .restartBar > button { | ||||
|     width: 90px; | ||||
|     transform: translateX(50%); | ||||
| } | ||||
| 
 | ||||
| .header { | ||||
|     color: var(--header-primary); | ||||
|     font-size: 1.5em; | ||||
|     position: relative; | ||||
|     bottom: 20px; | ||||
|     font-weight: bold; | ||||
| } | ||||
| .header2 { | ||||
|     color: var(--header-primary); | ||||
|     font-size: 1.5em; | ||||
|     position: relative; | ||||
|     bottom: 0px; | ||||
|     font-weight: bold; | ||||
| } | ||||
| .description2 { | ||||
|     position: relative; | ||||
|     color: white; | ||||
|     font-size: 1.2em; | ||||
|     font-weight: lighter; | ||||
|     bottom: 15px; | ||||
|     top: 15px; | ||||
|     margin: auto; | ||||
| } | ||||
| .description { | ||||
|     position: relative; | ||||
|     color: white; | ||||
|     font-size: 1.2em; | ||||
|     font-weight: lighter; | ||||
|     bottom: 40px; | ||||
| } | ||||
| .dropdown { | ||||
|     position: relative; | ||||
|     font-size: 25px; | ||||
|     margin-top: 20px; | ||||
| } | ||||
| .center { | ||||
|     text-align: center; | ||||
|  | @ -159,6 +163,9 @@ button:active { | |||
| #save { | ||||
|     font-size: 15px; | ||||
| } | ||||
| .acAdvSettings { | ||||
|     height: 400px !important; | ||||
| } | ||||
| .tgl { | ||||
|     display: none; | ||||
| } | ||||
|  | @ -170,6 +177,7 @@ button:active { | |||
| .tgl *:before, | ||||
| .tgl + .tgl-btn { | ||||
|     box-sizing: border-box; | ||||
|     margin-top: 20px; | ||||
| } | ||||
| .tgl::-moz-selection, | ||||
| .tgl:after::-moz-selection, | ||||
|  | @ -238,6 +246,17 @@ button:active { | |||
| .tgl-light:checked + .tgl-btn { | ||||
|     background: var(--brand-experiment); | ||||
| } | ||||
| 
 | ||||
| select optgroup { | ||||
|     color: #fff6; | ||||
|     font-weight: 200; | ||||
|     font-style: italic; | ||||
| } | ||||
| select option { | ||||
|     color: #fff; | ||||
|     font-weight: 400; | ||||
|     font-style: normal; | ||||
| } | ||||
| select { | ||||
|     -webkit-appearance: button; | ||||
|     -moz-appearance: button; | ||||
|  |  | |||
|  | @ -90,7 +90,18 @@ body { | |||
| } | ||||
| /* }}} */ | ||||
| 
 | ||||
| /* Logo {{{ */ | ||||
| /* Titlebar {{{ */ | ||||
| div { | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
|     border: 0; | ||||
|     display: block; | ||||
|     font-weight: inherit; | ||||
|     font-style: inherit; | ||||
|     font-family: inherit; | ||||
|     font-size: 100%; | ||||
|     vertical-align: baseline; | ||||
| } | ||||
| #logo { | ||||
|     display: flex; | ||||
|     flex-direction: row; | ||||
|  | @ -100,20 +111,25 @@ body { | |||
|     width: 292px; | ||||
| } | ||||
| 
 | ||||
| .titlebar #window-controls-container #maximize { | ||||
| 
 | ||||
| [armcord-platform="win32"] .titlebar #window-controls-container #maximize, | ||||
| [armcord-platform="linux"] .titlebar #window-controls-container #maximize { | ||||
|     display: none; | ||||
| } | ||||
| 
 | ||||
| .titlebar #window-controls-container #spacer { | ||||
| [armcord-platform="win32"] .titlebar #window-controls-container #spacer, | ||||
| [armcord-platform="linux"] .titlebar #window-controls-container #spacer { | ||||
|     float: left; | ||||
|     height: 100%; | ||||
|     width: 33%; | ||||
| } | ||||
| [armcord-platform="darwin"] .titlebar #window-controls-container { | ||||
|     margin-left: -26px; | ||||
| [armcord-platform="darwin"] .titlebar #window-controls-container #quit { | ||||
|     width: 18% !important; | ||||
| } | ||||
| [armcord-platform="darwin"] .titlebar #window-controls-container #minimize { | ||||
|     transform: translateX(5px); | ||||
| [armcord-platform="darwin"] .titlebar #window-controls-container #maximize, | ||||
| [armcord-platform="darwin"] .titlebar #window-controls-container #maximize #maximize-icon { | ||||
|     background-color: #d6d6d5 !important; | ||||
|     pointer-events: none; | ||||
| } | ||||
| /* }}} */ | ||||
| 
 | ||||
|  |  | |||
|  | @ -260,10 +260,11 @@ | |||
| [armcord-platform="darwin"][unFocused] .titlebar #window-controls-container #maximize, | ||||
| [armcord-platform="darwin"][unFocused] .titlebar #window-controls-container #quit { | ||||
|     background-color: #d6d6d5 !important; | ||||
|     pointer-events: none; | ||||
|     transition: background-color 0.1s ease-in; | ||||
| } | ||||
| 
 | ||||
| [armcord-platform="darwin"] .titlebar #window-controls-container #quit #quit-icon { | ||||
| [armcord-platform="darwin"]:not([unFocused]) .titlebar #window-controls-container #quit #quit-icon { | ||||
|     background-color: #79282b; | ||||
|     -webkit-mask: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIHZpZXdCb3g9IjAgMCAxMiAxMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNS4yOTI4OSA2TDIuODE4MDEgMy41MjUxM0wzLjUyNTEyIDIuODE4MDJMNS45OTk5OSA1LjI5Mjg5TDguNDc0ODcgMi44MTgwMkw5LjE4MTk3IDMuNTI1MTNMNi43MDcxIDZMOS4xODE5NyA4LjQ3NDg3TDguNDc0ODcgOS4xODE5OEw1Ljk5OTk5IDYuNzA3MTFMMy41MjUxMiA5LjE4MTk4TDIuODE4MDEgOC40NzQ4N0w1LjI5Mjg5IDZaIiBmaWxsPSJyZ2JhKDEyOCwgNiwgMCwgMSkiLz48L3N2Zz4=") | ||||
|         no-repeat 50% 50%; | ||||
|  | @ -271,7 +272,7 @@ | |||
|         no-repeat 50% 50%; | ||||
|     transform: translate(-0.3px, -11.7px); | ||||
| } | ||||
| [armcord-platform="darwin"] .titlebar #window-controls-container #minimize #minimize-icon { | ||||
| [armcord-platform="darwin"]:not([unFocused]) .titlebar #window-controls-container #minimize #minimize-icon { | ||||
|     background-color: #7d631b; | ||||
|     -webkit-mask: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIHZpZXdCb3g9IjAgMCAxMiAxMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTAgNS4zOTk5OUgyVjYuNTk5OTlIMTBWNS4zOTk5OVoiIGZpbGw9IiM5ODY4MDEiLz48L3N2Zz4=") | ||||
|         no-repeat 50% 50%; | ||||
|  | @ -279,7 +280,7 @@ | |||
|         no-repeat 50% 50%; | ||||
|     transform: translate(-0px, -11.7px); | ||||
| } | ||||
| [armcord-platform="darwin"] .titlebar #window-controls-container #maximize #maximize-icon { | ||||
| [armcord-platform="darwin"]:not([unFocused]) .titlebar #window-controls-container #maximize #maximize-icon { | ||||
|     background-color: #1d7525; | ||||
|     -webkit-mask: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIHZpZXdCb3g9IjAgMCAxMiAxMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNOC41ODgyMyA2Ljk5MDE1TDUuMDA5NzkgMy40MTE3QzQuODU1ODMgMy4yNTc3NCA0Ljk1ODYgMi45OTQyMiA1LjE3NjE0IDIuOTg1MTRMOC45MTA0MiAyLjgyOTMxQzkuMDU2NTggMi44MjMyMSA5LjE3NjczIDIuOTQzMzUgOS4xNzA2MyAzLjA4OTUyTDkuMDE0NzkgNi44MjM4QzkuMDA1NzEgNy4wNDEzNCA4Ljc0MjE5IDcuMTQ0MTEgOC41ODgyMyA2Ljk5MDE1WiIgZmlsbD0iIzEyNUUxRSIvPjxwYXRoIGQ9Ik0zLjQxMTc3IDUuMDA5ODJMNi45OTAyMSA4LjU4ODI3QzcuMTQ0MTcgOC43NDIyMyA3LjA0MTQgOS4wMDU3NSA2LjgyMzg2IDkuMDE0ODNMMy4wODk1OCA5LjE3MDY2QzIuOTQzNDIgOS4xNzY3NiAyLjgyMzI3IDkuMDU2NjEgMi44MjkzNyA4LjkxMDQ1TDIuOTg1MjEgNS4xNzYxN0MyLjk5NDI5IDQuOTU4NjMgMy4yNTc4MSA0Ljg1NTg2IDMuNDExNzcgNS4wMDk4MloiIGZpbGw9IiMxMjVFMUUiLz48L3N2Zz4=") | ||||
|         no-repeat 50% 50%; | ||||
|  |  | |||
							
								
								
									
										
											BIN
										
									
								
								src/content/favicon.ico
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/content/favicon.ico
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 24 KiB | 
|  | @ -2,6 +2,7 @@ | |||
| <html lang="en"> | ||||
|     <head> | ||||
|         <meta charset="UTF-8" /> | ||||
|         <link rel="icon" type="image/ico" href="./favicon.ico"> | ||||
|         <meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||||
|         <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
|         <title>ArmCord Setup</title> | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| <html lang="en"> | ||||
|     <head> | ||||
|         <meta charset="UTF-8" /> | ||||
|         <link rel="icon" type="image/ico" href="./favicon.ico"> | ||||
|         <title>ArmCord</title> | ||||
|         <style> | ||||
|             @import url("css/splash.css"); | ||||
|  |  | |||
							
								
								
									
										13
									
								
								src/ipc.ts
									
										
									
									
									
								
							
							
						
						
									
										13
									
								
								src/ipc.ts
									
										
									
									
									
								
							|  | @ -1,5 +1,5 @@ | |||
| //ipc stuff
 | ||||
| import {app, ipcMain, shell, desktopCapturer,nativeImage} from "electron"; | ||||
| import {app, ipcMain, shell, desktopCapturer, nativeImage} from "electron"; | ||||
| import {mainWindow} from "./window"; | ||||
| import { | ||||
|     setConfigBulk, | ||||
|  | @ -8,7 +8,8 @@ import { | |||
|     setLang, | ||||
|     getLang, | ||||
|     getWindowState, | ||||
|     packageVersion, getDisplayVersion | ||||
|     packageVersion, | ||||
|     getDisplayVersion | ||||
| } from "./utils"; | ||||
| import {customTitlebar} from "./main"; | ||||
| import {createSettingsWindow} from "./settings/main"; | ||||
|  | @ -30,14 +31,14 @@ export function registerIpc() { | |||
|     ipcMain.on("setPing", (event, pingCount: number) => { | ||||
|         switch (os.platform()) { | ||||
|             case "linux" ?? "macos": | ||||
|                 app.setBadgeCount(pingCount) | ||||
|                 app.setBadgeCount(pingCount); | ||||
|                 break; | ||||
|             case "win32": | ||||
|                 if (pingCount > 0) { | ||||
|                     var image = nativeImage.createFromPath(path.join(__dirname, "../", `/assets/ping.png`)) | ||||
|                     mainWindow.setOverlayIcon(image, "badgeCount") | ||||
|                     var image = nativeImage.createFromPath(path.join(__dirname, "../", `/assets/ping.png`)); | ||||
|                     mainWindow.setOverlayIcon(image, "badgeCount"); | ||||
|                 } else { | ||||
|                     mainWindow.setOverlayIcon(null, "badgeCount") | ||||
|                     mainWindow.setOverlayIcon(null, "badgeCount"); | ||||
|                 } | ||||
|                 break; | ||||
|         } | ||||
|  |  | |||
|  | @ -26,10 +26,10 @@ if (process.platform == "linux") { | |||
| checkIfConfigExists(); | ||||
| injectElectronFlags(); | ||||
| app.whenReady().then(async () => { | ||||
|     if (await getConfig("customIcon") !== undefined ?? null) { | ||||
|         iconPath = await getConfig("customIcon") | ||||
|     if ((await getConfig("customIcon")) !== undefined ?? null) { | ||||
|         iconPath = await getConfig("customIcon"); | ||||
|     } else { | ||||
|         iconPath = path.join(__dirname, "../", "/assets/ac_icon_transparent.png") | ||||
|         iconPath = path.join(__dirname, "../", "/assets/ac_icon_transparent.png"); | ||||
|     } | ||||
|     async function init() { | ||||
|         switch (await getConfig("windowStyle")) { | ||||
|  |  | |||
|  | @ -63,7 +63,7 @@ export async function setMenu() { | |||
|                     label: "Reload", | ||||
|                     accelerator: "CmdOrCtrl+R", | ||||
|                     click: function () { | ||||
|                         mainWindow.reload() | ||||
|                         mainWindow.reload(); | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|  | @ -97,7 +97,7 @@ export async function setMenu() { | |||
|             label: "Zoom", | ||||
|             submenu: [ | ||||
|                 {label: "Zoom in", accelerator: "CmdOrCtrl+Plus", role: "zoomIn"}, | ||||
|                 {label: "Zoom out", accelerator: "CmdOrCtrl+-", role: "zoomOut"}, | ||||
|                 {label: "Zoom out", accelerator: "CmdOrCtrl+-", role: "zoomOut"} | ||||
|             ] | ||||
|         } | ||||
|     ]; | ||||
|  |  | |||
|  | @ -2,12 +2,8 @@ | |||
| <html lang="en"> | ||||
|     <head> | ||||
|         <meta charset="UTF-8" /> | ||||
|         <title>ArmCord Settings</title> | ||||
|         <style> | ||||
|             @import url("../content/css/settings.css"); | ||||
|             .acAdvSettings { | ||||
|                 height: 19em !important; | ||||
|             } | ||||
|         </style> | ||||
|     </head> | ||||
| 
 | ||||
|  | @ -15,6 +11,9 @@ | |||
|         <div class="saveBar"> | ||||
|             <button id="settings-save" class="center">Save Settings</button> | ||||
|         </div> | ||||
|         <div class="restartBar"> | ||||
|             <button id="settings-restart" class="center">Restart App</button> | ||||
|         </div> | ||||
|         <div class="switch acTheme"> | ||||
|             <select name="theme" id="theme" class="left dropdown"> | ||||
|                 <option value="default">Default</option> | ||||
|  | @ -33,40 +32,40 @@ | |||
|         </div> | ||||
|         <br /> | ||||
|         <div class="switch acCSP"> | ||||
|             <label class="header2">ArmCord CSP</label> | ||||
|             <label class="header">ArmCord CSP</label> | ||||
|             <input class="tgl tgl-light left" id="csp" type="checkbox" /> | ||||
|             <label class="tgl-btn left" for="csp"></label> | ||||
|             <p class="description2"> | ||||
|             <p class="description"> | ||||
|                 ArmCord CSP is our system that manages loading custom content loading into the Discord app. Stuff like | ||||
|                 client mods and themes depend on it. Disable if you want to get rid of mods and custom styles. | ||||
|             </p> | ||||
|         </div> | ||||
|         <br /> | ||||
|         <div class="switch acTray"> | ||||
|             <label class="header2" id="settings-tray">Minimize to tray</label> | ||||
|             <label class="header" id="settings-tray">Minimize to tray</label> | ||||
|             <input class="tgl tgl-light left" id="tray" type="checkbox" /> | ||||
|             <label class="tgl-btn left" for="tray"></label> | ||||
|             <p class="description2"> | ||||
|             <p class="description"> | ||||
|                 When disabled, ArmCord will close like any other window when closed, otherwise it'll sit back and relax | ||||
|                 in your system tray for later. | ||||
|             </p> | ||||
|         </div> | ||||
|         <br /> | ||||
|         <div class="switch acPatches"> | ||||
|             <label class="header2" id="settings-patches">Automatic Patches</label> | ||||
|             <label class="header" id="settings-patches">Automatic Patches</label> | ||||
|             <input class="tgl tgl-light left" id="patches" type="checkbox" /> | ||||
|             <label class="tgl-btn left" for="patches"></label> | ||||
|             <p class="description2"> | ||||
|             <p class="description"> | ||||
|                 Fetches automatic patches that are distributed if release turns out to have bugs after release. Usually | ||||
|                 you don't have to keep this enabled, unless notified in support Discord. | ||||
|             </p> | ||||
|         </div> | ||||
|         <br /> | ||||
|         <div class="switch acAltPaste"> | ||||
|             <label class="header2" id="settings-alternativePaste">Alternative paste</label> | ||||
|             <label class="header" id="settings-alternativePaste">Alternative paste</label> | ||||
|             <input class="tgl tgl-light left" id="alternativePaste" type="checkbox" /> | ||||
|             <label class="tgl-btn left" for="alternativePaste"></label> | ||||
|             <p class="description2"> | ||||
|             <p class="description"> | ||||
|                 If you're on Gnome on Linux or just simply can't paste images copied from other messages, then this is | ||||
|                 for you. This enables alternative module for pasting images. Only enable this when you're experiencing | ||||
|                 issues. | ||||
|  | @ -100,10 +99,10 @@ | |||
|         </div> | ||||
|         <br /> | ||||
|         <div class="switch acCordwood"> | ||||
|             <label class="header2" id="settings-cordwood">Cordwood client mod</label> | ||||
|             <label class="header" id="settings-cordwood">Cordwood client mod</label> | ||||
|             <input class="tgl tgl-light left" id="cordwood" type="checkbox" /> | ||||
|             <label class="tgl-btn left" for="cordwood"></label> | ||||
|             <p class="description2"> | ||||
|             <p class="description"> | ||||
|                 Client mod for Hummus that focuses on making the plugin development experience easier. Minimal, light, | ||||
|                 and easy to use | ||||
|             </p> | ||||
|  | @ -124,13 +123,18 @@ | |||
|         <br /> | ||||
|         <div class="switch acTray"> | ||||
|             <select name="trayIcon" id="trayIcon" class="left dropdown"> | ||||
|                 <option value="default">Default (Dynamic)</option> | ||||
|                 <option value="ac_plug_colored">Plug colored</option> | ||||
|                 <option value="dsc-tray">Discord Icon</option> | ||||
|                 <option value="ac_white_plug">White Icon</option> | ||||
|                 <option value="ac_black_plug">Black Icon</option> | ||||
|                 <option value="ac_white_plug_hollow">White Hollowed Icon</option> | ||||
|                 <option value="ac_black_plug_hollow">Black Hollowed Icon</option> | ||||
|                 <optgroup label="- Discord -"> | ||||
|                     <option value="default">Dynamic</option> | ||||
|                     <option value="dsc-tray">Discord Icon</option> | ||||
|                     <option value="clsc-dsc-tray">Classic Discord Icon</option> | ||||
|                 </optgroup> | ||||
|                 <optgroup label="- ArmCord -"> | ||||
|                     <option value="ac_plug_colored">Colored Plug</option> | ||||
|                     <option value="ac_white_plug">White Plug</option> | ||||
|                     <option value="ac_white_plug_hollow">White Plug Alt</option> | ||||
|                     <option value="ac_black_plug">Black Plug</option> | ||||
|                     <option value="ac_black_plug_hollow">Black Plug Alt</option> | ||||
|                 </optgroup> | ||||
|             </select> | ||||
|             <p class="header" id="settings-trayIcon">Tray icon:</p> | ||||
|             <p class="description">Set the icon which will appear in tray menu.</p> | ||||
|  | @ -139,6 +143,13 @@ | |||
|         <br /> | ||||
|         <div class="switch acAdvSettings"> | ||||
|             <h1 class="center advancedText">⚠️ Advanced User Zone ⚠️</h1> | ||||
|             <br> | ||||
|             <br> | ||||
|             <label class="header" id="settings-skipSplash">Skip Splash Screen (Experimental)</label> | ||||
|             <input class="tgl tgl-light left" id="skipSplash" type="checkbox" /> | ||||
|             <label class="tgl-btn left" for="skipSplash"></label> | ||||
|             <p class="description">Skips ArmCord splash screen when you start up the app.</p> | ||||
|             <br /> | ||||
|             <button id="settings-pluginsFolder" class="center">Open Plugins Folder</button> | ||||
|             <br /> | ||||
|             <button id="settings-themesFolder" class="center">Open Themes Folder</button> | ||||
|  | @ -182,27 +193,27 @@ | |||
|             document.getElementById("trayIcon").value = await settings.get("trayIcon"); | ||||
|         } | ||||
|         loadSettings(); | ||||
|         document.getElementById("settings-save").addEventListener("click", async function () { | ||||
|             var cordwood; | ||||
|             if (document.getElementById("cordwood").checked) { | ||||
|                 cordwood = true; | ||||
|             } else { | ||||
|                 cordwood = false; | ||||
|             } | ||||
|         document.getElementById("settings-save").addEventListener("click", function () { | ||||
|             settings.save({ | ||||
|                 windowStyle: document.getElementById("theme").value, | ||||
|                 channel: document.getElementById("channel").value, | ||||
|                 armcordCSP: document.getElementById("csp").checked, | ||||
|                 minimizeToTray: document.getElementById("tray").checked, | ||||
|                 alternativePaste: document.getElementById("alternativePaste").checked, | ||||
|                 skipSplash: document.getElementById("skipSplash").checked, | ||||
|                 automaticPatches: document.getElementById("patches").checked, | ||||
|                 mods: cordwood, | ||||
|                 mobileMode: await settings.get("mobileMode"), | ||||
|                 inviteWebsocket: await settings.get("inviteWebsocket"), | ||||
|                 mobileMode: document.getElementById("mobile").checked, | ||||
|                 inviteWebsocket: document.getElementById("websocket").checked, | ||||
|                 performanceMode: document.getElementById("prfmMode").value, | ||||
|                 trayIcon: document.getElementById("trayIcon").value, | ||||
|                 doneSetup: true | ||||
|             }); | ||||
|             alert(`Your settings have been saved! | ||||
| Some changes may require the app to restart before taking effect.`) | ||||
|         }); | ||||
|         document.getElementById("settings-restart").addEventListener("click", function () { | ||||
|             settings.restart(); | ||||
|         }); | ||||
|         document.getElementById("settings-pluginsFolder").addEventListener("click", async function () { | ||||
|             settings.openPluginsFolder(); | ||||
|  |  | |||
|  | @ -1,5 +1,15 @@ | |||
| import {BrowserWindow, shell, ipcMain, app, clipboard} from "electron"; | ||||
| import {getConfig, setConfigBulk, Settings, getLang, getVersion, getConfigLocation, getLangName, sleep} from "../utils"; | ||||
| import { | ||||
|     getConfig, | ||||
|     setConfigBulk, | ||||
|     Settings, | ||||
|     getLang, | ||||
|     getVersion, | ||||
|     getConfigLocation, | ||||
|     getLangName, | ||||
|     sleep, | ||||
|     getDisplayVersion | ||||
| } from "../utils"; | ||||
| import path from "path"; | ||||
| import os from "os"; | ||||
| import fs from "fs"; | ||||
|  | @ -22,7 +32,7 @@ export function createSettingsWindow() { | |||
|         settingsWindow = new BrowserWindow({ | ||||
|             width: 660, | ||||
|             height: 670, | ||||
|             title: "ArmCord Settings", | ||||
|             title: `ArmCord Settings | Version: ${getDisplayVersion()}`, | ||||
|             darkTheme: true, | ||||
|             frame: true, | ||||
|             autoHideMenuBar: true, | ||||
|  |  | |||
|  | @ -6,6 +6,8 @@ console.log("ArmCord Settings"); | |||
| 
 | ||||
| contextBridge.exposeInMainWorld("settings", { | ||||
|     save: (...args: any) => ipcRenderer.send("saveSettings", ...args), | ||||
|     restart: () => ipcRenderer.send("restart"), | ||||
|     saveAlert: (restartFunc: any) => ipcRenderer.send("saveAlert", restartFunc), | ||||
|     getLang: (toGet: string) => | ||||
|         ipcRenderer.invoke("getLang", toGet).then((result) => { | ||||
|             return result; | ||||
|  | @ -19,13 +21,6 @@ contextBridge.exposeInMainWorld("settings", { | |||
|     openStorageFolder: () => ipcRenderer.send("openStorageFolder"), | ||||
|     copyDebugInfo: () => ipcRenderer.send("copyDebugInfo") | ||||
| }); | ||||
| if (ipcRenderer.sendSync("getLangName") == "en-US") { | ||||
|     console.log("[Settings]: Lang " + ipcRenderer.sendSync("getLangName")); | ||||
|     const cssPath = path.join(__dirname, "../", "/content/css/settingsEng.css"); | ||||
|     document.addEventListener("DOMContentLoaded", function (event) { | ||||
|         addStyle(fs.readFileSync(cssPath, "utf8")); | ||||
|     }); | ||||
| } | ||||
| ipcRenderer.on("themeLoader", (event, message) => { | ||||
|     addStyle(message); | ||||
| }); | ||||
|  |  | |||
|  | @ -2,12 +2,8 @@ | |||
| <html lang="en"> | ||||
|     <head> | ||||
|         <meta charset="UTF-8" /> | ||||
|         <title>ArmCord Settings</title> | ||||
|         <style> | ||||
|             @import url("../content/css/settings.css"); | ||||
|             .acAdvSettings { | ||||
|                 height: 26em !important; | ||||
|             } | ||||
|         </style> | ||||
|     </head> | ||||
| 
 | ||||
|  | @ -15,6 +11,9 @@ | |||
|         <div class="saveBar"> | ||||
|             <button id="settings-save" class="center">Save Settings</button> | ||||
|         </div> | ||||
|         <div class="restartBar"> | ||||
|             <button id="settings-restart" class="center">Restart App</button> | ||||
|         </div> | ||||
|         <div class="switch acTheme"> | ||||
|             <select name="theme" id="theme" class="left dropdown"> | ||||
|                 <option value="default">Default</option> | ||||
|  | @ -33,50 +32,50 @@ | |||
|         </div> | ||||
|         <br /> | ||||
|         <div class="switch acCSP"> | ||||
|             <label class="header2">ArmCord CSP</label> | ||||
|             <label class="header">ArmCord CSP</label> | ||||
|             <input class="tgl tgl-light left" id="csp" type="checkbox" /> | ||||
|             <label class="tgl-btn left" for="csp"></label> | ||||
|             <p class="description2"> | ||||
|             <p class="description"> | ||||
|                 ArmCord CSP is our system that manages loading custom content loading into the Discord app. Stuff like | ||||
|                 client mods and themes depend on it. Disable if you want to get rid of mods and custom styles. | ||||
|             </p> | ||||
|         </div> | ||||
|         <br /> | ||||
|         <div class="switch acTray"> | ||||
|             <label class="header2" id="settings-tray">Minimize to tray</label> | ||||
|             <label class="header" id="settings-tray">Minimize to tray</label> | ||||
|             <input class="tgl tgl-light left" id="tray" type="checkbox" /> | ||||
|             <label class="tgl-btn left" for="tray"></label> | ||||
|             <p class="description2"> | ||||
|             <p class="description"> | ||||
|                 When disabled, ArmCord will close like any other window when closed, otherwise it'll sit back and relax | ||||
|                 in your system tray for later. | ||||
|             </p> | ||||
|         </div> | ||||
|         <br /> | ||||
|         <div class="switch acPatches"> | ||||
|             <label class="header2" id="settings-patches">Automatic Patches</label> | ||||
|             <label class="header" id="settings-patches">Automatic Patches</label> | ||||
|             <input class="tgl tgl-light left" id="patches" type="checkbox" /> | ||||
|             <label class="tgl-btn left" for="patches"></label> | ||||
|             <p class="description2"> | ||||
|             <p class="description"> | ||||
|                 Fetches automatic patches that are distributed if release turns out to have bugs after release. Usually | ||||
|                 you don't have to keep this enabled, unless notified in support Discord. | ||||
|             </p> | ||||
|         </div> | ||||
|         <br /> | ||||
|         <div class="switch acWebsocket"> | ||||
|             <label class="header2" id="settings-invitewebsocket">Invite Websocket</label> | ||||
|             <label class="header" id="settings-invitewebsocket">Invite Websocket</label> | ||||
|             <input class="tgl tgl-light left" id="websocket" type="checkbox" /> | ||||
|             <label class="tgl-btn left" for="websocket"></label> | ||||
|             <p class="description2"> | ||||
|             <p class="description"> | ||||
|                 When enabled ArmCord will support Discord.gg links which means that if you open an invite link in your | ||||
|                 browser, ArmCord will automatically accept the invite. Can be unresponsive at times. | ||||
|             </p> | ||||
|         </div> | ||||
|         <br /> | ||||
|         <div class="switch acMobileMode"> | ||||
|             <label class="header2" id="settings-mobileMode">Mobile mode</label> | ||||
|             <label class="header" id="settings-mobileMode">Mobile mode</label> | ||||
|             <input class="tgl tgl-light left" id="mobile" type="checkbox" /> | ||||
|             <label class="tgl-btn left" for="mobile"></label> | ||||
|             <p class="description2"> | ||||
|             <p class="description"> | ||||
|                 If you're on a device with touch-screen this feature is for you! It activates Discord's hidden mobile | ||||
|                 mode meant for phones and tablets. Only major feature missing is voice chat support. This is ideal for | ||||
|                 users on PinePhone and similar. | ||||
|  | @ -84,10 +83,10 @@ | |||
|         </div> | ||||
|         <br /> | ||||
|         <div class="switch acAltPaste"> | ||||
|             <label class="header2" id="settings-alternativePaste">Alternative paste</label> | ||||
|             <label class="header" id="settings-alternativePaste">Alternative paste</label> | ||||
|             <input class="tgl tgl-light left" id="alternativePaste" type="checkbox" /> | ||||
|             <label class="tgl-btn left" for="alternativePaste"></label> | ||||
|             <p class="description2"> | ||||
|             <p class="description"> | ||||
|                 If you're on Gnome on Linux or just simply can't paste images copied from other messages, then this is | ||||
|                 for you. This enables alternative module for pasting images. Only enable this when you're experiencing | ||||
|                 issues. | ||||
|  | @ -99,7 +98,7 @@ | |||
|                 <option value="stable">Stable</option> | ||||
|                 <option value="canary">Canary</option> | ||||
|                 <option value="ptb">PTB</option> | ||||
|                 <option value="hummus">Hummus (unofficial, experimental)</option> | ||||
|                 <option value="hummus">Hummus (Experimental)</option> | ||||
|             </select> | ||||
|             <div> | ||||
|                 <p class="header" id="settings-channel">Discord channel</p> | ||||
|  | @ -159,13 +158,18 @@ | |||
|         <br /> | ||||
|         <div class="switch acTray"> | ||||
|             <select name="trayIcon" id="trayIcon" class="left dropdown"> | ||||
|                 <option value="default">Default (dynamic)</option> | ||||
|                 <option value="ac_plug_colored">Plug colored</option> | ||||
|                 <option value="dsc-tray">Discord Icon</option> | ||||
|                 <option value="ac_white_plug">White Icon</option> | ||||
|                 <option value="ac_black_plug">Black Icon</option> | ||||
|                 <option value="ac_white_plug_hollow">White Hollowed Icon</option> | ||||
|                 <option value="ac_black_plug_hollow">Black Hollowed Icon</option> | ||||
|                 <optgroup label="- Discord -"> | ||||
|                     <option value="default">Dynamic</option> | ||||
|                     <option value="dsc-tray">Discord Icon</option> | ||||
|                     <option value="clsc-dsc-tray">Classic Discord Icon</option> | ||||
|                 </optgroup> | ||||
|                 <optgroup label="- ArmCord -"> | ||||
|                     <option value="ac_plug_colored">Colored Plug</option> | ||||
|                     <option value="ac_white_plug">White Plug</option> | ||||
|                     <option value="ac_white_plug_hollow">White Plug Alt</option> | ||||
|                     <option value="ac_black_plug">Black Plug</option> | ||||
|                     <option value="ac_black_plug_hollow">Black Plug Alt</option> | ||||
|                 </optgroup> | ||||
|             </select> | ||||
|             <p class="header" id="settings-trayIcon">Tray icon</p> | ||||
|             <p class="description">Set the icon which will appear in tray menu.</p> | ||||
|  | @ -174,11 +178,12 @@ | |||
|         <br /> | ||||
|         <div class="switch acAdvSettings"> | ||||
|             <h1 class="center advancedText">⚠️ Advanced User Zone ⚠️</h1> | ||||
|             <br /> | ||||
|             <label class="header2" id="settings-skipSplash">Skip Splash Screen (Experimental)</label> | ||||
|             <br> | ||||
|             <br> | ||||
|             <label class="header" id="settings-skipSplash">Skip Splash Screen (Experimental)</label> | ||||
|             <input class="tgl tgl-light left" id="skipSplash" type="checkbox" /> | ||||
|             <label class="tgl-btn left" for="skipSplash"></label> | ||||
|             <p class="description2">Skips ArmCord splash screen when you start up the app.</p> | ||||
|             <p class="description">Skips ArmCord splash screen when you start up the app.</p> | ||||
|             <br /> | ||||
|             <button id="settings-pluginsFolder" class="center">Open Plugins Folder</button> | ||||
|             <br /> | ||||
|  | @ -191,6 +196,7 @@ | |||
|     </body> | ||||
|     <script> | ||||
|         async function loadLang() { | ||||
|             document.getElementById("settings-restart").innerHTML = await settings.getLang("settings-restart"); | ||||
|             document.getElementById("settings-save").innerHTML = await settings.getLang("settings-save"); | ||||
|             document.getElementById("settings-mod").innerHTML = await settings.getLang("settings-mod"); | ||||
|             document.getElementById("settings-channel").innerHTML = await settings.getLang("settings-channel"); | ||||
|  | @ -245,6 +251,15 @@ | |||
|                 trayIcon: document.getElementById("trayIcon").value, | ||||
|                 doneSetup: true | ||||
|             }); | ||||
|             if ( | ||||
|                 confirm(`Your settings have been saved! | ||||
| Some changes may require the app to restart before taking effect, would you like to do so now?`) == true | ||||
|             ) { | ||||
|                 settings.restart(); | ||||
|             } | ||||
|         }); | ||||
|         document.getElementById("settings-restart").addEventListener("click", function () { | ||||
|             settings.restart(); | ||||
|         }); | ||||
|         document.getElementById("settings-pluginsFolder").addEventListener("click", function () { | ||||
|             settings.openPluginsFolder(); | ||||
|  |  | |||
							
								
								
									
										59
									
								
								src/tray.ts
									
										
									
									
									
								
							
							
						
						
									
										59
									
								
								src/tray.ts
									
										
									
									
									
								
							|  | @ -1,18 +1,28 @@ | |||
| import * as fs from "fs"; | ||||
| import {app, Menu, Tray, nativeImage} from "electron"; | ||||
| import {mainWindow} from "./window"; | ||||
| import {getConfig, getConfigLocation, setWindowState} from "./utils"; | ||||
| import {getConfig, getConfigLocation, setWindowState, getDisplayVersion} from "./utils"; | ||||
| import * as path from "path"; | ||||
| import {createSettingsWindow} from "./settings/main"; | ||||
| export let tray: any = null; | ||||
| app.whenReady().then(async () => { | ||||
|     let finishedSetup = await getConfig("doneSetup"); | ||||
|     var trayIcon = (await getConfig("trayIcon")) ?? "ac_plug_colored"; | ||||
|     if (trayIcon = "default") { | ||||
|         trayIcon = "dsc-tray" | ||||
|     } | ||||
|     let trayPath = nativeImage.createFromPath(path.join(__dirname, "../", `/assets/${trayIcon}.png`)); | ||||
|     if (process.platform === "darwin" && trayPath.getSize().height > 22) trayPath = trayPath.resize({height: 22}); | ||||
|     let trayVerIcon; | ||||
|     trayVerIcon = function () { | ||||
|         if (process.platform == "win32") { | ||||
|             return trayPath.resize({height: 16}); | ||||
|         } else if (process.platform == "darwin") { | ||||
|             return trayPath.resize({height: 18}); | ||||
|         } else if (process.platform == "linux") { | ||||
|             return trayPath.resize({height: 24}); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     if (process.platform == "darwin" && trayPath.getSize().height > 22)  | ||||
|         trayPath = trayPath.resize({height: 22}); | ||||
|      | ||||
|     if ((await getConfig("windowStyle")) == "basic") { | ||||
|         var clientName = (await getConfig("clientName")) ?? "ArmCord"; | ||||
|         tray = new Tray(trayPath); | ||||
|  | @ -22,12 +32,13 @@ app.whenReady().then(async () => { | |||
|                     { | ||||
|                         label: `Finish the setup first!`, | ||||
|                         enabled: false | ||||
|                     }, | ||||
|                     { | ||||
|                     }, { | ||||
|                         label: `Quit ${clientName}`, | ||||
|                         click: async function () { | ||||
|                             fs.unlink(await getConfigLocation(), (err) => { | ||||
|                                 if (err) throw err; | ||||
|                                 if (err)  | ||||
|                                     throw err; | ||||
|                                  | ||||
|                                 console.log('Closed during setup. "settings.json" was deleted'); | ||||
|                                 app.quit(); | ||||
|                             }); | ||||
|  | @ -41,16 +52,11 @@ app.whenReady().then(async () => { | |||
|                         click: function () { | ||||
|                             mainWindow.show(); | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                     }, { | ||||
|                         label: `Quit ${clientName}`, | ||||
|                         click: function () { | ||||
|                             let [width, height] = mainWindow.getSize(); | ||||
|                             setWindowState({ | ||||
|                                 width: width, | ||||
|                                 height: height, | ||||
|                                 isMaximized: mainWindow.isMaximized() | ||||
|                             }); | ||||
|                             setWindowState({width: width, height: height, isMaximized: mainWindow.isMaximized()}); | ||||
|                             app.quit(); | ||||
|                         } | ||||
|                     } | ||||
|  | @ -68,12 +74,13 @@ app.whenReady().then(async () => { | |||
|                 { | ||||
|                     label: `Finish the setup first!`, | ||||
|                     enabled: false | ||||
|                 }, | ||||
|                 { | ||||
|                 }, { | ||||
|                     label: `Quit ${clientName}`, | ||||
|                     click: async function () { | ||||
|                         fs.unlink(await getConfigLocation(), (err) => { | ||||
|                             if (err) throw err; | ||||
|                             if (err)  | ||||
|                                 throw err; | ||||
|                              | ||||
|                             console.log('Closed during setup. "settings.json" was deleted'); | ||||
|                             app.quit(); | ||||
|                         }); | ||||
|  | @ -84,7 +91,8 @@ app.whenReady().then(async () => { | |||
|         } else { | ||||
|             const contextMenu = Menu.buildFromTemplate([ | ||||
|                 { | ||||
|                     label: `${clientName} ` + app.getVersion(), | ||||
|                     label: `${clientName} ` + getDisplayVersion(), | ||||
|                     icon: trayVerIcon(), | ||||
|                     enabled: false | ||||
|                 }, | ||||
|                 { | ||||
|  | @ -101,18 +109,15 @@ app.whenReady().then(async () => { | |||
|                     click: function () { | ||||
|                         createSettingsWindow(); | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                 }, { | ||||
|                     label: "Support Discord Server", | ||||
|                     click: function () { | ||||
|                         mainWindow.show(); | ||||
|                         mainWindow.loadURL("https://discord.gg/TnhxcqynZ2"); | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                 }, { | ||||
|                     type: "separator" | ||||
|                 }, | ||||
|                 { | ||||
|                 }, { | ||||
|                     label: `Quit ${clientName}`, | ||||
|                     click: function () { | ||||
|                         app.quit(); | ||||
|  | @ -121,9 +126,7 @@ app.whenReady().then(async () => { | |||
|             ]); | ||||
|             tray.setContextMenu(contextMenu); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     tray.setToolTip(clientName); | ||||
|     } tray.setToolTip(clientName); | ||||
|     tray.on("click", function () { | ||||
|         mainWindow.show(); | ||||
|     }); | ||||
|  |  | |||
|  | @ -62,7 +62,11 @@ export function getVersion() { | |||
| export function getDisplayVersion() { | ||||
|     //Checks if the app version # has 4 sections (3.1.0.0) instead of 3 (3.1.0) / Shitty way to check if Kernel Mod is installed
 | ||||
|     if ((app.getVersion() == packageVersion) == false) { | ||||
|         return `${packageVersion} [Kernel Mod]`; | ||||
|         if ((app.getVersion() == process.versions.electron) == true) { | ||||
|             return `Dev Build (${packageVersion})`; | ||||
|         } else { | ||||
|             return `${packageVersion} [Modified]`; | ||||
|         } | ||||
|     } else { | ||||
|         return packageVersion; | ||||
|     } | ||||
|  |  | |||
|  | @ -4,15 +4,7 @@ | |||
| // I'm sorry for this mess but I'm not sure how to fix it.
 | ||||
| import {BrowserWindow, shell, app, dialog, nativeImage} from "electron"; | ||||
| import path from "path"; | ||||
| import { | ||||
|     checkIfConfigIsBroken, | ||||
|     firstRun, | ||||
|     getConfig, | ||||
|     contentPath, | ||||
|     setConfig, | ||||
|     setLang, | ||||
|     setWindowState | ||||
| } from "./utils"; | ||||
| import {checkIfConfigIsBroken, firstRun, getConfig, contentPath, setConfig, setLang, setWindowState} from "./utils"; | ||||
| import {registerIpc} from "./ipc"; | ||||
| import {setMenu} from "./menu"; | ||||
| import * as fs from "fs"; | ||||
|  | @ -28,8 +20,25 @@ var osType = os.type(); | |||
| contextMenu({ | ||||
|     showSaveImageAs: true, | ||||
|     showCopyImageAddress: true, | ||||
|     showSearchWithGoogle: true, | ||||
|     showSearchWithDuckDuckGo: true | ||||
|     showSearchWithGoogle: false, | ||||
|     prepend: (defaultActions, parameters, browserWindow) => [ | ||||
|         { | ||||
|             label: "Search with Google", | ||||
|             // Only show it when right-clicking text
 | ||||
|             visible: parameters.selectionText.trim().length > 0, | ||||
|             click: () => { | ||||
|                 shell.openExternal(`https://google.com/search?q=${encodeURIComponent(parameters.selectionText)}`); | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             label: "Search with DuckDuckGo", | ||||
|             // Only show it when right-clicking text
 | ||||
|             visible: parameters.selectionText.trim().length > 0, | ||||
|             click: () => { | ||||
|                 shell.openExternal(`https://duckduckgo.com/?q=${encodeURIComponent(parameters.selectionText)}`); | ||||
|             } | ||||
|         } | ||||
|     ] | ||||
| }); | ||||
| async function doAfterDefiningTheWindow() { | ||||
|     var ignoreProtocolWarning = await getConfig("ignoreProtocolWarning"); | ||||
|  | @ -87,7 +96,7 @@ async function doAfterDefiningTheWindow() { | |||
|         if (/api\/v\d\/science$/g.test(details.url)) return callback({cancel: true}); | ||||
|         return callback({}); | ||||
|     }); | ||||
|     if (await getConfig("trayIcon") == "default") { | ||||
|     if ((await getConfig("trayIcon")) == "default") { | ||||
|         mainWindow.webContents.on("page-favicon-updated", async (event) => { | ||||
|             var faviconBase64 = await mainWindow.webContents.executeJavaScript(` | ||||
|                 var getFavicon = function(){ | ||||
|  | @ -103,13 +112,14 @@ async function doAfterDefiningTheWindow() { | |||
|                 return favicon;         | ||||
|                 } | ||||
|                 getFavicon() | ||||
|             `)
 | ||||
|             var buf = new Buffer(faviconBase64.replace(/^data:image\/\w+;base64,/, ""), 'base64'); | ||||
|             `);
 | ||||
|             var buf = new Buffer(faviconBase64.replace(/^data:image\/\w+;base64,/, ""), "base64"); | ||||
|             fs.writeFileSync(path.join(app.getPath("temp"), "/", "tray.png"), buf, "utf-8"); | ||||
|             let trayPath = nativeImage.createFromPath(path.join(app.getPath("temp"), "/", "tray.png")); | ||||
|             if (process.platform === "darwin" && trayPath.getSize().height > 22) trayPath = trayPath.resize({height: 22}); | ||||
|             tray.setImage(trayPath) | ||||
|         }) | ||||
|             if (process.platform === "darwin" && trayPath.getSize().height > 22) | ||||
|                 trayPath = trayPath.resize({height: 22}); | ||||
|             tray.setImage(trayPath); | ||||
|         }); | ||||
|     } | ||||
|     const userDataPath = app.getPath("userData"); | ||||
|     const themesFolder = userDataPath + "/themes/"; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue