From e095a3e035963f97dced4d14df6330e6ea5f8aac Mon Sep 17 00:00:00 2001 From: Aetherinox Date: Mon, 11 Mar 2024 04:48:45 -0700 Subject: [PATCH 01/25] chore: update plugin description --- dist/main.js | 12 - dist/manifest.json | 10 - dist/styles.css | 1284 --------------------- manifest.json | 2 +- package.json | 2 +- tests/Gistr Demo/.obsidian/workspace.json | 6 +- 6 files changed, 5 insertions(+), 1311 deletions(-) delete mode 100644 dist/main.js delete mode 100644 dist/manifest.json delete mode 100644 dist/styles.css diff --git a/dist/main.js b/dist/main.js deleted file mode 100644 index 24e1f80b..00000000 --- a/dist/main.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @name: gistr v1.4.2 - * @author: Aetherinox - * @url: https://github.com/Aetherinox/obsidian-gistr.git - * @copyright: (c) 2024 Aetherinox - * @license: MIT - * @build: 2024-03-11T10:53:09.723Z - * @build-id: 0a7f5dc4-b2fa-5064-9e54-f6e17fd14273 - */ - -Object.defineProperty(exports,"__esModule",{value:!0});var e=require("obsidian");function t(e,t,s,r){return new(s||(s=Promise))(((o,n)=>{function i(e){try{c(r.next(e))}catch(e){n(e)}}function a(e){try{c(r.throw(e))}catch(e){n(e)}}function c(e){var t;e.done?o(e.value):(t=e.value,t instanceof s?t:new s((e=>{e(t)}))).then(i,a)}c((r=r.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;var s={base_underdev_title:"Feature Under Development",base_underdev_msg:'I am currently working with the developer of OpenGist to make minor changes to how OpenGist pastes appear, including moving the "view raw" button to the bottom so that Obsidian\'s edit button does not overlap.',base_opt_enabled:"Enabled",base_opt_disabled:"Disabled",base_theme_light:"Light",base_theme_dark:"Dark",base_time_am:"AM",base_time_pm:"PM",base_component_reset:"Reset to Default",base_debug_loading:"Loading {0} v{1} [ {2} ]",base_debug_updater_1:"{0} Update Check",base_debug_updater_2:"{0} {1}",base_context_nofocus:"Obsidian does not have focus, please open a file",cfg_context_gist_public:"Save Gist (Github Public)",cfg_context_gist_secret:"Save Gist (Github Secret)",cfg_context_gist_copy:"Copy Gist URL",cfg_modal_desc:"Embed Github and Opengist snippets in your notes. For a detailed set of examples, view the demo vault in the support section.",cfg_modal_expand:"Expand",cfg_tab_ge_title:"Global",cfg_tab_ge_header:"These settings affect all aspects of this plugin, including both Opengist and Github. If you change the trigger keyword, ensure you go back through your existing gist snippets and change the keyword at the top of each codeblock; otherwise embedded gists will not appear.",cfg_tab_sy_title:"Save & Sync",cfg_tab_sy_header:"These settings allow you to create gists from your notes. The contents of your notes will be directly uploaded to Github under an existing account. You may also choose to create only new notes, or manage new and existing.",cfg_tab_og_title:"OpenGist",cfg_tab_og_header:"Opengist is a self-hosted pastebin powered by Git. All snippets are stored in a Git repository and can be read and/or modified using standard Git commands, or with the web interface. It is similiar to GitHub Gist, but open-source and is self-hosted. OpenGist supports Windows, Linux, and MacOS.",cfg_tab_gh_title:"Github",cfg_tab_gh_header:"Github Gists let you store and distribute code snippets without setting up a full-fledged repository. Store snippets such as strings, bash scripts, markdown, text files, and other small pieces of data.",cfg_tab_sp_title:"Support",cfg_tab_ge_keyword_name:"Trigger keyword",cfg_tab_ge_keyword_desc:"Word to use inside codeblocks to designate as a portal for showing gists",cfg_tab_ge_theme_name:"Theme",cfg_tab_ge_theme_desc:'This determines what color scheme will be used for gists. You can however, customize the colors in the Github and OpenGist categories below.

Note: When this is changed, place your cursor in the codeblock and then leave the codeblock to refresh it. Automatic refreshing only works in reading mode',cfg_tab_ge_wrap_name:"Text wrapping",cfg_tab_ge_wrap_desc:"If enabled, text will wrap to the next line. If disabled, you will see a horizontal scrollbar. This does not include gists that have no spaces anywhere in the body.",cfg_tab_ge_notitime_name:"Notification duration",cfg_tab_ge_notitime_desc:'How long a notification will display for (in seconds). Set to 0 to keep notification up until user dismisses it.',cfg_tab_ge_updatenoti_name:"Enable Gistr update notifications",cfg_tab_ge_updatenoti_desc:'Enabled: When launching Obsidian, you will get a notification if a new version of Gistr is available. This includes beta releases not available to the public yet.

Disabled: You will not get any notifications alerting you to new Gistr updates. You must manually check or use the Obsidian plugin checker.

Note: This update notification includes beta releases of Gistr. The Obsidian plugin updater does not track beta.',cfg_tab_ge_updatenoti_tip:"Enable or disable update notifications",cfg_tab_og_cb_light_name:"Codeblock bg (Light)",cfg_tab_og_cb_light_desc:'Color for Github codeblock background color (Light Theme)',cfg_tab_og_cb_dark_name:"Codeblock bg (Dark)",cfg_tab_og_cb_dark_desc:'Color for Github codeblock background color (Dark Theme)',cfg_tab_og_sb_light_name:"Scrollbar track (Light)",cfg_tab_og_sb_light_desc:'Color for gist scrollbar track (Light Theme)',cfg_tab_og_sb_dark_name:"Scrollbar track (Dark)",cfg_tab_og_sb_dark_desc:'Color for gist scrollbar track (Dark Theme)',cfg_tab_og_tx_light_name:"Codeblock text (Light)",cfg_tab_og_tx_light_desc:'Color for codeblock text color (Light Theme)',cfg_tab_og_tx_dark_name:"Codeblock text (Dark)",cfg_tab_og_tx_dark_desc:'Color for codeblock text color (Dark Theme)',cfg_tab_og_opacity_name:"Codeblock opacity",cfg_tab_og_opacity_desc:"Total opacity for codeblock. Do not set this too low, or your codeblocks will be invisible",cfg_tab_og_padding_top_name:"Padding: top",cfg_tab_og_padding_top_desc:"Padding between gist codeblock header and code.",cfg_tab_og_padding_bottom_name:"Padding: bottom",cfg_tab_og_padding_bottom_desc:"Padding between gist codeblock and the bottom scrollbar.",cfg_tab_og_css_name:"Custom CSS",cfg_tab_og_css_desc:"This textarea allows you to enter custom CSS properties to override existing colors.",cfg_tab_og_css_pholder:"Paste CSS here",cfg_tab_gh_cb_light_name:"Codeblock bg (Light)",cfg_tab_gh_cb_light_desc:'Color for Opengist codeblock background color (Light Theme)',cfg_tab_gh_cb_dark_name:"Codeblock bg (Dark)",cfg_tab_gh_cb_dark_desc:'Color for Opengist codeblock background color (Dark Theme)',cfg_tab_gh_sb_light_name:"Scrollbar track (Light)",cfg_tab_gh_sb_light_desc:'Color for gist scrollbar track (Light Theme)',cfg_tab_gh_sb_dark_name:"Scrollbar track (Dark)",cfg_tab_gh_sb_dark_desc:'Color for gist scrollbar track (Dark Theme)',cfg_tab_gh_tx_light_name:"Codeblock text (Light)",cfg_tab_gh_tx_light_desc:'Color for codeblock text color (Light Theme)',cfg_tab_gh_tx_dark_name:"Codeblock text (Dark)",cfg_tab_gh_tx_dark_desc:'Color for codeblock text color (Dark Theme)',cfg_tab_gh_opacity_name:"Codeblock opacity",cfg_tab_gh_opacity_desc:"Total opacity for codeblock. Do not set this too low, or your codeblocks will be invisible",cfg_tab_gh_css_name:"Custom CSS",cfg_tab_gh_css_desc:"This textarea allows you to enter custom CSS properties to override existing colors.",cfg_tab_gh_css_pholder:"Paste CSS here",cfg_tab_gh_pat_name:"Personal Access Token",cfg_tab_gh_pat_desc:"The personal access token (PAT) generated on Github.com which allows you to write gists from your Obsidian vault to Github gist.",cfg_tab_gh_pat_pholder:"githubpat_XXXXXX",cfg_tab_gh_pat_state_show:"Show token",cfg_tab_gh_pat_state_hide:"Hide token",cfg_tab_gh_pat_desc_l1:'This token allows you to authenticate with the GitHub API.
Create Token: here',cfg_tab_gh_pat_desc_l2:'For this to function with secret gists, select "All repositories" or "Only select repositories" from the dropdown on the Github Token page. The token must have at least the following permissions:',cfg_tab_gh_pat_perm_1:'Account Permissions ► Gists                                                                          Read-and-write',cfg_tab_gh_pat_perm_2:'Repository Permissions ► Pull Requests                                      Read-only',cfg_tab_gh_pat_perm_3:'Repository Permissions ► Contents                                                    Read-only',cfg_tab_gh_pat_perm_4:'Repository Permissions ► Issues                                                              Read-only',cfg_tab_gh_pat_footer:"Github icon to the right will turn into a checkmark when you've entered a valid token.",cfg_tab_gh_pat_help:'What is this for? Read the docs',cfg_tab_gh_pat_btn_tip:"Generate Github API Token",cfg_tab_gh_pat_btn_url:"https://github.com/settings/tokens?type=beta",cfg_tab_gh_pat_btn_tip_ok:"Valid Github API Token",cfg_tab_gh_pat_btn_tip_bad:"Invalid Github API Token entered\n\nClick here to generate one",cfg_tab_gh_pat_btn_tip_invalid:"Github API token is not valid, ensure you type it correctly\n\nClick here to generate one",cfg_tag_gh_pat_notice_msg:"Gistr has detected a valid Github personal access token which has been saved",cfg_tab_gh_pat_notice_type_fine:"Fine-Grained Github Token Detected",cfg_tab_gh_pat_notice_type_classic:"Classic Github Token Detected",cfg_tab_su_desc:"The following buttons are associated to useful resources for this plugin.",cfg_tab_su_gs_name:"Introduction",cfg_tab_su_gs_desc:"View brief introduction to getting started with this plugin",cfg_tab_su_gs_btn:"Open",cfg_tab_su_repo_label:"Plugin repo",cfg_tab_su_repo_url:"https://github.com/Aetherinox/obsidian-gistr",cfg_tab_su_repo_btn:"View",cfg_tab_su_vault_label:"Plugin demo vault",cfg_tab_su_vault_url:"https://github.com/Aetherinox/obsidian-gistr/tree/main/tests/gistr-vault",cfg_tab_su_vault_btn:"View",cfg_tab_su_ogrepo_label:"OpenGist: download",cfg_tab_su_ogrepo_url:"https://github.com/thomiceli/opengist/releases",cfg_tab_su_ogrepo_btn:"View",cfg_tab_su_ogdocs_label:"OpenGist: docs",cfg_tab_su_ogdocs_url:"https://github.com/thomiceli/opengist/blob/master/docs/index.md",cfg_tab_su_ogdocs_btn:"View",cfg_tab_su_ogdemo_label:"OpenGist: demo",cfg_tab_su_ogdemo_url:"https://opengist.thomice.li/all",cfg_tab_su_ogdemo_btn:"View",cfg_tab_su_gist_label:"Github gist",cfg_tab_su_gist_url:"https://gist.github.com/",cfg_tab_su_gist_btn:"View",cfg_tab_su_ver_cur:"Current version",cfg_tab_su_ver_stable:"Latest stable release ",cfg_tab_su_ver_beta:"Latest beta release ",cfg_tab_su_ver_connection_issues:"Server communication failed",cfg_tab_su_ver_status_checking:"Checking for newer version of Gistr",cfg_tab_su_ver_status_uptodate:"You are running the latest version of Gistr",cfg_tab_su_ver_status_stable_avail:"A newer stable release of Gistr is available",cfg_tab_su_ver_status_beta_avail:"A newer beta release of Gistr is available",cfg_tab_su_ver_status_tip_conn_error:"Could not communicate with the gistr server, retrying later",cfg_tab_su_ver_releases:"https://github.com/Aetherinox/obsidian-gistr/releases",cfg_tab_sy_list_icon_name:"Gist list icon color",cfg_tab_sy_list_icon_desc:"Color for icon in gist save list",cfg_tab_sy_tog_updatecreate_name:"Allow updating gists",cfg_tab_sy_tog_updatecreate_desc:'Enabled: After you initially create a new gist, the note can be updated with newer revisions.

Disabled: Gists can only be created; no updates are allowed.

To update a gist after enabling this setting, right-click on the note, or open the Obsidian command palette and select Save Gist',cfg_tab_sy_tog_updatecreate_tip:"",cfg_tab_sy_tog_autosave_enable_name:"Enable autosave",cfg_tab_sy_tog_autosave_enable_desc:'Enabled: This will allow gists to be updated once they are created. It will also enable autosaving which will detect new changes and push them.

Disabled: You will only be able to create gists by manually doing so; there will be no way to update them.

If you wish to keep this disabled, you can create gists by right-clicking in the note and selecting Save Gist. Or opening your command palette and selecting the save option from there.',cfg_tab_sy_tog_autosave_enable_tip:"",cfg_tab_sy_tog_autosave_strict_name:"Enable autosave strict saving",cfg_tab_sy_tog_autosave_strict_desc:'Enabled: Your notes will be saved to the gist service precisely on time every {0} seconds, whether you are still typing or not.

Disabled: Time until save will not start until you have finished typing in that note. If you continue typing, the saving countdown will not start until your final key is pressed.

Autosave duration can be modified further down in these settings.',cfg_tab_sy_tog_autosave_strict_tip:"",cfg_tab_sy_tog_autosave_noti_name:"Enable autosave notices",cfg_tab_sy_tog_autosave_noti_desc:'Each time your note is saved automatically, a notice will appear on-screen informing you of the action. This only works if Autosave is enabled.',cfg_tab_sy_tog_autosave_noti_tip:"",cfg_tab_sy_num_save_dur_name:"Autosave duration",cfg_tab_sy_num_save_dur_desc:'How often autosave will execute in seconds. Set this to a fair amount so that the calls aren\'t being ran excessively to the gist API server (Github or OpenGist).

The save countdown timer will begin shortly after you stop typing.

If you wish to change this to save precisely every {0} seconds, enable the setting Autosave Strict Saving located above.',cfg_tab_sy_tog_inc_fm_name:"Include frontmatter",cfg_tab_sy_tog_inc_fm_desc:'When saving a note as a new gist, frontmatter will be added to the top of your note with information about the gist.

Enabled: the note will be cleaned before it is pushed to the gist service and no frontmatter fields will be present in the online version.

Disabled: frontmatter added to your notes will be included when your note is pushed to a gist service.

Frontmatter can be found at the very top of each note, in-between `---` ',cfg_tab_sy_tog_inc_fm_tip:"Frontmatter starts with three hyphens `---`",gist_upload_req_allowupload:'Must enable "Allow Uploading Gists" in the Gistr settings before you can use this command.',gist_upload_no_active_file:"No active file present. Open a note in Obsidian before continuing.",gist_copy_fail_notagist:"No URL to copy. You must turn your note into a gist first.",gist_copy_success_file:"Copied {0} URL to your clipboard",gist_copy_success:"Copied gist URL to clipboard.",gist_upload_fail_api:"GitHub API error: {0}",gist_upload_success:"File {0} has been updated successfully to your gist service.",gist_status_connecting:"connecting ...",gist_status_operational:"operational",gist_status_connected:"Connected to API ...",gist_status_issues:"service issues",gist_status_btn_connecting:"Connecting to Github ...",gist_status_btn_success:"Connected to Github API",gist_status_btn_issues:"Github API is currently experiencing issues\n\nClick to view details.",gist_btn_create_new:"Create New Gist",gist_not_found:"Could not locate the specified Gist. Did you possibly delete it from Github?\n\nTo update this note as a new gist, remove the frontmatter text at the top of the note.",gs_base_header:"This plugin allows you to integrate both OpenGist and Github Gist pastes within your Obsidian notes. To use this plugin, you can either create a new Github gist, or setup your own OpenGist server. OpenGist is free, and takes only minutes to configure.",gs_og_btn_repo:"Download OpenGist",gs_og_btn_docs:"OpenGist Docs",gs_og_sub_1:"Once you install and set up OpenGist, you can sign in to your OpenGist website and create your first Gist. After your Gist is created, return to your Obsidian node, and integrate your Gist into your note using code similar to the following:",gs_og_name:"OpenGist integration",gs_og_desc:"OpenGist supports Windows, Linux, MacOS, and Docker. To download and set up OpenGist, click below.",gs_gh_name:"Github integration",gs_gh_desc:"To paste a Github Gist into your note, use a command similar to the following examples:",gs_btn_settings_open:"Open Settings",gs_btn_close:"Close",gh_status_error_api:"Github API Error: {0}",ver_update_stable:"An update is available for the Gistr plugin. Update to check out the latest features!",ver_update_beta:"A new beta release is available for the Gistr plugin. Update to check out the latest features coming to stable!",ver_url:"https://raw.githubusercontent.com/Aetherinox/obsidian-gistr/{0}/package.json",pickr_dialog:"Color Picker",pickr_swatch:"Color Swatch",pickr_toggle:"Pick Color",pickr_last:"Use Last Color",pickr_save:"Save",pickr_cancel:"Cancel",pickr_clear:"Clear",pickr_tip_restore_default:"Restore default color",pickr_dev_unknown:"Gistr: Unknown color format: {0}",err_gist_token_missing:"Github API token missing. Open the Gistr plugin settings, click the Github tab, and enter your token. Instructions are found on the settings page.",err_gist_loading_fail_name:"⚠️ Gistr: Failed to load the specified gist:",err_gist_loading_fail_resp:"{0}",err_gist_loading_fail_detail:"Could not load a valid Javascript from gist url: {0}",err_gist_loading_fail_url:"Could not find gist id -- Make sure correct URL is specified. {0}",lst_repotype_pub:"Public",lst_repotype_pri:"Secret"};const r={en:s}[e.moment.locale()];function o(t,...o){return r||console.error("Gistr language not found",e.moment.locale()),(r&&r[t]||s[t]).replace(/{(\d+)}/g,((e,t)=>{const s=o[t];return void 0!==s?s:e}))}const n="gistr";class i{constructor(e){this.messageEventHandler=e=>{if("null"!==e.origin)return;if(e.data.sender!==n)return;const t=e.data.gid,s=e.data.scrollHeight;document.querySelector("iframe#"+t).setAttribute("height",s)},this.processor=(e,s)=>t(this,void 0,void 0,(function*(){const r=e.trim().split("\n");return Promise.all(r.map((e=>t(this,void 0,void 0,(function*(){return this.GistHandle(s,e)})))))})),this.settings=e}GistHandle(s,r){return t(this,void 0,void 0,(function*(){const t=r.match(/(?https?:\/\/)?(?[^/]+\/)?((?[\w-]+)\/)?(?\w+)(\#(?\w+))?(\&(?\w+))?/).groups,n=t.host,i=t.username,a=t.uuid,c=t.filename,l=t.theme,p=/((https?:\/\/)?(.+?\.)?github\.com(\/[A-Za-z0-9\-\._~:\/\?#\[\]@!$&'\(\)\*\+,;\=]*)?)/g.test(n);if(void 0===typeof a)return this.ThrowError(s,r,o("err_gist_loading_fail_url",n));let u=void 0!==c?`https://${n}${i}/${a}.json?file=${c}`:`https://${n}${i}/${a}.json`,g=void 0!==l?l:"";const d={url:u,method:"GET",headers:{Accept:"application/json"}};try{const t=yield e.request(d),r=JSON.parse(t);return this.GistGenerate(s,n,a,r,p,g)}catch(e){return this.ThrowError(s,r,`Invalid gist url ${u} ( ${e} )`)}}))}EventListener(e){return`\n - ` - } - - /* - Gist > Generate - - create new iframe for each gist, assign it a uid, set the needed attributes, and generate the css, js - */ - - private async GistGenerate( el: HTMLElement, host: string, uuid: string, json: ItemJSON, bGithub: boolean, theme: string ) - { - - /* - create uuid and iframe - */ - - const gid = `${ sender }-${ uuid }-${ nanoid( ) }` - const ct_iframe = document.createElement( 'iframe' ) - ct_iframe.id = gid - - ct_iframe.classList.add ( `${ sender }-container` ) - ct_iframe.setAttribute ( 'sandbox', 'allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation' ) - ct_iframe.setAttribute ( 'loading', 'lazy' ) - ct_iframe.setAttribute ( 'width', '100%' ) - - /* - https://fonts.googleapis.com - - policy directive error if certain attributes arent used. doesnt affect the plugin, but erors are bad - */ - - ct_iframe.setAttribute ( 'csp', "default-src * data: blob: 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';" ) - // ct_iframe.setAttribute ( 'csp', "default-src * self 'unsafe-inline'; font-src 'self' *fonts.gstatic.com/; style-src-elem 'self' *fonts.googleapis.com *demo.opengist.io/ *thomice.li 'unsafe-inline'; script-src * 'self' 'unsafe-eval' 'unsafe-inline'; object-src * 'self'; img-src * self 'unsafe-inline'; connect-src self * 'unsafe-inline'; frame-src * self 'unsafe-inline';" ) - - /* - assign css, body, js - */ - - let css_theme_ovr = ( theme !== "" ) ? theme.toLowerCase( ) : "" - let css_theme_sel = ( css_theme_ovr !== "" ) ? css_theme_ovr : ( this.settings.theme == "Dark" ) ? "dark" : ( this.settings.theme == "Light" ) ? "light" : "light" - let css_og = "" - let css_gh = "" - - const content_css = await this.GetCSS( el, uuid, ( bGithub ? json.stylesheet: json.embed.css ) ) - const content_body = ( bGithub ? json.div : "" ) - const content_js = ( bGithub ? "" : await this.GetJavascript( el, uuid, ( css_theme_sel == "dark" ? json.embed.js_dark : json.embed.js ) ) ) - - /* - CSS Overrides > Github - */ - - const css_gh_bg_color = ( css_theme_sel == "dark" ? this.settings.gh_clr_bg_dark : this.settings.gh_clr_bg_light ) - const css_gh_sb_color = ( css_theme_sel == "dark" ? this.settings.gh_clr_sb_dark : this.settings.gh_clr_sb_light ) - const css_gh_bg_header_bg = ( css_theme_sel == "dark" ? "rgb( 35 36 41/var( --tw-bg-opacity ) )" : "rgb( 238 239 241/var( --tw-bg-opacity ) )" ) - const css_gh_bg_header_bor = ( css_theme_sel == "dark" ? "1px solid rgb( 54 56 64/var( --tw-border-opacity ) )" : "rgb( 222 223 227/var( --tw-border-opacity ) )" ) - const css_gh_tx_color = ( css_theme_sel == "dark" ? this.settings.gh_clr_tx_dark : this.settings.gh_clr_tx_light ) - - /* - Declare custom css override - */ - - const css_override = ( ( bGithub && this.settings.css_gh && this.settings.css_gh.length > 0 ) ? ( this.settings.css_gh ) : ( this.settings.css_og && this.settings.css_og.length > 0 && this.settings.css_og ) ) || "" - - /* - OpenGist specific CSS - - @note : these are edits the user should not need to edit. - OpenGist needs these edits in order to look right with the header - and footer. - - obviously this condition doesn't matter even if it is injected into Github pastes, - but it would be usless code. - - working with OpenGist developer to re-do the HTML generated when embedding a gist. - */ - - const css_og_append = this.CSS_Get_OpenGist( css_theme_sel ) - const css_gh_append = this.CSS_Load_Github( css_theme_sel ) - - /* - Github > Dark Theme - */ - - if ( bGithub === false ) - css_og = css_og_append - else - css_gh = css_gh_append - - /* - generate html output - */ - - const html_output = - ` - - - - - ${ this.EventListener( gid ) } - - - - - - - - - - - - ${ content_body } - - - ` - - ct_iframe.srcdoc = html_output - el.appendChild( ct_iframe ) - } - - /* - Theme > OpenGist - */ - - private CSS_Get_OpenGist( theme: string ) - { - - const css_og_bg_color = ( theme == "dark" ? this.settings.og_clr_bg_dark : this.settings.og_clr_bg_light ) - const css_og_sb_color = ( theme == "dark" ? this.settings.og_clr_sb_dark : this.settings.og_clr_sb_light ) - const css_og_bg_header_bg = ( theme == "dark" ? "rgb( 35 36 41/var( --tw-bg-opacity ) )" : "rgb( 238 239 241/var( --tw-bg-opacity ) )" ) - const css_og_bg_header_bor = ( theme == "dark" ? "1px solid rgb( 54 56 64/var( --tw-border-opacity ) )" : "rgb( 222 223 227/var( --tw-border-opacity ) )" ) - const css_og_tx_color = ( theme == "dark" ? this.settings.og_clr_tx_dark : this.settings.og_clr_tx_light ) - const css_og_wrap = ( this.settings.textwrap == "Enabled" ? "normal" : "pre" ) - const css_og_opacity = ( this.settings.og_opacity ) || 1 - - return ` - ::-webkit-scrollbar - { - width: 6px; - height: 10px; - } - - ::-webkit-scrollbar-track - { - background-color: transparent; - border-radius: 5px; - margin: 1px; - } - - ::-webkit-scrollbar-thumb - { - border-radius: 10px; - background-color: ${css_og_sb_color}; - } - - .opengist-embed .code - { - padding-top: ${this.settings.blk_pad_t}px; - padding-bottom: ${this.settings.blk_pad_b}px; - border-top: ${css_og_bg_header_bor}; - background-color: ${css_og_bg_color}; - width: fit-content; - margin-top: -1px; - } - - .opengist-embed .mb-4 - { - margin-bottom: 1rem; - backdrop-filter: opacity(0); - --tw-bg-opacity: 1; - background-color: ${css_og_bg_header_bg}; - opacity: ${css_og_opacity}; - } - - .opengist-embed .line-code - { - color: ${css_og_tx_color}; - } - - .opengist-embed .code .line-num - { - color: ${css_og_tx_color}; - opacity: 0.5; - } - - .opengist-embed .code .line-num:hover - { - color: ${css_og_tx_color}; - opacity: 1; - } - - .opengist-embed .whitespace-pre - { - white-space: ${css_og_wrap}; - } - ` - } - - /* - Theme > Github - */ - - private CSS_Load_Github( theme: string = 'light' ) - { - const css_gh_bg_color = ( theme == "dark" ? this.settings.gh_clr_bg_dark : this.settings.gh_clr_bg_light ) - const css_gh_sb_color = ( theme == "dark" ? this.settings.gh_clr_sb_dark : this.settings.gh_clr_sb_light ) - const css_gh_bg_header_bg = ( theme == "dark" ? "rgb( 35 36 41/var( --tw-bg-opacity ) )" : "rgb( 238 239 241/var( --tw-bg-opacity ) )" ) - const css_gh_bg_header_bor = ( theme == "dark" ? "1px solid rgb( 54 56 64/var( --tw-border-opacity ) )" : "rgb( 222 223 227/var( --tw-border-opacity ) )" ) - const css_gh_tx_color = ( theme == "dark" ? this.settings.gh_clr_tx_dark : this.settings.gh_clr_tx_light ) - const css_gh_wrap = ( this.settings.textwrap == "Enabled" ? "wrap" : "nowrap" ) - const css_gh_opacity = ( this.settings.gh_opacity ) || 1 - - return ` - ::-webkit-scrollbar - { - width: 6px; - height: 10px; - } - - ::-webkit-scrollbar-track - { - background-color: transparent; - border-radius: 5px; - margin: 1px; - } - - ::-webkit-scrollbar-thumb - { - border-radius: 10px; - background-color: ${css_gh_sb_color}; - } - - body - { - --tw-bg-opacity: 1; - --tw-border-opacity: 1; - } - - body .gist .gist-file - { - backdrop-filter: opacity( 0 ); - background-color: rgb( 35 36 41/var( --tw-bg-opacity ) ); - border: 2px solid rgba( 255, 255, 255, 0.1 ); - opacity: ${css_gh_opacity}; - } - - body .gist .gist-data - { - padding-left: 12px; - padding-right: 12px; - padding-top: 15px; - padding-bottom: 6px; - border-color: ${css_gh_bg_header_bor}; - background-color: ${css_gh_bg_color}; - } - - .gist .markdown-body>*:last-child - { - margin-bottom: 0 !important; - padding-bottom: 5px; - } - - body .gist .markdown-body - { - color: ${css_gh_tx_color}; - line-height: 18.2px; - font-size: 0.8em; - border-spacing: 0; - border-collapse: collapse; - font-family: Menlo,Consolas,Liberation Mono,monospace; - } - - body .gist .gist-meta - { - color: #6b869f; - border-top: ${css_gh_bg_header_bor}; - background-color: ${css_gh_bg_header_bg}; - padding-left: 22px; - padding-right: 16px; - padding-top: 8px; - padding-bottom: 8px; - } - - body .gist .gist-meta a - { - color: rgb( 186 188 197/var( --tw-text-opacity ) ); - opacity: 0.9; - } - - body .gist .gist-meta a.Link--inTextBlock:hover - { - color: ${css_gh_tx_color}; - opacity: 0.5; - } - - body .gist .gist-meta a.Link--inTextBlock - { - padding-left: 0px; - padding-right: 7px; - color: ${css_gh_tx_color}; - } - - body .gist .gist-meta > a:nth-child( 3 ) - { - padding-left: 5px; - } - - body .gist .gist-data .pl-s .pl-s1 - { - color: #a5c261 - } - - body .gist .highlight - { - background: transparent; - } - - body .gist .blob-wrapper - { - padding-bottom: 6px !important; - } - - body .gist .pl-s2, body .gist .pl-stj, body .gist .pl-vo, - body .gist .pl-id, body .gist .pl-ii - { - color: ${css_gh_tx_color}; - } - - body .gist .blob-code - { - color: ${css_gh_tx_color}; - } - - body .gist .blob-num, body .gist .blob-code-inner, - { - color: ${css_gh_tx_color}; - opacity: 0.5; - } - - body .gist .blob-num:hover - { - color: ${css_gh_tx_color}; - opacity: 1; - } - - body .gist .blob-wrapper tr:first-child td - { - text-wrap: ${css_gh_wrap}; - } - - body .gist .pl-enti, body .gist .pl-mb, body .gist .pl-pdb - { - font-weight: 700; - } - - body .gist .pl-c, body .gist .pl-c span, body .gist .pl-pdc - { - color: #bc9458; - font-style: italic; - } - - body .gist .pl-c1, body .gist .pl-pdc1, body .gist .pl-scp - { - color: #6c99bb; - } - - body .gist .pl-ent, body .gist .pl-eoa, body .gist .pl-eoai, body .gist .pl-eoai .pl-pde, - body .gist .pl-ko, body .gist .pl-kolp, body .gist .pl-mc, body .gist .pl-mr, body .gist .pl-ms, - body .gist .pl-s3, body .gist .pl-sok - { - color: #ffe5bb; - } - - body .gist .pl-mdh, body .gist .pl-mdi, body .gist .pl-mdr - { - font-weight: 400; - } - - body .gist .pl-mi, body .gist .pl-pdi - { - color: #ffe5bb; - font-style: italic; - } - - body .gist .pl-sra, - body .gist .pl-src, - body .gist .pl-sre - { - color: #cc3; - } - - body .gist .pl-mdht, body .gist .pl-mi1 - { - color: #a5c261; - background: #121315; - } - - body .gist .pl-md, body .gist .pl-mdhf - { - color: #b83426; - background: #121315; - } - - body .gist .pl-ib, body .gist .pl-id, - body .gist .pl-ii, body .gist .pl-iu - { - background: #121315; - } - - body .gist .pl-ms1 - { - background: #121315; - } - - body .gist .highlight-text-html-basic .pl-ent, - body .gist .pl-cce, body .gist .pl-cn, body .gist .pl-coc, body .gist .pl-enc, - body .gist .pl-ens, body .gist .pl-k, body .gist .pl-kos, body .gist .pl-kou, - body .gist .pl-mh .pl-pdh, body .gist .pl-mp, body .gist .pl-mp .pl-s3, - body .gist .pl-mp1 .pl-sf, body .gist .pl-mq, body .gist .pl-mri, - body .gist .pl-pde, body .gist .pl-pse, body .gist .pl-pse .pl-s2, - body .gist .pl-s, body .gist .pl-st, body .gist .pl-stp, body .gist .pl-sv, - body .gist .pl-v, body .gist .pl-va, body .gist .pl-vi, body .gist .pl-vpf, - body .gist .pl-vpu, body .gist .pl-mdr - { - color: #cc7833; - } - - body .gist .pl-cos, body .gist .pl-ml, body .gist .pl-pds, - body .gist .pl-s1, body .gist .pl-sol, body .gist .pl-mb, - body .gist .pl-pdb - { - color: #a5c261; - } - - body .gist .pl-e, body .gist .pl-en, body .gist .pl-entl, - body .gist .pl-mo, body .gist .pl-sc, body .gist .pl-sf, - body .gist .pl-smi, body .gist .pl-smp, body .gist .pl-mdh, - body .gist .pl-mdi - { - color: #ffc66d; - } - - body .gist .pl-ef, body .gist .pl-enf, body .gist .pl-enm, body .gist .pl-entc, - body .gist .pl-entm, body .gist .pl-eoac, body .gist .pl-eoac .pl-pde, body .gist .pl-eoi, - body .gist .pl-mai .pl-sf, body .gist .pl-mm, body .gist .pl-pdv, body .gist .pl-smc, - body .gist .pl-som, body .gist .pl-sr, body .gist .pl-enti - { - color: #b83426; - } - ` - } - - /* - Throw Error - */ - - private async ThrowError( el: HTMLElement, gistInfo: string, err: string = '' ) - { - const div_Error = el.createEl( 'div', { text: "", cls: 'gistr-container-error' } ) - div_Error.createEl( 'div', { text: lng( "err_gist_loading_fail_name" ), cls: 'gistr-load-error-l1' } ) - div_Error.createEl( 'div', { text: gistInfo, cls: "gistr-load-error-l2" } ) - div_Error.createEl( 'small', { text: lng( "err_gist_loading_fail_resp", err ) } ) - } - - /* - Get Javascript - */ - - private async GetJavascript( el: HTMLElement, data: string, url: string ) - { - const reqUrlParams: RequestUrlParam = { url: url, method: "GET", headers: { "Accept": "text/javascript" } } - try { return await request( reqUrlParams ) } - catch ( err ) - { - return this.ThrowError( el, data, lng( "err_gist_loading_fail_detail", err ) ) - } - } - - /* - Get CSS - */ - - private async GetCSS( el: HTMLElement, data: string, url: string ) - { - const reqUrlParams: RequestUrlParam = { url : url, method: "GET", headers: { "Accept": "text/css" } } - try { return await request( reqUrlParams ) } - catch ( err ) - { - return this.ThrowError( el, data, lng( "err_gist_loading_fail_detail", err ) ) - } - } - - /* - Collect message data from JS_EventListener - */ - - messageEventHandler = ( evn: MessageEvent ) => - { - if ( evn.origin !== 'null' ) return - if ( evn.data.sender !== sender ) return - - const uuid = evn.data.gid - const scrollHeight = evn.data.scrollHeight - const gist_Container: HTMLElement = document.querySelector( 'iframe#' + uuid ) - - gist_Container.setAttribute( 'height', scrollHeight ) - } - - /* - Event processor - */ - - processor = async ( src: string, el: HTMLElement ) => - { - const obj = src.trim( ).split( "\n" ) - - return Promise.all - ( - obj.map( async ( gist ) => - { - return this.GistHandle( el, gist ) - } ) - ) - } -} \ No newline at end of file diff --git a/src/backend/index.ts b/src/backend/index.ts deleted file mode 100644 index 02271a38..00000000 --- a/src/backend/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ItemJSON, BackendCore } from 'src/backend/Backend' \ No newline at end of file diff --git a/src/backend/services/Opengist.ts b/src/backend/services/Opengist.ts deleted file mode 100644 index 84a92acf..00000000 --- a/src/backend/services/Opengist.ts +++ /dev/null @@ -1,19 +0,0 @@ -const GISTR_OGIST_PAT = 'gistr_opengist_pat' - -/* - OpenGist > Personal Access Token > Set -*/ - -export const OGTokenSet = ( token: string ): void => -{ - localStorage.setItem( GISTR_OGIST_PAT, token ) -} - -/* - OpenGist > Personal Access Token > Get -*/ - -export const OGTokenGet = ( ): string => -{ - return localStorage.getItem( GISTR_OGIST_PAT ) -} \ No newline at end of file diff --git a/src/backend/services/github.ts b/src/backend/services/github.ts deleted file mode 100644 index 670507cd..00000000 --- a/src/backend/services/github.ts +++ /dev/null @@ -1,671 +0,0 @@ -import { App, Notice, SuggestModal, MarkdownView, TFile } from 'obsidian' -import GistrPlugin from 'src/main' -import { GistrSettings, SettingsGet } from 'src/settings/settings' -import { FrontmatterPrepare } from 'src/api' -import Noxkit from '@aetherinox/noxkit' -import frontmatter from 'front-matter' -import { Octokit } from '@octokit/rest' -import { lng } from 'src/lang' - -/* - Github Status - - do not change the values, these are assigned by Github -*/ - -enum Status -{ - success = 'succeeded', - fail = 'failed', -} - -/* - default api status types -*/ - -export const GHStatusAPI: Record< string, string > = -{ - 'operational': lng( "gist_status_connected" ), - "degraded_performance": lng( "gist_status_degraded_performance" ), - "partial_outage": lng( "gist_status_partial_outage" ), - "major_outage": lng( "gist_status_major_outage" ), -} - -/* - Github Gist Structure -*/ - -interface GistData -{ - file: string, - is_public: boolean, - id: string, - url: string, - user: string, - revisions: number, - created_at: string, - updated_at: string, -} - -/* - Github > Personal Access Token -*/ - -const GISTR_GITHUB_PAT = 'gistr_github_pat' - -/* - Github > Personal Access Token > Set -*/ - -export const GHTokenSet = ( token: string ): void => -{ - localStorage.setItem( GISTR_GITHUB_PAT, token ) -} - -/* - Github > Personal Access Token > Get -*/ - -export const GHTokenGet = ( ): string => -{ - return localStorage.getItem( GISTR_GITHUB_PAT ) -} - -/* - Interface > Gist Result -*/ - -interface ResultsCreateGist -{ - gistArray: GistData | null - status: Status - errorMessage: string | null -} - -/* - Interface > Options > Create -*/ - -interface OptionsCreate -{ - is_public: boolean - file: string - content: string - token: string -} - -/* - Interface > Options > Update -*/ - -interface OptionsUpdate -{ - gistArray: GistData - content: string - token: string -} - -/* - Interface > Params > Autosave -*/ - -interface ParamsAutosave -{ - plugin: GistrPlugin - app: App - note_full: string - file: TFile -} - -/* - Github > Args -*/ - -interface ArgsGet { app: App, plugin: GistrPlugin, is_public: boolean } -interface ArgsCopy { app: App, plugin: GistrPlugin } - -/* - Gist > Get File - - @json gist json - : { - "modified": "2024-03-06T14:59:39.000Z", - "gists": [ - { - "id": "XXXX", - "url": "https://gist.github.com/Aetherinox/XXXXXX", - "created_at": "2024-03-07T00:19:23Z", - "updated_at": "2024-03-07T00:31:12Z", - "file": "Note File.md", - "is_public": true || false - } - ] - } -*/ - -const FindExistingGist = ( gistContents: string ): GistData[ ] => -{ - const { attributes: json } = frontmatter<{ gists: GistData[] }>( gistContents ) - const gists = json.gists || [] - - return ( gists as GistData[] ) -} - -/* - Gist > Update - - @body: gist json - @content: formatted vault contents - - @package-frontmatter: - content.attributes contains the extracted yaml attributes in json form - content.body contains the string contents below the yaml separators - content.bodyBegin contains the line number the body contents begins at - content.frontmatter contains the original yaml string contents -*/ - -const InsertFrontmatter = ( gistArray: GistData, note_contents: string ): string => -{ - - const { body: note, attributes: data } = frontmatter<{ gists: GistData[] }>( note_contents ) - - const GistList = ( data.gists || [] ) as GistData[] - const GistMatching = GistList.find( ( GistExisting ) => GistExisting.id === gistArray.id ) - - /* - Existing Gist - */ - - if ( GistMatching ) - { - const otherGists = GistList.filter( ( GistExisting ) => GistExisting !== GistMatching ) - const gists = [ ... otherGists, gistArray ] - const updatedData = { ... data, gists } - - return Noxkit.stringify( note, updatedData ) - } - - /* - New Gist - - @updatedData: gist json - : { - "modified": "2024-03-06T14:59:39.000Z", - "gists": [ - { - "id": "XXXX", - "url": "https://gist.github.com/Aetherinox/XXXXXX", - "created_at": "2024-03-07T00:19:23Z", - "updated_at": "2024-03-07T00:31:12Z", - "file": "Note File.md", - "is_public": true || false - } - ] - } - - @note: Test Note Body Contents - */ - - const gists = [ ... GistList, gistArray ] - const updatedData = { ... data, gists } - - return Noxkit.stringify( note, updatedData ) -} - -/* - Gist > Update -*/ - -const Update = async ( args: OptionsUpdate ): Promise< ResultsCreateGist > => -{ - const { token, gistArray, content } = args - - try - { - const octokit = new Octokit( { auth: token } ) - const response = await octokit.rest.gists.update( { gist_id: gistArray.id, files: { [ gistArray.file ]: { content } } } ) - - return { status: Status.success, gistArray: { ... gistArray, updated_at: response.data.updated_at, user: response.data.owner.login, revisions: response.data.history.length }, errorMessage: null } - } - catch ( e ) - { - return { status: Status.fail, gistArray: gistArray, errorMessage: e.message } - } -} - -/* - Gist > Create -*/ - -const Create = async ( args: OptionsCreate ): Promise< ResultsCreateGist > => -{ - try - { - const { file, content, is_public, token } = args - - const octokit = new Octokit( { auth: token } ) - const octogist = await octokit.rest.gists.create( { description: file, public: is_public, files: { [ file ]: { content } } } ) - - return { - status: Status.success, gistArray: - { - file, - is_public, - id: octogist.data.id as string, - url: octogist.data.html_url as string, - user: octogist.data.owner.login as string, - revisions: octogist.data.history.length as number, - created_at: octogist.data.created_at as string, - updated_at: octogist.data.updated_at as string, - }, - - errorMessage: null, - } - } - catch ( e ) - { - return{ status: Status.fail, gistArray: null, errorMessage: e.message } - } -} - -/* - DateTime Format - - need to break it up into these crazy steps to allow for customization -*/ - -const dateTimeformat = ( date: Date ): string => -{ - const month = date.getMonth( ) + 1 - const month_str = month.toString( ).padStart( 2, '0' ) - const day = date.getDate( ).toString( ).padStart( 2, '0' ) - const year = date.getFullYear( ).toString( ).padStart( 2, '0' ) - - let hours = date.getHours( ) - const mins = date.getMinutes( ) - const mins_str = mins.toString( ).padStart( 2, '0' ) - const x = hours >= 12 ? lng( "base_time_pm" ) : lng( "base_time_am" ) - hours = hours % 12 - hours = hours ? hours : 12 - - return month_str + '.' + day + '.' + year + ' ' + hours + ':' + mins_str + ' ' + x -} - -/* - Modal > Select Existing Gist -*/ - -class SelectExistingModal extends SuggestModal < GistData > -{ - gists: GistData[] - bAllowGistCreateNew: boolean - settings: GistrSettings - - /* - Suggestion > Submit - */ - - onSubmit: ( gistArray: GistData | null ) => Promise < void > - - /* - Suggestion > Constructor - */ - - constructor( app: App, settings: GistrSettings, gists: GistData[ ], bAllowGistCreateNew: boolean, onSubmit: ( gistArray: GistData ) => Promise < void > ) - { - super( app ) - - this.settings = settings - this.gists = gists - this.bAllowGistCreateNew = bAllowGistCreateNew - this.onSubmit = onSubmit - } - - /* - Suggestion > Get - */ - - getSuggestions( ): Array < GistData | null > - { - if ( this.bAllowGistCreateNew ) - return this.gists.concat( null ) - else - return this.gists - } - - /* - Suggestion > Render - */ - - renderSuggestion( gistArray: GistData | null, el: HTMLElement ) - { - - /* - object empty, show "Create New Gist" button - */ - - if ( Object.is( gistArray, null ) ) - { - const div_Create = el.createEl( 'div', { text: "", cls: 'gistr-suggest-create' } ) - div_Create.createEl ( 'div', { text: lng( "gist_btn_create_new" ) } ) - - return - } - - /* - Existing gist found > populate list - */ - - const div_scope = gistArray.is_public ? lng( "lst_repotype_pub" ) : lng( "lst_repotype_pri" ) - let date = new Date( `${ gistArray.updated_at }` ) - let date_created = dateTimeformat( date ) - - const div_Parent = el.createEl( 'div', { text: "", cls: 'gistr-suggest-container' } ) - const svg_Icon = div_Parent.createEl ( 'div', { text: "", cls: 'gistr-suggest-icon' } ) - svg_Icon.insertAdjacentHTML ( 'afterbegin', "" ) - const div_Sub_l = div_Parent.createEl ( 'div', { text: "", cls: 'gistr-suggest-sub-container-l' } ) - div_Sub_l.createEl ( 'div', { text: gistArray.file, cls: "gistr-suggest-sub-title" } ) - const div_Sub_r = div_Parent.createEl ( 'div', { text: "", cls: 'gistr-suggest-sub-container-r' } ) - div_Sub_r.createEl ( 'div', { text: div_scope, cls: "gistr-suggest-sub-scope" } ) - div_Parent.createEl ( 'div', { text: "", cls: 'gistr-suggest-clear' } ) - div_Sub_l.createEl ( 'div', { text: "", cls: 'gistr-suggest-clear' } ) - div_Sub_l.createEl ( 'div', { text: `Created: ${ date_created }`, cls: "gistr-suggest-sub-time" } ) - } - - /* - Suggestion > Choose - */ - - onChooseSuggestion( gistArray: GistData | null ) - { - this.onSubmit( gistArray ).then( ( ) => this.close( ) ) - } -} - -/* - Github Gist > Get - - Initialized by Obsidian command palette and right-click menu -*/ - -export const GHGistGet = ( args: ArgsGet ) => async ( ) => -{ - - const { is_public, app, plugin } = args - const token = GHTokenGet( ) - const repoTarget = is_public ? lng( "lst_repotype_pub" ) : lng( "lst_repotype_pri" ) - const - { - sy_enable_autoupdate, - sy_add_frontmatter, - notitime - } = await SettingsGet( plugin ) - - /* - User token not specified in settings - */ - - if ( !token ) - { - new Notice( lng( "err_gist_token_missing" ), notitime * 1000 ) - return - } - - /* - Current view - */ - - if ( !app.workspace.getActiveViewOfType( MarkdownView ) ) - { - new Notice( lng( "gist_upload_no_active_file" ), notitime * 1000 ) - return - } - - /* - Continue fetching gist information - */ - - const getView = app.workspace.getActiveViewOfType( MarkdownView ) - const file = getView.file.name - const editor = getView.editor - const noteOrig = editor.getValue( ) - const ExistingGist = FindExistingGist( noteOrig ).filter( ( gistArray ) => gistArray.is_public === is_public ) - const gistContent = sy_add_frontmatter ? noteOrig : FrontmatterPrepare( noteOrig ) - - if ( ExistingGist.length && sy_enable_autoupdate ) - { - - new SelectExistingModal - ( - app, plugin.settings, ExistingGist, true, async ( gistArray ) => - { - - let output = null - - /* - Update or Create - */ - - if ( !gistArray ) - output = await Create( { file, content: gistContent, token, is_public } ) - else - output = await Update( { gistArray, token, content: gistContent } ) - - /* - API call failed - */ - - if ( process.env.ENV === "dev" ) - console.log( output ) - - if ( output.status === Status.fail ) - { - const error_msg = output.errorMessage - const bNotFound = error_msg.toLowerCase( ).includes( "not found" ) - - if ( bNotFound ) - new Notice( lng( "gist_not_found" ), notitime * 1000 ) - - new Notice( lng( "gist_upload_fail_api", output.errorMessage ), notitime * 1000 ) - return - } - - /* - Copy to clipboard - */ - - navigator.clipboard.writeText( output.gistArray.url ) - - /* - API call Success - */ - - new Notice( lng( "gist_copy_success_file", repoTarget ), notitime * 1000 ) - const editor_newvalue = InsertFrontmatter( output.gistArray, noteOrig ) - - if ( process.env.ENV === "dev" ) - console.log( "GHGistGet -> Insert into editor" ) - - editor.setValue( editor_newvalue ) - - }, - ).open( ) - } - else - { - const result = await Create( { file, content: gistContent, token, is_public } ) - - if ( process.env.ENV === "dev" ) - console.log( result ) - - /* - Failure - */ - - if ( result.status !== Status.success ) - new Notice( lng( "gh_status_error_api", result.errorMessage ) ) - - /* - Success - */ - - navigator.clipboard.writeText( result.gistArray.url ) - const repo_type = is_public ? lng( "lst_repotype_pub" ) : lng( "lst_repotype_pri" ) - new Notice( lng( "gist_copy_success_file", repo_type ), notitime * 1000 ) - - /* - Autosave Feature - */ - - if ( sy_enable_autoupdate ) - { - const contentResult = InsertFrontmatter( result.gistArray, noteOrig ) - - /* - Create -> Insert frontmatter text into editor window - */ - - if ( process.env.ENV === "dev" ) - console.log( "Create -> Insert into editor" ) - - app.vault.modify( getView.file, contentResult ) - editor.refresh( ) - } - - } -} - -/* - Github Gist > Copy - - Copies a gist url to the user's clipboard -*/ - -export const GHGistCopy = ( args: ArgsCopy ) => async ( ) => -{ - const { app, plugin } = args - const { sy_enable_autoupdate, notitime } = await SettingsGet( plugin ) - - if ( !sy_enable_autoupdate ) - return new Notice( lng( "gist_upload_req_allowupload" ), notitime * 1000 ) - - const getView = app.workspace.getActiveViewOfType( MarkdownView ) - - /* - No active file focus - */ - - if ( !getView ) - return new Notice( "gist_no_active_file" ) - - const editor = getView.editor - const noteOrig = editor.getValue( ) - const ExistingGist = FindExistingGist( noteOrig ) - - /* - Nothing to copy - */ - - if ( ExistingGist.length === 0 ) - return new Notice( lng( "gist_copy_fail_notagist" ), notitime * 1000 ); - - /* - Obsidian note only has one gist - */ - - if ( ExistingGist.length === 1 ) - { - const gistArray = ExistingGist[ 0 ] - navigator.clipboard.writeText( gistArray.url ) - - return new Notice( lng( "gist_copy_success" ), notitime * 1000 ) - } - - /* - Obsidian note has more than one gist associated to it. - will display the suggestion box and allow the user to select - which note they wish to copy the url for. - - such examples include making a gist both public and secret - */ - - new SelectExistingModal - ( - app, plugin.settings, ExistingGist, false, async ( gistArray ) => - { - navigator.clipboard.writeText( gistArray.url ) - new Notice( lng( "gist_copy_success" ), notitime * 1000 ) - }, - ).open( ) -} - -/* - Gist > Update Existing - - this process updates an existing github gist. - the user must have already manually saved the gist. - - if autosave is enabled, this will be ran every x seconds to ensure - the contents of the note are updated to a gist online. -*/ - -export const GHGistUpdate = async ( args: ParamsAutosave ) => -{ - const { plugin, file, note_full: note_full } = args - const { sy_add_frontmatter, sy_enable_autosave_notice, notitime } = await SettingsGet( plugin ) - - /* - User token not specified in settings - */ - - const token = GHTokenGet( ) - - if ( !token ) - return new Notice( lng( "err_gist_token_missing" ), notitime * 1000 ) - - /* - Find existing notes - */ - - const note_existing = FindExistingGist( note_full ) - const content = sy_add_frontmatter ? note_full : FrontmatterPrepare( note_full ) - - /* - Validate - */ - - if ( !note_existing.length ) return - - for ( const gistArray of note_existing ) - { - const res = await Update( { gistArray, token, content } ) - - if ( res.status !== Status.success ) - { - - /* - Update Failed - */ - - new Notice( lng( "gist_upload_fail_api", res.errorMessage ), notitime * 1000 ) - } - else - { - - /* - Update Success - */ - - const note_updated = InsertFrontmatter( res.gistArray, note_full ) - await file.vault.adapter.write( file.path, note_updated ) - - /* - Save Notice Enabled - */ - - if ( sy_enable_autosave_notice ) - new Notice( lng( "gist_upload_success", file.path ), notitime * 1000 ) - } - } -} - \ No newline at end of file diff --git a/src/backend/services/index.ts b/src/backend/services/index.ts deleted file mode 100644 index 62d57bb4..00000000 --- a/src/backend/services/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { GHTokenSet, GHTokenGet, GHGistGet, GHGistCopy, GHGistUpdate, GHStatusAPI } from 'src/backend/services/Github' -export { OGTokenSet, OGTokenGet } from 'src/backend/services/Opengist' \ No newline at end of file diff --git a/src/lang/index.ts b/src/lang/index.ts deleted file mode 100644 index 2f829cbf..00000000 --- a/src/lang/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - Languages Helper -*/ - -import { moment } from "obsidian" -import en from "./locale/en" - -/* - Language entries -*/ - -const SetupLocale: { [ i: string ]: Partial< typeof en > } = -{ - en, -} - -/* - get locale val -*/ - -const locale = SetupLocale[ moment.locale( ) ] - -/* - Language Method -*/ - -export function lng( item: keyof typeof en, ...args: string[] ) : string -{ - if ( !locale ) - console.error( "Gistr language not found", moment.locale( ) ) - - let val = ( locale && locale[ item ] ) || en[ item ] - return val.replace( /{(\d+)}/g, ( match, index ) => - { - const replace = args[ index ] - return typeof replace !== 'undefined' ? replace : match - } ) -} \ No newline at end of file diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts deleted file mode 100644 index c4637632..00000000 --- a/src/lang/locale/en.ts +++ /dev/null @@ -1,298 +0,0 @@ -/* - @locale : English (en) -*/ - -export default -{ - - /* - Base entries - */ - - base_underdev_title: 'Feature Under Development', - base_underdev_msg: 'I am currently working with the developer of OpenGist to make minor changes to how OpenGist pastes appear, including moving the "view raw" button to the bottom so that Obsidian\'s edit button does not overlap.', - base_opt_enabled: 'Enabled', - base_opt_disabled: 'Disabled', - base_theme_light: 'Light', - base_theme_dark: 'Dark', - base_time_am: 'AM', - base_time_pm: 'PM', - base_component_reset: 'Reset to Default', - base_debug_loading: 'Loading {0} v{1} [ {2} ]', - base_debug_updater_1: '{0} Update Check', - base_debug_updater_2: '{0} {1}', - base_context_nofocus: 'Obsidian does not have focus, please open a file', - - /* - Context menu options - */ - - cfg_context_gist_public: 'Save Gist (Github Public)', - cfg_context_gist_secret: 'Save Gist (Github Secret)', - cfg_context_gist_copy: 'Copy Gist URL', - - /* - Tab > Settings > Header - */ - - cfg_modal_desc: 'Gistr allows you to embed gists directly from Github and Opengist. You can also turn your notes into gists which can be updated manually, or monitored with autosave. For a detailed set of examples, view the demo vault in the support section below.', - cfg_modal_expand: 'Expand', - - /* - Tab > Settings > General - */ - - cfg_tab_ge_title: 'Global', - cfg_tab_ge_header: 'These settings affect all aspects of this plugin, including both Opengist and Github. If you change the trigger keyword, ensure you go back through your existing gist snippets and change the keyword at the top of each codeblock; otherwise embedded gists will not appear.', - cfg_tab_sy_title: 'Save / Sync', - cfg_tab_sy_header: 'These settings allow you to create gists from your notes. The contents of your notes will be directly uploaded to Github under an existing account. You may also choose to create only new notes, or manage new and existing.', - cfg_tab_og_title: 'OpenGist', - cfg_tab_og_header: 'Opengist is a self-hosted pastebin powered by Git. All snippets are stored in a Git repository and can be read and/or modified using standard Git commands, or with the web interface. It is similiar to GitHub Gist, but open-source and is self-hosted. OpenGist supports Windows, Linux, and MacOS.', - cfg_tab_gh_title: 'Github', - cfg_tab_gh_header: 'Github Gists let you store and distribute code snippets without setting up a full-fledged repository. Store snippets such as strings, bash scripts, markdown, text files, and other small pieces of data.', - cfg_tab_sp_title: 'Support', - cfg_tab_ge_keyword_name: 'Trigger keyword', - cfg_tab_ge_keyword_desc: 'Word to use inside codeblocks to designate as a portal for showing gists', - cfg_tab_ge_theme_name: 'Theme', - cfg_tab_ge_theme_desc: 'This determines what color scheme will be used for gists. You can however, customize the colors in the Github and OpenGist categories below.

Note: When this is changed, place your cursor in the codeblock and then leave the codeblock to refresh it. Automatic refreshing only works in reading mode', - cfg_tab_ge_wrap_name: 'Text wrapping', - cfg_tab_ge_wrap_desc: 'If enabled, text will wrap to the next line. If disabled, you will see a horizontal scrollbar. This does not include gists that have no spaces anywhere in the body.', - cfg_tab_ge_noti_dur_name: 'Notification duration', - cfg_tab_ge_noti_dur_desc: 'How long a notification will display for (in seconds). Set to 0 to keep notification up until user dismisses it.', - cfg_tab_ge_noti_update_name: 'Enable Gistr update notifications', - cfg_tab_ge_noti_update_desc: 'Enabled: When launching Obsidian, you will get a notification if a new version of Gistr is available. This includes beta releases not available to the public yet.

Disabled: You will not get any notifications alerting you to new Gistr updates. You must manually check or use the Obsidian plugin checker.

Note: This update notification includes beta releases of Gistr. The Obsidian plugin updater does not track beta.', - - /* - Tab > Settings > OpenGist - */ - - cfg_tab_og_cb_light_name: 'Codeblock bg (Light)', - cfg_tab_og_cb_light_desc: 'Color for Github codeblock background color (Light Theme)', - cfg_tab_og_cb_dark_name: 'Codeblock bg (Dark)', - cfg_tab_og_cb_dark_desc: 'Color for Github codeblock background color (Dark Theme)', - cfg_tab_og_sb_light_name: 'Scrollbar track (Light)', - cfg_tab_og_sb_light_desc: 'Color for gist scrollbar track (Light Theme)', - cfg_tab_og_sb_dark_name: 'Scrollbar track (Dark)', - cfg_tab_og_sb_dark_desc: 'Color for gist scrollbar track (Dark Theme)', - cfg_tab_og_tx_light_name: 'Codeblock text (Light)', - cfg_tab_og_tx_light_desc: 'Color for codeblock text color (Light Theme)', - cfg_tab_og_tx_dark_name: 'Codeblock text (Dark)', - cfg_tab_og_tx_dark_desc: 'Color for codeblock text color (Dark Theme)', - cfg_tab_og_opacity_name: 'Codeblock opacity', - cfg_tab_og_opacity_desc: 'Total opacity for codeblock. Do not set this too low, or your codeblocks will be invisible', - cfg_tab_og_pad_top_name: 'Padding: top', - cfg_tab_og_pad_top_desc: 'Padding between gist codeblock header and code.', - cfg_tab_og_pad_btm_name: 'Padding: bottom', - cfg_tab_og_pad_btm_desc: 'Padding between gist codeblock and the bottom scrollbar.', - cfg_tab_og_css_name: 'Custom CSS', - cfg_tab_og_css_desc: 'This textarea allows you to enter custom CSS properties to override existing colors.', - cfg_tab_og_css_pholder: 'Paste CSS here', - - /* - Tab > Settings > Github - */ - - cfg_tab_gh_cb_light_name: 'Codeblock bg (Light)', - cfg_tab_gh_cb_light_desc: 'Color for Opengist codeblock background color (Light Theme)', - cfg_tab_gh_cb_dark_name: 'Codeblock bg (Dark)', - cfg_tab_gh_cb_dark_desc: 'Color for Opengist codeblock background color (Dark Theme)', - cfg_tab_gh_sb_light_name: 'Scrollbar track (Light)', - cfg_tab_gh_sb_light_desc: 'Color for gist scrollbar track (Light Theme)', - cfg_tab_gh_sb_dark_name: 'Scrollbar track (Dark)', - cfg_tab_gh_sb_dark_desc: 'Color for gist scrollbar track (Dark Theme)', - cfg_tab_gh_tx_light_name: 'Codeblock text (Light)', - cfg_tab_gh_tx_light_desc: 'Color for codeblock text color (Light Theme)', - cfg_tab_gh_tx_dark_name: 'Codeblock text (Dark)', - cfg_tab_gh_tx_dark_desc: 'Color for codeblock text color (Dark Theme)', - cfg_tab_gh_opacity_name: 'Codeblock opacity', - cfg_tab_gh_opacity_desc: 'Total opacity for codeblock. Do not set this too low, or your codeblocks will be invisible', - cfg_tab_gh_css_name: 'Custom CSS', - cfg_tab_gh_css_desc: 'This textarea allows you to enter custom CSS properties to override existing colors.', - cfg_tab_gh_css_pholder: 'Paste CSS here', - cfg_tab_gh_pat_name: 'Personal access token', - cfg_tab_gh_pat_desc: 'The personal access token (PAT) generated on Github.com which allows you to write gists from your Obsidian vault to Github gist.', - cfg_tab_gh_pat_pholder: 'githubpat_XXXXXX', - cfg_tab_gh_pat_btn_tip_state_show: 'Show token', - cfg_tab_gh_pat_btn_tip_state_hide: 'Hide token', - cfg_tab_gh_pat_desc_l1: 'This token allows you to authenticate with the GitHub API.
Create Token: here', - cfg_tab_gh_pat_desc_l2: 'For this to function with secret gists, select "All repositories" or "Only select repositories" from the dropdown on the Github Token page. The token must have at least the following permissions:', - cfg_tab_gh_pat_perm_1: 'Account Permissions ► Gists                                                                          Read-and-write', - cfg_tab_gh_pat_perm_2: 'Repository Permissions ► Pull Requests                                      Read-only', - cfg_tab_gh_pat_perm_3: 'Repository Permissions ► Contents                                                    Read-only', - cfg_tab_gh_pat_perm_4: 'Repository Permissions ► Issues                                                              Read-only', - cfg_tab_gh_pat_footer: 'Github icon to the right will turn into a checkmark when you\'ve entered a valid token.', - cfg_tab_gh_pat_help: 'What is this for? Read the docs', - cfg_tab_gh_pat_url_btn: 'https://github.com/settings/tokens?type=beta', - cfg_tab_gh_pat_ok_btn_tip: 'Valid Github API Token', - cfg_tab_gh_pat_bad_btn_tip: 'Invalid Github API Token entered\n\nClick here to generate one', - cfg_tab_gh_pat_invalid_btn_tip: 'Github API token is not valid, ensure you type it correctly\n\nClick here to generate one', - cfg_tag_gh_pat_notice_msg_success: 'Gistr has detected a valid Github personal access token which has been saved', - cfg_tag_gh_pat_notice_msg_cleared: 'Personal access token cleared', - cfg_tab_gh_pat_notice_type_fine: 'Fine-Grained Github Token Detected', - cfg_tab_gh_pat_notice_type_classic: 'Classic Github Token Detected', - - /* - Tab > Settings > Support - */ - - cfg_tab_su_desc: 'The following buttons are associated to useful resources for this plugin.', - cfg_tab_su_gs_name: 'Introduction', - cfg_tab_su_gs_desc: 'View brief introduction to getting started with this plugin', - cfg_tab_su_gs_btn: 'Open', - cfg_tab_su_repo_label: 'Plugin repo', - cfg_tab_su_repo_url: 'https://github.com/Aetherinox/obsidian-gistr', - cfg_tab_su_repo_btn: 'View', - cfg_tab_su_vault_label: 'Plugin demo vault', - cfg_tab_su_vault_url: 'https://github.com/Aetherinox/obsidian-gistr/tree/main/tests/gistr-vault', - cfg_tab_su_vault_btn: 'View', - cfg_tab_su_ogrepo_label: 'OpenGist: download', - cfg_tab_su_ogrepo_url: 'https://github.com/thomiceli/opengist/releases', - cfg_tab_su_ogrepo_btn: 'View', - cfg_tab_su_ogdocs_label: 'OpenGist: docs', - cfg_tab_su_ogdocs_url: 'https://github.com/thomiceli/opengist/blob/master/docs/index.md', - cfg_tab_su_ogdocs_btn: 'View', - cfg_tab_su_ogdemo_label: 'OpenGist: demo', - cfg_tab_su_ogdemo_url: 'https://opengist.thomice.li/all', - cfg_tab_su_ogdemo_btn: 'View', - cfg_tab_su_gist_label: 'Github gist', - cfg_tab_su_gist_url: 'https://gist.github.com/', - cfg_tab_su_gist_btn: 'View', - cfg_tab_su_ver_cur_name: 'Current version', - cfg_tab_su_ver_cur_desc: 'Current running version of Gistr', - cfg_tab_su_guid_cur_name: 'GUID', - cfg_tab_su_guid_cur_desc: 'Gistr plugin release', - cfg_tab_su_guid_btn_tip: 'Copy to clipboard', - cfg_tab_su_guid_notice: 'Copied Gistr GUID to clipboard\n\n{0}', - cfg_tab_su_uuid_cur_name: 'UUID', - cfg_tab_su_uuid_cur_desc: 'Unique id for your current running release of Gistr', - cfg_tab_su_uuid_btn_tip: 'Copy to clipboard', - cfg_tab_su_uuid_notice: 'Copied Gistr release UUID to clipboard\n\n{0}', - cfg_tab_su_ver_stable: 'Latest stable release ', - cfg_tab_su_ver_beta: 'Latest beta release ', - cfg_tab_su_ver_connection_issues: 'Server communication failed', - cfg_tab_su_ver_status_checking: 'Checking for newer version of Gistr', - cfg_tab_su_ver_status_checking_btn_tip: 'Checking for newer version of Gistr', - cfg_tab_su_ver_status_updated_btn_tip: 'You are running the latest version of Gistr', - cfg_tab_su_ver_status_new_stable_btn_tip: 'A newer stable release of Gistr is available', - cfg_tab_su_ver_status_new_beta_btn_tip: 'A newer beta release of Gistr is available', - cfg_tab_su_ver_status_error_btn_tip: 'Could not communicate with the gistr server, retrying later', - cfg_tab_su_ver_releases: 'https://github.com/Aetherinox/obsidian-gistr/releases', - - /* - Tab > Sync - */ - - cfg_tab_sy_list_icon_name: 'Gist list icon color', - cfg_tab_sy_list_icon_desc: 'Color for icon in gist save list', - - cfg_tab_sy_tog_allow_gist_updates_name: 'Allow updating gists', - cfg_tab_sy_tog_allow_gist_updates_desc: 'Enabled: After you initially create a new gist, the note can be updated with newer revisions.

Disabled: Gists can only be created; no updates are allowed.

To update a gist after enabling this setting, right-click on the note, or open the Obsidian command palette and select Save Gist', - cfg_tab_sy_tog_allow_gist_updates_tip: '', - - cfg_tab_sy_tog_autosave_enable_name: 'Enable autosave', - cfg_tab_sy_tog_autosave_enable_desc: 'Enabled: This will allow gists to be updated once they are created. It will also enable autosaving which will detect new changes and push them.

Disabled: You will only be able to create gists by manually doing so; there will be no way to update them.

If you wish to keep this disabled, you can create gists by right-clicking in the note and selecting Save Gist. Or opening your command palette and selecting the save option from there.', - cfg_tab_sy_tog_autosave_enable_tip: '', - - cfg_tab_sy_tog_autosave_strict_name: 'Enable autosave strict saving', - cfg_tab_sy_tog_autosave_strict_desc: 'Enabled: Your notes will be saved to the gist service precisely on time every {0} seconds, whether you are still typing or not.

Disabled: Time until save will not start until you have finished typing in that note. If you continue typing, the saving countdown will not start until your final key is pressed.

Autosave duration can be modified further down in these settings.', - cfg_tab_sy_tog_autosave_strict_tip: '', - - cfg_tab_sy_tog_autosave_noti_name: 'Enable autosave notices', - cfg_tab_sy_tog_autosave_noti_desc: 'Each time your note is saved automatically, a notice will appear on-screen informing you of the action. This only works if Autosave is enabled.', - cfg_tab_sy_tog_autosave_noti_tip: '', - - cfg_tab_sy_num_save_dur_name: 'Autosave duration', - cfg_tab_sy_num_save_dur_desc: 'How often autosave will execute in seconds. Set this to a fair amount so that the calls aren\'t being ran excessively to the gist API server (Github or OpenGist).

The save countdown timer will begin shortly after you stop typing.

If you wish to change this to save precisely every {0} seconds, enable the setting Autosave Strict Saving located above.', - - cfg_tab_sy_tog_inc_fm_name: 'Include frontmatter', - cfg_tab_sy_tog_inc_fm_desc: 'When saving a note as a new gist, frontmatter will be added to the top of your note with information about the gist.

Enabled: the note will be cleaned before it is pushed to the gist service and no frontmatter fields will be present in the online version.

Disabled: frontmatter added to your notes will be included when your note is pushed to a gist service.

Frontmatter can be found at the very top of each note, in-between `---` ', - cfg_tab_sy_tog_inc_fm_tip: 'Frontmatter starts with three hyphens `---`', - - /* - Gists - */ - - gist_upload_req_allowupload: 'Must enable \"Allow Uploading Gists\" in the Gistr settings before you can use this command.', - gist_upload_no_active_file: 'No active file present. Open a note in Obsidian before continuing.', - gist_copy_fail_notagist: 'No URL to copy. You must turn your note into a gist first.', - gist_copy_success_file: `Copied {0} URL to your clipboard`, - gist_copy_success: 'Copied gist URL to clipboard.', - gist_upload_fail_api: 'GitHub API error: {0}', - gist_upload_success: 'File {0} has been updated successfully to your gist service.', - gist_status_operational_raw: 'operational', - gist_status_connecting: 'connecting ...', - gist_status_connected: 'Connected to API ...', - gist_status_no_api: 'Github token missing, no connection', - gist_status_no_api_btn_tip: 'You must create and specify a Github API token.\n\nAborting connection', - gist_status_noconnection: 'Failed to communicate with Github', - gist_status_degraded_performance: 'Degraded Performance', - gist_status_partial_outage: 'Partial Outage', - gist_status_major_outage: 'Major Outage', - gist_status_issues: 'service issues', - gist_status_connecting_btn_tip: 'Connecting to Github ...', - gist_status_success_btn_tip: 'Connected to Github API', - gist_status_issues_btn_tip: 'Github API is currently experiencing issues\n\nClick to view details.', - gist_btn_create_new: 'Create New Gist', - gist_not_found: `Could not locate the specified Gist. Did you possibly delete it from Github?\n\nTo update this note as a new gist, remove the frontmatter text at the top of the note.`, - - /* - Getting Started - */ - - gs_base_header: 'This plugin allows you to integrate both OpenGist and Github Gist pastes within your Obsidian notes. To use this plugin, you can either create a new Github gist, or setup your own OpenGist server. OpenGist is free, and takes only minutes to configure.', - gs_og_btn_repo: 'Download OpenGist', - gs_og_btn_docs: 'OpenGist Docs', - gs_og_sub_1: 'Once you install and set up OpenGist, you can sign in to your OpenGist website and create your first Gist. After your Gist is created, return to your Obsidian node, and integrate your Gist into your note using code similar to the following:', - gs_og_name: 'OpenGist integration', - gs_og_desc: 'OpenGist supports Windows, Linux, MacOS, and Docker. To download and set up OpenGist, click below.', - gs_gh_name: 'Github integration', - gs_gh_desc: 'To paste a Github Gist into your note, use a command similar to the following examples:', - gs_btn_settings_open: 'Open Settings', - gs_btn_close: 'Close', - - /* - Bithub - */ - - gh_status_error_api: 'Github API Error: {0}', - - /* - Version Updates - */ - - ver_update_stable: 'An update is available for the Gistr plugin. Update to check out the latest features!', - ver_update_beta: 'A new beta release is available for the Gistr plugin. Update to check out the latest features coming to stable!', - ver_url: 'https://raw.githubusercontent.com/Aetherinox/obsidian-gistr/{0}/package.json', - - /* - Element > Color Picker - */ - - pickr_dialog: 'Color Picker', - pickr_swatch: 'Color Swatch', - pickr_toggle: 'Pick Color', - pickr_last: 'Use Last Color', - pickr_save: 'Save', - pickr_cancel: 'Cancel', - pickr_clear: 'Clear', - pickr_restore_default_btn_tip: 'Restore default color', - pickr_dev_unknown: 'Gistr: Unknown color format: {0}', - - /* - Gist Load Error - */ - - err_gist_token_missing: 'Github API token missing. Open the Gistr plugin settings, click the Github tab, and enter your token. Instructions are found on the settings page.', - err_gist_loading_fail_name: '⚠️ Gistr: Failed to load the specified gist:', - err_gist_loading_fail_resp: '{0}', - err_gist_loading_fail_detail: 'Could not load a valid Javascript from gist url: {0}', - err_gist_loading_fail_url: 'Could not find gist id -- Make sure correct URL is specified. {0}', - - /* - Gist List - */ - - lst_repotype_pub: 'Public', - lst_repotype_pri: 'Secret', - -} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index 2ca946d5..00000000 --- a/src/main.ts +++ /dev/null @@ -1,341 +0,0 @@ -/* - Import - - @note : semver has issues with rollup. do not import semver's entire package. - import the methods you need individually, otherwise you'll receive circular dependencies error. -*/ - -import { App, Plugin, WorkspaceLeaf, Debouncer, debounce, TFile, Menu, MarkdownView, PluginManifest, Notice, requestUrl } from 'obsidian' -import { GistrSettings, SettingsGet, SettingsDefaults, SettingsSection } from 'src/settings/' -import { BackendCore } from 'src/backend' -import { GHGistGet, GHGistCopy, GHGistUpdate } from 'src/backend/services' -import { Env, PID, FrontmatterPrepare, GistrAPI, GistrEditor } from 'src/api' -import { lng } from 'src/lang' -import ModalGettingStarted from "src/modals/GettingStartedModal" -import ShowContextMenu from 'src/menus/context' -import lt from 'semver/functions/lt' -import gt from 'semver/functions/gt' - -/* - Basic Declrations -*/ - -const AppBase = 'app://obsidian.md' - -/* - Extend Plugin -*/ - -export default class GistrPlugin extends Plugin -{ - readonly plugin: GistrPlugin - readonly api: GistrAPI - readonly editor: GistrEditor - // private think_last = +new Date( ) - // private think_now = +new Date( ) - private bLayoutReady = false - settings: GistrSettings - - constructor( app: App, manifest: PluginManifest ) - { - super( app, manifest ) - Env._Initialize( app, manifest ) - } - - /* - Rehash Reading View - */ - - renderModeReading( ): void - { - this.app.workspace.iterateRootLeaves( ( leaf: WorkspaceLeaf ) => - { - if ( leaf.view instanceof MarkdownView && leaf.view.getMode( ) === "preview" ) - leaf.view.previewMode.rerender( true ) - } ) - } - - /* - Development use re-rendering - */ - - async renderDevelopment( ) - { - for ( const leaf of this.app.workspace.getLeavesOfType( 'markdown' ) ) - { - const view = leaf.view as MarkdownView - const state = view.getState( ) - const etateEph = view.getEphemeralState( ) - - view.previewMode.rerender( true ) - - const editor = view.editor - editor.setValue ( editor.getValue( ) ) - - if ( state.mode === 'preview' ) - { - state.mode = 'source' - await view.setState( state, { history: false } ) - state.mode = 'preview' - await view.setState( state, { history: false } ) - } - - view.setEphemeralState( etateEph ) - } - } - - /* - Settings > Load - */ - - async onload( ) - { - console.debug( lng( "base_debug_loading", process.env.NAME, process.env.PLUGIN_VERSION, process.env.AUTHOR ) ) - - await this.loadSettings ( ) - this.addSettingTab ( new SettingsSection( this.app, this ) ) - - this.app.workspace.onLayoutReady( async ( ) => - { - if ( this.settings.firststart === true ) - { - const opt_selected = await new ModalGettingStarted( this.app, this.plugin, this.manifest, this.settings, true ).openAndAwait( ) - if ( opt_selected === "settings-open" ) - { - - /* - open settings - */ - - // @ts-ignore - this.app.setting.open( this.manifest.id ) - // @ts-ignore - this.app.setting.openTabById( this.manifest.id ) - } - - this.settings.firststart = false - this.saveSettings( ) - } - } ) - - /* - Command Palette Items - */ - - this.addCommand - ( - { - id: 'gistr-github-gist-public-save', - name: lng( "cfg_context_gist_public" ), - editorCallback: GHGistGet( { plugin: this, app: this.app, is_public: true } ) - } - ) - - this.addCommand - ( - { - id: 'gistr-github-gist-secret-save', - name: lng( "cfg_context_gist_secret" ), - callback: GHGistGet( { plugin: this, app: this.app, is_public: false } ), - } - ) - - this.addCommand - ( - { - id: 'gistr-github-gist-copy', - name: lng( "cfg_context_gist_copy" ), - callback: GHGistCopy( { plugin: this, app: this.app } ), - } - ) - - /* - Gist > Monitor Changes - */ - - this.gistMonitorChanges( ) - - /* - Register Events - */ - - const gistBackend = new BackendCore( this.settings ) - this.registerDomEvent ( window, "message", gistBackend.messageEventHandler ) - this.registerMarkdownCodeBlockProcessor ( this.settings.keyword, gistBackend.processor ) - this.registerEvent ( this.app.workspace.on( "editor-menu", this.GetContextMenu ) ) - - /* - Version checking - */ - - if ( this.settings.ge_enable_updatenoti ) - { - this.versionCheck( ) - } - } - - /* - Unload - */ - - async onunload( ) - { - console.debug( "Unloaded " + this.manifest.name ) - } - - /* - Gist Saving > Monitor for Changes - */ - - gistMonitorChanges( ) - { - - const note_previous: Record< string, string > = { } - const denounce_register: Record< string, Debouncer< [ string, TFile ], Promise< Notice > > > = { } - - let last = +new Date( ) - this.app.vault.on( 'modify', async( file: TFile ) => - { - /* - this.think_now = +new Date( ) - if ( this.think_now - this.think_last < ( this.settings.sy_save_duration * 1000 ) ) - { - if ( process.env.ENV === "dev" ) - console.log( "gistMonitorChanges.modify on cooldown" ) - - return - } - */ - - /* - Get note contents with frontmatter - */ - - const note_full = await file.vault.adapter.read( file.path ) - const note_raw = FrontmatterPrepare( note_full ) - - /* - Strip Frontmatter from note contents and leave just the note body - */ - - if ( note_raw === note_previous[ file.path ] ) - { - return - } - - /* - Store current copy of note to record for comparison the next time the note is changed. - */ - - note_previous[ file.path ] = note_raw - - /* - Initialize bouncer - */ - - if ( !denounce_register[ file.path ] ) - { - if ( process.env.ENV === "dev" ) - console.log( "gistMonitorChanges.modify: Denouncer does not exist, creating" ) - - denounce_register[ file.path ] = debounce( async ( note_full: string, file: TFile ) => - await GHGistUpdate( { plugin: this, app: this.app, note_full, file } ), this.settings.sy_save_duration * 1000, this.settings.sy_enable_autosave_strict ) - } - - const { sy_enable_autosave } = await SettingsGet( this ) - if ( sy_enable_autosave ) - { - - await denounce_register[ file.path ]( note_full, file ) - - if ( process.env.ENV === "dev" ) - console.log( "gistMonitorChanges.modify: Autosave Denouncer" ) - - /* - if ( now - last > ( this.settings.sy_save_duration * 1000 ) ) - { - last = now - await denounce_register[ file.path ]( note_full, file ) - - if ( process.env.ENV === "dev" ) - console.log( "gistMonitorChanges.modify: Autosave Denouncer" ) - } - */ - } - } ) - } - - /* - Right-click context menu - */ - - GetContextMenu = ( menu: Menu, editor: GistrEditor ): void => - { - ShowContextMenu( this, this.settings, this.api, menu, editor ) - } - - /* - Settings > Load - */ - - async loadSettings( ) - { - this.settings = Object.assign( { }, SettingsDefaults, await this.loadData( ) ) - } - - /* - Settings > Save - */ - - async saveSettings( ) - { - await this.saveData( this.settings ) - } - - /* - Check for newer versions - - utilizes env variables in rollup.config.js - */ - - async versionCheck( ) - { - const ver_running = this.manifest.version - const ver_stable = await requestUrl( lng( "ver_url", "main" ) ).then( async ( res ) => - { - if ( res.status === 200 ) - { - const resp = await res.json - return resp.version - } - } ) - - const ver_beta = await requestUrl( lng( "ver_url", "beta" ) ).then( async ( res ) => - { - if ( res.status === 200 ) - { - const resp = await res.json - return resp.version - } - } ) - - /* - Output notice to user on possible updates - */ - - console.debug( lng( "base_debug_updater_1", process.env.NAME ) ) - console.debug( lng( "base_debug_updater_2", "Current : ..... ", ver_running ) ) - console.debug( lng( "base_debug_updater_2", "Stable : ..... ", ver_stable ) ) - console.debug( lng( "base_debug_updater_2", "Beta : ..... ", ver_beta ) ) - - if ( gt( ver_beta, ver_stable ) && lt( ver_running, ver_beta ) ) - { - new Notice( lng( "ver_update_beta" ), 0 ) - } - else if ( lt( ver_beta, ver_stable ) && lt( ver_running, ver_stable ) ) - { - new Notice( lng( "ver_update_stable" ), 0 ) - } - } - -} \ No newline at end of file diff --git a/src/menus/context.ts b/src/menus/context.ts deleted file mode 100644 index 89cede71..00000000 --- a/src/menus/context.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Menu } from "obsidian" -import GistrPlugin from "src/main" -import { GistrSettings } from 'src/settings/' -import { GHGistGet } from 'src/backend/services' -import { GistrAPI, GistrEditor } from "src/api/Types" -import { lng } from 'src/lang' - -/* - Interface -*/ - -interface Element -{ - dom?: HTMLDivElement -} - -/* - Context Menu -*/ - -export default function ShowContextMenu( plugin: GistrPlugin, settings: GistrSettings, api: GistrAPI, menu: Menu, editor: GistrEditor ): void -{ - - /* - Context Menu Separator - */ - - menu.addItem( ( item ) => - { - const elm = ( item as Element ).dom as HTMLElement - elm.addClass( "menu-separator" ) - // required to make separator not hoverable - elm.setAttribute( "style", "background-color: transparent" ) - } ) - - /* - Text Selection - */ - - const selection = editor.getSelection( ) - - /* - Context Menu > Item > Gistr Github (Public) - */ - - menu.addItem( ( item ) => - { - const elm = ( item as Element ).dom as HTMLElement - elm.addClass( "gistr-button" ) - item - .setTitle( lng( "cfg_context_gist_public" ) ) - .setIcon( "github" ) - .onClick( async ( e ) => - { - await GHGistGet( - { - plugin: plugin, - app: plugin.app, - is_public: true - } )( ) - } ) - } ) - - /* - Context Menu > Item > Gistr Github (Secret) - */ - - menu.addItem( ( item ) => - { - const elm = ( item as Element ).dom as HTMLElement - elm.addClass( "gistr-button" ) - item - .setTitle( lng( "cfg_context_gist_secret" ) ) - .setIcon( "github" ) - .onClick( async ( e ) => - { - await GHGistGet( - { - plugin: plugin, - app: plugin.app, - is_public: false - } )( ) - } ) - } ) - -} \ No newline at end of file diff --git a/src/menus/menu.ts b/src/menus/menu.ts deleted file mode 100644 index dbdac84d..00000000 --- a/src/menus/menu.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Menu, Notice } from 'obsidian' -import { GistrSettings } from 'src/settings/' -import { GistrAPI, GistrEditor, ContextMenu, Coords } from 'src/api' -import { lng } from 'src/lang' - -/* - Context Menu -*/ - -const ContextMenu = ( app: GistrAPI, cfg: GistrSettings, editor: GistrEditor ): void => -{ - - if ( !editor || !editor.hasFocus() ) - throw new Notice( lng( "base_context_nofocus" )) - - let coords: Coords - const cursor = editor.getCursor( "from" ) - const menu = new Menu( ) as unknown as ContextMenu - - const elm = menu.DOM - elm.addClass( "gistr-container" ) - - cfg.context_sorting.forEach( ( obj ) => - { - menu.addItem( ( menuItem ) => - { - menuItem.setTitle( obj ) - menuItem.setIcon( `gistr-${ obj }`.toLowerCase( ) ) - menuItem.onClick( ( ) => - { - app.Commands.RunCommandByID( `gistr-plugin:${ obj }` ) - }) - }) - }) - - if ( editor.coordsCur ) - coords = editor.coordsCur( true, "window" ) - - else if ( editor.coordsPos ) - { - const offset = editor.posToOffset( cursor ) - coords = editor.cm.coordsPos?.( offset ) ?? editor.coordsPos( offset ) - } - else return - - menu.showAtPosition( - { - x: coords.right + 25, - y: coords.top + 20, - } ) -} - -export default ContextMenu \ No newline at end of file diff --git a/src/modals/GettingStartedModal.ts b/src/modals/GettingStartedModal.ts deleted file mode 100644 index 27ab3132..00000000 --- a/src/modals/GettingStartedModal.ts +++ /dev/null @@ -1,293 +0,0 @@ -/* - Modal > Getting Started -*/ - -import { App, Modal, ButtonComponent, Setting, requestUrl, MarkdownRenderer } from "obsidian" -import GistrPlugin from "src/main" -import { GistrSettings } from 'src/settings/' -import { lng } from 'src/lang' - -/* - Declare Json -*/ - -export interface ManifestJson -{ - id: string - name: string - version?: string - description?: string - author?: string - authorUrl?: string -} - -/* - Modal > Getting Started > Class -*/ - -export default class ModalGettingStarted extends Modal -{ - private resolve: ( ( value: string ) => void ) - private plugin: GistrPlugin - private manifest: ManifestJson - private settings: GistrSettings - private cblk_preview: HTMLElement - private firststart: boolean - - /* - Getting Started > Constructor - */ - - constructor( app: App, plugin: GistrPlugin, manifest: ManifestJson, settings: GistrSettings, bFirstLoad: boolean ) - { - super( app ) - - this.plugin = plugin - this.manifest = manifest - this.settings = settings - this.firststart = bFirstLoad - - } - - /* - Modal > Getting Started > Action > Open & Wait - */ - - openAndAwait( ) - { - return new Promise( ( call ) => - { - this.resolve = call - this.open( ) - } ) - } - - /* - Modal > Getting Started > Action > On Open - */ - - onOpen( ) - { - const { contentEl } = this - - /* - Helper method for handling add each line of content - */ - - const AddLine = ( elmParent: HTMLElement, value: string, htmltag: keyof HTMLElementTagNameMap | null = null, cls: string | null = null ) => - { - if ( htmltag ) - return elmParent.createEl( htmltag, { text: value, cls: cls } ) - else - { - elmParent.appendText( value ) - return elmParent - } - } - - /* - Style main modal - */ - - this.modalEl.classList.add( 'gistr-gs-modal' ) - - /* - Modal > Getting Started > Content > Header - */ - - AddLine( contentEl, ( this.manifest.name || process.env.name ) + " " + "v" + ( this.manifest.version || process.env.PLUGIN_VERSION ), "h2" ) - AddLine( contentEl, lng( "gs_base_header" ), "small" ) - AddLine( contentEl, "", "div", "gistr-gs-separator" ) - - /* - Modal > Getting Started > Content > Getting Started - */ - - AddLine( contentEl, "", "div", "gistr-gs-separator" ) - - const Tab_GH_L = contentEl.createEl( "h3", { text: lng( "gs_gh_name" ), cls: `gistr-gs-header-int-l` } ) - const Tab_GH_R = contentEl.createEl( "h2", { text: " ", cls: `gistr-gs-header-int-r` } ) - const Tab_GH_C = contentEl.createEl( "div", { text: "", cls: `gistr-gs-header-int-c` } ) - contentEl.createEl ( 'small', { cls: "", text: lng( "gs_gh_desc" ) } ) - - /* - Get github api status - */ - - let json_delay = 1 * 1000 - const gh_status = requestUrl( "https://www.githubstatus.com/api/v2/summary.json" ).then( ( res ) => - { - if ( res.status === 200 ) - return res.json.components[ 0 ].status || lng( "gist_status_issues" ) - else - return lng( "gist_status_issues" ) - } ) - - new Setting( Tab_GH_R ) - .addText( async ( text ) => - { - text - .setPlaceholder( lng( "gist_status_connecting" ) ) - .setValue( lng( "gist_status_connecting" ) ) - .setDisabled( true ) - - const controlEl = Tab_GH_R.querySelector( ".setting-item-control" ) - controlEl.addClass( "gistr-settings-status-connecting" ) - - let github_status = await gh_status - - setTimeout( function( ) - { - if ( github_status === lng( "gist_status_operational_raw" ) ) - { - const controlEl = Tab_GH_R.querySelector( ".setting-item-control" ) - controlEl.removeClass ( "gistr-settings-status-connecting" ) - controlEl.addClass ( "gistr-settings-status-success" ) - text.setValue ( lng( "gist_status_connected" ) ) - } - else - { - const controlEl = Tab_GH_R.querySelector(".setting-item-control" ) - controlEl.removeClass ( "gistr-settings-status-connecting" ) - controlEl.addClass ( "gistr-settings-status-warning" ) - text.setValue ( github_status ) - } - }, json_delay ) - } ) - .addExtraButton( async ( btn ) => - { - btn - .setIcon ( 'circle-off' ) - .setTooltip ( lng( "gist_status_connecting_btn_tip" ) ) - - btn.extraSettingsEl.classList.add( "gistr-settings-icon-cur" ) - btn.extraSettingsEl.classList.add( "gistr-anim-spin" ) - btn.extraSettingsEl.classList.add( "gistr-settings-status-connecting" ) - - let github_status = await gh_status - - setTimeout( function( ) - { - if ( github_status === lng( "gist_status_operational_raw" ) ) - { - btn.setIcon ( "github" ) - btn.setTooltip ( lng( "gist_status_success_btn_tip" ) ) - - btn.extraSettingsEl.classList.remove ( "gistr-settings-status-connecting" ) - btn.extraSettingsEl.classList.add ( "gistr-settings-icon-ok" ) - } - else - { - btn.setIcon ( "circle-off" ) - btn.setTooltip ( lng( "gist_status_issues_btn_tip" ) ) - - btn.extraSettingsEl.classList.remove ( "gistr-settings-status-connecting" ) - btn.extraSettingsEl.classList.add ( "gistr-settings-icon-error" ) - btn.extraSettingsEl.classList.remove ( "gistr-settings-icon-ok" ) - } - - btn.onClick( ( ) => - { - window.open( "https://www.githubstatus.com/" ) - } ) - - }, json_delay ) - } ) - - - this.cblk_preview = contentEl.createDiv( ) - - const gs_UsageCodeblock_gh = "```````" + "\n" + "```" + this.settings.keyword + "\n" + "gist.github.com/username/YOUR_GIST_ID" + "\n" + "gist.github.com/username/YOUR_GIST_ID#file_name" + "\n" + "gist.github.com/username/YOUR_GIST_ID&theme_name" + "\n" + "```" + "\n```````" - MarkdownRenderer.render( this.app, gs_UsageCodeblock_gh, this.cblk_preview, gs_UsageCodeblock_gh, this.plugin ) - - AddLine( contentEl, "", "div", "gistr-gs-separator" ) - - /* - Modal > Getting Started > Content > Getting Started - */ - - AddLine( contentEl, lng( "gs_og_name" ), "h3" ) - AddLine( contentEl, lng( "gs_og_desc" ), "small" ) - - const div_GettingStarted = contentEl.createDiv( { cls: "gistr-gs-modal-button-container" } ) - - new ButtonComponent( div_GettingStarted ) - .setButtonText( lng( "gs_og_btn_repo" ) ) - .setCta( ) - .onClick( ( ) => - { - window.open( lng( "cfg_tab_su_ogrepo_url" ) ) - } ) - - new ButtonComponent( div_GettingStarted ) - .setButtonText( lng( "gs_og_btn_docs" ) ) - .onClick( ( ) => - { - window.open( lng( "cfg_tab_su_ogdocs_url" ) ) - } ) - - AddLine( contentEl, lng( "gs_og_sub_1" ), "small" ) - - /* - Markdown Render Preview - */ - - this.cblk_preview = contentEl.createDiv( ) - - const gs_UsageCodeblock = "```````" + "\n" + "```" + this.settings.keyword + "\n" + "gist.domain.com/username/YOUR_GIST_ID" + "\n" + "gist.domain.com/username/YOUR_GIST_ID&theme_name" + "\n" + "```" + "\n```````" - MarkdownRenderer.render( this.app, gs_UsageCodeblock, this.cblk_preview, gs_UsageCodeblock, this.plugin ) - - /* - Footer Buttons - */ - - const div_Footer = contentEl.createDiv( { cls: "modal-button-container" } ) - - if ( this.firststart === true ) - { - new ButtonComponent( div_Footer ) - .setButtonText( lng( "gs_btn_settings_open" ) ) - .setCta( ) - .onClick( ( ) => - { - this.resolve( "settings-open" ) - this.close( ) - } ) - - new ButtonComponent( div_Footer ) - .setButtonText( lng( "gs_btn_close" ) ) - .onClick( ( ) => - { - this.close() - } ) - } - else - { - new ButtonComponent( div_Footer ) - .setButtonText( lng( "gs_btn_close" ) ) - .onClick( ( ) => - { - this.close( ) - } ) - } - } - - /* - Modal > Getting Started > Action > Close - */ - - close( ) - { - this.resolve( "" ) - super.close( ) - } - - /* - Modal > Getting Started > Action > On Close - */ - - onClose( ): void - { - this.contentEl.empty( ) - } -} \ No newline at end of file diff --git a/src/settings/defaults.ts b/src/settings/defaults.ts deleted file mode 100644 index 4492a7a1..00000000 --- a/src/settings/defaults.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { GistrSettings } from 'src/settings' - -/* - Default Settings -*/ - -export const SettingsDefaults: GistrSettings = -{ - keyword: "gistr", - firststart: true, - css_og: "", - css_gh: "", - theme: "Light", - blk_pad_t: 16, - blk_pad_b: 19, - textwrap: "Enabled", - notitime: 10, - ge_enable_updatenoti: true, - - sy_clr_lst_icon: "#757575E6", - - og_clr_bg_light: "#CBCBCB", - og_clr_bg_dark: "#121315", - og_clr_sb_light: "#BA4956", - og_clr_sb_dark: "#4960ba", - og_clr_tx_light: "#2A2626", - og_clr_tx_dark: "#CAD3F5", - og_opacity: 1, - - gh_clr_bg_light: "#E5E5E5", - gh_clr_bg_dark: "#121315", - gh_clr_sb_light: "#BA4956", - gh_clr_sb_dark: "#BA496A", - gh_clr_tx_light: "#2A2626", - gh_clr_tx_dark: "#CAD3F5", - gh_opacity: 1, - - sy_enable_autoupdate: true, - sy_enable_autosave: false, - sy_enable_autosave_strict: false, - sy_enable_autosave_notice: false, - sy_add_frontmatter: false, - sy_save_duration: 15, - context_sorting: [], -} diff --git a/src/settings/index.ts b/src/settings/index.ts deleted file mode 100644 index 84c07ca0..00000000 --- a/src/settings/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { SettingsDefaults } from 'src/settings/defaults' -export { SettingsSection } from 'src/settings/sections' -export { SettingsGet } from 'src/settings/settings' -export { GistrSettings } from 'src/settings/settings' \ No newline at end of file diff --git a/src/settings/sections/SettingsSection.ts b/src/settings/sections/SettingsSection.ts deleted file mode 100644 index f96c98be..00000000 --- a/src/settings/sections/SettingsSection.ts +++ /dev/null @@ -1,2000 +0,0 @@ -import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, ExtraButtonComponent, MarkdownRenderer, Notice, requestUrl } from 'obsidian' -import GistrPlugin from "src/main" -import { SettingsDefaults } from 'src/settings/defaults' -import { ColorPicker, GetColor } from 'src/utils' -import { GHStatusAPI, GHTokenSet, GHTokenGet } from 'src/backend/services' -import ModalGettingStarted from "src/modals/GettingStartedModal" -import { NoxComponent } from 'src/api' -import { lng } from 'src/lang' -import Pickr from "@simonwep/pickr" -import lt from 'semver/functions/lt' -import gt from 'semver/functions/gt' - -/* - Color picker options - - @todo : if a large variety of options are available in the future, possible - theme system should be integrated. -*/ - -export interface ColorPickrOpts -{ - 'sy_clr_lst_icon'?: string - - 'og_clr_bg_light'?: string - 'og_clr_bg_dark'?: string - 'og_clr_sb_light'?: string - 'og_clr_sb_dark'?: string - 'og_clr_tx_light'?: string - 'og_clr_tx_dark'?: string - - 'gh_clr_bg_light'?: string - 'gh_clr_bg_dark'?: string - 'gh_clr_sb_light'?: string - 'gh_clr_sb_dark'?: string - 'gh_clr_tx_light'?: string - 'gh_clr_tx_dark'?: string -} - -/* - default colors : type Color -*/ - -const ColorPickrDefaults: Record< string, Color > = -{ - 'sy_clr_lst_icon': "#757575E6", - - "og_clr_bg_light": "#CBCBCB", - "og_clr_bg_dark": "#121315", - "og_clr_sb_light": "#BA4956", - "og_clr_sb_dark": "#4960ba", - "og_clr_tx_light": "#2A2626", - "og_clr_tx_dark": "#CAD3F5", - - "gh_clr_bg_light": "#E5E5E5", - "gh_clr_bg_dark": "#121315", - "gh_clr_sb_light": "#BA4956", - "gh_clr_sb_dark": "#BA496A", - "gh_clr_tx_light": "#2A2626", - "gh_clr_tx_dark": "#CAD3F5", -} - -/* - CSS Color Values -*/ - -export type CLR_VAR = `--${string}` // css variable -export type CLR_HEX = `#${string}` // css hex -export type Color = CLR_HEX | CLR_VAR - -/* - Get > Theme Options -*/ - -export enum THEMES { LIGHT = "Light", DARK = "Dark" } -export const GetTheme: { [ key in THEMES ]: string } = -{ - [ THEMES.LIGHT ]: lng( "base_theme_light" ), - [ THEMES.DARK ]: lng( "base_theme_dark" ), -} - -/* - Get > Text Wrap Option -*/ - -export enum TEXTWRAP { WRAP_OFF = "Disabled", WRAP_ON = "Enabled" } -export const GetTextwrap: { [ key in TEXTWRAP ]: string } = -{ - [ TEXTWRAP.WRAP_OFF ]: lng( "base_opt_disabled" ), - [ TEXTWRAP.WRAP_ON ]: lng( "base_opt_enabled" ), -} - -/* - Settings Tab -*/ - -export class SettingsSection extends PluginSettingTab -{ - readonly plugin: GistrPlugin - private Hide_Global: boolean - private Hide_Github: boolean - private Hide_Opengist: boolean - private Hide_SaveSync: boolean - private Hide_Support: boolean - private Tab_Global: HTMLElement - private Tab_Github: HTMLElement - private Tab_OpenGist: HTMLElement - private Tab_SaveSync: HTMLElement - private Tab_Support: HTMLElement - private Opacity_Enabled: string - private Opacity_Disabled: string - private Obj_Github_Api: Setting - private cPickr: Record< string, ColorPicker > - - /* - Class > Constructor - */ - - constructor( app: App, plugin: GistrPlugin ) - { - super( app, plugin ) - - this.plugin = plugin - this.Hide_Global = true - this.Hide_Github = true - this.Hide_Opengist = true - this.Hide_SaveSync = true - this.Hide_Support = false - this.Opacity_Enabled = "1" - this.Opacity_Disabled = "0.4" - this.Obj_Github_Api = null - this.cPickr = { } - } - - /* - Create Object > Color Picker - - @arg : bHidden - associated to hovering color picker, not color element - */ - - new_ColorPicker( plugin: GistrPlugin, el: HTMLElement, setting: Setting, id: keyof ColorPickrOpts, bHidden?: ( ) => boolean ) - { - const pickr: ColorPicker = new ColorPicker( plugin, el, setting ) - - pickr - .on( "init", ( color: Pickr.HSVaColor, instance: Pickr ) => - { - const currColor = this.plugin.settings[ id ] - pickr.setColor( currColor ) - } ) - - .on( "show", ( color: Pickr.HSVaColor, instance: Pickr ) => - { - if ( typeof bHidden !== "undefined" && bHidden( ) ) - instance.hide( ) - } ) - - .on( "save", ( color: Pickr.HSVaColor, instance: ColorPicker ) => - { - - const clr : Color = `#${ color.toHEXA( ).toString( ).substring( 1 ) }` - - this.plugin.settings[ id ] = clr - this.plugin.saveSettings( ) - - instance.hide( ) - instance.addSwatch( clr ) - instance.ActionSave( clr ) - - this.plugin.renderModeReading( ) - } ) - - .on( "cancel", ( instance: ColorPicker ) => - { - instance.hide( ) - } ) - - setting.addExtraButton - ( - ( btn ) => - { - pickr.AddButtonReset = btn - - .setIcon ( "reset" ) - .setDisabled ( false ) - .setTooltip ( lng( "pickr_restore_default_btn_tip" ) ) - .onClick( ( ) => - { - const resetColour: Color = ColorPickrDefaults[ id ] - pickr.setColor ( GetColor( resetColour ) ) - pickr.ActionSave ( resetColour ) - } ) - } - ) - - this.cPickr[ id ] = pickr - } - - /* - Display - */ - - display( ): void - { - const { containerEl } = this - - this.Hide_Global = true - this.Hide_Github = true - this.Hide_Opengist = true - this.Hide_SaveSync = true - this.Hide_Support = false - - - this.CreateHeader ( containerEl ) - this.CreateMenus ( containerEl ) - } - - /* - Section -> Header - */ - - CreateHeader( elm: HTMLElement ) - { - elm.empty( ) - elm.addClass( 'gistr-settings-modal' ) - elm.createEl( "p", { cls: "gistr-settings-section-header", text: lng( "cfg_modal_desc" ) } ) - } - - /* - Create Menus - */ - - CreateMenus( elm: HTMLElement ) - { - this.Tab_Global_New ( elm ) - this.Tab_Global = elm.createDiv( ) - - this.Tab_OpenGist_New ( elm ) - this.Tab_OpenGist = elm.createDiv( ) - - this.Tab_Github_New ( elm ) - this.Tab_Github = elm.createDiv( ) - - this.Tab_SaveSync_New ( elm ) - this.Tab_SaveSync = elm.createDiv( ) - - this.Tab_Support_New ( elm ) - this.Tab_Support = elm.createDiv( ) - - this.Tab_Support_ShowSettings( this.Tab_Support ) - } - - /* - Tab > General > New - */ - - Tab_Global_New( elm: HTMLElement ) - { - const Tab_GN = elm.createEl( "h2", { text: lng( "cfg_tab_ge_title" ), cls: `gistr-settings-header${ this.Hide_Global?" isfold" : "" }` } ) - Tab_GN.addEventListener( "click", ( )=> - { - this.Hide_Global = !this.Hide_Global - Tab_GN.classList.toggle( "isfold", this.Hide_Global ) - this.Tab_Global_CreateSettings( ) - } ) - } - - Tab_Global_CreateSettings( ) - { - this.Tab_Global.empty( ) - if ( this.Hide_Global ) return - - this.Tab_Global_ShowSettings( this.Tab_Global ) - } - - Tab_Global_ShowSettings( elm: HTMLElement ) - { - - /* - Github > Header Intro - */ - - elm.createEl( 'small', { cls: "gistr-settings-section-description", text: lng( "cfg_tab_ge_header" ) } ) - - /* - Codeblock > Theme - */ - - const cfg_tab_ge_theme_desc = new DocumentFragment( ) - cfg_tab_ge_theme_desc.append( - sanitizeHTMLToDom( `${ lng( "cfg_tab_ge_theme_desc" ) }` ), - ) - - new NoxComponent( elm ) - .setName( lng( "cfg_tab_ge_theme_name" ) ) - .setDesc( cfg_tab_ge_theme_desc ) - .setClass( "gistr-dropdown" ) - .addNoxDropdown( dropdown => dropdown - .addOption( THEMES.LIGHT, GetTheme[ THEMES.LIGHT ] ) - .addOption( THEMES.DARK, GetTheme[ THEMES.DARK ] ) - .setValue( this.plugin.settings.theme ) - .onChange( async ( val ) => - { - this.plugin.settings.theme = val - await this.plugin.saveSettings( ) - this.plugin.renderModeReading( ) - }), - ( ) => - ( - SettingsDefaults.theme as string - ), - ) - - elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) - - /* - Dropdown > Text Wrap - */ - - const cfg_tab_ge_wrap_desc = new DocumentFragment( ) - cfg_tab_ge_wrap_desc.append( - sanitizeHTMLToDom( `${ lng( "cfg_tab_ge_wrap_desc" ) }` ), - ) - - new NoxComponent( elm ) - .setName( lng( "cfg_tab_ge_wrap_name" ) ) - .setDesc( cfg_tab_ge_wrap_desc ) - .setClass( "gistr-dropdown" ) - .addNoxDropdown( dropdown => dropdown - .addOption( TEXTWRAP.WRAP_OFF, GetTextwrap[ TEXTWRAP.WRAP_OFF ] ) - .addOption( TEXTWRAP.WRAP_ON, GetTextwrap[ TEXTWRAP.WRAP_ON ] ) - .setValue( this.plugin.settings.textwrap ) - .onChange( async ( val ) => - { - this.plugin.settings.textwrap = val - await this.plugin.saveSettings( ) - this.plugin.renderModeReading( ) - }), - ( ) => - ( - SettingsDefaults.textwrap as string - ), - ) - - elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) - - /* - Command Keyword - - changing this will cause all opengist portals to not function until the keyword is changed - within the box. - */ - - const cfg_tab_ge_keyword_desc = new DocumentFragment( ) - cfg_tab_ge_keyword_desc.append( - sanitizeHTMLToDom( `${ lng( "cfg_tab_ge_keyword_desc" ) }` ), - ) - - new NoxComponent( elm ) - .setName( lng( "cfg_tab_ge_keyword_name" ) ) - .setDesc( cfg_tab_ge_keyword_desc ) - .addNoxTextbox( text => text - .setValue( this.plugin.settings.keyword ) - .onChange( async ( val ) => - { - this.plugin.settings.keyword = val - await this.plugin.saveSettings( ) - this.plugin.renderModeReading( ) - }), - ( ) => - ( - SettingsDefaults.keyword.toString( ) as string - ), - ) - - elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) - - /* - Plugin update notifications - */ - - const cfg_tab_ge_noti_update_desc = new DocumentFragment( ) - cfg_tab_ge_noti_update_desc.append( - sanitizeHTMLToDom( `${ lng( "cfg_tab_ge_noti_update_desc" ) }` ), - ) - - new NoxComponent( elm ) - .setName( lng( "cfg_tab_ge_noti_update_name" ) ) - .setDesc( cfg_tab_ge_noti_update_desc ) - .addNoxToggle( toggle => toggle - .setValue( this.plugin.settings.ge_enable_updatenoti ) - .onChange( async ( val ) => - { - this.plugin.settings.ge_enable_updatenoti = val - await this.plugin.saveSettings( ) - }), - ( ) => - ( - SettingsDefaults.ge_enable_updatenoti as boolean - ), - ) - - elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) - - /* - Notification Time (in seconds) - */ - - const cfg_tab_ge_noti_dur_desc = new DocumentFragment( ) - cfg_tab_ge_noti_dur_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_ge_noti_dur_desc" ) }`), - ) - - let val_st_notitime: HTMLDivElement - new NoxComponent( elm ) - .setName( lng( "cfg_tab_ge_noti_dur_name" ) ) - .setDesc( cfg_tab_ge_noti_dur_desc ) - .setClass( "gistr-slider" ) - .addNoxSlider( slider => slider - .setLimits( 0, 120, 1 ) - .setDynamicTooltip( ) - .setValue( this.plugin.settings.notitime ) - .onChange( async ( val ) => - { - val_st_notitime.innerText = " " + val.toString( ) + "s" - - this.plugin.settings.notitime = val - await this.plugin.saveSettings( ) - }), - ( ) => - ( - SettingsDefaults.notitime as number - ), - ).settingEl.createDiv( '', ( el ) => - { - val_st_notitime = el - el.innerText = " " + this.plugin.settings.notitime.toString( ) + "s" - } ).classList.add( 'gistr-settings-elm-slider-preview' ) - - elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) - - /* - Tab Footer Spacer - */ - - elm.createEl( 'div', { cls: "gistr-settings-section-footer", text: "" } ) - } - - /* - Tab > OpenGist > New - */ - - Tab_OpenGist_New( elm: HTMLElement ) - { - const Tab_OG = elm.createEl( "h2", { text: lng( "cfg_tab_og_title" ), cls: `gistr-settings-header${ this.Hide_Opengist?" isfold" : "" }` } ) - Tab_OG.addEventListener( "click", ( )=> - { - this.Hide_Opengist = !this.Hide_Opengist - Tab_OG.classList.toggle( "isfold", this.Hide_Opengist ) - this.Tab_OpenGist_CreateSettings( ) - } ) - } - - Tab_OpenGist_CreateSettings( ) - { - this.Tab_OpenGist.empty( ) - if ( this.Hide_Opengist ) return - - this.Tab_OpenGist_ShowSettings( this.Tab_OpenGist ) - } - - Tab_OpenGist_ShowSettings( elm: HTMLElement ) - { - - elm.createEl( 'small', { cls: "gistr-settings-section-description", text: lng( "cfg_tab_og_header" ) } ) - - /* - Development notice - */ - - const ct_Note = elm.createDiv( ) - const md_notFinished = "> [!NOTE] " + lng( "base_underdev_title" ) + "\n> " + lng( "base_underdev_msg" ) + "" - MarkdownRenderer.render( this.plugin.app, md_notFinished, ct_Note, "" + md_notFinished, this.plugin ) - - /* - Background color (Light) - */ - - const cfg_tab_og_cb_light_desc = new DocumentFragment( ) - cfg_tab_og_cb_light_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_og_cb_light_desc" ) }`), - ) - - new Setting( elm ) - .setName( lng( "cfg_tab_og_cb_light_name" ) ) - .setDesc( cfg_tab_og_cb_light_desc ) - .then( ( setting ) => { this.new_ColorPicker - ( - this.plugin, elm, setting, - "og_clr_bg_light", - ) } ) - - /* - Background color (Dark) - */ - - const cfg_tab_og_cb_dark_desc = new DocumentFragment( ) - cfg_tab_og_cb_dark_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_og_cb_dark_desc" ) }`), - ) - - new Setting( elm ) - .setName( lng( "cfg_tab_og_cb_dark_name" ) ) - .setDesc( cfg_tab_og_cb_dark_desc ) - .then( ( setting ) => { this.new_ColorPicker - ( - this.plugin, elm, setting, - "og_clr_bg_dark", - ) } ) - - /* - Text color (Light) - */ - - const cfg_tab_og_tx_light_desc = new DocumentFragment( ) - cfg_tab_og_tx_light_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_og_tx_light_desc" ) }`), - ) - - new Setting( elm ) - .setName( lng( "cfg_tab_og_tx_light_name" ) ) - .setDesc( cfg_tab_og_tx_light_desc ) - .then( ( setting ) => { this.new_ColorPicker - ( - this.plugin, elm, setting, - "og_clr_tx_light", - ) } ) - - /* - Text color (Dark) - */ - - const cfg_tab_og_tx_dark_desc = new DocumentFragment( ) - cfg_tab_og_tx_dark_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_og_tx_dark_desc" ) }`), - ) - - new Setting( elm ) - .setName( lng( "cfg_tab_og_tx_dark_name" ) ) - .setDesc( cfg_tab_og_tx_dark_desc ) - .then( ( setting ) => { this.new_ColorPicker - ( - this.plugin, elm, setting, - "og_clr_tx_dark", - ) } ) - - /* - Scrollbar Track Color (Light) - */ - - const cfg_tab_og_sb_light_desc = new DocumentFragment( ) - cfg_tab_og_sb_light_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_og_sb_light_desc" ) }`), - ) - - new Setting( elm ) - .setName( lng( "cfg_tab_og_sb_light_name" ) ) - .setDesc( cfg_tab_og_sb_light_desc ) - .then( ( setting ) => { this.new_ColorPicker - ( - this.plugin, elm, setting, - "og_clr_sb_light", - ) } ) - - /* - Scrollbar Track Color (Dark) - */ - - const cfg_tab_og_sb_dark_desc = new DocumentFragment( ) - cfg_tab_og_sb_dark_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_og_sb_dark_desc" ) }`), - ) - - new Setting( elm ) - .setName( lng( "cfg_tab_og_sb_dark_name" ) ) - .setDesc( cfg_tab_og_sb_dark_desc ) - .then( ( setting ) => { this.new_ColorPicker - ( - this.plugin, elm, setting, - "og_clr_sb_dark", - ) } ) - - /* - Codeblock Opacity - */ - - const cfg_tab_og_opacity_desc = new DocumentFragment( ) - cfg_tab_og_opacity_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_og_opacity_desc" ) }`), - ) - - let val_og_opacity: HTMLDivElement - new NoxComponent( elm ) - .setName( lng( "cfg_tab_og_opacity_name" ) ) - .setDesc( cfg_tab_og_opacity_desc ) - .setClass( "gistr-slider" ) - .addNoxSlider( slider => slider - .setDynamicTooltip( ) - .setLimits( 0.20, 1, 0.05 ) - .setValue( this.plugin.settings.og_opacity ) - .onChange( async ( val ) => - { - const opacity_calc = val * 100 - val_og_opacity.innerText = " " + opacity_calc.toString( ) + "%" - - this.plugin.settings.og_opacity = val - await this.plugin.saveSettings( ) - this.plugin.renderModeReading( ) - }), - ( ) => - ( - SettingsDefaults.og_opacity as number - ), - ).settingEl.createDiv( '', ( el ) => - { - val_og_opacity = el - const opacity_calc = this.plugin.settings.og_opacity * 100 - el.innerText = " " + opacity_calc.toString( ) + "%" - } ).classList.add( 'gistr-settings-elm-slider-preview' ) - - /* - Codeblock > Padding > Top - */ - - const cfg_tab_og_pad_top_desc = new DocumentFragment( ) - cfg_tab_og_pad_top_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_og_pad_top_desc" ) }`), - ) - - let val_og_padding_top: HTMLDivElement - new NoxComponent( elm ) - .setName( lng( "cfg_tab_og_pad_top_name" ) ) - .setDesc( cfg_tab_og_pad_top_desc ) - .setClass( "gistr-slider" ) - .addNoxSlider( slider => slider - .setDynamicTooltip( ) - .setLimits( 0, 30, 1 ) - .setValue( this.plugin.settings.blk_pad_t ) - .onChange( async ( val ) => - { - const padding_calc = val - val_og_padding_top.innerText = " " + padding_calc.toString( ) + "px" - - this.plugin.settings.blk_pad_t = val - await this.plugin.saveSettings( ) - this.plugin.renderModeReading( ) - }), - ( ) => - ( - SettingsDefaults.blk_pad_t as number - ), - ).settingEl.createDiv( '', ( el ) => - { - val_og_padding_top = el - const padding_calc = this.plugin.settings.blk_pad_t - el.innerText = " " + padding_calc.toString( ) + "px" - } ).classList.add( 'gistr-settings-elm-slider-preview' ) - - /* - Codeblock > Padding > Bottom - */ - - const cfg_tab_og_pad_btm_desc = new DocumentFragment( ) - cfg_tab_og_pad_btm_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_og_pad_btm_desc" ) }`), - ) - - let val_og_padding_btm: HTMLDivElement - new NoxComponent( elm ) - .setName( lng( "cfg_tab_og_pad_btm_name" ) ) - .setDesc( cfg_tab_og_pad_btm_desc ) - .setClass( "gistr-slider" ) - .addNoxSlider( slider => slider - .setDynamicTooltip( ) - .setLimits( 0, 30, 1 ) - .setValue( this.plugin.settings.blk_pad_b ) - .onChange( async ( val ) => - { - const padding_calc = val - val_og_padding_btm.innerText = " " + padding_calc.toString( ) + "px" - - this.plugin.settings.blk_pad_b = val - await this.plugin.saveSettings( ) - this.plugin.renderModeReading( ) - }), - ( ) => - ( - SettingsDefaults.blk_pad_b as number - ), - ).settingEl.createDiv( '', ( el ) => - { - val_og_padding_btm = el - const padding_calc = this.plugin.settings.blk_pad_b - el.innerText = " " + padding_calc.toString( ) + "px" - } ).classList.add( 'gistr-settings-elm-slider-preview' ) - - /* - Codeblock > CSS Override - */ - - const cfg_tab_og_css_desc = new DocumentFragment( ) - cfg_tab_og_css_desc.append( - sanitizeHTMLToDom( `${ lng( "cfg_tab_og_css_desc" ) }` ), - ) - - new NoxComponent( elm ) - .setName( lng( "cfg_tab_og_css_name" ) ) - .setDesc( cfg_tab_og_css_desc ) - .setClass( "gistr-settings-elm-textarea" ) - .addNoxTextarea( text => text - .setPlaceholder( lng( "cfg_tab_og_css_pholder" ) ) - .setValue( this.plugin.settings.css_og ) - .onChange( async ( val ) => - { - this.plugin.settings.css_og = val - await this.plugin.saveSettings( ) - this.plugin.renderModeReading( ) - }), - ( ) => - ( - SettingsDefaults.css_og.toString( ) as string - ), - ) - - /* - Tab Footer Spacer - */ - - elm.createEl( 'div', { cls: "gistr-settings-section-footer", text: "" } ) - } - - /* - Tab > Github Gists > New - */ - - Tab_Github_New( elm: HTMLElement ) - { - - /* - Get github api status - */ - - let json_delay = 0.5 * 1000 - const gh_status = requestUrl( "https://www.githubstatus.com/api/v2/summary.json" ).then( ( res ) => - { - if ( res.status === 200 ) - return res.json.components[ 0 ].status || lng( "gist_status_issues" ) - else - return lng( "gist_status_issues" ) - } ) - - const Tab_GH = elm.createEl( "h2", { text: "", cls: `gistr-settings-header-sublevel` } ) - const Tab_GH_L = Tab_GH.createEl( "h2", { text: lng( "cfg_tab_gh_title" ), cls: `gistr-settings-header-int-l${ this.Hide_Github?" isfold" : "" }` } ) - const Tab_GH_R = Tab_GH.createEl( "h2", { text: " ", cls: `gistr-settings-header-int-r` } ) - const Tab_GH_C = Tab_GH.createEl( "div", { text: "", cls: `gistr-settings-header-int-c` } ) - - new Setting( Tab_GH_R ) - .addText( async ( text ) => - { - text - .setPlaceholder ( lng( "gist_status_connecting" ) ) - .setValue ( lng( "gist_status_connecting" ) ) - .setDisabled ( true ) - - const el = Tab_GH_R.querySelector( ".setting-item-control" ) - el.addClass ( "gistr-settings-status-connecting" ) - - /* - Fetch Github API status - - operational - - degraded_performance - - partial_outage - - major_outage - */ - - let github_status = await gh_status - - setTimeout( function( ) - { - - /* - Find API status language entry in array - */ - - const gb_api_status: string = GHStatusAPI[ github_status ] - - /* - Text > Github Token Not Specified - */ - - if ( !GHTokenGet( ) ) - { - const el = Tab_GH_R.querySelector( ".setting-item-control" ) - el.removeClass ( "gistr-settings-status-connecting" ) - el.addClass ( "gistr-settings-status-error" ) - text.inputEl.setAttribute ( "size", lng( "gist_status_no_api" ).length.toString( ) ) - text.setValue ( lng( "gist_status_no_api" ) ) - - return - } - - /* - Text > Github API > Operational - */ - - if ( github_status === lng( "gist_status_operational_raw" ) ) - { - const el = Tab_GH_R.querySelector( ".setting-item-control" ) - el.removeClass ( "gistr-settings-status-connecting" ) - el.addClass ( "gistr-settings-status-success" ) - text.inputEl.setAttribute ( "size", lng( "gist_status_connected" ).length.toString( ) ) - text.setValue ( lng( "gist_status_connected" ) ) - } - else if ( github_status === lng( "gist_status_issues" ) ) - { - text.inputEl.setAttribute ( "size", lng( "gist_status_noconnection" ).length.toString( ) ) - text.setValue ( lng( "gist_status_noconnection" ) ) - } - else - { - - /* - Button > Github API > Connection Issue - */ - - const el = Tab_GH_R.querySelector( ".setting-item-control" ) - el.removeClass ( "gistr-settings-status-connecting" ) - el.addClass ( "gistr-settings-status-warning" ) - text.inputEl.setAttribute ( "size", gb_api_status.length.toString( ) ) - text.setValue ( gb_api_status ) - } - }, json_delay ) - } ) - .addExtraButton( async ( btn ) => - { - btn - .setIcon ( 'circle-off' ) - .setTooltip ( lng( "gist_status_connecting_btn_tip" ) ) - - btn.extraSettingsEl.classList.add( "gistr-settings-icon-cur" ) - btn.extraSettingsEl.classList.add( "gistr-anim-spin" ) - btn.extraSettingsEl.classList.add( "gistr-settings-status-connecting" ) - - /* - Fetch Github API status - - operational - - degraded_performance - - partial_outage - - major_outage - */ - - let github_status = await gh_status - - setTimeout( function( ) - { - - /* - Find API status language entry in array - */ - - const gb_api_status: string = GHStatusAPI[ github_status ] - - /* - Text > Github Token Not Specified - */ - - if ( !GHTokenGet( ) ) - { - btn.setIcon ( "circle-off" ) - btn.setTooltip ( lng( "gist_status_no_api_btn_tip" ) ) - - btn.extraSettingsEl.classList.remove ( "gistr-settings-status-connecting" ) - btn.extraSettingsEl.classList.add ( "gistr-settings-icon-error" ) - btn.extraSettingsEl.classList.remove ( "gistr-settings-icon-ok" ) - - return - } - - /* - Button > Github API > Operational - */ - - if ( github_status === lng( "gist_status_operational_raw" ) ) - { - btn.setIcon ( "github" ) - btn.setTooltip ( lng( "gist_status_success_btn_tip" ) ) - - btn.extraSettingsEl.classList.remove ( "gistr-settings-status-connecting" ) - btn.extraSettingsEl.classList.add ( "gistr-settings-icon-ok" ) - } - else - { - - /* - Button > Github API > Connection Issue - */ - - btn.setIcon ( "circle-off" ) - btn.setTooltip ( lng( "gist_status_issues_btn_tip" ) ) - - btn.extraSettingsEl.classList.remove ( "gistr-settings-status-connecting" ) - btn.extraSettingsEl.classList.add ( "gistr-settings-icon-error" ) - btn.extraSettingsEl.classList.remove ( "gistr-settings-icon-ok" ) - } - - btn.onClick( ( ) => - { - window.open( "https://www.githubstatus.com/" ) - } ) - - }, json_delay ) - } ) - - - Tab_GH_L.addEventListener( "click", ( )=> - { - this.Hide_Github = !this.Hide_Github - Tab_GH_L.classList.toggle( "isfold", this.Hide_Github ) - this.Tab_Github_CreateSettings( ) - } ) - } - - Tab_Github_CreateSettings( ) - { - this.Tab_Github.empty( ) - if ( this.Hide_Github ) return - - this.Tab_Github_ShowSettings( this.Tab_Github ) - } - - Tab_Github_ShowSettings( elm: HTMLElement ) - { - - /* - Github > Define - */ - - const gistToken = GHTokenGet( ) - - /* - Section -> Support Buttons - */ - - elm.createEl( 'small', { cls: "gistr-settings-section-description", text: lng( "cfg_tab_gh_header" ) } ) - - /* - Personal Access Token > Description - - This is used for sharing gists between the obsidian vault and Github Gist. - */ - - const DOM_Token_Desc = new DocumentFragment( ) - DOM_Token_Desc.append( - sanitizeHTMLToDom(` - ${ lng( "cfg_tab_gh_pat_desc_l1" ) } -
-
- ${ lng( "cfg_tab_gh_pat_desc_l2" ) } -
    -
  • - ${ lng( "cfg_tab_gh_pat_perm_1" ) } -
  • -
  • - ${ lng( "cfg_tab_gh_pat_perm_2" ) } -
  • -
  • - ${ lng( "cfg_tab_gh_pat_perm_3" ) } -
  • -
  • - ${ lng( "cfg_tab_gh_pat_perm_4" ) } -
  • -
-
- ${ lng( "cfg_tab_gh_pat_footer" ) } -
- ${ lng( "cfg_tab_gh_pat_help" ) } - `), - ) - - /* - Personal Access Token - - This is used for sharing gists between the obsidian vault and Github Gist. - */ - - let val_Token: HTMLInputElement | null = null - let btn_Github: ExtraButtonComponent - - this.Obj_Github_Api = new Setting( elm ) - .setName( lng( "cfg_tab_gh_pat_name" ) ) - .setDesc( DOM_Token_Desc ) - .addText( ( val ) => - { - val_Token = val.inputEl - val.inputEl.type = 'password' - - val.setPlaceholder( lng( "cfg_tab_gh_pat_pholder" ) ) - .setValue( gistToken ?? '' ) - .onChange( async ( val ) => - { - const input_PAT = val.trim( ) - const b_PAT_Token = /^github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$/g.test( input_PAT ) - const b_PAT_Classic = /^ghp_[A-Za-z0-9_]{36,251}$/g.test( input_PAT ) - - /* - Personal Access Token Valid - */ - - if ( b_PAT_Token || b_PAT_Classic ) - { - btn_Github.setIcon ( 'check' ) - btn_Github.setTooltip ( lng( "cfg_tab_gh_pat_ok_btn_tip" ) ) - - btn_Github.extraSettingsEl.classList.add ( "gistr-settings-icon-ok" ) - btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-github" ) - btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-invalid" ) - - let token_Type = lng( "cfg_tab_gh_pat_notice_type_fine" ) - if ( b_PAT_Classic ) - token_Type = lng( "cfg_tab_gh_pat_notice_type_classic" ) - - new Notice ( lng( "cfg_tag_gh_pat_notice_msg_success" ) + "\n\n" + token_Type ) - - GHTokenSet( input_PAT ) - - this.display( ) - } - else - { - - /* - Personal Access Token > invalid - */ - - if ( input_PAT.length > 0 ) - { - btn_Github.setTooltip ( lng( "cfg_tab_gh_pat_invalid_btn_tip" ) ) - - btn_Github.extraSettingsEl.classList.add ( "gistr-settings-icon-invalid" ) - btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-github" ) - btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-ok" ) - } - - /* - Personal Access Token > Empty - */ - - else - { - btn_Github.setIcon ( 'github' ) - btn_Github.setTooltip ( lng( "cfg_tab_gh_pat_bad_btn_tip" ) ) - - btn_Github.extraSettingsEl.classList.add ( "gistr-settings-icon-github" ) - btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-ok" ) - btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-invalid" ) - - GHTokenSet( "" ) - - new Notice ( lng( "cfg_tag_gh_pat_notice_msg_cleared" ) ) - } - } - } ) - } ) - - .addExtraButton( ( btn ) => - { - const btn_Visibility = ( bTokenVis: boolean ) => - { - btn - .setIcon ( bTokenVis ? 'eye' : 'eye-off' ) - .setTooltip ( bTokenVis ? lng( "cfg_tab_gh_pat_btn_tip_state_show" ) : lng( "cfg_tab_gh_pat_btn_tip_state_hide" ) ) - - btn.extraSettingsEl.classList.add( "gistr-settings-icon-cur" ) - } - - btn_Visibility( true ) - - btn.onClick( ( ) => - { - if ( !val_Token ) return - - if ( val_Token.type === 'password' ) - { - val_Token.type = 'text' - btn_Visibility( false ) - } - else - { - val_Token.type = 'password' - btn_Visibility( true ) - } - } ) - } ) - - .addExtraButton( ( btn ) => - { - btn_Github = btn - const btn_GetTokenStatus = ( bHasValue: boolean ) => - { - btn - .setIcon ( 'github' ) - .setTooltip ( lng( "cfg_tab_gh_pat_bad_btn_tip" ) ) - - btn.extraSettingsEl.classList.add( "gistr-settings-icon-cur" ) - btn.extraSettingsEl.classList.add( "gistr-settings-icon-github" ) - - const b_PAT_Token = /^github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$/g.test( gistToken ) - const b_PAT_Classic = /^ghp_[A-Za-z0-9_]{36,251}$/g.test( gistToken ) - - if ( b_PAT_Token == true || b_PAT_Classic == true ) - { - btn_Github.setIcon ( 'check' ) - btn_Github.setTooltip ( lng( "cfg_tab_gh_pat_ok_btn_tip" ) ) - - btn_Github.extraSettingsEl.classList.add ( "gistr-settings-icon-ok" ) - btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-github" ) - } - else - { - btn_Github.setIcon ( 'github' ) - btn_Github.setTooltip ( lng( "cfg_tab_gh_pat_bad_btn_tip" ) ) - - btn_Github.extraSettingsEl.classList.add ( "gistr-settings-icon-github" ) - btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-ok" ) - } - } - - btn_GetTokenStatus( true ) - btn.onClick( ( ) => - { - window.open( lng( "cfg_tab_gh_pat_url_btn" ) ) - } ) - } ) - - - /* - Background color (Light) - */ - - const cfg_tab_gh_cb_light_desc = new DocumentFragment( ) - cfg_tab_gh_cb_light_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_gh_cb_light_desc" ) }`), - ) - - new Setting( elm ) - .setName( lng( "cfg_tab_gh_cb_light_name" ) ) - .setDesc( cfg_tab_gh_cb_light_desc ) - .then( ( setting ) => { this.new_ColorPicker - ( - this.plugin, elm, setting, - "gh_clr_bg_light", - ) } ) - - /* - Background color (Dark) - */ - - const cfg_tab_gh_cb_dark_desc = new DocumentFragment( ) - cfg_tab_gh_cb_dark_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_gh_cb_dark_desc" ) }`), - ) - - new Setting( elm ) - .setName( lng( "cfg_tab_gh_cb_dark_name" ) ) - .setDesc( cfg_tab_gh_cb_dark_desc ) - .then( ( setting ) => { this.new_ColorPicker - ( - this.plugin, elm, setting, - "gh_clr_bg_dark", - ) } ) - - /* - Text color (Light) - */ - - const cfg_tab_gh_tx_light_desc = new DocumentFragment( ) - cfg_tab_gh_tx_light_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_gh_tx_light_desc" ) }`), - ) - - new Setting( elm ) - .setName( lng( "cfg_tab_gh_tx_light_name" ) ) - .setDesc( cfg_tab_gh_tx_light_desc ) - .then( ( setting ) => { this.new_ColorPicker - ( - this.plugin, elm, setting, - "gh_clr_tx_light", - ) } ) - - /* - Text color (Dark) - */ - - const cfg_tab_gh_tx_dark_desc = new DocumentFragment( ) - cfg_tab_gh_tx_dark_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_gh_tx_dark_desc" ) }`), - ) - - new Setting( elm ) - .setName( lng( "cfg_tab_gh_tx_dark_name" ) ) - .setDesc( cfg_tab_gh_tx_dark_desc ) - .then( ( setting ) => { this.new_ColorPicker - ( - this.plugin, elm, setting, - "gh_clr_tx_dark", - ) } ) - - - /* - Scrollbar Track Color (Light) - */ - - const cfg_tab_gh_sb_light_name = new DocumentFragment( ) - cfg_tab_gh_sb_light_name.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_gh_sb_light_desc" ) }`), - ) - - new Setting( elm ) - .setName( lng( "cfg_tab_gh_sb_light_name" ) ) - .setDesc( cfg_tab_gh_sb_light_name ) - .then( ( setting ) => { this.new_ColorPicker - ( - this.plugin, elm, setting, - "gh_clr_sb_light", - ) } ) - - /* - Scrollbar Track Color (Dark) - */ - - const cfg_tab_gh_sb_dark_desc = new DocumentFragment( ) - cfg_tab_gh_sb_dark_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_gh_sb_dark_desc" ) }`), - ) - - new Setting( elm ) - .setName( lng( "cfg_tab_gh_sb_dark_name" ) ) - .setDesc( cfg_tab_gh_sb_dark_desc ) - .then( ( setting ) => { this.new_ColorPicker - ( - this.plugin, elm, setting, - "gh_clr_sb_dark", - ) } ) - - /* - Codeblock Opacity - */ - - const cfg_tab_gh_opacity_desc = new DocumentFragment( ) - cfg_tab_gh_opacity_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_gh_opacity_desc" ) }`), - ) - - let val_gh_opacity: HTMLDivElement - new NoxComponent( elm ) - .setName( lng( "cfg_tab_gh_opacity_name" ) ) - .setDesc( cfg_tab_gh_opacity_desc ) - .setClass( "gistr-slider" ) - .addNoxSlider( slider => slider - .setDynamicTooltip( ) - .setLimits( 0.20, 1, 0.05 ) - .setValue( this.plugin.settings.gh_opacity ) - .onChange( async ( val ) => - { - const opacity_calc = val * 100 - val_gh_opacity.innerText = " " + opacity_calc.toString( ) + "%" - - this.plugin.settings.gh_opacity = val - await this.plugin.saveSettings( ) - this.plugin.renderModeReading( ) - }), - ( ) => - ( - SettingsDefaults.gh_opacity as number - ), - ).settingEl.createDiv( '', ( el ) => - { - val_gh_opacity = el - const opacity_calc = this.plugin.settings.gh_opacity * 100 - el.innerText = " " + opacity_calc.toString( ) + "%" - } ).classList.add( 'gistr-settings-elm-slider-preview' ) - - /* - Codeblock > CSS Override - */ - - const cfg_tab_gh_css_desc = new DocumentFragment( ) - cfg_tab_gh_css_desc.append( - sanitizeHTMLToDom( `${ lng( "cfg_tab_gh_css_desc" ) }` ), - ) - - let gh_css = new NoxComponent( elm ) - .setName( lng( "cfg_tab_gh_css_name" ) ) - .setDesc( cfg_tab_gh_css_desc ) - .setClass( "gistr-settings-elm-textarea" ) - .addNoxTextarea( text => text - .setPlaceholder( lng( "cfg_tab_gh_css_pholder" ) ) - .setValue( this.plugin.settings.css_gh ) - .onChange( async ( val ) => - { - this.plugin.settings.css_gh = val - await this.plugin.saveSettings( ) - this.plugin.renderModeReading( ) - }), - ( ) => - ( - SettingsDefaults.css_gh.toString( ) as string - ), - ) - - /* - Tab Footer Spacer - */ - - elm.createEl( 'div', { cls: "gistr-settings-section-footer", text: "" } ) - } - - /* - Tab > Save & Sync > New - */ - - Tab_SaveSync_New( elm: HTMLElement ) - { - const Tab_SY = elm.createEl( "h2", { text: lng( "cfg_tab_sy_title" ), cls: `gistr-settings-header${ this.Hide_SaveSync?" isfold" : "" }` } ) - Tab_SY.addEventListener( "click", ( )=> - { - this.Hide_SaveSync = !this.Hide_SaveSync - Tab_SY.classList.toggle( "isfold", this.Hide_SaveSync ) - this.Tab_SaveSync_CreateSettings( ) - } ) - } - - Tab_SaveSync_CreateSettings( ) - { - this.Tab_SaveSync.empty( ) - if ( this.Hide_SaveSync ) return - - this.Tab_SaveSync_ShowSettings( this.Tab_SaveSync ) - } - - Tab_SaveSync_ShowSettings( elm: HTMLElement ) - { - - let setting_allow_gist_updates: NoxComponent - let setting_autosave_enable: NoxComponent - let setting_autosave_strict: NoxComponent - let setting_autosave_noti: NoxComponent - let setting_autosave_dur: NoxComponent - - let bAutosaveEnabled = this.plugin.settings.sy_enable_autosave - - /* - Github > Header Intro - */ - - elm.createEl( 'small', { cls: "gistr-settings-section-description", text: lng( "cfg_tab_sy_header" ) } ) - - /* - Enable Allow Gist Updates - - Notes must be manually saved - */ - - const cfg_tab_sy_tog_allow_gist_updates_desc = new DocumentFragment( ) - cfg_tab_sy_tog_allow_gist_updates_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_sy_tog_allow_gist_updates_desc" ) }`), - ) - - setting_allow_gist_updates = new NoxComponent( elm ) - .setName( lng( "cfg_tab_sy_tog_allow_gist_updates_name" ) ) - .setDesc( cfg_tab_sy_tog_allow_gist_updates_desc ) - .addNoxToggle( toggle => toggle - .setValue( this.plugin.settings.sy_enable_autoupdate ) - .onChange( async ( val ) => - { - this.plugin.settings.sy_enable_autoupdate = val - await this.plugin.saveSettings( ) - }), - ( ) => - ( - SettingsDefaults.sy_enable_autoupdate as boolean - ), - ) - - elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) - - /* - Autosave > Toggle - */ - - const cfg_tab_sy_tog_autosave_enable_desc = new DocumentFragment( ) - cfg_tab_sy_tog_autosave_enable_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_sy_tog_autosave_enable_desc" ) }`), - ) - - setting_autosave_enable = new NoxComponent( elm ) - .setName( lng( "cfg_tab_sy_tog_autosave_enable_name" ) ) - .setDesc( cfg_tab_sy_tog_autosave_enable_desc ) - .addNoxToggle( toggle => toggle - .setValue( this.plugin.settings.sy_enable_autosave ) - .onChange( async ( val ) => - { - this.plugin.settings.sy_enable_autosave = val - await this.plugin.saveSettings( ) - - setting_autosave_strict.setDisabled( !val ) - setting_autosave_strict.settingEl.style.opacity = ( val == false ? this.Opacity_Disabled : this.Opacity_Enabled ) - - setting_autosave_noti.setDisabled( !val ) - setting_autosave_noti.settingEl.style.opacity = ( val == false ? this.Opacity_Disabled : this.Opacity_Enabled ) - - setting_autosave_dur.setDisabled( !val ) - setting_autosave_dur.settingEl.style.opacity = ( val == false ? this.Opacity_Disabled : this.Opacity_Enabled ) - } ), - ( ) => - ( - SettingsDefaults.sy_enable_autosave as boolean - ), - ) - - elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) - - /* - Autosave > Strict Mode - */ - - const cfg_tab_sy_tog_autosave_strict_desc = new DocumentFragment( ) - cfg_tab_sy_tog_autosave_strict_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_sy_tog_autosave_strict_desc", this.plugin.settings.sy_save_duration.toString( ) ) }`), - ) - - setting_autosave_strict = new NoxComponent( elm ) - .setName( lng( "cfg_tab_sy_tog_autosave_strict_name" ) ) - .setDesc( cfg_tab_sy_tog_autosave_strict_desc ) - .addNoxToggle( toggle => toggle - .setValue( this.plugin.settings.sy_enable_autosave_strict ) - .onChange( async ( val ) => - { - this.plugin.settings.sy_enable_autosave_strict = val - await this.plugin.saveSettings( ) - }), - ( ) => - ( - SettingsDefaults.sy_enable_autosave_strict as boolean - ), - ) - - setting_autosave_strict.setDisabled( !bAutosaveEnabled ) - setting_autosave_strict.settingEl.style.opacity = ( bAutosaveEnabled == false ? this.Opacity_Disabled : this.Opacity_Enabled ) - - elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) - - /* - Autosave > Notifications - */ - - const cfg_tab_sy_tog_autosave_noti_desc = new DocumentFragment( ) - cfg_tab_sy_tog_autosave_noti_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_sy_tog_autosave_noti_desc" ) }`), - ) - - setting_autosave_noti = new NoxComponent( elm ) - .setName( lng( "cfg_tab_sy_tog_autosave_noti_name" ) ) - .setDesc( cfg_tab_sy_tog_autosave_noti_desc ) - .addNoxToggle( toggle => toggle - .setValue( this.plugin.settings.sy_enable_autosave_notice ) - .onChange( async ( val ) => - { - this.plugin.settings.sy_enable_autosave_notice = val - await this.plugin.saveSettings( ) - }), - ( ) => - ( - SettingsDefaults.sy_enable_autosave_notice as boolean - ), - ) - - setting_autosave_noti.setDisabled( !bAutosaveEnabled ) - setting_autosave_noti.settingEl.style.opacity = ( bAutosaveEnabled == false ? this.Opacity_Disabled : this.Opacity_Enabled ) - - elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) - - /* - Autosave > Duration - */ - - const cfg_tab_sy_num_save_dur_desc = new DocumentFragment( ) - cfg_tab_sy_num_save_dur_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_sy_num_save_dur_desc", this.plugin.settings.sy_save_duration.toString( ) ) }`), - ) - - let val_save_dur: HTMLDivElement - setting_autosave_dur = new NoxComponent( elm ) - .setName( lng( "cfg_tab_sy_num_save_dur_name" ) ) - .setDesc( cfg_tab_sy_num_save_dur_desc ) - .setClass( "gistr-slider" ) - .addNoxSlider( slider => slider - .setLimits( 0, 120, 1 ) - .setDynamicTooltip( ) - .setValue( this.plugin.settings.sy_save_duration ) - .onChange( async ( val ) => - { - val_save_dur.innerText = " " + val.toString( ) + "s" - - this.plugin.settings.sy_save_duration = val - await this.plugin.saveSettings( ) - - const lng_desc_autosave_strict = new DocumentFragment( ) - lng_desc_autosave_strict.append ( sanitizeHTMLToDom( `${ lng( "cfg_tab_sy_tog_autosave_strict_desc", this.plugin.settings.sy_save_duration.toString( ) ) }` ) ) - setting_autosave_strict.setDesc ( lng_desc_autosave_strict ) - - const lng_desc_autosave_duration = new DocumentFragment( ) - lng_desc_autosave_duration.append ( sanitizeHTMLToDom( `${ lng( "cfg_tab_sy_num_save_dur_desc", this.plugin.settings.sy_save_duration.toString( ) ) }` ) ) - setting_autosave_dur.setDesc ( lng_desc_autosave_duration ) - }), - ( ) => - ( - SettingsDefaults.sy_save_duration as number - ), - ) - - setting_autosave_dur.settingEl.createDiv( '', ( el ) => - { - val_save_dur = el - el.innerText = " " + this.plugin.settings.sy_save_duration.toString( ) + "s" - } ).classList.add( 'gistr-settings-elm-slider-preview' ) - - setting_autosave_dur.setDisabled( !bAutosaveEnabled ) - setting_autosave_dur.settingEl.style.opacity = ( bAutosaveEnabled == false ? this.Opacity_Disabled : this.Opacity_Enabled ) - - elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) - - /* - Include frontmatter when gist saved online - */ - - const cfg_tab_sy_tog_inc_fm_desc = new DocumentFragment( ) - cfg_tab_sy_tog_inc_fm_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_sy_tog_inc_fm_desc" ) }`), - ) - - new NoxComponent( elm ) - .setName( lng( "cfg_tab_sy_tog_inc_fm_name" ) ) - .setDesc( cfg_tab_sy_tog_inc_fm_desc ) - .addNoxToggle( toggle => toggle - .setValue( this.plugin.settings.sy_add_frontmatter ) - .onChange( async ( val ) => - { - this.plugin.settings.sy_add_frontmatter = val - await this.plugin.saveSettings( ) - }), - ( ) => - ( - SettingsDefaults.sy_add_frontmatter as boolean - ), - ) - - elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) - - /* - Background color (Dark) - */ - - const cfg_tab_sy_list_icon_desc = new DocumentFragment( ) - cfg_tab_sy_list_icon_desc.append( - sanitizeHTMLToDom(`${ lng( "cfg_tab_sy_list_icon_desc" ) }`), - ) - - new Setting( elm ) - .setName( lng( "cfg_tab_sy_list_icon_name" ) ) - .setDesc( cfg_tab_sy_list_icon_desc ) - .then( ( setting ) => { this.new_ColorPicker - ( - this.plugin, elm, setting, - "sy_clr_lst_icon", - ) } ) - - /* - Tab Footer Spacer - */ - - elm.createEl( 'div', { cls: "gistr-settings-section-footer", text: "" } ) - } - - /* - Tab > Support > New - */ - - Tab_Support_New( elm: HTMLElement ) - { - const tab_og = elm.createEl( "h2", { text: lng( "cfg_tab_sp_title" ), cls: `gistr-settings-header${ this.Hide_Support?" isfold" : "" }` } ) - tab_og.addEventListener( "click", ( )=> - { - this.Hide_Support = !this.Hide_Support - tab_og.classList.toggle( "isfold", this.Hide_Support ) - this.Tab_Support_CreateSettings( ) - } ) - } - - Tab_Support_CreateSettings( ) - { - this.Tab_Support.empty( ) - if ( this.Hide_Support ) return - - this.Tab_Support_ShowSettings( this.Tab_Support ) - } - - Tab_Support_ShowSettings( elm: HTMLElement ) - { - - let json_delay = 0.5 * 1000 - const get_ver_stable = requestUrl( lng( "ver_url", "main" ) ).then( ( res ) => - { - if ( res.status === 200 ) - return res.json.version || lng( "cfg_tab_su_ver_connection_issues" ) - else - return lng( "cfg_tab_su_ver_connection_issues" ) - } ) - - const get_ver_beta = requestUrl( lng( "ver_url", "beta" ) ).then( ( res ) => - { - if ( res.status === 200 ) - return res.json.version || lng( "cfg_tab_su_ver_connection_issues" ) - else - return lng( "cfg_tab_su_ver_connection_issues" ) - } ) - - /* - Section -> Support Buttons - */ - - elm.createEl( 'small', { cls: "gistr-settings-section-description", text: lng( "cfg_tab_su_desc" ) } ) - - /* - Current Version - */ - - const Tab_SU_Ver_Stable = elm.createEl( "div", { text: "", cls: `gistr-settings-ver-sublevel` } ) - const Tab_SU_Ver_Stable_L = Tab_SU_Ver_Stable.createEl( "div", { text: lng( "cfg_tab_su_ver_cur_name" ), cls: `setting-item-name gistr-settings-ver-int-l` } ) - const Tab_SU_Ver_Stable_R = Tab_SU_Ver_Stable.createEl( "div", { text: " ", cls: `gistr-settings-ver-int-r` } ) - const Tab_SU_Ver_Stable_C = Tab_SU_Ver_Stable.createEl( "div", { text: "", cls: `gistr-settings-ver-int-c` } ) - const Tab_SU_Ver_Desc = Tab_SU_Ver_Stable.createEl( "div", { text: lng( "cfg_tab_su_ver_cur_desc" ), cls: `setting-item-description` } ) - - new Setting( Tab_SU_Ver_Stable_R ) - .addText( async ( text ) => - { - text - .setPlaceholder( lng( "cfg_tab_su_ver_status_checking" ) ) - .setValue( lng( "cfg_tab_su_ver_status_checking" ) ) - .setDisabled( true ) - .inputEl.setAttribute( "size", lng( "cfg_tab_su_ver_status_checking" ).length.toString( ) ) - - const el = Tab_SU_Ver_Stable_R.querySelector( ".setting-item-control" ) - el.addClass( "gistr-settings-status-connecting" ) - - let ver_running = this.plugin.manifest.version - let ver_stable = await get_ver_stable - let ver_beta = await get_ver_beta - - setTimeout( function( ) - { - - /* - Text > Could not communicate with server and get stable / beta version - */ - - if ( ver_stable == lng( "cfg_tab_su_ver_connection_issues" ) || ver_beta == lng( "cfg_tab_su_ver_connection_issues" ) ) - { - const el = Tab_SU_Ver_Stable_R.querySelector( ".setting-item-control" ) - el.removeClass ( "gistr-settings-status-connecting" ) - el.addClass ( "gistr-settings-status-error" ) - text.setValue ( lng( "cfg_tab_su_ver_connection_issues" ) ) - } - else - { - /* - Text > Beta version available - */ - - if ( gt( ver_beta, ver_stable ) && lt( ver_running, ver_beta ) ) - { - const el = Tab_SU_Ver_Stable_R.querySelector( ".setting-item-control" ) - text.setValue ( ver_running + " ➜ " + ver_beta + "-beta" ) - } - - /* - Text > Stable version available - */ - - else if ( lt( ver_beta, ver_stable ) && lt( ver_running, ver_stable ) ) - { - const el = Tab_SU_Ver_Stable_R.querySelector( ".setting-item-control" ) - text.setValue ( ver_running + " ➜ " + ver_stable + "-stable" ) - } - - /* - Text > No Updates - */ - - else - { - const el = Tab_SU_Ver_Stable_R.querySelector( ".setting-item-control" ) - el.removeClass ( "gistr-settings-status-connecting" ) - el.addClass ( "gistr-settings-status-success" ) - text.setValue ( ver_running ) - } - } - - - }, json_delay ) - } ) - .addExtraButton( async ( btn ) => - { - btn - .setIcon ( 'circle-off' ) - .setTooltip ( lng( "cfg_tab_su_ver_status_checking_btn_tip" ) ) - - btn.extraSettingsEl.classList.add( "gistr-settings-icon-cur" ) - btn.extraSettingsEl.classList.add( "gistr-anim-spin" ) - btn.extraSettingsEl.classList.add( "gistr-settings-status-connecting" ) - - let ver_running = this.plugin.manifest.version - let ver_stable = await get_ver_stable - let ver_beta = await get_ver_beta - - setTimeout( function( ) - { - - /* - Button > Could not communicate with server and get stable / beta version - */ - - if ( ver_stable == lng( "cfg_tab_su_ver_connection_issues" ) || ver_beta == lng( "cfg_tab_su_ver_connection_issues" ) ) - { - btn.setIcon ( "circle-off" ) - btn.setTooltip ( lng( "cfg_tab_su_ver_status_error_btn_tip" ) ) - - btn.extraSettingsEl.classList.remove ( "gistr-settings-status-connecting" ) - btn.extraSettingsEl.classList.add ( "gistr-settings-icon-error" ) - btn.extraSettingsEl.classList.remove ( "gistr-settings-icon-ok" ) - } - else - { - - /* - Button > Beta version available - */ - - if ( gt( ver_beta, ver_stable ) && lt( ver_running, ver_beta ) ) - { - btn.setTooltip ( lng( "cfg_tab_su_ver_status_new_beta_btn_tip" ) ) - btn.setIcon ( "alert" ) - btn.extraSettingsEl.classList.add ( "gistr-settings-icon-update" ) - } - - /* - Button > Stable version available - */ - - else if ( lt( ver_beta, ver_stable ) && lt( ver_running, ver_stable ) ) - { - btn.setTooltip ( lng( "cfg_tab_su_ver_status_new_stable_btn_tip" ) ) - btn.setIcon ( "alert" ) - btn.extraSettingsEl.classList.add ( "gistr-settings-icon-update" ) - } - - /* - Button > No Updates - */ - - else - { - btn.setIcon ( "check" ) - btn.setTooltip ( lng( "cfg_tab_su_ver_status_updated_btn_tip" ) ) - btn.extraSettingsEl.classList.remove ( "gistr-settings-status-connecting" ) - btn.extraSettingsEl.classList.add ( "gistr-settings-icon-ok" ) - } - } - - btn.onClick( ( ) => - { - window.open( lng( "cfg_tab_su_ver_releases" ) ) - } ) - - }, json_delay ) - } ) - - elm.createEl( 'div', { cls: "gistr-settings-section-separator-15", text: "" } ) - - /* - GUID & UUID - */ - - const env_guid = process.env.BUILD_GUID // static - const env_uuid = process.env.BUILD_UUID // dynamic - - const Tab_SU_GUID = elm.createEl( "div", { text: "", cls: `gistr-settings-ver-sublevel` } ) - const Tab_SU_GUID_L = Tab_SU_GUID.createEl( "div", { text: lng( "cfg_tab_su_guid_cur_name" ), cls: `setting-item-name gistr-settings-ver-int-l` } ) - const Tab_SU_GUID_R = Tab_SU_GUID.createEl( "div", { text: " ", cls: `gistr-settings-ver-int-r` } ) - const Tab_SU_GUID_C = Tab_SU_GUID.createEl( "div", { text: "", cls: `gistr-settings-ver-int-c` } ) - const Tab_SU_GUID_esc = Tab_SU_GUID.createEl( "div", { text: lng( "cfg_tab_su_guid_cur_desc" ), cls: `setting-item-description` } ) - - new Setting( Tab_SU_GUID_R ) - .addText( async ( text ) => - { - text - .setPlaceholder( env_guid ) - .setValue( env_guid ) - .setDisabled( true ) - .inputEl.setAttribute( "size", lng( "cfg_tab_su_ver_status_checking" ).length.toString( ) ) - - const el = Tab_SU_GUID_R.querySelector( ".setting-item-control" ) - el.addClass ( "gistr-settings-support-build-id" ) - } ) - .addExtraButton( async ( btn ) => - { - btn - .setIcon ( 'copy' ) - .setTooltip ( lng( "cfg_tab_su_guid_btn_tip" ) ) - - btn.onClick( ( ) => - { - navigator.clipboard.writeText( env_guid ) - new Notice( lng( "cfg_tab_su_guid_notice", env_guid ) ) - } ) - } ) - - elm.createEl( 'div', { cls: "gistr-settings-section-separator-15", text: "" } ) - - /* - GUID & UUID - */ - - const Tab_SU_UUID = elm.createEl( "div", { text: "", cls: `gistr-settings-ver-sublevel` } ) - const Tab_SU_UUID_L = Tab_SU_UUID.createEl( "div", { text: lng( "cfg_tab_su_uuid_cur_name" ), cls: `setting-item-name gistr-settings-ver-int-l` } ) - const Tab_SU_UUID_R = Tab_SU_UUID.createEl( "div", { text: " ", cls: `gistr-settings-ver-int-r` } ) - const Tab_SU_UUID_C = Tab_SU_UUID.createEl( "div", { text: "", cls: `gistr-settings-ver-int-c` } ) - const Tab_SU_UUID_esc = Tab_SU_UUID.createEl( "div", { text: lng( "cfg_tab_su_uuid_cur_desc" ), cls: `setting-item-description` } ) - - new Setting( Tab_SU_UUID_R ) - .addText( async ( text ) => - { - text - .setPlaceholder( env_uuid ) - .setValue( env_uuid ) - .setDisabled( true ) - .inputEl.setAttribute( "size", lng( "cfg_tab_su_ver_status_checking" ).length.toString( ) ) - - const el = Tab_SU_UUID_R.querySelector( ".setting-item-control" ) - el.addClass ( "gistr-settings-support-build-id" ) - } ) - .addExtraButton( async ( btn ) => - { - btn - .setIcon ( 'copy' ) - .setTooltip ( lng( "cfg_tab_su_uuid_btn_tip" ) ) - - btn.onClick( ( ) => - { - navigator.clipboard.writeText( env_uuid ) - new Notice( lng( "cfg_tab_su_uuid_notice", env_uuid ) ) - } ) - } ) - - elm.createEl( 'div', { cls: "gistr-settings-section-separator-15", text: "" } ) - - /* - Button > Getting Started > Open Interface - */ - - new Setting( elm ) - .setName( lng( "cfg_tab_su_gs_name" ) ) - .setDesc( lng( "cfg_tab_su_gs_desc" ) ) - .addButton( btn => - { - btn.setButtonText( lng( "cfg_tab_su_gs_btn" ) ) - .setCta( ) - .onClick( async( ) => - { - const action = await new ModalGettingStarted( this.app, this.plugin, this.plugin.manifest, this.plugin.settings, false ).openAndAwait( ) - } ) - } ) - - /* - Button -> Plugin Repo - */ - - new Setting( elm ) - .setName( lng( "cfg_tab_su_repo_label" ) ) - .setDesc( lng( "cfg_tab_su_repo_url" ) ) - .addButton( ( btn ) => - { - btn.setButtonText( lng( "cfg_tab_su_repo_btn" ) ).onClick( ( ) => - { - window.open( lng( "cfg_tab_su_repo_url" ) ) - } ) - } ) - - /* - Button -> Plugin Demo Vault - */ - - new Setting( elm ) - .setName( lng( "cfg_tab_su_vault_label" ) ) - .setDesc( lng( "cfg_tab_su_vault_url" ) ) - .addButton( ( btn ) => - { - btn.setButtonText( lng( "cfg_tab_su_vault_btn" ) ).onClick( ( ) => - { - window.open( lng( "cfg_tab_su_vault_url" ) ) - } ) - } ) - - /* - Button -> OpenGist > Download - */ - - new Setting( elm ) - .setName( lng( "cfg_tab_su_ogrepo_label" ) ) - .setDesc( lng( "cfg_tab_su_ogrepo_url" ) ) - .addButton( ( btn ) => - { - btn.setButtonText( lng( "cfg_tab_su_ogrepo_btn" ) ).onClick( ( ) => - { - window.open( lng( "cfg_tab_su_ogrepo_url" ) ) - } ) - } ) - - /* - Button -> OpenGist > Docs - */ - - new Setting( elm ) - .setName( lng( "cfg_tab_su_ogdocs_label" ) ) - .setDesc( lng( "cfg_tab_su_ogdocs_url" ) ) - .addButton( ( btn ) => - { - btn.setButtonText( lng( "cfg_tab_su_ogdocs_btn" ) ).onClick( ( ) => - { - window.open( lng( "cfg_tab_su_ogdocs_url" ) ) - } ) - } ) - - /* - Button -> OpenGist > Docs - */ - - new Setting( elm ) - .setName( lng( "cfg_tab_su_ogdemo_label" ) ) - .setDesc( lng( "cfg_tab_su_ogdemo_url" ) ) - .addButton( ( btn ) => - { - btn.setButtonText( lng( "cfg_tab_su_ogdemo_btn" ) ).onClick( ( ) => - { - window.open( lng( "cfg_tab_su_ogdemo_url" ) ) - } ) - } ) - - /* - Button -> Github Gist - */ - - new Setting( elm ) - .setName( lng( "cfg_tab_su_gist_label" ) ) - .setDesc( lng( "cfg_tab_su_gist_url" ) ) - .addButton( ( btn ) => - { - btn.setButtonText( lng( "cfg_tab_su_gist_btn" ) ).onClick( ( ) => - { - window.open( lng( "cfg_tab_su_gist_url" ) ) - } ) - } ) - - /* - Button -> Donate - */ - - const div_Donate = elm.createDiv( { cls: "gistr-donate" } ) - const lnk_Donate = new DocumentFragment( ) - lnk_Donate.append( - sanitizeHTMLToDom(` - - - - `), - ) - - new Setting( div_Donate ).setDesc( lnk_Donate ) - } -} \ No newline at end of file diff --git a/src/settings/sections/index.ts b/src/settings/sections/index.ts deleted file mode 100644 index f592b286..00000000 --- a/src/settings/sections/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SettingsSection } from 'src/settings/sections/SettingsSection' \ No newline at end of file diff --git a/src/settings/settings.ts b/src/settings/settings.ts deleted file mode 100644 index 3d892064..00000000 --- a/src/settings/settings.ts +++ /dev/null @@ -1,55 +0,0 @@ -import GistrPlugin from "src/main" - -/* - Settings -*/ - -export interface GistrSettings -{ - firststart: boolean - keyword: string | "gistr" - css_og: string | null - css_gh: string | null - theme: string | null - blk_pad_t: number | 16 - blk_pad_b: number | 19 - textwrap: string | "Enabled" - notitime: number | 10 - sy_clr_lst_icon: string | "757575E6" - ge_enable_updatenoti: boolean | true - - og_clr_bg_light: string | "CBCBCB" - og_clr_bg_dark: string | "121315" - og_clr_sb_light: string | "808080" - og_clr_sb_dark: string | "4960BA" - og_clr_tx_light: string | "2A2626" - og_clr_tx_dark: string | "CAD3F5" - og_opacity: number | 1 - - gh_clr_bg_light: string | "E5E5E5" - gh_clr_bg_dark: string | "121315" - gh_clr_sb_light: string | "3D85C4" - gh_clr_sb_dark: string | "BA496A" - gh_clr_tx_light: string | "2A2626" - gh_clr_tx_dark: string | "CAD3F5" - gh_opacity: number | 1 - - sy_enable_autoupdate: boolean | true - sy_enable_autosave: boolean | false - sy_enable_autosave_strict: boolean | false - sy_enable_autosave_notice: boolean | false - sy_add_frontmatter: boolean | false - sy_save_duration: number | 10 - - context_sorting: [], -} - -/* - Settings > Get -*/ - -export const SettingsGet = async ( plugin: GistrPlugin ): Promise < GistrSettings > => -{ - await plugin.loadSettings( ) - return plugin.settings -} \ No newline at end of file diff --git a/src/utils/colorpicker/index.ts b/src/utils/colorpicker/index.ts deleted file mode 100644 index 4fd9a8f6..00000000 --- a/src/utils/colorpicker/index.ts +++ /dev/null @@ -1,189 +0,0 @@ -/* - Import -*/ - -import { Setting, ExtraButtonComponent } from 'obsidian' -import GistrPlugin from "src/main" -import { lng } from 'src/lang' -import { ColorTranslator } from 'colortranslator' -import Pickr from "@simonwep/pickr" - -/* - CSS Color Values -*/ - -export type CLR_VAR = `--${string}` // css variable -export type CLR_HEX = `#${string}` // css hex -export type Color = CLR_HEX | CLR_VAR - -/* - Color Picker -*/ - -export class ColorPicker extends Pickr -{ - ActionSave: ( ActionSave: Color ) => void - ColorReset: ( ) => void - AddButtonReset: ExtraButtonComponent - - constructor( plugin : GistrPlugin, el : HTMLElement, setting : Setting, tip? : string ) - { - const settings : Pickr.Options = - { - el: setting.controlEl.createDiv( { cls: "picker" } ), - theme: "nano", - default: "#FFFFFF", - position: "left-middle", - lockOpacity: false, - components: - { - preview: true, - hue: true, - opacity: true, - interaction: - { - hex: true, - rgba: true, - hsla: true, - input: true, - cancel: true, - save: true, - }, - }, - i18n: - { - "ui:dialog": lng( "pickr_dialog" ), - "btn:swatch": lng( "pickr_swatch" ), - "btn:toggle": ( typeof tip !== "undefined" ) ? tip : lng( "pickr_toggle" ), - "btn:last-color": lng( "pickr_last" ), - "btn:save": lng( "pickr_save" ), - "btn:cancel": lng( "pickr_cancel" ), - "btn:clear": lng( "pickr_clear" ), - } - } - - if ( el.parentElement !== null ) - settings.container = el.parentElement - - super( settings ) - - /* - Colorpicker > Save - */ - - this.ActionSave = ( ActionSave: Color ) => - { - ( - async ( ) => - { - await plugin.saveSettings( ) - } - )( ) - } - - /* - Colorpicker > Reset Color - */ - - this.ColorReset = ( ) => - { - const clr: Color = "#FFFFFF" - this.setColor ( GetColor( clr ) ) - this.ActionSave ( clr ) - } - } -} - -/* - Color > Get -*/ - -export function GetColor( clr: Color ): Color -{ - return bValidCSS( clr ) ? CSS_GetValue( clr ) : clr -} - -/* - Converts colors when converting hsl and rgb -*/ - -export function ConvertColor( str : string ) : string -{ - const strSplit = str.trim( ).replace( /(\d*)%/g, "$1" ).split( " " ) - - const operators: { [ key: string ] : ( n1 : number, n2 : number ) => number } = - { - "+" : ( n1 : number, n2 : number ) : number => Math.max( n1 + n2, 0 ), - "-" : ( n1 : number, n2 : number ) : number => Math.max( n1 - n2, 0 ), - } - - if ( strSplit.length === 3 ) - { - if ( strSplit[ 1 ] in operators ) - { - return `${ operators[ strSplit[ 1 ] ]( parseFloat( strSplit[ 0 ] ), parseFloat( strSplit[ 2 ] ) ) }%` - } - } - - return str -} - -/* - CSS > Get Value -*/ - -export function CSS_GetValue( property: CLR_VAR ): CLR_HEX -{ - - const value = window.getComputedStyle( document.body ).getPropertyValue( property ).trim( ) - - /* - type : hex - #ff0000 - */ - - if ( typeof value === "string" && value.startsWith( "#" ) ) - return `#${ value.trim( ).substring( 1 ) }` - - /* - type : hsl - hsl( 0, 100%, 50% ) - */ - - else if ( value.startsWith( "hsl" ) ) - return `#${ ColorTranslator.toHEXA - ( - value.replace( /ConvertColor\((.*?)\)/g, ( match, capture ) => - ConvertColor( capture ) ) - ).substring( 1 ) }` - - /* - type : rgb - rgb( 255, 0, 0 ) - */ - - else if ( value.startsWith( "rgb" ) ) - return `#${ ColorTranslator.toHEXA - ( - value.replace( /ConvertColor\((.*?)\)/g, ( match, capture ) => - ConvertColor( capture ) ) - ).substring( 1 ) }` - - /* - Unknown type - */ - - else - console.warn( lng( "pickr_dev_unknown", value ) ) - - return `#${ ColorTranslator.toHEXA( value ).substring( 1 ) }` -} - -/* - Check Valid CSS -*/ - -export function bValidCSS( css: string ): css is CLR_VAR -{ - return typeof css === "string" && css.startsWith( "--" ) -} \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts deleted file mode 100644 index edd1c037..00000000 --- a/src/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ColorPicker, GetColor } from 'src/utils/colorpicker' \ No newline at end of file From 405f1ec3796bdd969e29660450a7a00d4c5d2ad0 Mon Sep 17 00:00:00 2001 From: Aetherinox Date: Tue, 12 Mar 2024 19:14:30 -0700 Subject: [PATCH 24/25] refactor: reload --- src/api/Env.ts | 139 ++ src/api/Frontmatter.ts | 7 + src/api/NoxComponent.ts | 176 ++ src/api/Types.ts | 28 + src/api/index.ts | 4 + src/backend/Backend.ts | 661 +++++++ src/backend/index.ts | 1 + src/backend/services/Github.ts | 671 ++++++++ src/backend/services/Opengist.ts | 19 + src/backend/services/index.ts | 2 + src/lang/index.ts | 38 + src/lang/locale/en.ts | 298 ++++ src/main.ts | 341 ++++ src/menus/context.ts | 86 + src/menus/menu.ts | 53 + src/modals/GettingStartedModal.ts | 293 ++++ src/settings/defaults.ts | 45 + src/settings/index.ts | 4 + src/settings/sections/SettingsSection.ts | 2000 ++++++++++++++++++++++ src/settings/sections/index.ts | 1 + src/settings/settings.ts | 55 + src/utils/colorpicker/index.ts | 189 ++ src/utils/index.ts | 1 + 23 files changed, 5112 insertions(+) create mode 100644 src/api/Env.ts create mode 100644 src/api/Frontmatter.ts create mode 100644 src/api/NoxComponent.ts create mode 100644 src/api/Types.ts create mode 100644 src/api/index.ts create mode 100644 src/backend/Backend.ts create mode 100644 src/backend/index.ts create mode 100644 src/backend/services/Github.ts create mode 100644 src/backend/services/Opengist.ts create mode 100644 src/backend/services/index.ts create mode 100644 src/lang/index.ts create mode 100644 src/lang/locale/en.ts create mode 100644 src/main.ts create mode 100644 src/menus/context.ts create mode 100644 src/menus/menu.ts create mode 100644 src/modals/GettingStartedModal.ts create mode 100644 src/settings/defaults.ts create mode 100644 src/settings/index.ts create mode 100644 src/settings/sections/SettingsSection.ts create mode 100644 src/settings/sections/index.ts create mode 100644 src/settings/settings.ts create mode 100644 src/utils/colorpicker/index.ts create mode 100644 src/utils/index.ts diff --git a/src/api/Env.ts b/src/api/Env.ts new file mode 100644 index 00000000..517ca5c5 --- /dev/null +++ b/src/api/Env.ts @@ -0,0 +1,139 @@ +import { App, PluginManifest, apiVersion } from 'obsidian' + +/* + Plugin ID + + hardcoded because it's needed as quickly as possible. + without this id, gists will not load. it doesnt have time + to wait on Env.manifest.id +*/ + +export function PID( ) : string +{ + return 'gistr' +} + +/* + Web URL string +*/ + +type HttpsUrl = `https://${string}` + +/* + Repository Strings +*/ + +type Repo = +{ + urlWiki?: HttpsUrl + urlIssues?: HttpsUrl +} + +/* + Class > Env +*/ + +export abstract class Env +{ + + private static _obsidianApiVer: string + private static _manifest: PluginManifest + + public static readonly repository: Repo = + { + urlWiki: 'https://github.com/Aetherinox/obsidian-gistr/wiki', + urlIssues: 'https://github.com/Aetherinox/obsidian-gistr/issues' + } + + /* + Initialize + */ + + static _Initialize( app: App, manifest: PluginManifest ) + { + if ( this._manifest || this._obsidianApiVer ) + throw console.log( 'Plugin attempted to define data more than once' ) + + this._obsidianApiVer = apiVersion + this._manifest = manifest + } + + /* + Obsidian API Version + */ + + static get obsidianVersion( ) + { + if ( !this._obsidianApiVer ) + throw console.log( 'Obsidian version not set. Ensure Env._Initialize() has fired. ' ) + + return this._obsidianApiVer + } + + /* + Plugin ID + */ + + static get pluginId( ): string + { + return this.manifest.id + } + + /* + Plugin Name + */ + + static get pluginName( ): string + { + return this.manifest.name + } + + /* + Plugin manifest + */ + + static get manifest( ): PluginManifest + { + if ( !this._manifest ) + throw console.log( 'Plugin manifest not set. Ensure Env._Initialize() has fired.' ) + + return this._manifest + } + + /* + Long Identity + */ + + static get longIdent( ): string + { + return `${ this.manifest.name } ( ${ this.manifest.id } @ v${ this.manifest.version } )` + } + + /* + GUID ( global identifier ) + */ + + static get guid( ): string + { + return `${ process.env.BUILD_GUID }` + } + + /* + UUID ( unique identifier ) + */ + + static get uuid( ): string + { + return `${ process.env.BUILD_UUID }` + } + + /* + build date + */ + + static get buildDate( ): string + { + return `${ process.env.BUILD_DATE }` + } + +} \ No newline at end of file diff --git a/src/api/Frontmatter.ts b/src/api/Frontmatter.ts new file mode 100644 index 00000000..80c2d96a --- /dev/null +++ b/src/api/Frontmatter.ts @@ -0,0 +1,7 @@ +import frontmatter from 'front-matter' + +/* + Clean Frontmatter +*/ + +export const FrontmatterPrepare = ( body: string ): string => frontmatter( body ).body \ No newline at end of file diff --git a/src/api/NoxComponent.ts b/src/api/NoxComponent.ts new file mode 100644 index 00000000..9e8d5d49 --- /dev/null +++ b/src/api/NoxComponent.ts @@ -0,0 +1,176 @@ +import { Setting, MomentFormatComponent, DropdownComponent, TextComponent, ToggleComponent, ValueComponent, SliderComponent, TextAreaComponent } from 'obsidian' +import { lng } from 'src/lang' + +/* + Class to extend obsidian components to include a reset button + next to each control / component. +*/ + +export class NoxComponent extends Setting +{ + + containerEl: HTMLElement + private name: string | DocumentFragment = '' + private desc: string | DocumentFragment = '' + + constructor( containerEl: HTMLElement ) + { + super( containerEl ) + this.setName( this.name ) + this.setDesc( this.desc ) + + return this + } + + public setName( name: string | DocumentFragment ): this + { + super.setName( name ) + this.name = name + + return this + } + + public setDesc( desc: string | DocumentFragment ): this + { + super.setDesc( desc ) + this.desc = desc + + return this + } + + /* + Textbox + */ + + public addNoxTextbox + ( + cb: ( comp: TextComponent ) => unknown, + onReset: ( comp: TextComponent ) => string, + ): this + { + return super.addText( ( comp ) => + { + this.addReset( comp, onReset ) + cb( comp ) + } ) + } + + /* + Textarea + */ + + public addNoxTextarea + ( + cb: ( comp: TextAreaComponent ) => unknown, + onReset: ( comp: TextAreaComponent ) => string, + ): this + { + return super.addTextArea( ( comp ) => + { + this.addReset( comp, onReset ) + cb( comp ) + } ) + } + + /* + Toggle Button + */ + + public addNoxToggle + ( + cb: ( comp: ToggleComponent ) => unknown, + onReset: ( comp: ToggleComponent ) => boolean, + ): this + { + return super.addToggle( ( comp ) => + { + this.addReset( comp, onReset ) + cb( comp ) + } ) + } + + /* + Dropdown + */ + + public addNoxDropdown< T extends string = string > + ( + cb: ( comp: DropdownComponent ) => unknown, + onReset: ( comp: DropdownComponent ) => T, + ): this + { + return super.addDropdown( ( comp ) => + { + this.addReset< string, DropdownComponent >( comp, onReset ) + cb( comp ) + } ) + } + + /* + Slider + */ + + public addNoxSlider + ( + cb: ( comp: SliderComponent ) => unknown, + onReset: ( comp: SliderComponent ) => number, + ): this + { + return super.addSlider( ( comp ) => + { + this.addReset( comp, onReset ) + cb( comp ) + } ) + } + + /* + Formatter (e.g.: dates) + */ + + public addNoxMomentFormat + ( + cb: ( comp: MomentFormatComponent ) => unknown, + onReset: ( comp: MomentFormatComponent ) => string, + ): this + { + return super.addMomentFormat( ( comp ) => + { + this.addReset( comp, onReset ) + cb( comp ) + } ) + } + + /* + Reset button functionality + */ + + private addReset< T, V extends ValueComponent< T > >( comp: V, onReset: ( comp: V ) => T ): V + { + this.addExtraButton( ( btn ) => + { + btn + .setIcon( 'reset' ) + .setTooltip( lng( "base_component_reset" ) ) + .onClick( ( ) => + { + comp.setValue( onReset( comp ) ) + + if ( comp instanceof MomentFormatComponent ) + { + comp.onChanged( ) + } + else if ( comp instanceof DropdownComponent ) + { + const event = new Event( 'change' ) + comp.selectEl.dispatchEvent( event ) + } + else if ( comp instanceof TextComponent || comp instanceof TextAreaComponent ) + { + comp.onChanged( ) + } + } ) + } ) + + return comp + } +} diff --git a/src/api/Types.ts b/src/api/Types.ts new file mode 100644 index 00000000..cf0b5a1f --- /dev/null +++ b/src/api/Types.ts @@ -0,0 +1,28 @@ +import { App, Editor, Menu } from "obsidian" + +/* + Cursor Coordinates +*/ + +export interface Coords +{ + top: number + left: number + right: number + bottom: number +} + +/* + Export Types +*/ + +export type ContextMenu = Menu & { DOM: HTMLElement } +export type GistrAPI = App & { Commands: { RunCommandByID: Function } } +export type GistrEditor = Editor & +{ + cm: CodeMirror.Editor & { coordsPos: Function } + coordsCur: Function + coordsPos: Function + hasFocus: Function + getSelection: Function +} \ No newline at end of file diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 00000000..ec750f08 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,4 @@ +export { FrontmatterPrepare } from 'src/api/Frontmatter' +export { NoxComponent } from 'src/api/NoxComponent' +export { Env, PID } from 'src/api/Env' +export { GistrAPI, GistrEditor, ContextMenu, Coords } from 'src/api/Types' \ No newline at end of file diff --git a/src/backend/Backend.ts b/src/backend/Backend.ts new file mode 100644 index 00000000..6f1b374b --- /dev/null +++ b/src/backend/Backend.ts @@ -0,0 +1,661 @@ +/* + Import +*/ + +import { request, RequestUrlParam } from "obsidian" +import { Env, PID } from 'src/api' +import { GistrSettings } from 'src/settings/settings' +import { lng } from 'src/lang' +import { nanoid } from 'nanoid' + +/* + Basic Declrations +*/ + +const sender = PID( ) +const AppBase = 'app://obsidian.md' + +/* + Declare Json +*/ + +export interface ItemJSON +{ + embed: + { + [ key: string ]: string + }, + files: + { + [ key: string ]: string + }, + description: string, + created_at: string, + id: string, + owner: string, + title: string, + uuid: string, + visibility: string, + stylesheet: string + div: string, +} + +/* + Gistr Backend +*/ + +export class BackendCore +{ + private readonly settings: GistrSettings + private manifest: Env + + constructor( settings: GistrSettings ) + { + this.settings = settings + this.manifest = Env + } + + /* + Gist > Handle + */ + + private async GistHandle( el: HTMLElement, data: string ) + { + const pattern = /(?https?:\/\/)?(?[^/]+\/)?((?[\w-]+)\/)?(?\w+)(\#(?\w+))?(\&(?\w+))?/ + const find = data.match( pattern ).groups + const host = find.host + const username = find.username + const uuid = find.uuid + const file = find.filename + const theme = find.theme + + const asd = PID( ) + + /* + Since opengist can really be any website, check for matching github links + */ + + const bMatchGithub = /((https?:\/\/)?(.+?\.)?github\.com(\/[A-Za-z0-9\-\._~:\/\?#\[\]@!$&'\(\)\*\+,;\=]*)?)/g.test( host ) + + /* + No UUID match + */ + + if ( typeof uuid === undefined ) + return this.ThrowError( el, data, lng( "err_gist_loading_fail_url", host ) ) + + /* + compile url to gist + */ + + let gistSrcURL = ( file !== undefined ? `https://${host}${username}/${uuid}.json?file=${file}` : `https://${host}${username}/${uuid}.json` ) + let og_ThemeOV = ( theme !== undefined ) ? theme : "" + + const reqUrlParams: RequestUrlParam = { url: gistSrcURL, method: "GET", headers: { "Accept": "application/json" } } + try + { + const req = await request( reqUrlParams ) + const json = JSON.parse( req ) as ItemJSON + + return this.GistGenerate( el, host, uuid, json, bMatchGithub, og_ThemeOV ) + } + catch ( err ) + { + return this.ThrowError( el, data, `Invalid gist url ${gistSrcURL} ( ${err} )` ) + } + } + + /* + Gist > Generate + + create new iframe for each gist, assign it a uid, set the needed attributes, and generate the css, js + */ + + private EventListener( uuid: string ) : string + { + return ` + + ` + } + + /* + Gist > Generate + + create new iframe for each gist, assign it a uid, set the needed attributes, and generate the css, js + */ + + private async GistGenerate( el: HTMLElement, host: string, uuid: string, json: ItemJSON, bGithub: boolean, theme: string ) + { + + /* + create uuid and iframe + */ + + const gid = `${ sender }-${ uuid }-${ nanoid( ) }` + const ct_iframe = document.createElement( 'iframe' ) + ct_iframe.id = gid + + ct_iframe.classList.add ( `${ sender }-container` ) + ct_iframe.setAttribute ( 'sandbox', 'allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation' ) + ct_iframe.setAttribute ( 'loading', 'lazy' ) + ct_iframe.setAttribute ( 'width', '100%' ) + + /* + https://fonts.googleapis.com + + policy directive error if certain attributes arent used. doesnt affect the plugin, but erors are bad + */ + + ct_iframe.setAttribute ( 'csp', "default-src * data: blob: 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';" ) + // ct_iframe.setAttribute ( 'csp', "default-src * self 'unsafe-inline'; font-src 'self' *fonts.gstatic.com/; style-src-elem 'self' *fonts.googleapis.com *demo.opengist.io/ *thomice.li 'unsafe-inline'; script-src * 'self' 'unsafe-eval' 'unsafe-inline'; object-src * 'self'; img-src * self 'unsafe-inline'; connect-src self * 'unsafe-inline'; frame-src * self 'unsafe-inline';" ) + + /* + assign css, body, js + */ + + let css_theme_ovr = ( theme !== "" ) ? theme.toLowerCase( ) : "" + let css_theme_sel = ( css_theme_ovr !== "" ) ? css_theme_ovr : ( this.settings.theme == "Dark" ) ? "dark" : ( this.settings.theme == "Light" ) ? "light" : "light" + let css_og = "" + let css_gh = "" + + const content_css = await this.GetCSS( el, uuid, ( bGithub ? json.stylesheet: json.embed.css ) ) + const content_body = ( bGithub ? json.div : "" ) + const content_js = ( bGithub ? "" : await this.GetJavascript( el, uuid, ( css_theme_sel == "dark" ? json.embed.js_dark : json.embed.js ) ) ) + + /* + CSS Overrides > Github + */ + + const css_gh_bg_color = ( css_theme_sel == "dark" ? this.settings.gh_clr_bg_dark : this.settings.gh_clr_bg_light ) + const css_gh_sb_color = ( css_theme_sel == "dark" ? this.settings.gh_clr_sb_dark : this.settings.gh_clr_sb_light ) + const css_gh_bg_header_bg = ( css_theme_sel == "dark" ? "rgb( 35 36 41/var( --tw-bg-opacity ) )" : "rgb( 238 239 241/var( --tw-bg-opacity ) )" ) + const css_gh_bg_header_bor = ( css_theme_sel == "dark" ? "1px solid rgb( 54 56 64/var( --tw-border-opacity ) )" : "rgb( 222 223 227/var( --tw-border-opacity ) )" ) + const css_gh_tx_color = ( css_theme_sel == "dark" ? this.settings.gh_clr_tx_dark : this.settings.gh_clr_tx_light ) + + /* + Declare custom css override + */ + + const css_override = ( ( bGithub && this.settings.css_gh && this.settings.css_gh.length > 0 ) ? ( this.settings.css_gh ) : ( this.settings.css_og && this.settings.css_og.length > 0 && this.settings.css_og ) ) || "" + + /* + OpenGist specific CSS + + @note : these are edits the user should not need to edit. + OpenGist needs these edits in order to look right with the header + and footer. + + obviously this condition doesn't matter even if it is injected into Github pastes, + but it would be usless code. + + working with OpenGist developer to re-do the HTML generated when embedding a gist. + */ + + const css_og_append = this.CSS_Get_OpenGist( css_theme_sel ) + const css_gh_append = this.CSS_Load_Github( css_theme_sel ) + + /* + Github > Dark Theme + */ + + if ( bGithub === false ) + css_og = css_og_append + else + css_gh = css_gh_append + + /* + generate html output + */ + + const html_output = + ` + + + + + ${ this.EventListener( gid ) } + + + + + + + + + + + + ${ content_body } + + + ` + + ct_iframe.srcdoc = html_output + el.appendChild( ct_iframe ) + } + + /* + Theme > OpenGist + */ + + private CSS_Get_OpenGist( theme: string ) + { + + const css_og_bg_color = ( theme == "dark" ? this.settings.og_clr_bg_dark : this.settings.og_clr_bg_light ) + const css_og_sb_color = ( theme == "dark" ? this.settings.og_clr_sb_dark : this.settings.og_clr_sb_light ) + const css_og_bg_header_bg = ( theme == "dark" ? "rgb( 35 36 41/var( --tw-bg-opacity ) )" : "rgb( 238 239 241/var( --tw-bg-opacity ) )" ) + const css_og_bg_header_bor = ( theme == "dark" ? "1px solid rgb( 54 56 64/var( --tw-border-opacity ) )" : "rgb( 222 223 227/var( --tw-border-opacity ) )" ) + const css_og_tx_color = ( theme == "dark" ? this.settings.og_clr_tx_dark : this.settings.og_clr_tx_light ) + const css_og_wrap = ( this.settings.textwrap == "Enabled" ? "normal" : "pre" ) + const css_og_opacity = ( this.settings.og_opacity ) || 1 + + return ` + ::-webkit-scrollbar + { + width: 6px; + height: 10px; + } + + ::-webkit-scrollbar-track + { + background-color: transparent; + border-radius: 5px; + margin: 1px; + } + + ::-webkit-scrollbar-thumb + { + border-radius: 10px; + background-color: ${css_og_sb_color}; + } + + .opengist-embed .code + { + padding-top: ${this.settings.blk_pad_t}px; + padding-bottom: ${this.settings.blk_pad_b}px; + border-top: ${css_og_bg_header_bor}; + background-color: ${css_og_bg_color}; + width: fit-content; + margin-top: -1px; + } + + .opengist-embed .mb-4 + { + margin-bottom: 1rem; + backdrop-filter: opacity(0); + --tw-bg-opacity: 1; + background-color: ${css_og_bg_header_bg}; + opacity: ${css_og_opacity}; + } + + .opengist-embed .line-code + { + color: ${css_og_tx_color}; + } + + .opengist-embed .code .line-num + { + color: ${css_og_tx_color}; + opacity: 0.5; + } + + .opengist-embed .code .line-num:hover + { + color: ${css_og_tx_color}; + opacity: 1; + } + + .opengist-embed .whitespace-pre + { + white-space: ${css_og_wrap}; + } + ` + } + + /* + Theme > Github + */ + + private CSS_Load_Github( theme: string = 'light' ) + { + const css_gh_bg_color = ( theme == "dark" ? this.settings.gh_clr_bg_dark : this.settings.gh_clr_bg_light ) + const css_gh_sb_color = ( theme == "dark" ? this.settings.gh_clr_sb_dark : this.settings.gh_clr_sb_light ) + const css_gh_bg_header_bg = ( theme == "dark" ? "rgb( 35 36 41/var( --tw-bg-opacity ) )" : "rgb( 238 239 241/var( --tw-bg-opacity ) )" ) + const css_gh_bg_header_bor = ( theme == "dark" ? "1px solid rgb( 54 56 64/var( --tw-border-opacity ) )" : "rgb( 222 223 227/var( --tw-border-opacity ) )" ) + const css_gh_tx_color = ( theme == "dark" ? this.settings.gh_clr_tx_dark : this.settings.gh_clr_tx_light ) + const css_gh_wrap = ( this.settings.textwrap == "Enabled" ? "wrap" : "nowrap" ) + const css_gh_opacity = ( this.settings.gh_opacity ) || 1 + + return ` + ::-webkit-scrollbar + { + width: 6px; + height: 10px; + } + + ::-webkit-scrollbar-track + { + background-color: transparent; + border-radius: 5px; + margin: 1px; + } + + ::-webkit-scrollbar-thumb + { + border-radius: 10px; + background-color: ${css_gh_sb_color}; + } + + body + { + --tw-bg-opacity: 1; + --tw-border-opacity: 1; + } + + body .gist .gist-file + { + backdrop-filter: opacity( 0 ); + background-color: rgb( 35 36 41/var( --tw-bg-opacity ) ); + border: 2px solid rgba( 255, 255, 255, 0.1 ); + opacity: ${css_gh_opacity}; + } + + body .gist .gist-data + { + padding-left: 12px; + padding-right: 12px; + padding-top: 15px; + padding-bottom: 6px; + border-color: ${css_gh_bg_header_bor}; + background-color: ${css_gh_bg_color}; + } + + .gist .markdown-body>*:last-child + { + margin-bottom: 0 !important; + padding-bottom: 5px; + } + + body .gist .markdown-body + { + color: ${css_gh_tx_color}; + line-height: 18.2px; + font-size: 0.8em; + border-spacing: 0; + border-collapse: collapse; + font-family: Menlo,Consolas,Liberation Mono,monospace; + } + + body .gist .gist-meta + { + color: #6b869f; + border-top: ${css_gh_bg_header_bor}; + background-color: ${css_gh_bg_header_bg}; + padding-left: 22px; + padding-right: 16px; + padding-top: 8px; + padding-bottom: 8px; + } + + body .gist .gist-meta a + { + color: rgb( 186 188 197/var( --tw-text-opacity ) ); + opacity: 0.9; + } + + body .gist .gist-meta a.Link--inTextBlock:hover + { + color: ${css_gh_tx_color}; + opacity: 0.5; + } + + body .gist .gist-meta a.Link--inTextBlock + { + padding-left: 0px; + padding-right: 7px; + color: ${css_gh_tx_color}; + } + + body .gist .gist-meta > a:nth-child( 3 ) + { + padding-left: 5px; + } + + body .gist .gist-data .pl-s .pl-s1 + { + color: #a5c261 + } + + body .gist .highlight + { + background: transparent; + } + + body .gist .blob-wrapper + { + padding-bottom: 6px !important; + } + + body .gist .pl-s2, body .gist .pl-stj, body .gist .pl-vo, + body .gist .pl-id, body .gist .pl-ii + { + color: ${css_gh_tx_color}; + } + + body .gist .blob-code + { + color: ${css_gh_tx_color}; + } + + body .gist .blob-num, body .gist .blob-code-inner, + { + color: ${css_gh_tx_color}; + opacity: 0.5; + } + + body .gist .blob-num:hover + { + color: ${css_gh_tx_color}; + opacity: 1; + } + + body .gist .blob-wrapper tr:first-child td + { + text-wrap: ${css_gh_wrap}; + } + + body .gist .pl-enti, body .gist .pl-mb, body .gist .pl-pdb + { + font-weight: 700; + } + + body .gist .pl-c, body .gist .pl-c span, body .gist .pl-pdc + { + color: #bc9458; + font-style: italic; + } + + body .gist .pl-c1, body .gist .pl-pdc1, body .gist .pl-scp + { + color: #6c99bb; + } + + body .gist .pl-ent, body .gist .pl-eoa, body .gist .pl-eoai, body .gist .pl-eoai .pl-pde, + body .gist .pl-ko, body .gist .pl-kolp, body .gist .pl-mc, body .gist .pl-mr, body .gist .pl-ms, + body .gist .pl-s3, body .gist .pl-sok + { + color: #ffe5bb; + } + + body .gist .pl-mdh, body .gist .pl-mdi, body .gist .pl-mdr + { + font-weight: 400; + } + + body .gist .pl-mi, body .gist .pl-pdi + { + color: #ffe5bb; + font-style: italic; + } + + body .gist .pl-sra, + body .gist .pl-src, + body .gist .pl-sre + { + color: #cc3; + } + + body .gist .pl-mdht, body .gist .pl-mi1 + { + color: #a5c261; + background: #121315; + } + + body .gist .pl-md, body .gist .pl-mdhf + { + color: #b83426; + background: #121315; + } + + body .gist .pl-ib, body .gist .pl-id, + body .gist .pl-ii, body .gist .pl-iu + { + background: #121315; + } + + body .gist .pl-ms1 + { + background: #121315; + } + + body .gist .highlight-text-html-basic .pl-ent, + body .gist .pl-cce, body .gist .pl-cn, body .gist .pl-coc, body .gist .pl-enc, + body .gist .pl-ens, body .gist .pl-k, body .gist .pl-kos, body .gist .pl-kou, + body .gist .pl-mh .pl-pdh, body .gist .pl-mp, body .gist .pl-mp .pl-s3, + body .gist .pl-mp1 .pl-sf, body .gist .pl-mq, body .gist .pl-mri, + body .gist .pl-pde, body .gist .pl-pse, body .gist .pl-pse .pl-s2, + body .gist .pl-s, body .gist .pl-st, body .gist .pl-stp, body .gist .pl-sv, + body .gist .pl-v, body .gist .pl-va, body .gist .pl-vi, body .gist .pl-vpf, + body .gist .pl-vpu, body .gist .pl-mdr + { + color: #cc7833; + } + + body .gist .pl-cos, body .gist .pl-ml, body .gist .pl-pds, + body .gist .pl-s1, body .gist .pl-sol, body .gist .pl-mb, + body .gist .pl-pdb + { + color: #a5c261; + } + + body .gist .pl-e, body .gist .pl-en, body .gist .pl-entl, + body .gist .pl-mo, body .gist .pl-sc, body .gist .pl-sf, + body .gist .pl-smi, body .gist .pl-smp, body .gist .pl-mdh, + body .gist .pl-mdi + { + color: #ffc66d; + } + + body .gist .pl-ef, body .gist .pl-enf, body .gist .pl-enm, body .gist .pl-entc, + body .gist .pl-entm, body .gist .pl-eoac, body .gist .pl-eoac .pl-pde, body .gist .pl-eoi, + body .gist .pl-mai .pl-sf, body .gist .pl-mm, body .gist .pl-pdv, body .gist .pl-smc, + body .gist .pl-som, body .gist .pl-sr, body .gist .pl-enti + { + color: #b83426; + } + ` + } + + /* + Throw Error + */ + + private async ThrowError( el: HTMLElement, gistInfo: string, err: string = '' ) + { + const div_Error = el.createEl( 'div', { text: "", cls: 'gistr-container-error' } ) + div_Error.createEl( 'div', { text: lng( "err_gist_loading_fail_name" ), cls: 'gistr-load-error-l1' } ) + div_Error.createEl( 'div', { text: gistInfo, cls: "gistr-load-error-l2" } ) + div_Error.createEl( 'small', { text: lng( "err_gist_loading_fail_resp", err ) } ) + } + + /* + Get Javascript + */ + + private async GetJavascript( el: HTMLElement, data: string, url: string ) + { + const reqUrlParams: RequestUrlParam = { url: url, method: "GET", headers: { "Accept": "text/javascript" } } + try { return await request( reqUrlParams ) } + catch ( err ) + { + return this.ThrowError( el, data, lng( "err_gist_loading_fail_detail", err ) ) + } + } + + /* + Get CSS + */ + + private async GetCSS( el: HTMLElement, data: string, url: string ) + { + const reqUrlParams: RequestUrlParam = { url : url, method: "GET", headers: { "Accept": "text/css" } } + try { return await request( reqUrlParams ) } + catch ( err ) + { + return this.ThrowError( el, data, lng( "err_gist_loading_fail_detail", err ) ) + } + } + + /* + Collect message data from JS_EventListener + */ + + messageEventHandler = ( evn: MessageEvent ) => + { + if ( evn.origin !== 'null' ) return + if ( evn.data.sender !== sender ) return + + const uuid = evn.data.gid + const scrollHeight = evn.data.scrollHeight + const gist_Container: HTMLElement = document.querySelector( 'iframe#' + uuid ) + + gist_Container.setAttribute( 'height', scrollHeight ) + } + + /* + Event processor + */ + + processor = async ( src: string, el: HTMLElement ) => + { + const obj = src.trim( ).split( "\n" ) + + return Promise.all + ( + obj.map( async ( gist ) => + { + return this.GistHandle( el, gist ) + } ) + ) + } +} \ No newline at end of file diff --git a/src/backend/index.ts b/src/backend/index.ts new file mode 100644 index 00000000..02271a38 --- /dev/null +++ b/src/backend/index.ts @@ -0,0 +1 @@ +export { ItemJSON, BackendCore } from 'src/backend/Backend' \ No newline at end of file diff --git a/src/backend/services/Github.ts b/src/backend/services/Github.ts new file mode 100644 index 00000000..670507cd --- /dev/null +++ b/src/backend/services/Github.ts @@ -0,0 +1,671 @@ +import { App, Notice, SuggestModal, MarkdownView, TFile } from 'obsidian' +import GistrPlugin from 'src/main' +import { GistrSettings, SettingsGet } from 'src/settings/settings' +import { FrontmatterPrepare } from 'src/api' +import Noxkit from '@aetherinox/noxkit' +import frontmatter from 'front-matter' +import { Octokit } from '@octokit/rest' +import { lng } from 'src/lang' + +/* + Github Status + + do not change the values, these are assigned by Github +*/ + +enum Status +{ + success = 'succeeded', + fail = 'failed', +} + +/* + default api status types +*/ + +export const GHStatusAPI: Record< string, string > = +{ + 'operational': lng( "gist_status_connected" ), + "degraded_performance": lng( "gist_status_degraded_performance" ), + "partial_outage": lng( "gist_status_partial_outage" ), + "major_outage": lng( "gist_status_major_outage" ), +} + +/* + Github Gist Structure +*/ + +interface GistData +{ + file: string, + is_public: boolean, + id: string, + url: string, + user: string, + revisions: number, + created_at: string, + updated_at: string, +} + +/* + Github > Personal Access Token +*/ + +const GISTR_GITHUB_PAT = 'gistr_github_pat' + +/* + Github > Personal Access Token > Set +*/ + +export const GHTokenSet = ( token: string ): void => +{ + localStorage.setItem( GISTR_GITHUB_PAT, token ) +} + +/* + Github > Personal Access Token > Get +*/ + +export const GHTokenGet = ( ): string => +{ + return localStorage.getItem( GISTR_GITHUB_PAT ) +} + +/* + Interface > Gist Result +*/ + +interface ResultsCreateGist +{ + gistArray: GistData | null + status: Status + errorMessage: string | null +} + +/* + Interface > Options > Create +*/ + +interface OptionsCreate +{ + is_public: boolean + file: string + content: string + token: string +} + +/* + Interface > Options > Update +*/ + +interface OptionsUpdate +{ + gistArray: GistData + content: string + token: string +} + +/* + Interface > Params > Autosave +*/ + +interface ParamsAutosave +{ + plugin: GistrPlugin + app: App + note_full: string + file: TFile +} + +/* + Github > Args +*/ + +interface ArgsGet { app: App, plugin: GistrPlugin, is_public: boolean } +interface ArgsCopy { app: App, plugin: GistrPlugin } + +/* + Gist > Get File + + @json gist json + : { + "modified": "2024-03-06T14:59:39.000Z", + "gists": [ + { + "id": "XXXX", + "url": "https://gist.github.com/Aetherinox/XXXXXX", + "created_at": "2024-03-07T00:19:23Z", + "updated_at": "2024-03-07T00:31:12Z", + "file": "Note File.md", + "is_public": true || false + } + ] + } +*/ + +const FindExistingGist = ( gistContents: string ): GistData[ ] => +{ + const { attributes: json } = frontmatter<{ gists: GistData[] }>( gistContents ) + const gists = json.gists || [] + + return ( gists as GistData[] ) +} + +/* + Gist > Update + + @body: gist json + @content: formatted vault contents + + @package-frontmatter: + content.attributes contains the extracted yaml attributes in json form + content.body contains the string contents below the yaml separators + content.bodyBegin contains the line number the body contents begins at + content.frontmatter contains the original yaml string contents +*/ + +const InsertFrontmatter = ( gistArray: GistData, note_contents: string ): string => +{ + + const { body: note, attributes: data } = frontmatter<{ gists: GistData[] }>( note_contents ) + + const GistList = ( data.gists || [] ) as GistData[] + const GistMatching = GistList.find( ( GistExisting ) => GistExisting.id === gistArray.id ) + + /* + Existing Gist + */ + + if ( GistMatching ) + { + const otherGists = GistList.filter( ( GistExisting ) => GistExisting !== GistMatching ) + const gists = [ ... otherGists, gistArray ] + const updatedData = { ... data, gists } + + return Noxkit.stringify( note, updatedData ) + } + + /* + New Gist + + @updatedData: gist json + : { + "modified": "2024-03-06T14:59:39.000Z", + "gists": [ + { + "id": "XXXX", + "url": "https://gist.github.com/Aetherinox/XXXXXX", + "created_at": "2024-03-07T00:19:23Z", + "updated_at": "2024-03-07T00:31:12Z", + "file": "Note File.md", + "is_public": true || false + } + ] + } + + @note: Test Note Body Contents + */ + + const gists = [ ... GistList, gistArray ] + const updatedData = { ... data, gists } + + return Noxkit.stringify( note, updatedData ) +} + +/* + Gist > Update +*/ + +const Update = async ( args: OptionsUpdate ): Promise< ResultsCreateGist > => +{ + const { token, gistArray, content } = args + + try + { + const octokit = new Octokit( { auth: token } ) + const response = await octokit.rest.gists.update( { gist_id: gistArray.id, files: { [ gistArray.file ]: { content } } } ) + + return { status: Status.success, gistArray: { ... gistArray, updated_at: response.data.updated_at, user: response.data.owner.login, revisions: response.data.history.length }, errorMessage: null } + } + catch ( e ) + { + return { status: Status.fail, gistArray: gistArray, errorMessage: e.message } + } +} + +/* + Gist > Create +*/ + +const Create = async ( args: OptionsCreate ): Promise< ResultsCreateGist > => +{ + try + { + const { file, content, is_public, token } = args + + const octokit = new Octokit( { auth: token } ) + const octogist = await octokit.rest.gists.create( { description: file, public: is_public, files: { [ file ]: { content } } } ) + + return { + status: Status.success, gistArray: + { + file, + is_public, + id: octogist.data.id as string, + url: octogist.data.html_url as string, + user: octogist.data.owner.login as string, + revisions: octogist.data.history.length as number, + created_at: octogist.data.created_at as string, + updated_at: octogist.data.updated_at as string, + }, + + errorMessage: null, + } + } + catch ( e ) + { + return{ status: Status.fail, gistArray: null, errorMessage: e.message } + } +} + +/* + DateTime Format + + need to break it up into these crazy steps to allow for customization +*/ + +const dateTimeformat = ( date: Date ): string => +{ + const month = date.getMonth( ) + 1 + const month_str = month.toString( ).padStart( 2, '0' ) + const day = date.getDate( ).toString( ).padStart( 2, '0' ) + const year = date.getFullYear( ).toString( ).padStart( 2, '0' ) + + let hours = date.getHours( ) + const mins = date.getMinutes( ) + const mins_str = mins.toString( ).padStart( 2, '0' ) + const x = hours >= 12 ? lng( "base_time_pm" ) : lng( "base_time_am" ) + hours = hours % 12 + hours = hours ? hours : 12 + + return month_str + '.' + day + '.' + year + ' ' + hours + ':' + mins_str + ' ' + x +} + +/* + Modal > Select Existing Gist +*/ + +class SelectExistingModal extends SuggestModal < GistData > +{ + gists: GistData[] + bAllowGistCreateNew: boolean + settings: GistrSettings + + /* + Suggestion > Submit + */ + + onSubmit: ( gistArray: GistData | null ) => Promise < void > + + /* + Suggestion > Constructor + */ + + constructor( app: App, settings: GistrSettings, gists: GistData[ ], bAllowGistCreateNew: boolean, onSubmit: ( gistArray: GistData ) => Promise < void > ) + { + super( app ) + + this.settings = settings + this.gists = gists + this.bAllowGistCreateNew = bAllowGistCreateNew + this.onSubmit = onSubmit + } + + /* + Suggestion > Get + */ + + getSuggestions( ): Array < GistData | null > + { + if ( this.bAllowGistCreateNew ) + return this.gists.concat( null ) + else + return this.gists + } + + /* + Suggestion > Render + */ + + renderSuggestion( gistArray: GistData | null, el: HTMLElement ) + { + + /* + object empty, show "Create New Gist" button + */ + + if ( Object.is( gistArray, null ) ) + { + const div_Create = el.createEl( 'div', { text: "", cls: 'gistr-suggest-create' } ) + div_Create.createEl ( 'div', { text: lng( "gist_btn_create_new" ) } ) + + return + } + + /* + Existing gist found > populate list + */ + + const div_scope = gistArray.is_public ? lng( "lst_repotype_pub" ) : lng( "lst_repotype_pri" ) + let date = new Date( `${ gistArray.updated_at }` ) + let date_created = dateTimeformat( date ) + + const div_Parent = el.createEl( 'div', { text: "", cls: 'gistr-suggest-container' } ) + const svg_Icon = div_Parent.createEl ( 'div', { text: "", cls: 'gistr-suggest-icon' } ) + svg_Icon.insertAdjacentHTML ( 'afterbegin', "" ) + const div_Sub_l = div_Parent.createEl ( 'div', { text: "", cls: 'gistr-suggest-sub-container-l' } ) + div_Sub_l.createEl ( 'div', { text: gistArray.file, cls: "gistr-suggest-sub-title" } ) + const div_Sub_r = div_Parent.createEl ( 'div', { text: "", cls: 'gistr-suggest-sub-container-r' } ) + div_Sub_r.createEl ( 'div', { text: div_scope, cls: "gistr-suggest-sub-scope" } ) + div_Parent.createEl ( 'div', { text: "", cls: 'gistr-suggest-clear' } ) + div_Sub_l.createEl ( 'div', { text: "", cls: 'gistr-suggest-clear' } ) + div_Sub_l.createEl ( 'div', { text: `Created: ${ date_created }`, cls: "gistr-suggest-sub-time" } ) + } + + /* + Suggestion > Choose + */ + + onChooseSuggestion( gistArray: GistData | null ) + { + this.onSubmit( gistArray ).then( ( ) => this.close( ) ) + } +} + +/* + Github Gist > Get + + Initialized by Obsidian command palette and right-click menu +*/ + +export const GHGistGet = ( args: ArgsGet ) => async ( ) => +{ + + const { is_public, app, plugin } = args + const token = GHTokenGet( ) + const repoTarget = is_public ? lng( "lst_repotype_pub" ) : lng( "lst_repotype_pri" ) + const + { + sy_enable_autoupdate, + sy_add_frontmatter, + notitime + } = await SettingsGet( plugin ) + + /* + User token not specified in settings + */ + + if ( !token ) + { + new Notice( lng( "err_gist_token_missing" ), notitime * 1000 ) + return + } + + /* + Current view + */ + + if ( !app.workspace.getActiveViewOfType( MarkdownView ) ) + { + new Notice( lng( "gist_upload_no_active_file" ), notitime * 1000 ) + return + } + + /* + Continue fetching gist information + */ + + const getView = app.workspace.getActiveViewOfType( MarkdownView ) + const file = getView.file.name + const editor = getView.editor + const noteOrig = editor.getValue( ) + const ExistingGist = FindExistingGist( noteOrig ).filter( ( gistArray ) => gistArray.is_public === is_public ) + const gistContent = sy_add_frontmatter ? noteOrig : FrontmatterPrepare( noteOrig ) + + if ( ExistingGist.length && sy_enable_autoupdate ) + { + + new SelectExistingModal + ( + app, plugin.settings, ExistingGist, true, async ( gistArray ) => + { + + let output = null + + /* + Update or Create + */ + + if ( !gistArray ) + output = await Create( { file, content: gistContent, token, is_public } ) + else + output = await Update( { gistArray, token, content: gistContent } ) + + /* + API call failed + */ + + if ( process.env.ENV === "dev" ) + console.log( output ) + + if ( output.status === Status.fail ) + { + const error_msg = output.errorMessage + const bNotFound = error_msg.toLowerCase( ).includes( "not found" ) + + if ( bNotFound ) + new Notice( lng( "gist_not_found" ), notitime * 1000 ) + + new Notice( lng( "gist_upload_fail_api", output.errorMessage ), notitime * 1000 ) + return + } + + /* + Copy to clipboard + */ + + navigator.clipboard.writeText( output.gistArray.url ) + + /* + API call Success + */ + + new Notice( lng( "gist_copy_success_file", repoTarget ), notitime * 1000 ) + const editor_newvalue = InsertFrontmatter( output.gistArray, noteOrig ) + + if ( process.env.ENV === "dev" ) + console.log( "GHGistGet -> Insert into editor" ) + + editor.setValue( editor_newvalue ) + + }, + ).open( ) + } + else + { + const result = await Create( { file, content: gistContent, token, is_public } ) + + if ( process.env.ENV === "dev" ) + console.log( result ) + + /* + Failure + */ + + if ( result.status !== Status.success ) + new Notice( lng( "gh_status_error_api", result.errorMessage ) ) + + /* + Success + */ + + navigator.clipboard.writeText( result.gistArray.url ) + const repo_type = is_public ? lng( "lst_repotype_pub" ) : lng( "lst_repotype_pri" ) + new Notice( lng( "gist_copy_success_file", repo_type ), notitime * 1000 ) + + /* + Autosave Feature + */ + + if ( sy_enable_autoupdate ) + { + const contentResult = InsertFrontmatter( result.gistArray, noteOrig ) + + /* + Create -> Insert frontmatter text into editor window + */ + + if ( process.env.ENV === "dev" ) + console.log( "Create -> Insert into editor" ) + + app.vault.modify( getView.file, contentResult ) + editor.refresh( ) + } + + } +} + +/* + Github Gist > Copy + + Copies a gist url to the user's clipboard +*/ + +export const GHGistCopy = ( args: ArgsCopy ) => async ( ) => +{ + const { app, plugin } = args + const { sy_enable_autoupdate, notitime } = await SettingsGet( plugin ) + + if ( !sy_enable_autoupdate ) + return new Notice( lng( "gist_upload_req_allowupload" ), notitime * 1000 ) + + const getView = app.workspace.getActiveViewOfType( MarkdownView ) + + /* + No active file focus + */ + + if ( !getView ) + return new Notice( "gist_no_active_file" ) + + const editor = getView.editor + const noteOrig = editor.getValue( ) + const ExistingGist = FindExistingGist( noteOrig ) + + /* + Nothing to copy + */ + + if ( ExistingGist.length === 0 ) + return new Notice( lng( "gist_copy_fail_notagist" ), notitime * 1000 ); + + /* + Obsidian note only has one gist + */ + + if ( ExistingGist.length === 1 ) + { + const gistArray = ExistingGist[ 0 ] + navigator.clipboard.writeText( gistArray.url ) + + return new Notice( lng( "gist_copy_success" ), notitime * 1000 ) + } + + /* + Obsidian note has more than one gist associated to it. + will display the suggestion box and allow the user to select + which note they wish to copy the url for. + + such examples include making a gist both public and secret + */ + + new SelectExistingModal + ( + app, plugin.settings, ExistingGist, false, async ( gistArray ) => + { + navigator.clipboard.writeText( gistArray.url ) + new Notice( lng( "gist_copy_success" ), notitime * 1000 ) + }, + ).open( ) +} + +/* + Gist > Update Existing + + this process updates an existing github gist. + the user must have already manually saved the gist. + + if autosave is enabled, this will be ran every x seconds to ensure + the contents of the note are updated to a gist online. +*/ + +export const GHGistUpdate = async ( args: ParamsAutosave ) => +{ + const { plugin, file, note_full: note_full } = args + const { sy_add_frontmatter, sy_enable_autosave_notice, notitime } = await SettingsGet( plugin ) + + /* + User token not specified in settings + */ + + const token = GHTokenGet( ) + + if ( !token ) + return new Notice( lng( "err_gist_token_missing" ), notitime * 1000 ) + + /* + Find existing notes + */ + + const note_existing = FindExistingGist( note_full ) + const content = sy_add_frontmatter ? note_full : FrontmatterPrepare( note_full ) + + /* + Validate + */ + + if ( !note_existing.length ) return + + for ( const gistArray of note_existing ) + { + const res = await Update( { gistArray, token, content } ) + + if ( res.status !== Status.success ) + { + + /* + Update Failed + */ + + new Notice( lng( "gist_upload_fail_api", res.errorMessage ), notitime * 1000 ) + } + else + { + + /* + Update Success + */ + + const note_updated = InsertFrontmatter( res.gistArray, note_full ) + await file.vault.adapter.write( file.path, note_updated ) + + /* + Save Notice Enabled + */ + + if ( sy_enable_autosave_notice ) + new Notice( lng( "gist_upload_success", file.path ), notitime * 1000 ) + } + } +} + \ No newline at end of file diff --git a/src/backend/services/Opengist.ts b/src/backend/services/Opengist.ts new file mode 100644 index 00000000..84a92acf --- /dev/null +++ b/src/backend/services/Opengist.ts @@ -0,0 +1,19 @@ +const GISTR_OGIST_PAT = 'gistr_opengist_pat' + +/* + OpenGist > Personal Access Token > Set +*/ + +export const OGTokenSet = ( token: string ): void => +{ + localStorage.setItem( GISTR_OGIST_PAT, token ) +} + +/* + OpenGist > Personal Access Token > Get +*/ + +export const OGTokenGet = ( ): string => +{ + return localStorage.getItem( GISTR_OGIST_PAT ) +} \ No newline at end of file diff --git a/src/backend/services/index.ts b/src/backend/services/index.ts new file mode 100644 index 00000000..62d57bb4 --- /dev/null +++ b/src/backend/services/index.ts @@ -0,0 +1,2 @@ +export { GHTokenSet, GHTokenGet, GHGistGet, GHGistCopy, GHGistUpdate, GHStatusAPI } from 'src/backend/services/Github' +export { OGTokenSet, OGTokenGet } from 'src/backend/services/Opengist' \ No newline at end of file diff --git a/src/lang/index.ts b/src/lang/index.ts new file mode 100644 index 00000000..2f829cbf --- /dev/null +++ b/src/lang/index.ts @@ -0,0 +1,38 @@ +/* + Languages Helper +*/ + +import { moment } from "obsidian" +import en from "./locale/en" + +/* + Language entries +*/ + +const SetupLocale: { [ i: string ]: Partial< typeof en > } = +{ + en, +} + +/* + get locale val +*/ + +const locale = SetupLocale[ moment.locale( ) ] + +/* + Language Method +*/ + +export function lng( item: keyof typeof en, ...args: string[] ) : string +{ + if ( !locale ) + console.error( "Gistr language not found", moment.locale( ) ) + + let val = ( locale && locale[ item ] ) || en[ item ] + return val.replace( /{(\d+)}/g, ( match, index ) => + { + const replace = args[ index ] + return typeof replace !== 'undefined' ? replace : match + } ) +} \ No newline at end of file diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts new file mode 100644 index 00000000..c4637632 --- /dev/null +++ b/src/lang/locale/en.ts @@ -0,0 +1,298 @@ +/* + @locale : English (en) +*/ + +export default +{ + + /* + Base entries + */ + + base_underdev_title: 'Feature Under Development', + base_underdev_msg: 'I am currently working with the developer of OpenGist to make minor changes to how OpenGist pastes appear, including moving the "view raw" button to the bottom so that Obsidian\'s edit button does not overlap.', + base_opt_enabled: 'Enabled', + base_opt_disabled: 'Disabled', + base_theme_light: 'Light', + base_theme_dark: 'Dark', + base_time_am: 'AM', + base_time_pm: 'PM', + base_component_reset: 'Reset to Default', + base_debug_loading: 'Loading {0} v{1} [ {2} ]', + base_debug_updater_1: '{0} Update Check', + base_debug_updater_2: '{0} {1}', + base_context_nofocus: 'Obsidian does not have focus, please open a file', + + /* + Context menu options + */ + + cfg_context_gist_public: 'Save Gist (Github Public)', + cfg_context_gist_secret: 'Save Gist (Github Secret)', + cfg_context_gist_copy: 'Copy Gist URL', + + /* + Tab > Settings > Header + */ + + cfg_modal_desc: 'Gistr allows you to embed gists directly from Github and Opengist. You can also turn your notes into gists which can be updated manually, or monitored with autosave. For a detailed set of examples, view the demo vault in the support section below.', + cfg_modal_expand: 'Expand', + + /* + Tab > Settings > General + */ + + cfg_tab_ge_title: 'Global', + cfg_tab_ge_header: 'These settings affect all aspects of this plugin, including both Opengist and Github. If you change the trigger keyword, ensure you go back through your existing gist snippets and change the keyword at the top of each codeblock; otherwise embedded gists will not appear.', + cfg_tab_sy_title: 'Save / Sync', + cfg_tab_sy_header: 'These settings allow you to create gists from your notes. The contents of your notes will be directly uploaded to Github under an existing account. You may also choose to create only new notes, or manage new and existing.', + cfg_tab_og_title: 'OpenGist', + cfg_tab_og_header: 'Opengist is a self-hosted pastebin powered by Git. All snippets are stored in a Git repository and can be read and/or modified using standard Git commands, or with the web interface. It is similiar to GitHub Gist, but open-source and is self-hosted. OpenGist supports Windows, Linux, and MacOS.', + cfg_tab_gh_title: 'Github', + cfg_tab_gh_header: 'Github Gists let you store and distribute code snippets without setting up a full-fledged repository. Store snippets such as strings, bash scripts, markdown, text files, and other small pieces of data.', + cfg_tab_sp_title: 'Support', + cfg_tab_ge_keyword_name: 'Trigger keyword', + cfg_tab_ge_keyword_desc: 'Word to use inside codeblocks to designate as a portal for showing gists', + cfg_tab_ge_theme_name: 'Theme', + cfg_tab_ge_theme_desc: 'This determines what color scheme will be used for gists. You can however, customize the colors in the Github and OpenGist categories below.

Note: When this is changed, place your cursor in the codeblock and then leave the codeblock to refresh it. Automatic refreshing only works in reading mode', + cfg_tab_ge_wrap_name: 'Text wrapping', + cfg_tab_ge_wrap_desc: 'If enabled, text will wrap to the next line. If disabled, you will see a horizontal scrollbar. This does not include gists that have no spaces anywhere in the body.', + cfg_tab_ge_noti_dur_name: 'Notification duration', + cfg_tab_ge_noti_dur_desc: 'How long a notification will display for (in seconds). Set to 0 to keep notification up until user dismisses it.', + cfg_tab_ge_noti_update_name: 'Enable Gistr update notifications', + cfg_tab_ge_noti_update_desc: 'Enabled: When launching Obsidian, you will get a notification if a new version of Gistr is available. This includes beta releases not available to the public yet.

Disabled: You will not get any notifications alerting you to new Gistr updates. You must manually check or use the Obsidian plugin checker.

Note: This update notification includes beta releases of Gistr. The Obsidian plugin updater does not track beta.', + + /* + Tab > Settings > OpenGist + */ + + cfg_tab_og_cb_light_name: 'Codeblock bg (Light)', + cfg_tab_og_cb_light_desc: 'Color for Github codeblock background color (Light Theme)', + cfg_tab_og_cb_dark_name: 'Codeblock bg (Dark)', + cfg_tab_og_cb_dark_desc: 'Color for Github codeblock background color (Dark Theme)', + cfg_tab_og_sb_light_name: 'Scrollbar track (Light)', + cfg_tab_og_sb_light_desc: 'Color for gist scrollbar track (Light Theme)', + cfg_tab_og_sb_dark_name: 'Scrollbar track (Dark)', + cfg_tab_og_sb_dark_desc: 'Color for gist scrollbar track (Dark Theme)', + cfg_tab_og_tx_light_name: 'Codeblock text (Light)', + cfg_tab_og_tx_light_desc: 'Color for codeblock text color (Light Theme)', + cfg_tab_og_tx_dark_name: 'Codeblock text (Dark)', + cfg_tab_og_tx_dark_desc: 'Color for codeblock text color (Dark Theme)', + cfg_tab_og_opacity_name: 'Codeblock opacity', + cfg_tab_og_opacity_desc: 'Total opacity for codeblock. Do not set this too low, or your codeblocks will be invisible', + cfg_tab_og_pad_top_name: 'Padding: top', + cfg_tab_og_pad_top_desc: 'Padding between gist codeblock header and code.', + cfg_tab_og_pad_btm_name: 'Padding: bottom', + cfg_tab_og_pad_btm_desc: 'Padding between gist codeblock and the bottom scrollbar.', + cfg_tab_og_css_name: 'Custom CSS', + cfg_tab_og_css_desc: 'This textarea allows you to enter custom CSS properties to override existing colors.', + cfg_tab_og_css_pholder: 'Paste CSS here', + + /* + Tab > Settings > Github + */ + + cfg_tab_gh_cb_light_name: 'Codeblock bg (Light)', + cfg_tab_gh_cb_light_desc: 'Color for Opengist codeblock background color (Light Theme)', + cfg_tab_gh_cb_dark_name: 'Codeblock bg (Dark)', + cfg_tab_gh_cb_dark_desc: 'Color for Opengist codeblock background color (Dark Theme)', + cfg_tab_gh_sb_light_name: 'Scrollbar track (Light)', + cfg_tab_gh_sb_light_desc: 'Color for gist scrollbar track (Light Theme)', + cfg_tab_gh_sb_dark_name: 'Scrollbar track (Dark)', + cfg_tab_gh_sb_dark_desc: 'Color for gist scrollbar track (Dark Theme)', + cfg_tab_gh_tx_light_name: 'Codeblock text (Light)', + cfg_tab_gh_tx_light_desc: 'Color for codeblock text color (Light Theme)', + cfg_tab_gh_tx_dark_name: 'Codeblock text (Dark)', + cfg_tab_gh_tx_dark_desc: 'Color for codeblock text color (Dark Theme)', + cfg_tab_gh_opacity_name: 'Codeblock opacity', + cfg_tab_gh_opacity_desc: 'Total opacity for codeblock. Do not set this too low, or your codeblocks will be invisible', + cfg_tab_gh_css_name: 'Custom CSS', + cfg_tab_gh_css_desc: 'This textarea allows you to enter custom CSS properties to override existing colors.', + cfg_tab_gh_css_pholder: 'Paste CSS here', + cfg_tab_gh_pat_name: 'Personal access token', + cfg_tab_gh_pat_desc: 'The personal access token (PAT) generated on Github.com which allows you to write gists from your Obsidian vault to Github gist.', + cfg_tab_gh_pat_pholder: 'githubpat_XXXXXX', + cfg_tab_gh_pat_btn_tip_state_show: 'Show token', + cfg_tab_gh_pat_btn_tip_state_hide: 'Hide token', + cfg_tab_gh_pat_desc_l1: 'This token allows you to authenticate with the GitHub API.
Create Token: here', + cfg_tab_gh_pat_desc_l2: 'For this to function with secret gists, select "All repositories" or "Only select repositories" from the dropdown on the Github Token page. The token must have at least the following permissions:', + cfg_tab_gh_pat_perm_1: 'Account Permissions ► Gists                                                                          Read-and-write', + cfg_tab_gh_pat_perm_2: 'Repository Permissions ► Pull Requests                                      Read-only', + cfg_tab_gh_pat_perm_3: 'Repository Permissions ► Contents                                                    Read-only', + cfg_tab_gh_pat_perm_4: 'Repository Permissions ► Issues                                                              Read-only', + cfg_tab_gh_pat_footer: 'Github icon to the right will turn into a checkmark when you\'ve entered a valid token.', + cfg_tab_gh_pat_help: 'What is this for? Read the docs', + cfg_tab_gh_pat_url_btn: 'https://github.com/settings/tokens?type=beta', + cfg_tab_gh_pat_ok_btn_tip: 'Valid Github API Token', + cfg_tab_gh_pat_bad_btn_tip: 'Invalid Github API Token entered\n\nClick here to generate one', + cfg_tab_gh_pat_invalid_btn_tip: 'Github API token is not valid, ensure you type it correctly\n\nClick here to generate one', + cfg_tag_gh_pat_notice_msg_success: 'Gistr has detected a valid Github personal access token which has been saved', + cfg_tag_gh_pat_notice_msg_cleared: 'Personal access token cleared', + cfg_tab_gh_pat_notice_type_fine: 'Fine-Grained Github Token Detected', + cfg_tab_gh_pat_notice_type_classic: 'Classic Github Token Detected', + + /* + Tab > Settings > Support + */ + + cfg_tab_su_desc: 'The following buttons are associated to useful resources for this plugin.', + cfg_tab_su_gs_name: 'Introduction', + cfg_tab_su_gs_desc: 'View brief introduction to getting started with this plugin', + cfg_tab_su_gs_btn: 'Open', + cfg_tab_su_repo_label: 'Plugin repo', + cfg_tab_su_repo_url: 'https://github.com/Aetherinox/obsidian-gistr', + cfg_tab_su_repo_btn: 'View', + cfg_tab_su_vault_label: 'Plugin demo vault', + cfg_tab_su_vault_url: 'https://github.com/Aetherinox/obsidian-gistr/tree/main/tests/gistr-vault', + cfg_tab_su_vault_btn: 'View', + cfg_tab_su_ogrepo_label: 'OpenGist: download', + cfg_tab_su_ogrepo_url: 'https://github.com/thomiceli/opengist/releases', + cfg_tab_su_ogrepo_btn: 'View', + cfg_tab_su_ogdocs_label: 'OpenGist: docs', + cfg_tab_su_ogdocs_url: 'https://github.com/thomiceli/opengist/blob/master/docs/index.md', + cfg_tab_su_ogdocs_btn: 'View', + cfg_tab_su_ogdemo_label: 'OpenGist: demo', + cfg_tab_su_ogdemo_url: 'https://opengist.thomice.li/all', + cfg_tab_su_ogdemo_btn: 'View', + cfg_tab_su_gist_label: 'Github gist', + cfg_tab_su_gist_url: 'https://gist.github.com/', + cfg_tab_su_gist_btn: 'View', + cfg_tab_su_ver_cur_name: 'Current version', + cfg_tab_su_ver_cur_desc: 'Current running version of Gistr', + cfg_tab_su_guid_cur_name: 'GUID', + cfg_tab_su_guid_cur_desc: 'Gistr plugin release', + cfg_tab_su_guid_btn_tip: 'Copy to clipboard', + cfg_tab_su_guid_notice: 'Copied Gistr GUID to clipboard\n\n{0}', + cfg_tab_su_uuid_cur_name: 'UUID', + cfg_tab_su_uuid_cur_desc: 'Unique id for your current running release of Gistr', + cfg_tab_su_uuid_btn_tip: 'Copy to clipboard', + cfg_tab_su_uuid_notice: 'Copied Gistr release UUID to clipboard\n\n{0}', + cfg_tab_su_ver_stable: 'Latest stable release ', + cfg_tab_su_ver_beta: 'Latest beta release ', + cfg_tab_su_ver_connection_issues: 'Server communication failed', + cfg_tab_su_ver_status_checking: 'Checking for newer version of Gistr', + cfg_tab_su_ver_status_checking_btn_tip: 'Checking for newer version of Gistr', + cfg_tab_su_ver_status_updated_btn_tip: 'You are running the latest version of Gistr', + cfg_tab_su_ver_status_new_stable_btn_tip: 'A newer stable release of Gistr is available', + cfg_tab_su_ver_status_new_beta_btn_tip: 'A newer beta release of Gistr is available', + cfg_tab_su_ver_status_error_btn_tip: 'Could not communicate with the gistr server, retrying later', + cfg_tab_su_ver_releases: 'https://github.com/Aetherinox/obsidian-gistr/releases', + + /* + Tab > Sync + */ + + cfg_tab_sy_list_icon_name: 'Gist list icon color', + cfg_tab_sy_list_icon_desc: 'Color for icon in gist save list', + + cfg_tab_sy_tog_allow_gist_updates_name: 'Allow updating gists', + cfg_tab_sy_tog_allow_gist_updates_desc: 'Enabled: After you initially create a new gist, the note can be updated with newer revisions.

Disabled: Gists can only be created; no updates are allowed.

To update a gist after enabling this setting, right-click on the note, or open the Obsidian command palette and select Save Gist', + cfg_tab_sy_tog_allow_gist_updates_tip: '', + + cfg_tab_sy_tog_autosave_enable_name: 'Enable autosave', + cfg_tab_sy_tog_autosave_enable_desc: 'Enabled: This will allow gists to be updated once they are created. It will also enable autosaving which will detect new changes and push them.

Disabled: You will only be able to create gists by manually doing so; there will be no way to update them.

If you wish to keep this disabled, you can create gists by right-clicking in the note and selecting Save Gist. Or opening your command palette and selecting the save option from there.', + cfg_tab_sy_tog_autosave_enable_tip: '', + + cfg_tab_sy_tog_autosave_strict_name: 'Enable autosave strict saving', + cfg_tab_sy_tog_autosave_strict_desc: 'Enabled: Your notes will be saved to the gist service precisely on time every {0} seconds, whether you are still typing or not.

Disabled: Time until save will not start until you have finished typing in that note. If you continue typing, the saving countdown will not start until your final key is pressed.

Autosave duration can be modified further down in these settings.', + cfg_tab_sy_tog_autosave_strict_tip: '', + + cfg_tab_sy_tog_autosave_noti_name: 'Enable autosave notices', + cfg_tab_sy_tog_autosave_noti_desc: 'Each time your note is saved automatically, a notice will appear on-screen informing you of the action. This only works if Autosave is enabled.', + cfg_tab_sy_tog_autosave_noti_tip: '', + + cfg_tab_sy_num_save_dur_name: 'Autosave duration', + cfg_tab_sy_num_save_dur_desc: 'How often autosave will execute in seconds. Set this to a fair amount so that the calls aren\'t being ran excessively to the gist API server (Github or OpenGist).

The save countdown timer will begin shortly after you stop typing.

If you wish to change this to save precisely every {0} seconds, enable the setting Autosave Strict Saving located above.', + + cfg_tab_sy_tog_inc_fm_name: 'Include frontmatter', + cfg_tab_sy_tog_inc_fm_desc: 'When saving a note as a new gist, frontmatter will be added to the top of your note with information about the gist.

Enabled: the note will be cleaned before it is pushed to the gist service and no frontmatter fields will be present in the online version.

Disabled: frontmatter added to your notes will be included when your note is pushed to a gist service.

Frontmatter can be found at the very top of each note, in-between `---` ', + cfg_tab_sy_tog_inc_fm_tip: 'Frontmatter starts with three hyphens `---`', + + /* + Gists + */ + + gist_upload_req_allowupload: 'Must enable \"Allow Uploading Gists\" in the Gistr settings before you can use this command.', + gist_upload_no_active_file: 'No active file present. Open a note in Obsidian before continuing.', + gist_copy_fail_notagist: 'No URL to copy. You must turn your note into a gist first.', + gist_copy_success_file: `Copied {0} URL to your clipboard`, + gist_copy_success: 'Copied gist URL to clipboard.', + gist_upload_fail_api: 'GitHub API error: {0}', + gist_upload_success: 'File {0} has been updated successfully to your gist service.', + gist_status_operational_raw: 'operational', + gist_status_connecting: 'connecting ...', + gist_status_connected: 'Connected to API ...', + gist_status_no_api: 'Github token missing, no connection', + gist_status_no_api_btn_tip: 'You must create and specify a Github API token.\n\nAborting connection', + gist_status_noconnection: 'Failed to communicate with Github', + gist_status_degraded_performance: 'Degraded Performance', + gist_status_partial_outage: 'Partial Outage', + gist_status_major_outage: 'Major Outage', + gist_status_issues: 'service issues', + gist_status_connecting_btn_tip: 'Connecting to Github ...', + gist_status_success_btn_tip: 'Connected to Github API', + gist_status_issues_btn_tip: 'Github API is currently experiencing issues\n\nClick to view details.', + gist_btn_create_new: 'Create New Gist', + gist_not_found: `Could not locate the specified Gist. Did you possibly delete it from Github?\n\nTo update this note as a new gist, remove the frontmatter text at the top of the note.`, + + /* + Getting Started + */ + + gs_base_header: 'This plugin allows you to integrate both OpenGist and Github Gist pastes within your Obsidian notes. To use this plugin, you can either create a new Github gist, or setup your own OpenGist server. OpenGist is free, and takes only minutes to configure.', + gs_og_btn_repo: 'Download OpenGist', + gs_og_btn_docs: 'OpenGist Docs', + gs_og_sub_1: 'Once you install and set up OpenGist, you can sign in to your OpenGist website and create your first Gist. After your Gist is created, return to your Obsidian node, and integrate your Gist into your note using code similar to the following:', + gs_og_name: 'OpenGist integration', + gs_og_desc: 'OpenGist supports Windows, Linux, MacOS, and Docker. To download and set up OpenGist, click below.', + gs_gh_name: 'Github integration', + gs_gh_desc: 'To paste a Github Gist into your note, use a command similar to the following examples:', + gs_btn_settings_open: 'Open Settings', + gs_btn_close: 'Close', + + /* + Bithub + */ + + gh_status_error_api: 'Github API Error: {0}', + + /* + Version Updates + */ + + ver_update_stable: 'An update is available for the Gistr plugin. Update to check out the latest features!', + ver_update_beta: 'A new beta release is available for the Gistr plugin. Update to check out the latest features coming to stable!', + ver_url: 'https://raw.githubusercontent.com/Aetherinox/obsidian-gistr/{0}/package.json', + + /* + Element > Color Picker + */ + + pickr_dialog: 'Color Picker', + pickr_swatch: 'Color Swatch', + pickr_toggle: 'Pick Color', + pickr_last: 'Use Last Color', + pickr_save: 'Save', + pickr_cancel: 'Cancel', + pickr_clear: 'Clear', + pickr_restore_default_btn_tip: 'Restore default color', + pickr_dev_unknown: 'Gistr: Unknown color format: {0}', + + /* + Gist Load Error + */ + + err_gist_token_missing: 'Github API token missing. Open the Gistr plugin settings, click the Github tab, and enter your token. Instructions are found on the settings page.', + err_gist_loading_fail_name: '⚠️ Gistr: Failed to load the specified gist:', + err_gist_loading_fail_resp: '{0}', + err_gist_loading_fail_detail: 'Could not load a valid Javascript from gist url: {0}', + err_gist_loading_fail_url: 'Could not find gist id -- Make sure correct URL is specified. {0}', + + /* + Gist List + */ + + lst_repotype_pub: 'Public', + lst_repotype_pri: 'Secret', + +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 00000000..2ca946d5 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,341 @@ +/* + Import + + @note : semver has issues with rollup. do not import semver's entire package. + import the methods you need individually, otherwise you'll receive circular dependencies error. +*/ + +import { App, Plugin, WorkspaceLeaf, Debouncer, debounce, TFile, Menu, MarkdownView, PluginManifest, Notice, requestUrl } from 'obsidian' +import { GistrSettings, SettingsGet, SettingsDefaults, SettingsSection } from 'src/settings/' +import { BackendCore } from 'src/backend' +import { GHGistGet, GHGistCopy, GHGistUpdate } from 'src/backend/services' +import { Env, PID, FrontmatterPrepare, GistrAPI, GistrEditor } from 'src/api' +import { lng } from 'src/lang' +import ModalGettingStarted from "src/modals/GettingStartedModal" +import ShowContextMenu from 'src/menus/context' +import lt from 'semver/functions/lt' +import gt from 'semver/functions/gt' + +/* + Basic Declrations +*/ + +const AppBase = 'app://obsidian.md' + +/* + Extend Plugin +*/ + +export default class GistrPlugin extends Plugin +{ + readonly plugin: GistrPlugin + readonly api: GistrAPI + readonly editor: GistrEditor + // private think_last = +new Date( ) + // private think_now = +new Date( ) + private bLayoutReady = false + settings: GistrSettings + + constructor( app: App, manifest: PluginManifest ) + { + super( app, manifest ) + Env._Initialize( app, manifest ) + } + + /* + Rehash Reading View + */ + + renderModeReading( ): void + { + this.app.workspace.iterateRootLeaves( ( leaf: WorkspaceLeaf ) => + { + if ( leaf.view instanceof MarkdownView && leaf.view.getMode( ) === "preview" ) + leaf.view.previewMode.rerender( true ) + } ) + } + + /* + Development use re-rendering + */ + + async renderDevelopment( ) + { + for ( const leaf of this.app.workspace.getLeavesOfType( 'markdown' ) ) + { + const view = leaf.view as MarkdownView + const state = view.getState( ) + const etateEph = view.getEphemeralState( ) + + view.previewMode.rerender( true ) + + const editor = view.editor + editor.setValue ( editor.getValue( ) ) + + if ( state.mode === 'preview' ) + { + state.mode = 'source' + await view.setState( state, { history: false } ) + state.mode = 'preview' + await view.setState( state, { history: false } ) + } + + view.setEphemeralState( etateEph ) + } + } + + /* + Settings > Load + */ + + async onload( ) + { + console.debug( lng( "base_debug_loading", process.env.NAME, process.env.PLUGIN_VERSION, process.env.AUTHOR ) ) + + await this.loadSettings ( ) + this.addSettingTab ( new SettingsSection( this.app, this ) ) + + this.app.workspace.onLayoutReady( async ( ) => + { + if ( this.settings.firststart === true ) + { + const opt_selected = await new ModalGettingStarted( this.app, this.plugin, this.manifest, this.settings, true ).openAndAwait( ) + if ( opt_selected === "settings-open" ) + { + + /* + open settings + */ + + // @ts-ignore + this.app.setting.open( this.manifest.id ) + // @ts-ignore + this.app.setting.openTabById( this.manifest.id ) + } + + this.settings.firststart = false + this.saveSettings( ) + } + } ) + + /* + Command Palette Items + */ + + this.addCommand + ( + { + id: 'gistr-github-gist-public-save', + name: lng( "cfg_context_gist_public" ), + editorCallback: GHGistGet( { plugin: this, app: this.app, is_public: true } ) + } + ) + + this.addCommand + ( + { + id: 'gistr-github-gist-secret-save', + name: lng( "cfg_context_gist_secret" ), + callback: GHGistGet( { plugin: this, app: this.app, is_public: false } ), + } + ) + + this.addCommand + ( + { + id: 'gistr-github-gist-copy', + name: lng( "cfg_context_gist_copy" ), + callback: GHGistCopy( { plugin: this, app: this.app } ), + } + ) + + /* + Gist > Monitor Changes + */ + + this.gistMonitorChanges( ) + + /* + Register Events + */ + + const gistBackend = new BackendCore( this.settings ) + this.registerDomEvent ( window, "message", gistBackend.messageEventHandler ) + this.registerMarkdownCodeBlockProcessor ( this.settings.keyword, gistBackend.processor ) + this.registerEvent ( this.app.workspace.on( "editor-menu", this.GetContextMenu ) ) + + /* + Version checking + */ + + if ( this.settings.ge_enable_updatenoti ) + { + this.versionCheck( ) + } + } + + /* + Unload + */ + + async onunload( ) + { + console.debug( "Unloaded " + this.manifest.name ) + } + + /* + Gist Saving > Monitor for Changes + */ + + gistMonitorChanges( ) + { + + const note_previous: Record< string, string > = { } + const denounce_register: Record< string, Debouncer< [ string, TFile ], Promise< Notice > > > = { } + + let last = +new Date( ) + this.app.vault.on( 'modify', async( file: TFile ) => + { + /* + this.think_now = +new Date( ) + if ( this.think_now - this.think_last < ( this.settings.sy_save_duration * 1000 ) ) + { + if ( process.env.ENV === "dev" ) + console.log( "gistMonitorChanges.modify on cooldown" ) + + return + } + */ + + /* + Get note contents with frontmatter + */ + + const note_full = await file.vault.adapter.read( file.path ) + const note_raw = FrontmatterPrepare( note_full ) + + /* + Strip Frontmatter from note contents and leave just the note body + */ + + if ( note_raw === note_previous[ file.path ] ) + { + return + } + + /* + Store current copy of note to record for comparison the next time the note is changed. + */ + + note_previous[ file.path ] = note_raw + + /* + Initialize bouncer + */ + + if ( !denounce_register[ file.path ] ) + { + if ( process.env.ENV === "dev" ) + console.log( "gistMonitorChanges.modify: Denouncer does not exist, creating" ) + + denounce_register[ file.path ] = debounce( async ( note_full: string, file: TFile ) => + await GHGistUpdate( { plugin: this, app: this.app, note_full, file } ), this.settings.sy_save_duration * 1000, this.settings.sy_enable_autosave_strict ) + } + + const { sy_enable_autosave } = await SettingsGet( this ) + if ( sy_enable_autosave ) + { + + await denounce_register[ file.path ]( note_full, file ) + + if ( process.env.ENV === "dev" ) + console.log( "gistMonitorChanges.modify: Autosave Denouncer" ) + + /* + if ( now - last > ( this.settings.sy_save_duration * 1000 ) ) + { + last = now + await denounce_register[ file.path ]( note_full, file ) + + if ( process.env.ENV === "dev" ) + console.log( "gistMonitorChanges.modify: Autosave Denouncer" ) + } + */ + } + } ) + } + + /* + Right-click context menu + */ + + GetContextMenu = ( menu: Menu, editor: GistrEditor ): void => + { + ShowContextMenu( this, this.settings, this.api, menu, editor ) + } + + /* + Settings > Load + */ + + async loadSettings( ) + { + this.settings = Object.assign( { }, SettingsDefaults, await this.loadData( ) ) + } + + /* + Settings > Save + */ + + async saveSettings( ) + { + await this.saveData( this.settings ) + } + + /* + Check for newer versions + + utilizes env variables in rollup.config.js + */ + + async versionCheck( ) + { + const ver_running = this.manifest.version + const ver_stable = await requestUrl( lng( "ver_url", "main" ) ).then( async ( res ) => + { + if ( res.status === 200 ) + { + const resp = await res.json + return resp.version + } + } ) + + const ver_beta = await requestUrl( lng( "ver_url", "beta" ) ).then( async ( res ) => + { + if ( res.status === 200 ) + { + const resp = await res.json + return resp.version + } + } ) + + /* + Output notice to user on possible updates + */ + + console.debug( lng( "base_debug_updater_1", process.env.NAME ) ) + console.debug( lng( "base_debug_updater_2", "Current : ..... ", ver_running ) ) + console.debug( lng( "base_debug_updater_2", "Stable : ..... ", ver_stable ) ) + console.debug( lng( "base_debug_updater_2", "Beta : ..... ", ver_beta ) ) + + if ( gt( ver_beta, ver_stable ) && lt( ver_running, ver_beta ) ) + { + new Notice( lng( "ver_update_beta" ), 0 ) + } + else if ( lt( ver_beta, ver_stable ) && lt( ver_running, ver_stable ) ) + { + new Notice( lng( "ver_update_stable" ), 0 ) + } + } + +} \ No newline at end of file diff --git a/src/menus/context.ts b/src/menus/context.ts new file mode 100644 index 00000000..89cede71 --- /dev/null +++ b/src/menus/context.ts @@ -0,0 +1,86 @@ +import { Menu } from "obsidian" +import GistrPlugin from "src/main" +import { GistrSettings } from 'src/settings/' +import { GHGistGet } from 'src/backend/services' +import { GistrAPI, GistrEditor } from "src/api/Types" +import { lng } from 'src/lang' + +/* + Interface +*/ + +interface Element +{ + dom?: HTMLDivElement +} + +/* + Context Menu +*/ + +export default function ShowContextMenu( plugin: GistrPlugin, settings: GistrSettings, api: GistrAPI, menu: Menu, editor: GistrEditor ): void +{ + + /* + Context Menu Separator + */ + + menu.addItem( ( item ) => + { + const elm = ( item as Element ).dom as HTMLElement + elm.addClass( "menu-separator" ) + // required to make separator not hoverable + elm.setAttribute( "style", "background-color: transparent" ) + } ) + + /* + Text Selection + */ + + const selection = editor.getSelection( ) + + /* + Context Menu > Item > Gistr Github (Public) + */ + + menu.addItem( ( item ) => + { + const elm = ( item as Element ).dom as HTMLElement + elm.addClass( "gistr-button" ) + item + .setTitle( lng( "cfg_context_gist_public" ) ) + .setIcon( "github" ) + .onClick( async ( e ) => + { + await GHGistGet( + { + plugin: plugin, + app: plugin.app, + is_public: true + } )( ) + } ) + } ) + + /* + Context Menu > Item > Gistr Github (Secret) + */ + + menu.addItem( ( item ) => + { + const elm = ( item as Element ).dom as HTMLElement + elm.addClass( "gistr-button" ) + item + .setTitle( lng( "cfg_context_gist_secret" ) ) + .setIcon( "github" ) + .onClick( async ( e ) => + { + await GHGistGet( + { + plugin: plugin, + app: plugin.app, + is_public: false + } )( ) + } ) + } ) + +} \ No newline at end of file diff --git a/src/menus/menu.ts b/src/menus/menu.ts new file mode 100644 index 00000000..dbdac84d --- /dev/null +++ b/src/menus/menu.ts @@ -0,0 +1,53 @@ +import { Menu, Notice } from 'obsidian' +import { GistrSettings } from 'src/settings/' +import { GistrAPI, GistrEditor, ContextMenu, Coords } from 'src/api' +import { lng } from 'src/lang' + +/* + Context Menu +*/ + +const ContextMenu = ( app: GistrAPI, cfg: GistrSettings, editor: GistrEditor ): void => +{ + + if ( !editor || !editor.hasFocus() ) + throw new Notice( lng( "base_context_nofocus" )) + + let coords: Coords + const cursor = editor.getCursor( "from" ) + const menu = new Menu( ) as unknown as ContextMenu + + const elm = menu.DOM + elm.addClass( "gistr-container" ) + + cfg.context_sorting.forEach( ( obj ) => + { + menu.addItem( ( menuItem ) => + { + menuItem.setTitle( obj ) + menuItem.setIcon( `gistr-${ obj }`.toLowerCase( ) ) + menuItem.onClick( ( ) => + { + app.Commands.RunCommandByID( `gistr-plugin:${ obj }` ) + }) + }) + }) + + if ( editor.coordsCur ) + coords = editor.coordsCur( true, "window" ) + + else if ( editor.coordsPos ) + { + const offset = editor.posToOffset( cursor ) + coords = editor.cm.coordsPos?.( offset ) ?? editor.coordsPos( offset ) + } + else return + + menu.showAtPosition( + { + x: coords.right + 25, + y: coords.top + 20, + } ) +} + +export default ContextMenu \ No newline at end of file diff --git a/src/modals/GettingStartedModal.ts b/src/modals/GettingStartedModal.ts new file mode 100644 index 00000000..27ab3132 --- /dev/null +++ b/src/modals/GettingStartedModal.ts @@ -0,0 +1,293 @@ +/* + Modal > Getting Started +*/ + +import { App, Modal, ButtonComponent, Setting, requestUrl, MarkdownRenderer } from "obsidian" +import GistrPlugin from "src/main" +import { GistrSettings } from 'src/settings/' +import { lng } from 'src/lang' + +/* + Declare Json +*/ + +export interface ManifestJson +{ + id: string + name: string + version?: string + description?: string + author?: string + authorUrl?: string +} + +/* + Modal > Getting Started > Class +*/ + +export default class ModalGettingStarted extends Modal +{ + private resolve: ( ( value: string ) => void ) + private plugin: GistrPlugin + private manifest: ManifestJson + private settings: GistrSettings + private cblk_preview: HTMLElement + private firststart: boolean + + /* + Getting Started > Constructor + */ + + constructor( app: App, plugin: GistrPlugin, manifest: ManifestJson, settings: GistrSettings, bFirstLoad: boolean ) + { + super( app ) + + this.plugin = plugin + this.manifest = manifest + this.settings = settings + this.firststart = bFirstLoad + + } + + /* + Modal > Getting Started > Action > Open & Wait + */ + + openAndAwait( ) + { + return new Promise( ( call ) => + { + this.resolve = call + this.open( ) + } ) + } + + /* + Modal > Getting Started > Action > On Open + */ + + onOpen( ) + { + const { contentEl } = this + + /* + Helper method for handling add each line of content + */ + + const AddLine = ( elmParent: HTMLElement, value: string, htmltag: keyof HTMLElementTagNameMap | null = null, cls: string | null = null ) => + { + if ( htmltag ) + return elmParent.createEl( htmltag, { text: value, cls: cls } ) + else + { + elmParent.appendText( value ) + return elmParent + } + } + + /* + Style main modal + */ + + this.modalEl.classList.add( 'gistr-gs-modal' ) + + /* + Modal > Getting Started > Content > Header + */ + + AddLine( contentEl, ( this.manifest.name || process.env.name ) + " " + "v" + ( this.manifest.version || process.env.PLUGIN_VERSION ), "h2" ) + AddLine( contentEl, lng( "gs_base_header" ), "small" ) + AddLine( contentEl, "", "div", "gistr-gs-separator" ) + + /* + Modal > Getting Started > Content > Getting Started + */ + + AddLine( contentEl, "", "div", "gistr-gs-separator" ) + + const Tab_GH_L = contentEl.createEl( "h3", { text: lng( "gs_gh_name" ), cls: `gistr-gs-header-int-l` } ) + const Tab_GH_R = contentEl.createEl( "h2", { text: " ", cls: `gistr-gs-header-int-r` } ) + const Tab_GH_C = contentEl.createEl( "div", { text: "", cls: `gistr-gs-header-int-c` } ) + contentEl.createEl ( 'small', { cls: "", text: lng( "gs_gh_desc" ) } ) + + /* + Get github api status + */ + + let json_delay = 1 * 1000 + const gh_status = requestUrl( "https://www.githubstatus.com/api/v2/summary.json" ).then( ( res ) => + { + if ( res.status === 200 ) + return res.json.components[ 0 ].status || lng( "gist_status_issues" ) + else + return lng( "gist_status_issues" ) + } ) + + new Setting( Tab_GH_R ) + .addText( async ( text ) => + { + text + .setPlaceholder( lng( "gist_status_connecting" ) ) + .setValue( lng( "gist_status_connecting" ) ) + .setDisabled( true ) + + const controlEl = Tab_GH_R.querySelector( ".setting-item-control" ) + controlEl.addClass( "gistr-settings-status-connecting" ) + + let github_status = await gh_status + + setTimeout( function( ) + { + if ( github_status === lng( "gist_status_operational_raw" ) ) + { + const controlEl = Tab_GH_R.querySelector( ".setting-item-control" ) + controlEl.removeClass ( "gistr-settings-status-connecting" ) + controlEl.addClass ( "gistr-settings-status-success" ) + text.setValue ( lng( "gist_status_connected" ) ) + } + else + { + const controlEl = Tab_GH_R.querySelector(".setting-item-control" ) + controlEl.removeClass ( "gistr-settings-status-connecting" ) + controlEl.addClass ( "gistr-settings-status-warning" ) + text.setValue ( github_status ) + } + }, json_delay ) + } ) + .addExtraButton( async ( btn ) => + { + btn + .setIcon ( 'circle-off' ) + .setTooltip ( lng( "gist_status_connecting_btn_tip" ) ) + + btn.extraSettingsEl.classList.add( "gistr-settings-icon-cur" ) + btn.extraSettingsEl.classList.add( "gistr-anim-spin" ) + btn.extraSettingsEl.classList.add( "gistr-settings-status-connecting" ) + + let github_status = await gh_status + + setTimeout( function( ) + { + if ( github_status === lng( "gist_status_operational_raw" ) ) + { + btn.setIcon ( "github" ) + btn.setTooltip ( lng( "gist_status_success_btn_tip" ) ) + + btn.extraSettingsEl.classList.remove ( "gistr-settings-status-connecting" ) + btn.extraSettingsEl.classList.add ( "gistr-settings-icon-ok" ) + } + else + { + btn.setIcon ( "circle-off" ) + btn.setTooltip ( lng( "gist_status_issues_btn_tip" ) ) + + btn.extraSettingsEl.classList.remove ( "gistr-settings-status-connecting" ) + btn.extraSettingsEl.classList.add ( "gistr-settings-icon-error" ) + btn.extraSettingsEl.classList.remove ( "gistr-settings-icon-ok" ) + } + + btn.onClick( ( ) => + { + window.open( "https://www.githubstatus.com/" ) + } ) + + }, json_delay ) + } ) + + + this.cblk_preview = contentEl.createDiv( ) + + const gs_UsageCodeblock_gh = "```````" + "\n" + "```" + this.settings.keyword + "\n" + "gist.github.com/username/YOUR_GIST_ID" + "\n" + "gist.github.com/username/YOUR_GIST_ID#file_name" + "\n" + "gist.github.com/username/YOUR_GIST_ID&theme_name" + "\n" + "```" + "\n```````" + MarkdownRenderer.render( this.app, gs_UsageCodeblock_gh, this.cblk_preview, gs_UsageCodeblock_gh, this.plugin ) + + AddLine( contentEl, "", "div", "gistr-gs-separator" ) + + /* + Modal > Getting Started > Content > Getting Started + */ + + AddLine( contentEl, lng( "gs_og_name" ), "h3" ) + AddLine( contentEl, lng( "gs_og_desc" ), "small" ) + + const div_GettingStarted = contentEl.createDiv( { cls: "gistr-gs-modal-button-container" } ) + + new ButtonComponent( div_GettingStarted ) + .setButtonText( lng( "gs_og_btn_repo" ) ) + .setCta( ) + .onClick( ( ) => + { + window.open( lng( "cfg_tab_su_ogrepo_url" ) ) + } ) + + new ButtonComponent( div_GettingStarted ) + .setButtonText( lng( "gs_og_btn_docs" ) ) + .onClick( ( ) => + { + window.open( lng( "cfg_tab_su_ogdocs_url" ) ) + } ) + + AddLine( contentEl, lng( "gs_og_sub_1" ), "small" ) + + /* + Markdown Render Preview + */ + + this.cblk_preview = contentEl.createDiv( ) + + const gs_UsageCodeblock = "```````" + "\n" + "```" + this.settings.keyword + "\n" + "gist.domain.com/username/YOUR_GIST_ID" + "\n" + "gist.domain.com/username/YOUR_GIST_ID&theme_name" + "\n" + "```" + "\n```````" + MarkdownRenderer.render( this.app, gs_UsageCodeblock, this.cblk_preview, gs_UsageCodeblock, this.plugin ) + + /* + Footer Buttons + */ + + const div_Footer = contentEl.createDiv( { cls: "modal-button-container" } ) + + if ( this.firststart === true ) + { + new ButtonComponent( div_Footer ) + .setButtonText( lng( "gs_btn_settings_open" ) ) + .setCta( ) + .onClick( ( ) => + { + this.resolve( "settings-open" ) + this.close( ) + } ) + + new ButtonComponent( div_Footer ) + .setButtonText( lng( "gs_btn_close" ) ) + .onClick( ( ) => + { + this.close() + } ) + } + else + { + new ButtonComponent( div_Footer ) + .setButtonText( lng( "gs_btn_close" ) ) + .onClick( ( ) => + { + this.close( ) + } ) + } + } + + /* + Modal > Getting Started > Action > Close + */ + + close( ) + { + this.resolve( "" ) + super.close( ) + } + + /* + Modal > Getting Started > Action > On Close + */ + + onClose( ): void + { + this.contentEl.empty( ) + } +} \ No newline at end of file diff --git a/src/settings/defaults.ts b/src/settings/defaults.ts new file mode 100644 index 00000000..4492a7a1 --- /dev/null +++ b/src/settings/defaults.ts @@ -0,0 +1,45 @@ +import { GistrSettings } from 'src/settings' + +/* + Default Settings +*/ + +export const SettingsDefaults: GistrSettings = +{ + keyword: "gistr", + firststart: true, + css_og: "", + css_gh: "", + theme: "Light", + blk_pad_t: 16, + blk_pad_b: 19, + textwrap: "Enabled", + notitime: 10, + ge_enable_updatenoti: true, + + sy_clr_lst_icon: "#757575E6", + + og_clr_bg_light: "#CBCBCB", + og_clr_bg_dark: "#121315", + og_clr_sb_light: "#BA4956", + og_clr_sb_dark: "#4960ba", + og_clr_tx_light: "#2A2626", + og_clr_tx_dark: "#CAD3F5", + og_opacity: 1, + + gh_clr_bg_light: "#E5E5E5", + gh_clr_bg_dark: "#121315", + gh_clr_sb_light: "#BA4956", + gh_clr_sb_dark: "#BA496A", + gh_clr_tx_light: "#2A2626", + gh_clr_tx_dark: "#CAD3F5", + gh_opacity: 1, + + sy_enable_autoupdate: true, + sy_enable_autosave: false, + sy_enable_autosave_strict: false, + sy_enable_autosave_notice: false, + sy_add_frontmatter: false, + sy_save_duration: 15, + context_sorting: [], +} diff --git a/src/settings/index.ts b/src/settings/index.ts new file mode 100644 index 00000000..84c07ca0 --- /dev/null +++ b/src/settings/index.ts @@ -0,0 +1,4 @@ +export { SettingsDefaults } from 'src/settings/defaults' +export { SettingsSection } from 'src/settings/sections' +export { SettingsGet } from 'src/settings/settings' +export { GistrSettings } from 'src/settings/settings' \ No newline at end of file diff --git a/src/settings/sections/SettingsSection.ts b/src/settings/sections/SettingsSection.ts new file mode 100644 index 00000000..f96c98be --- /dev/null +++ b/src/settings/sections/SettingsSection.ts @@ -0,0 +1,2000 @@ +import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, ExtraButtonComponent, MarkdownRenderer, Notice, requestUrl } from 'obsidian' +import GistrPlugin from "src/main" +import { SettingsDefaults } from 'src/settings/defaults' +import { ColorPicker, GetColor } from 'src/utils' +import { GHStatusAPI, GHTokenSet, GHTokenGet } from 'src/backend/services' +import ModalGettingStarted from "src/modals/GettingStartedModal" +import { NoxComponent } from 'src/api' +import { lng } from 'src/lang' +import Pickr from "@simonwep/pickr" +import lt from 'semver/functions/lt' +import gt from 'semver/functions/gt' + +/* + Color picker options + + @todo : if a large variety of options are available in the future, possible + theme system should be integrated. +*/ + +export interface ColorPickrOpts +{ + 'sy_clr_lst_icon'?: string + + 'og_clr_bg_light'?: string + 'og_clr_bg_dark'?: string + 'og_clr_sb_light'?: string + 'og_clr_sb_dark'?: string + 'og_clr_tx_light'?: string + 'og_clr_tx_dark'?: string + + 'gh_clr_bg_light'?: string + 'gh_clr_bg_dark'?: string + 'gh_clr_sb_light'?: string + 'gh_clr_sb_dark'?: string + 'gh_clr_tx_light'?: string + 'gh_clr_tx_dark'?: string +} + +/* + default colors : type Color +*/ + +const ColorPickrDefaults: Record< string, Color > = +{ + 'sy_clr_lst_icon': "#757575E6", + + "og_clr_bg_light": "#CBCBCB", + "og_clr_bg_dark": "#121315", + "og_clr_sb_light": "#BA4956", + "og_clr_sb_dark": "#4960ba", + "og_clr_tx_light": "#2A2626", + "og_clr_tx_dark": "#CAD3F5", + + "gh_clr_bg_light": "#E5E5E5", + "gh_clr_bg_dark": "#121315", + "gh_clr_sb_light": "#BA4956", + "gh_clr_sb_dark": "#BA496A", + "gh_clr_tx_light": "#2A2626", + "gh_clr_tx_dark": "#CAD3F5", +} + +/* + CSS Color Values +*/ + +export type CLR_VAR = `--${string}` // css variable +export type CLR_HEX = `#${string}` // css hex +export type Color = CLR_HEX | CLR_VAR + +/* + Get > Theme Options +*/ + +export enum THEMES { LIGHT = "Light", DARK = "Dark" } +export const GetTheme: { [ key in THEMES ]: string } = +{ + [ THEMES.LIGHT ]: lng( "base_theme_light" ), + [ THEMES.DARK ]: lng( "base_theme_dark" ), +} + +/* + Get > Text Wrap Option +*/ + +export enum TEXTWRAP { WRAP_OFF = "Disabled", WRAP_ON = "Enabled" } +export const GetTextwrap: { [ key in TEXTWRAP ]: string } = +{ + [ TEXTWRAP.WRAP_OFF ]: lng( "base_opt_disabled" ), + [ TEXTWRAP.WRAP_ON ]: lng( "base_opt_enabled" ), +} + +/* + Settings Tab +*/ + +export class SettingsSection extends PluginSettingTab +{ + readonly plugin: GistrPlugin + private Hide_Global: boolean + private Hide_Github: boolean + private Hide_Opengist: boolean + private Hide_SaveSync: boolean + private Hide_Support: boolean + private Tab_Global: HTMLElement + private Tab_Github: HTMLElement + private Tab_OpenGist: HTMLElement + private Tab_SaveSync: HTMLElement + private Tab_Support: HTMLElement + private Opacity_Enabled: string + private Opacity_Disabled: string + private Obj_Github_Api: Setting + private cPickr: Record< string, ColorPicker > + + /* + Class > Constructor + */ + + constructor( app: App, plugin: GistrPlugin ) + { + super( app, plugin ) + + this.plugin = plugin + this.Hide_Global = true + this.Hide_Github = true + this.Hide_Opengist = true + this.Hide_SaveSync = true + this.Hide_Support = false + this.Opacity_Enabled = "1" + this.Opacity_Disabled = "0.4" + this.Obj_Github_Api = null + this.cPickr = { } + } + + /* + Create Object > Color Picker + + @arg : bHidden + associated to hovering color picker, not color element + */ + + new_ColorPicker( plugin: GistrPlugin, el: HTMLElement, setting: Setting, id: keyof ColorPickrOpts, bHidden?: ( ) => boolean ) + { + const pickr: ColorPicker = new ColorPicker( plugin, el, setting ) + + pickr + .on( "init", ( color: Pickr.HSVaColor, instance: Pickr ) => + { + const currColor = this.plugin.settings[ id ] + pickr.setColor( currColor ) + } ) + + .on( "show", ( color: Pickr.HSVaColor, instance: Pickr ) => + { + if ( typeof bHidden !== "undefined" && bHidden( ) ) + instance.hide( ) + } ) + + .on( "save", ( color: Pickr.HSVaColor, instance: ColorPicker ) => + { + + const clr : Color = `#${ color.toHEXA( ).toString( ).substring( 1 ) }` + + this.plugin.settings[ id ] = clr + this.plugin.saveSettings( ) + + instance.hide( ) + instance.addSwatch( clr ) + instance.ActionSave( clr ) + + this.plugin.renderModeReading( ) + } ) + + .on( "cancel", ( instance: ColorPicker ) => + { + instance.hide( ) + } ) + + setting.addExtraButton + ( + ( btn ) => + { + pickr.AddButtonReset = btn + + .setIcon ( "reset" ) + .setDisabled ( false ) + .setTooltip ( lng( "pickr_restore_default_btn_tip" ) ) + .onClick( ( ) => + { + const resetColour: Color = ColorPickrDefaults[ id ] + pickr.setColor ( GetColor( resetColour ) ) + pickr.ActionSave ( resetColour ) + } ) + } + ) + + this.cPickr[ id ] = pickr + } + + /* + Display + */ + + display( ): void + { + const { containerEl } = this + + this.Hide_Global = true + this.Hide_Github = true + this.Hide_Opengist = true + this.Hide_SaveSync = true + this.Hide_Support = false + + + this.CreateHeader ( containerEl ) + this.CreateMenus ( containerEl ) + } + + /* + Section -> Header + */ + + CreateHeader( elm: HTMLElement ) + { + elm.empty( ) + elm.addClass( 'gistr-settings-modal' ) + elm.createEl( "p", { cls: "gistr-settings-section-header", text: lng( "cfg_modal_desc" ) } ) + } + + /* + Create Menus + */ + + CreateMenus( elm: HTMLElement ) + { + this.Tab_Global_New ( elm ) + this.Tab_Global = elm.createDiv( ) + + this.Tab_OpenGist_New ( elm ) + this.Tab_OpenGist = elm.createDiv( ) + + this.Tab_Github_New ( elm ) + this.Tab_Github = elm.createDiv( ) + + this.Tab_SaveSync_New ( elm ) + this.Tab_SaveSync = elm.createDiv( ) + + this.Tab_Support_New ( elm ) + this.Tab_Support = elm.createDiv( ) + + this.Tab_Support_ShowSettings( this.Tab_Support ) + } + + /* + Tab > General > New + */ + + Tab_Global_New( elm: HTMLElement ) + { + const Tab_GN = elm.createEl( "h2", { text: lng( "cfg_tab_ge_title" ), cls: `gistr-settings-header${ this.Hide_Global?" isfold" : "" }` } ) + Tab_GN.addEventListener( "click", ( )=> + { + this.Hide_Global = !this.Hide_Global + Tab_GN.classList.toggle( "isfold", this.Hide_Global ) + this.Tab_Global_CreateSettings( ) + } ) + } + + Tab_Global_CreateSettings( ) + { + this.Tab_Global.empty( ) + if ( this.Hide_Global ) return + + this.Tab_Global_ShowSettings( this.Tab_Global ) + } + + Tab_Global_ShowSettings( elm: HTMLElement ) + { + + /* + Github > Header Intro + */ + + elm.createEl( 'small', { cls: "gistr-settings-section-description", text: lng( "cfg_tab_ge_header" ) } ) + + /* + Codeblock > Theme + */ + + const cfg_tab_ge_theme_desc = new DocumentFragment( ) + cfg_tab_ge_theme_desc.append( + sanitizeHTMLToDom( `${ lng( "cfg_tab_ge_theme_desc" ) }` ), + ) + + new NoxComponent( elm ) + .setName( lng( "cfg_tab_ge_theme_name" ) ) + .setDesc( cfg_tab_ge_theme_desc ) + .setClass( "gistr-dropdown" ) + .addNoxDropdown( dropdown => dropdown + .addOption( THEMES.LIGHT, GetTheme[ THEMES.LIGHT ] ) + .addOption( THEMES.DARK, GetTheme[ THEMES.DARK ] ) + .setValue( this.plugin.settings.theme ) + .onChange( async ( val ) => + { + this.plugin.settings.theme = val + await this.plugin.saveSettings( ) + this.plugin.renderModeReading( ) + }), + ( ) => + ( + SettingsDefaults.theme as string + ), + ) + + elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) + + /* + Dropdown > Text Wrap + */ + + const cfg_tab_ge_wrap_desc = new DocumentFragment( ) + cfg_tab_ge_wrap_desc.append( + sanitizeHTMLToDom( `${ lng( "cfg_tab_ge_wrap_desc" ) }` ), + ) + + new NoxComponent( elm ) + .setName( lng( "cfg_tab_ge_wrap_name" ) ) + .setDesc( cfg_tab_ge_wrap_desc ) + .setClass( "gistr-dropdown" ) + .addNoxDropdown( dropdown => dropdown + .addOption( TEXTWRAP.WRAP_OFF, GetTextwrap[ TEXTWRAP.WRAP_OFF ] ) + .addOption( TEXTWRAP.WRAP_ON, GetTextwrap[ TEXTWRAP.WRAP_ON ] ) + .setValue( this.plugin.settings.textwrap ) + .onChange( async ( val ) => + { + this.plugin.settings.textwrap = val + await this.plugin.saveSettings( ) + this.plugin.renderModeReading( ) + }), + ( ) => + ( + SettingsDefaults.textwrap as string + ), + ) + + elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) + + /* + Command Keyword + + changing this will cause all opengist portals to not function until the keyword is changed + within the box. + */ + + const cfg_tab_ge_keyword_desc = new DocumentFragment( ) + cfg_tab_ge_keyword_desc.append( + sanitizeHTMLToDom( `${ lng( "cfg_tab_ge_keyword_desc" ) }` ), + ) + + new NoxComponent( elm ) + .setName( lng( "cfg_tab_ge_keyword_name" ) ) + .setDesc( cfg_tab_ge_keyword_desc ) + .addNoxTextbox( text => text + .setValue( this.plugin.settings.keyword ) + .onChange( async ( val ) => + { + this.plugin.settings.keyword = val + await this.plugin.saveSettings( ) + this.plugin.renderModeReading( ) + }), + ( ) => + ( + SettingsDefaults.keyword.toString( ) as string + ), + ) + + elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) + + /* + Plugin update notifications + */ + + const cfg_tab_ge_noti_update_desc = new DocumentFragment( ) + cfg_tab_ge_noti_update_desc.append( + sanitizeHTMLToDom( `${ lng( "cfg_tab_ge_noti_update_desc" ) }` ), + ) + + new NoxComponent( elm ) + .setName( lng( "cfg_tab_ge_noti_update_name" ) ) + .setDesc( cfg_tab_ge_noti_update_desc ) + .addNoxToggle( toggle => toggle + .setValue( this.plugin.settings.ge_enable_updatenoti ) + .onChange( async ( val ) => + { + this.plugin.settings.ge_enable_updatenoti = val + await this.plugin.saveSettings( ) + }), + ( ) => + ( + SettingsDefaults.ge_enable_updatenoti as boolean + ), + ) + + elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) + + /* + Notification Time (in seconds) + */ + + const cfg_tab_ge_noti_dur_desc = new DocumentFragment( ) + cfg_tab_ge_noti_dur_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_ge_noti_dur_desc" ) }`), + ) + + let val_st_notitime: HTMLDivElement + new NoxComponent( elm ) + .setName( lng( "cfg_tab_ge_noti_dur_name" ) ) + .setDesc( cfg_tab_ge_noti_dur_desc ) + .setClass( "gistr-slider" ) + .addNoxSlider( slider => slider + .setLimits( 0, 120, 1 ) + .setDynamicTooltip( ) + .setValue( this.plugin.settings.notitime ) + .onChange( async ( val ) => + { + val_st_notitime.innerText = " " + val.toString( ) + "s" + + this.plugin.settings.notitime = val + await this.plugin.saveSettings( ) + }), + ( ) => + ( + SettingsDefaults.notitime as number + ), + ).settingEl.createDiv( '', ( el ) => + { + val_st_notitime = el + el.innerText = " " + this.plugin.settings.notitime.toString( ) + "s" + } ).classList.add( 'gistr-settings-elm-slider-preview' ) + + elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) + + /* + Tab Footer Spacer + */ + + elm.createEl( 'div', { cls: "gistr-settings-section-footer", text: "" } ) + } + + /* + Tab > OpenGist > New + */ + + Tab_OpenGist_New( elm: HTMLElement ) + { + const Tab_OG = elm.createEl( "h2", { text: lng( "cfg_tab_og_title" ), cls: `gistr-settings-header${ this.Hide_Opengist?" isfold" : "" }` } ) + Tab_OG.addEventListener( "click", ( )=> + { + this.Hide_Opengist = !this.Hide_Opengist + Tab_OG.classList.toggle( "isfold", this.Hide_Opengist ) + this.Tab_OpenGist_CreateSettings( ) + } ) + } + + Tab_OpenGist_CreateSettings( ) + { + this.Tab_OpenGist.empty( ) + if ( this.Hide_Opengist ) return + + this.Tab_OpenGist_ShowSettings( this.Tab_OpenGist ) + } + + Tab_OpenGist_ShowSettings( elm: HTMLElement ) + { + + elm.createEl( 'small', { cls: "gistr-settings-section-description", text: lng( "cfg_tab_og_header" ) } ) + + /* + Development notice + */ + + const ct_Note = elm.createDiv( ) + const md_notFinished = "> [!NOTE] " + lng( "base_underdev_title" ) + "\n> " + lng( "base_underdev_msg" ) + "" + MarkdownRenderer.render( this.plugin.app, md_notFinished, ct_Note, "" + md_notFinished, this.plugin ) + + /* + Background color (Light) + */ + + const cfg_tab_og_cb_light_desc = new DocumentFragment( ) + cfg_tab_og_cb_light_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_og_cb_light_desc" ) }`), + ) + + new Setting( elm ) + .setName( lng( "cfg_tab_og_cb_light_name" ) ) + .setDesc( cfg_tab_og_cb_light_desc ) + .then( ( setting ) => { this.new_ColorPicker + ( + this.plugin, elm, setting, + "og_clr_bg_light", + ) } ) + + /* + Background color (Dark) + */ + + const cfg_tab_og_cb_dark_desc = new DocumentFragment( ) + cfg_tab_og_cb_dark_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_og_cb_dark_desc" ) }`), + ) + + new Setting( elm ) + .setName( lng( "cfg_tab_og_cb_dark_name" ) ) + .setDesc( cfg_tab_og_cb_dark_desc ) + .then( ( setting ) => { this.new_ColorPicker + ( + this.plugin, elm, setting, + "og_clr_bg_dark", + ) } ) + + /* + Text color (Light) + */ + + const cfg_tab_og_tx_light_desc = new DocumentFragment( ) + cfg_tab_og_tx_light_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_og_tx_light_desc" ) }`), + ) + + new Setting( elm ) + .setName( lng( "cfg_tab_og_tx_light_name" ) ) + .setDesc( cfg_tab_og_tx_light_desc ) + .then( ( setting ) => { this.new_ColorPicker + ( + this.plugin, elm, setting, + "og_clr_tx_light", + ) } ) + + /* + Text color (Dark) + */ + + const cfg_tab_og_tx_dark_desc = new DocumentFragment( ) + cfg_tab_og_tx_dark_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_og_tx_dark_desc" ) }`), + ) + + new Setting( elm ) + .setName( lng( "cfg_tab_og_tx_dark_name" ) ) + .setDesc( cfg_tab_og_tx_dark_desc ) + .then( ( setting ) => { this.new_ColorPicker + ( + this.plugin, elm, setting, + "og_clr_tx_dark", + ) } ) + + /* + Scrollbar Track Color (Light) + */ + + const cfg_tab_og_sb_light_desc = new DocumentFragment( ) + cfg_tab_og_sb_light_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_og_sb_light_desc" ) }`), + ) + + new Setting( elm ) + .setName( lng( "cfg_tab_og_sb_light_name" ) ) + .setDesc( cfg_tab_og_sb_light_desc ) + .then( ( setting ) => { this.new_ColorPicker + ( + this.plugin, elm, setting, + "og_clr_sb_light", + ) } ) + + /* + Scrollbar Track Color (Dark) + */ + + const cfg_tab_og_sb_dark_desc = new DocumentFragment( ) + cfg_tab_og_sb_dark_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_og_sb_dark_desc" ) }`), + ) + + new Setting( elm ) + .setName( lng( "cfg_tab_og_sb_dark_name" ) ) + .setDesc( cfg_tab_og_sb_dark_desc ) + .then( ( setting ) => { this.new_ColorPicker + ( + this.plugin, elm, setting, + "og_clr_sb_dark", + ) } ) + + /* + Codeblock Opacity + */ + + const cfg_tab_og_opacity_desc = new DocumentFragment( ) + cfg_tab_og_opacity_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_og_opacity_desc" ) }`), + ) + + let val_og_opacity: HTMLDivElement + new NoxComponent( elm ) + .setName( lng( "cfg_tab_og_opacity_name" ) ) + .setDesc( cfg_tab_og_opacity_desc ) + .setClass( "gistr-slider" ) + .addNoxSlider( slider => slider + .setDynamicTooltip( ) + .setLimits( 0.20, 1, 0.05 ) + .setValue( this.plugin.settings.og_opacity ) + .onChange( async ( val ) => + { + const opacity_calc = val * 100 + val_og_opacity.innerText = " " + opacity_calc.toString( ) + "%" + + this.plugin.settings.og_opacity = val + await this.plugin.saveSettings( ) + this.plugin.renderModeReading( ) + }), + ( ) => + ( + SettingsDefaults.og_opacity as number + ), + ).settingEl.createDiv( '', ( el ) => + { + val_og_opacity = el + const opacity_calc = this.plugin.settings.og_opacity * 100 + el.innerText = " " + opacity_calc.toString( ) + "%" + } ).classList.add( 'gistr-settings-elm-slider-preview' ) + + /* + Codeblock > Padding > Top + */ + + const cfg_tab_og_pad_top_desc = new DocumentFragment( ) + cfg_tab_og_pad_top_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_og_pad_top_desc" ) }`), + ) + + let val_og_padding_top: HTMLDivElement + new NoxComponent( elm ) + .setName( lng( "cfg_tab_og_pad_top_name" ) ) + .setDesc( cfg_tab_og_pad_top_desc ) + .setClass( "gistr-slider" ) + .addNoxSlider( slider => slider + .setDynamicTooltip( ) + .setLimits( 0, 30, 1 ) + .setValue( this.plugin.settings.blk_pad_t ) + .onChange( async ( val ) => + { + const padding_calc = val + val_og_padding_top.innerText = " " + padding_calc.toString( ) + "px" + + this.plugin.settings.blk_pad_t = val + await this.plugin.saveSettings( ) + this.plugin.renderModeReading( ) + }), + ( ) => + ( + SettingsDefaults.blk_pad_t as number + ), + ).settingEl.createDiv( '', ( el ) => + { + val_og_padding_top = el + const padding_calc = this.plugin.settings.blk_pad_t + el.innerText = " " + padding_calc.toString( ) + "px" + } ).classList.add( 'gistr-settings-elm-slider-preview' ) + + /* + Codeblock > Padding > Bottom + */ + + const cfg_tab_og_pad_btm_desc = new DocumentFragment( ) + cfg_tab_og_pad_btm_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_og_pad_btm_desc" ) }`), + ) + + let val_og_padding_btm: HTMLDivElement + new NoxComponent( elm ) + .setName( lng( "cfg_tab_og_pad_btm_name" ) ) + .setDesc( cfg_tab_og_pad_btm_desc ) + .setClass( "gistr-slider" ) + .addNoxSlider( slider => slider + .setDynamicTooltip( ) + .setLimits( 0, 30, 1 ) + .setValue( this.plugin.settings.blk_pad_b ) + .onChange( async ( val ) => + { + const padding_calc = val + val_og_padding_btm.innerText = " " + padding_calc.toString( ) + "px" + + this.plugin.settings.blk_pad_b = val + await this.plugin.saveSettings( ) + this.plugin.renderModeReading( ) + }), + ( ) => + ( + SettingsDefaults.blk_pad_b as number + ), + ).settingEl.createDiv( '', ( el ) => + { + val_og_padding_btm = el + const padding_calc = this.plugin.settings.blk_pad_b + el.innerText = " " + padding_calc.toString( ) + "px" + } ).classList.add( 'gistr-settings-elm-slider-preview' ) + + /* + Codeblock > CSS Override + */ + + const cfg_tab_og_css_desc = new DocumentFragment( ) + cfg_tab_og_css_desc.append( + sanitizeHTMLToDom( `${ lng( "cfg_tab_og_css_desc" ) }` ), + ) + + new NoxComponent( elm ) + .setName( lng( "cfg_tab_og_css_name" ) ) + .setDesc( cfg_tab_og_css_desc ) + .setClass( "gistr-settings-elm-textarea" ) + .addNoxTextarea( text => text + .setPlaceholder( lng( "cfg_tab_og_css_pholder" ) ) + .setValue( this.plugin.settings.css_og ) + .onChange( async ( val ) => + { + this.plugin.settings.css_og = val + await this.plugin.saveSettings( ) + this.plugin.renderModeReading( ) + }), + ( ) => + ( + SettingsDefaults.css_og.toString( ) as string + ), + ) + + /* + Tab Footer Spacer + */ + + elm.createEl( 'div', { cls: "gistr-settings-section-footer", text: "" } ) + } + + /* + Tab > Github Gists > New + */ + + Tab_Github_New( elm: HTMLElement ) + { + + /* + Get github api status + */ + + let json_delay = 0.5 * 1000 + const gh_status = requestUrl( "https://www.githubstatus.com/api/v2/summary.json" ).then( ( res ) => + { + if ( res.status === 200 ) + return res.json.components[ 0 ].status || lng( "gist_status_issues" ) + else + return lng( "gist_status_issues" ) + } ) + + const Tab_GH = elm.createEl( "h2", { text: "", cls: `gistr-settings-header-sublevel` } ) + const Tab_GH_L = Tab_GH.createEl( "h2", { text: lng( "cfg_tab_gh_title" ), cls: `gistr-settings-header-int-l${ this.Hide_Github?" isfold" : "" }` } ) + const Tab_GH_R = Tab_GH.createEl( "h2", { text: " ", cls: `gistr-settings-header-int-r` } ) + const Tab_GH_C = Tab_GH.createEl( "div", { text: "", cls: `gistr-settings-header-int-c` } ) + + new Setting( Tab_GH_R ) + .addText( async ( text ) => + { + text + .setPlaceholder ( lng( "gist_status_connecting" ) ) + .setValue ( lng( "gist_status_connecting" ) ) + .setDisabled ( true ) + + const el = Tab_GH_R.querySelector( ".setting-item-control" ) + el.addClass ( "gistr-settings-status-connecting" ) + + /* + Fetch Github API status + - operational + - degraded_performance + - partial_outage + - major_outage + */ + + let github_status = await gh_status + + setTimeout( function( ) + { + + /* + Find API status language entry in array + */ + + const gb_api_status: string = GHStatusAPI[ github_status ] + + /* + Text > Github Token Not Specified + */ + + if ( !GHTokenGet( ) ) + { + const el = Tab_GH_R.querySelector( ".setting-item-control" ) + el.removeClass ( "gistr-settings-status-connecting" ) + el.addClass ( "gistr-settings-status-error" ) + text.inputEl.setAttribute ( "size", lng( "gist_status_no_api" ).length.toString( ) ) + text.setValue ( lng( "gist_status_no_api" ) ) + + return + } + + /* + Text > Github API > Operational + */ + + if ( github_status === lng( "gist_status_operational_raw" ) ) + { + const el = Tab_GH_R.querySelector( ".setting-item-control" ) + el.removeClass ( "gistr-settings-status-connecting" ) + el.addClass ( "gistr-settings-status-success" ) + text.inputEl.setAttribute ( "size", lng( "gist_status_connected" ).length.toString( ) ) + text.setValue ( lng( "gist_status_connected" ) ) + } + else if ( github_status === lng( "gist_status_issues" ) ) + { + text.inputEl.setAttribute ( "size", lng( "gist_status_noconnection" ).length.toString( ) ) + text.setValue ( lng( "gist_status_noconnection" ) ) + } + else + { + + /* + Button > Github API > Connection Issue + */ + + const el = Tab_GH_R.querySelector( ".setting-item-control" ) + el.removeClass ( "gistr-settings-status-connecting" ) + el.addClass ( "gistr-settings-status-warning" ) + text.inputEl.setAttribute ( "size", gb_api_status.length.toString( ) ) + text.setValue ( gb_api_status ) + } + }, json_delay ) + } ) + .addExtraButton( async ( btn ) => + { + btn + .setIcon ( 'circle-off' ) + .setTooltip ( lng( "gist_status_connecting_btn_tip" ) ) + + btn.extraSettingsEl.classList.add( "gistr-settings-icon-cur" ) + btn.extraSettingsEl.classList.add( "gistr-anim-spin" ) + btn.extraSettingsEl.classList.add( "gistr-settings-status-connecting" ) + + /* + Fetch Github API status + - operational + - degraded_performance + - partial_outage + - major_outage + */ + + let github_status = await gh_status + + setTimeout( function( ) + { + + /* + Find API status language entry in array + */ + + const gb_api_status: string = GHStatusAPI[ github_status ] + + /* + Text > Github Token Not Specified + */ + + if ( !GHTokenGet( ) ) + { + btn.setIcon ( "circle-off" ) + btn.setTooltip ( lng( "gist_status_no_api_btn_tip" ) ) + + btn.extraSettingsEl.classList.remove ( "gistr-settings-status-connecting" ) + btn.extraSettingsEl.classList.add ( "gistr-settings-icon-error" ) + btn.extraSettingsEl.classList.remove ( "gistr-settings-icon-ok" ) + + return + } + + /* + Button > Github API > Operational + */ + + if ( github_status === lng( "gist_status_operational_raw" ) ) + { + btn.setIcon ( "github" ) + btn.setTooltip ( lng( "gist_status_success_btn_tip" ) ) + + btn.extraSettingsEl.classList.remove ( "gistr-settings-status-connecting" ) + btn.extraSettingsEl.classList.add ( "gistr-settings-icon-ok" ) + } + else + { + + /* + Button > Github API > Connection Issue + */ + + btn.setIcon ( "circle-off" ) + btn.setTooltip ( lng( "gist_status_issues_btn_tip" ) ) + + btn.extraSettingsEl.classList.remove ( "gistr-settings-status-connecting" ) + btn.extraSettingsEl.classList.add ( "gistr-settings-icon-error" ) + btn.extraSettingsEl.classList.remove ( "gistr-settings-icon-ok" ) + } + + btn.onClick( ( ) => + { + window.open( "https://www.githubstatus.com/" ) + } ) + + }, json_delay ) + } ) + + + Tab_GH_L.addEventListener( "click", ( )=> + { + this.Hide_Github = !this.Hide_Github + Tab_GH_L.classList.toggle( "isfold", this.Hide_Github ) + this.Tab_Github_CreateSettings( ) + } ) + } + + Tab_Github_CreateSettings( ) + { + this.Tab_Github.empty( ) + if ( this.Hide_Github ) return + + this.Tab_Github_ShowSettings( this.Tab_Github ) + } + + Tab_Github_ShowSettings( elm: HTMLElement ) + { + + /* + Github > Define + */ + + const gistToken = GHTokenGet( ) + + /* + Section -> Support Buttons + */ + + elm.createEl( 'small', { cls: "gistr-settings-section-description", text: lng( "cfg_tab_gh_header" ) } ) + + /* + Personal Access Token > Description + + This is used for sharing gists between the obsidian vault and Github Gist. + */ + + const DOM_Token_Desc = new DocumentFragment( ) + DOM_Token_Desc.append( + sanitizeHTMLToDom(` + ${ lng( "cfg_tab_gh_pat_desc_l1" ) } +
+
+ ${ lng( "cfg_tab_gh_pat_desc_l2" ) } +
    +
  • + ${ lng( "cfg_tab_gh_pat_perm_1" ) } +
  • +
  • + ${ lng( "cfg_tab_gh_pat_perm_2" ) } +
  • +
  • + ${ lng( "cfg_tab_gh_pat_perm_3" ) } +
  • +
  • + ${ lng( "cfg_tab_gh_pat_perm_4" ) } +
  • +
+
+ ${ lng( "cfg_tab_gh_pat_footer" ) } +
+ ${ lng( "cfg_tab_gh_pat_help" ) } + `), + ) + + /* + Personal Access Token + + This is used for sharing gists between the obsidian vault and Github Gist. + */ + + let val_Token: HTMLInputElement | null = null + let btn_Github: ExtraButtonComponent + + this.Obj_Github_Api = new Setting( elm ) + .setName( lng( "cfg_tab_gh_pat_name" ) ) + .setDesc( DOM_Token_Desc ) + .addText( ( val ) => + { + val_Token = val.inputEl + val.inputEl.type = 'password' + + val.setPlaceholder( lng( "cfg_tab_gh_pat_pholder" ) ) + .setValue( gistToken ?? '' ) + .onChange( async ( val ) => + { + const input_PAT = val.trim( ) + const b_PAT_Token = /^github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$/g.test( input_PAT ) + const b_PAT_Classic = /^ghp_[A-Za-z0-9_]{36,251}$/g.test( input_PAT ) + + /* + Personal Access Token Valid + */ + + if ( b_PAT_Token || b_PAT_Classic ) + { + btn_Github.setIcon ( 'check' ) + btn_Github.setTooltip ( lng( "cfg_tab_gh_pat_ok_btn_tip" ) ) + + btn_Github.extraSettingsEl.classList.add ( "gistr-settings-icon-ok" ) + btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-github" ) + btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-invalid" ) + + let token_Type = lng( "cfg_tab_gh_pat_notice_type_fine" ) + if ( b_PAT_Classic ) + token_Type = lng( "cfg_tab_gh_pat_notice_type_classic" ) + + new Notice ( lng( "cfg_tag_gh_pat_notice_msg_success" ) + "\n\n" + token_Type ) + + GHTokenSet( input_PAT ) + + this.display( ) + } + else + { + + /* + Personal Access Token > invalid + */ + + if ( input_PAT.length > 0 ) + { + btn_Github.setTooltip ( lng( "cfg_tab_gh_pat_invalid_btn_tip" ) ) + + btn_Github.extraSettingsEl.classList.add ( "gistr-settings-icon-invalid" ) + btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-github" ) + btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-ok" ) + } + + /* + Personal Access Token > Empty + */ + + else + { + btn_Github.setIcon ( 'github' ) + btn_Github.setTooltip ( lng( "cfg_tab_gh_pat_bad_btn_tip" ) ) + + btn_Github.extraSettingsEl.classList.add ( "gistr-settings-icon-github" ) + btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-ok" ) + btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-invalid" ) + + GHTokenSet( "" ) + + new Notice ( lng( "cfg_tag_gh_pat_notice_msg_cleared" ) ) + } + } + } ) + } ) + + .addExtraButton( ( btn ) => + { + const btn_Visibility = ( bTokenVis: boolean ) => + { + btn + .setIcon ( bTokenVis ? 'eye' : 'eye-off' ) + .setTooltip ( bTokenVis ? lng( "cfg_tab_gh_pat_btn_tip_state_show" ) : lng( "cfg_tab_gh_pat_btn_tip_state_hide" ) ) + + btn.extraSettingsEl.classList.add( "gistr-settings-icon-cur" ) + } + + btn_Visibility( true ) + + btn.onClick( ( ) => + { + if ( !val_Token ) return + + if ( val_Token.type === 'password' ) + { + val_Token.type = 'text' + btn_Visibility( false ) + } + else + { + val_Token.type = 'password' + btn_Visibility( true ) + } + } ) + } ) + + .addExtraButton( ( btn ) => + { + btn_Github = btn + const btn_GetTokenStatus = ( bHasValue: boolean ) => + { + btn + .setIcon ( 'github' ) + .setTooltip ( lng( "cfg_tab_gh_pat_bad_btn_tip" ) ) + + btn.extraSettingsEl.classList.add( "gistr-settings-icon-cur" ) + btn.extraSettingsEl.classList.add( "gistr-settings-icon-github" ) + + const b_PAT_Token = /^github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$/g.test( gistToken ) + const b_PAT_Classic = /^ghp_[A-Za-z0-9_]{36,251}$/g.test( gistToken ) + + if ( b_PAT_Token == true || b_PAT_Classic == true ) + { + btn_Github.setIcon ( 'check' ) + btn_Github.setTooltip ( lng( "cfg_tab_gh_pat_ok_btn_tip" ) ) + + btn_Github.extraSettingsEl.classList.add ( "gistr-settings-icon-ok" ) + btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-github" ) + } + else + { + btn_Github.setIcon ( 'github' ) + btn_Github.setTooltip ( lng( "cfg_tab_gh_pat_bad_btn_tip" ) ) + + btn_Github.extraSettingsEl.classList.add ( "gistr-settings-icon-github" ) + btn_Github.extraSettingsEl.classList.remove ( "gistr-settings-icon-ok" ) + } + } + + btn_GetTokenStatus( true ) + btn.onClick( ( ) => + { + window.open( lng( "cfg_tab_gh_pat_url_btn" ) ) + } ) + } ) + + + /* + Background color (Light) + */ + + const cfg_tab_gh_cb_light_desc = new DocumentFragment( ) + cfg_tab_gh_cb_light_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_gh_cb_light_desc" ) }`), + ) + + new Setting( elm ) + .setName( lng( "cfg_tab_gh_cb_light_name" ) ) + .setDesc( cfg_tab_gh_cb_light_desc ) + .then( ( setting ) => { this.new_ColorPicker + ( + this.plugin, elm, setting, + "gh_clr_bg_light", + ) } ) + + /* + Background color (Dark) + */ + + const cfg_tab_gh_cb_dark_desc = new DocumentFragment( ) + cfg_tab_gh_cb_dark_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_gh_cb_dark_desc" ) }`), + ) + + new Setting( elm ) + .setName( lng( "cfg_tab_gh_cb_dark_name" ) ) + .setDesc( cfg_tab_gh_cb_dark_desc ) + .then( ( setting ) => { this.new_ColorPicker + ( + this.plugin, elm, setting, + "gh_clr_bg_dark", + ) } ) + + /* + Text color (Light) + */ + + const cfg_tab_gh_tx_light_desc = new DocumentFragment( ) + cfg_tab_gh_tx_light_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_gh_tx_light_desc" ) }`), + ) + + new Setting( elm ) + .setName( lng( "cfg_tab_gh_tx_light_name" ) ) + .setDesc( cfg_tab_gh_tx_light_desc ) + .then( ( setting ) => { this.new_ColorPicker + ( + this.plugin, elm, setting, + "gh_clr_tx_light", + ) } ) + + /* + Text color (Dark) + */ + + const cfg_tab_gh_tx_dark_desc = new DocumentFragment( ) + cfg_tab_gh_tx_dark_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_gh_tx_dark_desc" ) }`), + ) + + new Setting( elm ) + .setName( lng( "cfg_tab_gh_tx_dark_name" ) ) + .setDesc( cfg_tab_gh_tx_dark_desc ) + .then( ( setting ) => { this.new_ColorPicker + ( + this.plugin, elm, setting, + "gh_clr_tx_dark", + ) } ) + + + /* + Scrollbar Track Color (Light) + */ + + const cfg_tab_gh_sb_light_name = new DocumentFragment( ) + cfg_tab_gh_sb_light_name.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_gh_sb_light_desc" ) }`), + ) + + new Setting( elm ) + .setName( lng( "cfg_tab_gh_sb_light_name" ) ) + .setDesc( cfg_tab_gh_sb_light_name ) + .then( ( setting ) => { this.new_ColorPicker + ( + this.plugin, elm, setting, + "gh_clr_sb_light", + ) } ) + + /* + Scrollbar Track Color (Dark) + */ + + const cfg_tab_gh_sb_dark_desc = new DocumentFragment( ) + cfg_tab_gh_sb_dark_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_gh_sb_dark_desc" ) }`), + ) + + new Setting( elm ) + .setName( lng( "cfg_tab_gh_sb_dark_name" ) ) + .setDesc( cfg_tab_gh_sb_dark_desc ) + .then( ( setting ) => { this.new_ColorPicker + ( + this.plugin, elm, setting, + "gh_clr_sb_dark", + ) } ) + + /* + Codeblock Opacity + */ + + const cfg_tab_gh_opacity_desc = new DocumentFragment( ) + cfg_tab_gh_opacity_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_gh_opacity_desc" ) }`), + ) + + let val_gh_opacity: HTMLDivElement + new NoxComponent( elm ) + .setName( lng( "cfg_tab_gh_opacity_name" ) ) + .setDesc( cfg_tab_gh_opacity_desc ) + .setClass( "gistr-slider" ) + .addNoxSlider( slider => slider + .setDynamicTooltip( ) + .setLimits( 0.20, 1, 0.05 ) + .setValue( this.plugin.settings.gh_opacity ) + .onChange( async ( val ) => + { + const opacity_calc = val * 100 + val_gh_opacity.innerText = " " + opacity_calc.toString( ) + "%" + + this.plugin.settings.gh_opacity = val + await this.plugin.saveSettings( ) + this.plugin.renderModeReading( ) + }), + ( ) => + ( + SettingsDefaults.gh_opacity as number + ), + ).settingEl.createDiv( '', ( el ) => + { + val_gh_opacity = el + const opacity_calc = this.plugin.settings.gh_opacity * 100 + el.innerText = " " + opacity_calc.toString( ) + "%" + } ).classList.add( 'gistr-settings-elm-slider-preview' ) + + /* + Codeblock > CSS Override + */ + + const cfg_tab_gh_css_desc = new DocumentFragment( ) + cfg_tab_gh_css_desc.append( + sanitizeHTMLToDom( `${ lng( "cfg_tab_gh_css_desc" ) }` ), + ) + + let gh_css = new NoxComponent( elm ) + .setName( lng( "cfg_tab_gh_css_name" ) ) + .setDesc( cfg_tab_gh_css_desc ) + .setClass( "gistr-settings-elm-textarea" ) + .addNoxTextarea( text => text + .setPlaceholder( lng( "cfg_tab_gh_css_pholder" ) ) + .setValue( this.plugin.settings.css_gh ) + .onChange( async ( val ) => + { + this.plugin.settings.css_gh = val + await this.plugin.saveSettings( ) + this.plugin.renderModeReading( ) + }), + ( ) => + ( + SettingsDefaults.css_gh.toString( ) as string + ), + ) + + /* + Tab Footer Spacer + */ + + elm.createEl( 'div', { cls: "gistr-settings-section-footer", text: "" } ) + } + + /* + Tab > Save & Sync > New + */ + + Tab_SaveSync_New( elm: HTMLElement ) + { + const Tab_SY = elm.createEl( "h2", { text: lng( "cfg_tab_sy_title" ), cls: `gistr-settings-header${ this.Hide_SaveSync?" isfold" : "" }` } ) + Tab_SY.addEventListener( "click", ( )=> + { + this.Hide_SaveSync = !this.Hide_SaveSync + Tab_SY.classList.toggle( "isfold", this.Hide_SaveSync ) + this.Tab_SaveSync_CreateSettings( ) + } ) + } + + Tab_SaveSync_CreateSettings( ) + { + this.Tab_SaveSync.empty( ) + if ( this.Hide_SaveSync ) return + + this.Tab_SaveSync_ShowSettings( this.Tab_SaveSync ) + } + + Tab_SaveSync_ShowSettings( elm: HTMLElement ) + { + + let setting_allow_gist_updates: NoxComponent + let setting_autosave_enable: NoxComponent + let setting_autosave_strict: NoxComponent + let setting_autosave_noti: NoxComponent + let setting_autosave_dur: NoxComponent + + let bAutosaveEnabled = this.plugin.settings.sy_enable_autosave + + /* + Github > Header Intro + */ + + elm.createEl( 'small', { cls: "gistr-settings-section-description", text: lng( "cfg_tab_sy_header" ) } ) + + /* + Enable Allow Gist Updates + + Notes must be manually saved + */ + + const cfg_tab_sy_tog_allow_gist_updates_desc = new DocumentFragment( ) + cfg_tab_sy_tog_allow_gist_updates_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_sy_tog_allow_gist_updates_desc" ) }`), + ) + + setting_allow_gist_updates = new NoxComponent( elm ) + .setName( lng( "cfg_tab_sy_tog_allow_gist_updates_name" ) ) + .setDesc( cfg_tab_sy_tog_allow_gist_updates_desc ) + .addNoxToggle( toggle => toggle + .setValue( this.plugin.settings.sy_enable_autoupdate ) + .onChange( async ( val ) => + { + this.plugin.settings.sy_enable_autoupdate = val + await this.plugin.saveSettings( ) + }), + ( ) => + ( + SettingsDefaults.sy_enable_autoupdate as boolean + ), + ) + + elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) + + /* + Autosave > Toggle + */ + + const cfg_tab_sy_tog_autosave_enable_desc = new DocumentFragment( ) + cfg_tab_sy_tog_autosave_enable_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_sy_tog_autosave_enable_desc" ) }`), + ) + + setting_autosave_enable = new NoxComponent( elm ) + .setName( lng( "cfg_tab_sy_tog_autosave_enable_name" ) ) + .setDesc( cfg_tab_sy_tog_autosave_enable_desc ) + .addNoxToggle( toggle => toggle + .setValue( this.plugin.settings.sy_enable_autosave ) + .onChange( async ( val ) => + { + this.plugin.settings.sy_enable_autosave = val + await this.plugin.saveSettings( ) + + setting_autosave_strict.setDisabled( !val ) + setting_autosave_strict.settingEl.style.opacity = ( val == false ? this.Opacity_Disabled : this.Opacity_Enabled ) + + setting_autosave_noti.setDisabled( !val ) + setting_autosave_noti.settingEl.style.opacity = ( val == false ? this.Opacity_Disabled : this.Opacity_Enabled ) + + setting_autosave_dur.setDisabled( !val ) + setting_autosave_dur.settingEl.style.opacity = ( val == false ? this.Opacity_Disabled : this.Opacity_Enabled ) + } ), + ( ) => + ( + SettingsDefaults.sy_enable_autosave as boolean + ), + ) + + elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) + + /* + Autosave > Strict Mode + */ + + const cfg_tab_sy_tog_autosave_strict_desc = new DocumentFragment( ) + cfg_tab_sy_tog_autosave_strict_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_sy_tog_autosave_strict_desc", this.plugin.settings.sy_save_duration.toString( ) ) }`), + ) + + setting_autosave_strict = new NoxComponent( elm ) + .setName( lng( "cfg_tab_sy_tog_autosave_strict_name" ) ) + .setDesc( cfg_tab_sy_tog_autosave_strict_desc ) + .addNoxToggle( toggle => toggle + .setValue( this.plugin.settings.sy_enable_autosave_strict ) + .onChange( async ( val ) => + { + this.plugin.settings.sy_enable_autosave_strict = val + await this.plugin.saveSettings( ) + }), + ( ) => + ( + SettingsDefaults.sy_enable_autosave_strict as boolean + ), + ) + + setting_autosave_strict.setDisabled( !bAutosaveEnabled ) + setting_autosave_strict.settingEl.style.opacity = ( bAutosaveEnabled == false ? this.Opacity_Disabled : this.Opacity_Enabled ) + + elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) + + /* + Autosave > Notifications + */ + + const cfg_tab_sy_tog_autosave_noti_desc = new DocumentFragment( ) + cfg_tab_sy_tog_autosave_noti_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_sy_tog_autosave_noti_desc" ) }`), + ) + + setting_autosave_noti = new NoxComponent( elm ) + .setName( lng( "cfg_tab_sy_tog_autosave_noti_name" ) ) + .setDesc( cfg_tab_sy_tog_autosave_noti_desc ) + .addNoxToggle( toggle => toggle + .setValue( this.plugin.settings.sy_enable_autosave_notice ) + .onChange( async ( val ) => + { + this.plugin.settings.sy_enable_autosave_notice = val + await this.plugin.saveSettings( ) + }), + ( ) => + ( + SettingsDefaults.sy_enable_autosave_notice as boolean + ), + ) + + setting_autosave_noti.setDisabled( !bAutosaveEnabled ) + setting_autosave_noti.settingEl.style.opacity = ( bAutosaveEnabled == false ? this.Opacity_Disabled : this.Opacity_Enabled ) + + elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) + + /* + Autosave > Duration + */ + + const cfg_tab_sy_num_save_dur_desc = new DocumentFragment( ) + cfg_tab_sy_num_save_dur_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_sy_num_save_dur_desc", this.plugin.settings.sy_save_duration.toString( ) ) }`), + ) + + let val_save_dur: HTMLDivElement + setting_autosave_dur = new NoxComponent( elm ) + .setName( lng( "cfg_tab_sy_num_save_dur_name" ) ) + .setDesc( cfg_tab_sy_num_save_dur_desc ) + .setClass( "gistr-slider" ) + .addNoxSlider( slider => slider + .setLimits( 0, 120, 1 ) + .setDynamicTooltip( ) + .setValue( this.plugin.settings.sy_save_duration ) + .onChange( async ( val ) => + { + val_save_dur.innerText = " " + val.toString( ) + "s" + + this.plugin.settings.sy_save_duration = val + await this.plugin.saveSettings( ) + + const lng_desc_autosave_strict = new DocumentFragment( ) + lng_desc_autosave_strict.append ( sanitizeHTMLToDom( `${ lng( "cfg_tab_sy_tog_autosave_strict_desc", this.plugin.settings.sy_save_duration.toString( ) ) }` ) ) + setting_autosave_strict.setDesc ( lng_desc_autosave_strict ) + + const lng_desc_autosave_duration = new DocumentFragment( ) + lng_desc_autosave_duration.append ( sanitizeHTMLToDom( `${ lng( "cfg_tab_sy_num_save_dur_desc", this.plugin.settings.sy_save_duration.toString( ) ) }` ) ) + setting_autosave_dur.setDesc ( lng_desc_autosave_duration ) + }), + ( ) => + ( + SettingsDefaults.sy_save_duration as number + ), + ) + + setting_autosave_dur.settingEl.createDiv( '', ( el ) => + { + val_save_dur = el + el.innerText = " " + this.plugin.settings.sy_save_duration.toString( ) + "s" + } ).classList.add( 'gistr-settings-elm-slider-preview' ) + + setting_autosave_dur.setDisabled( !bAutosaveEnabled ) + setting_autosave_dur.settingEl.style.opacity = ( bAutosaveEnabled == false ? this.Opacity_Disabled : this.Opacity_Enabled ) + + elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) + + /* + Include frontmatter when gist saved online + */ + + const cfg_tab_sy_tog_inc_fm_desc = new DocumentFragment( ) + cfg_tab_sy_tog_inc_fm_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_sy_tog_inc_fm_desc" ) }`), + ) + + new NoxComponent( elm ) + .setName( lng( "cfg_tab_sy_tog_inc_fm_name" ) ) + .setDesc( cfg_tab_sy_tog_inc_fm_desc ) + .addNoxToggle( toggle => toggle + .setValue( this.plugin.settings.sy_add_frontmatter ) + .onChange( async ( val ) => + { + this.plugin.settings.sy_add_frontmatter = val + await this.plugin.saveSettings( ) + }), + ( ) => + ( + SettingsDefaults.sy_add_frontmatter as boolean + ), + ) + + elm.createEl( 'div', { cls: "gistr-settings-section-separator", text: "" } ) + + /* + Background color (Dark) + */ + + const cfg_tab_sy_list_icon_desc = new DocumentFragment( ) + cfg_tab_sy_list_icon_desc.append( + sanitizeHTMLToDom(`${ lng( "cfg_tab_sy_list_icon_desc" ) }`), + ) + + new Setting( elm ) + .setName( lng( "cfg_tab_sy_list_icon_name" ) ) + .setDesc( cfg_tab_sy_list_icon_desc ) + .then( ( setting ) => { this.new_ColorPicker + ( + this.plugin, elm, setting, + "sy_clr_lst_icon", + ) } ) + + /* + Tab Footer Spacer + */ + + elm.createEl( 'div', { cls: "gistr-settings-section-footer", text: "" } ) + } + + /* + Tab > Support > New + */ + + Tab_Support_New( elm: HTMLElement ) + { + const tab_og = elm.createEl( "h2", { text: lng( "cfg_tab_sp_title" ), cls: `gistr-settings-header${ this.Hide_Support?" isfold" : "" }` } ) + tab_og.addEventListener( "click", ( )=> + { + this.Hide_Support = !this.Hide_Support + tab_og.classList.toggle( "isfold", this.Hide_Support ) + this.Tab_Support_CreateSettings( ) + } ) + } + + Tab_Support_CreateSettings( ) + { + this.Tab_Support.empty( ) + if ( this.Hide_Support ) return + + this.Tab_Support_ShowSettings( this.Tab_Support ) + } + + Tab_Support_ShowSettings( elm: HTMLElement ) + { + + let json_delay = 0.5 * 1000 + const get_ver_stable = requestUrl( lng( "ver_url", "main" ) ).then( ( res ) => + { + if ( res.status === 200 ) + return res.json.version || lng( "cfg_tab_su_ver_connection_issues" ) + else + return lng( "cfg_tab_su_ver_connection_issues" ) + } ) + + const get_ver_beta = requestUrl( lng( "ver_url", "beta" ) ).then( ( res ) => + { + if ( res.status === 200 ) + return res.json.version || lng( "cfg_tab_su_ver_connection_issues" ) + else + return lng( "cfg_tab_su_ver_connection_issues" ) + } ) + + /* + Section -> Support Buttons + */ + + elm.createEl( 'small', { cls: "gistr-settings-section-description", text: lng( "cfg_tab_su_desc" ) } ) + + /* + Current Version + */ + + const Tab_SU_Ver_Stable = elm.createEl( "div", { text: "", cls: `gistr-settings-ver-sublevel` } ) + const Tab_SU_Ver_Stable_L = Tab_SU_Ver_Stable.createEl( "div", { text: lng( "cfg_tab_su_ver_cur_name" ), cls: `setting-item-name gistr-settings-ver-int-l` } ) + const Tab_SU_Ver_Stable_R = Tab_SU_Ver_Stable.createEl( "div", { text: " ", cls: `gistr-settings-ver-int-r` } ) + const Tab_SU_Ver_Stable_C = Tab_SU_Ver_Stable.createEl( "div", { text: "", cls: `gistr-settings-ver-int-c` } ) + const Tab_SU_Ver_Desc = Tab_SU_Ver_Stable.createEl( "div", { text: lng( "cfg_tab_su_ver_cur_desc" ), cls: `setting-item-description` } ) + + new Setting( Tab_SU_Ver_Stable_R ) + .addText( async ( text ) => + { + text + .setPlaceholder( lng( "cfg_tab_su_ver_status_checking" ) ) + .setValue( lng( "cfg_tab_su_ver_status_checking" ) ) + .setDisabled( true ) + .inputEl.setAttribute( "size", lng( "cfg_tab_su_ver_status_checking" ).length.toString( ) ) + + const el = Tab_SU_Ver_Stable_R.querySelector( ".setting-item-control" ) + el.addClass( "gistr-settings-status-connecting" ) + + let ver_running = this.plugin.manifest.version + let ver_stable = await get_ver_stable + let ver_beta = await get_ver_beta + + setTimeout( function( ) + { + + /* + Text > Could not communicate with server and get stable / beta version + */ + + if ( ver_stable == lng( "cfg_tab_su_ver_connection_issues" ) || ver_beta == lng( "cfg_tab_su_ver_connection_issues" ) ) + { + const el = Tab_SU_Ver_Stable_R.querySelector( ".setting-item-control" ) + el.removeClass ( "gistr-settings-status-connecting" ) + el.addClass ( "gistr-settings-status-error" ) + text.setValue ( lng( "cfg_tab_su_ver_connection_issues" ) ) + } + else + { + /* + Text > Beta version available + */ + + if ( gt( ver_beta, ver_stable ) && lt( ver_running, ver_beta ) ) + { + const el = Tab_SU_Ver_Stable_R.querySelector( ".setting-item-control" ) + text.setValue ( ver_running + " ➜ " + ver_beta + "-beta" ) + } + + /* + Text > Stable version available + */ + + else if ( lt( ver_beta, ver_stable ) && lt( ver_running, ver_stable ) ) + { + const el = Tab_SU_Ver_Stable_R.querySelector( ".setting-item-control" ) + text.setValue ( ver_running + " ➜ " + ver_stable + "-stable" ) + } + + /* + Text > No Updates + */ + + else + { + const el = Tab_SU_Ver_Stable_R.querySelector( ".setting-item-control" ) + el.removeClass ( "gistr-settings-status-connecting" ) + el.addClass ( "gistr-settings-status-success" ) + text.setValue ( ver_running ) + } + } + + + }, json_delay ) + } ) + .addExtraButton( async ( btn ) => + { + btn + .setIcon ( 'circle-off' ) + .setTooltip ( lng( "cfg_tab_su_ver_status_checking_btn_tip" ) ) + + btn.extraSettingsEl.classList.add( "gistr-settings-icon-cur" ) + btn.extraSettingsEl.classList.add( "gistr-anim-spin" ) + btn.extraSettingsEl.classList.add( "gistr-settings-status-connecting" ) + + let ver_running = this.plugin.manifest.version + let ver_stable = await get_ver_stable + let ver_beta = await get_ver_beta + + setTimeout( function( ) + { + + /* + Button > Could not communicate with server and get stable / beta version + */ + + if ( ver_stable == lng( "cfg_tab_su_ver_connection_issues" ) || ver_beta == lng( "cfg_tab_su_ver_connection_issues" ) ) + { + btn.setIcon ( "circle-off" ) + btn.setTooltip ( lng( "cfg_tab_su_ver_status_error_btn_tip" ) ) + + btn.extraSettingsEl.classList.remove ( "gistr-settings-status-connecting" ) + btn.extraSettingsEl.classList.add ( "gistr-settings-icon-error" ) + btn.extraSettingsEl.classList.remove ( "gistr-settings-icon-ok" ) + } + else + { + + /* + Button > Beta version available + */ + + if ( gt( ver_beta, ver_stable ) && lt( ver_running, ver_beta ) ) + { + btn.setTooltip ( lng( "cfg_tab_su_ver_status_new_beta_btn_tip" ) ) + btn.setIcon ( "alert" ) + btn.extraSettingsEl.classList.add ( "gistr-settings-icon-update" ) + } + + /* + Button > Stable version available + */ + + else if ( lt( ver_beta, ver_stable ) && lt( ver_running, ver_stable ) ) + { + btn.setTooltip ( lng( "cfg_tab_su_ver_status_new_stable_btn_tip" ) ) + btn.setIcon ( "alert" ) + btn.extraSettingsEl.classList.add ( "gistr-settings-icon-update" ) + } + + /* + Button > No Updates + */ + + else + { + btn.setIcon ( "check" ) + btn.setTooltip ( lng( "cfg_tab_su_ver_status_updated_btn_tip" ) ) + btn.extraSettingsEl.classList.remove ( "gistr-settings-status-connecting" ) + btn.extraSettingsEl.classList.add ( "gistr-settings-icon-ok" ) + } + } + + btn.onClick( ( ) => + { + window.open( lng( "cfg_tab_su_ver_releases" ) ) + } ) + + }, json_delay ) + } ) + + elm.createEl( 'div', { cls: "gistr-settings-section-separator-15", text: "" } ) + + /* + GUID & UUID + */ + + const env_guid = process.env.BUILD_GUID // static + const env_uuid = process.env.BUILD_UUID // dynamic + + const Tab_SU_GUID = elm.createEl( "div", { text: "", cls: `gistr-settings-ver-sublevel` } ) + const Tab_SU_GUID_L = Tab_SU_GUID.createEl( "div", { text: lng( "cfg_tab_su_guid_cur_name" ), cls: `setting-item-name gistr-settings-ver-int-l` } ) + const Tab_SU_GUID_R = Tab_SU_GUID.createEl( "div", { text: " ", cls: `gistr-settings-ver-int-r` } ) + const Tab_SU_GUID_C = Tab_SU_GUID.createEl( "div", { text: "", cls: `gistr-settings-ver-int-c` } ) + const Tab_SU_GUID_esc = Tab_SU_GUID.createEl( "div", { text: lng( "cfg_tab_su_guid_cur_desc" ), cls: `setting-item-description` } ) + + new Setting( Tab_SU_GUID_R ) + .addText( async ( text ) => + { + text + .setPlaceholder( env_guid ) + .setValue( env_guid ) + .setDisabled( true ) + .inputEl.setAttribute( "size", lng( "cfg_tab_su_ver_status_checking" ).length.toString( ) ) + + const el = Tab_SU_GUID_R.querySelector( ".setting-item-control" ) + el.addClass ( "gistr-settings-support-build-id" ) + } ) + .addExtraButton( async ( btn ) => + { + btn + .setIcon ( 'copy' ) + .setTooltip ( lng( "cfg_tab_su_guid_btn_tip" ) ) + + btn.onClick( ( ) => + { + navigator.clipboard.writeText( env_guid ) + new Notice( lng( "cfg_tab_su_guid_notice", env_guid ) ) + } ) + } ) + + elm.createEl( 'div', { cls: "gistr-settings-section-separator-15", text: "" } ) + + /* + GUID & UUID + */ + + const Tab_SU_UUID = elm.createEl( "div", { text: "", cls: `gistr-settings-ver-sublevel` } ) + const Tab_SU_UUID_L = Tab_SU_UUID.createEl( "div", { text: lng( "cfg_tab_su_uuid_cur_name" ), cls: `setting-item-name gistr-settings-ver-int-l` } ) + const Tab_SU_UUID_R = Tab_SU_UUID.createEl( "div", { text: " ", cls: `gistr-settings-ver-int-r` } ) + const Tab_SU_UUID_C = Tab_SU_UUID.createEl( "div", { text: "", cls: `gistr-settings-ver-int-c` } ) + const Tab_SU_UUID_esc = Tab_SU_UUID.createEl( "div", { text: lng( "cfg_tab_su_uuid_cur_desc" ), cls: `setting-item-description` } ) + + new Setting( Tab_SU_UUID_R ) + .addText( async ( text ) => + { + text + .setPlaceholder( env_uuid ) + .setValue( env_uuid ) + .setDisabled( true ) + .inputEl.setAttribute( "size", lng( "cfg_tab_su_ver_status_checking" ).length.toString( ) ) + + const el = Tab_SU_UUID_R.querySelector( ".setting-item-control" ) + el.addClass ( "gistr-settings-support-build-id" ) + } ) + .addExtraButton( async ( btn ) => + { + btn + .setIcon ( 'copy' ) + .setTooltip ( lng( "cfg_tab_su_uuid_btn_tip" ) ) + + btn.onClick( ( ) => + { + navigator.clipboard.writeText( env_uuid ) + new Notice( lng( "cfg_tab_su_uuid_notice", env_uuid ) ) + } ) + } ) + + elm.createEl( 'div', { cls: "gistr-settings-section-separator-15", text: "" } ) + + /* + Button > Getting Started > Open Interface + */ + + new Setting( elm ) + .setName( lng( "cfg_tab_su_gs_name" ) ) + .setDesc( lng( "cfg_tab_su_gs_desc" ) ) + .addButton( btn => + { + btn.setButtonText( lng( "cfg_tab_su_gs_btn" ) ) + .setCta( ) + .onClick( async( ) => + { + const action = await new ModalGettingStarted( this.app, this.plugin, this.plugin.manifest, this.plugin.settings, false ).openAndAwait( ) + } ) + } ) + + /* + Button -> Plugin Repo + */ + + new Setting( elm ) + .setName( lng( "cfg_tab_su_repo_label" ) ) + .setDesc( lng( "cfg_tab_su_repo_url" ) ) + .addButton( ( btn ) => + { + btn.setButtonText( lng( "cfg_tab_su_repo_btn" ) ).onClick( ( ) => + { + window.open( lng( "cfg_tab_su_repo_url" ) ) + } ) + } ) + + /* + Button -> Plugin Demo Vault + */ + + new Setting( elm ) + .setName( lng( "cfg_tab_su_vault_label" ) ) + .setDesc( lng( "cfg_tab_su_vault_url" ) ) + .addButton( ( btn ) => + { + btn.setButtonText( lng( "cfg_tab_su_vault_btn" ) ).onClick( ( ) => + { + window.open( lng( "cfg_tab_su_vault_url" ) ) + } ) + } ) + + /* + Button -> OpenGist > Download + */ + + new Setting( elm ) + .setName( lng( "cfg_tab_su_ogrepo_label" ) ) + .setDesc( lng( "cfg_tab_su_ogrepo_url" ) ) + .addButton( ( btn ) => + { + btn.setButtonText( lng( "cfg_tab_su_ogrepo_btn" ) ).onClick( ( ) => + { + window.open( lng( "cfg_tab_su_ogrepo_url" ) ) + } ) + } ) + + /* + Button -> OpenGist > Docs + */ + + new Setting( elm ) + .setName( lng( "cfg_tab_su_ogdocs_label" ) ) + .setDesc( lng( "cfg_tab_su_ogdocs_url" ) ) + .addButton( ( btn ) => + { + btn.setButtonText( lng( "cfg_tab_su_ogdocs_btn" ) ).onClick( ( ) => + { + window.open( lng( "cfg_tab_su_ogdocs_url" ) ) + } ) + } ) + + /* + Button -> OpenGist > Docs + */ + + new Setting( elm ) + .setName( lng( "cfg_tab_su_ogdemo_label" ) ) + .setDesc( lng( "cfg_tab_su_ogdemo_url" ) ) + .addButton( ( btn ) => + { + btn.setButtonText( lng( "cfg_tab_su_ogdemo_btn" ) ).onClick( ( ) => + { + window.open( lng( "cfg_tab_su_ogdemo_url" ) ) + } ) + } ) + + /* + Button -> Github Gist + */ + + new Setting( elm ) + .setName( lng( "cfg_tab_su_gist_label" ) ) + .setDesc( lng( "cfg_tab_su_gist_url" ) ) + .addButton( ( btn ) => + { + btn.setButtonText( lng( "cfg_tab_su_gist_btn" ) ).onClick( ( ) => + { + window.open( lng( "cfg_tab_su_gist_url" ) ) + } ) + } ) + + /* + Button -> Donate + */ + + const div_Donate = elm.createDiv( { cls: "gistr-donate" } ) + const lnk_Donate = new DocumentFragment( ) + lnk_Donate.append( + sanitizeHTMLToDom(` + + + + `), + ) + + new Setting( div_Donate ).setDesc( lnk_Donate ) + } +} \ No newline at end of file diff --git a/src/settings/sections/index.ts b/src/settings/sections/index.ts new file mode 100644 index 00000000..f592b286 --- /dev/null +++ b/src/settings/sections/index.ts @@ -0,0 +1 @@ +export { SettingsSection } from 'src/settings/sections/SettingsSection' \ No newline at end of file diff --git a/src/settings/settings.ts b/src/settings/settings.ts new file mode 100644 index 00000000..3d892064 --- /dev/null +++ b/src/settings/settings.ts @@ -0,0 +1,55 @@ +import GistrPlugin from "src/main" + +/* + Settings +*/ + +export interface GistrSettings +{ + firststart: boolean + keyword: string | "gistr" + css_og: string | null + css_gh: string | null + theme: string | null + blk_pad_t: number | 16 + blk_pad_b: number | 19 + textwrap: string | "Enabled" + notitime: number | 10 + sy_clr_lst_icon: string | "757575E6" + ge_enable_updatenoti: boolean | true + + og_clr_bg_light: string | "CBCBCB" + og_clr_bg_dark: string | "121315" + og_clr_sb_light: string | "808080" + og_clr_sb_dark: string | "4960BA" + og_clr_tx_light: string | "2A2626" + og_clr_tx_dark: string | "CAD3F5" + og_opacity: number | 1 + + gh_clr_bg_light: string | "E5E5E5" + gh_clr_bg_dark: string | "121315" + gh_clr_sb_light: string | "3D85C4" + gh_clr_sb_dark: string | "BA496A" + gh_clr_tx_light: string | "2A2626" + gh_clr_tx_dark: string | "CAD3F5" + gh_opacity: number | 1 + + sy_enable_autoupdate: boolean | true + sy_enable_autosave: boolean | false + sy_enable_autosave_strict: boolean | false + sy_enable_autosave_notice: boolean | false + sy_add_frontmatter: boolean | false + sy_save_duration: number | 10 + + context_sorting: [], +} + +/* + Settings > Get +*/ + +export const SettingsGet = async ( plugin: GistrPlugin ): Promise < GistrSettings > => +{ + await plugin.loadSettings( ) + return plugin.settings +} \ No newline at end of file diff --git a/src/utils/colorpicker/index.ts b/src/utils/colorpicker/index.ts new file mode 100644 index 00000000..4fd9a8f6 --- /dev/null +++ b/src/utils/colorpicker/index.ts @@ -0,0 +1,189 @@ +/* + Import +*/ + +import { Setting, ExtraButtonComponent } from 'obsidian' +import GistrPlugin from "src/main" +import { lng } from 'src/lang' +import { ColorTranslator } from 'colortranslator' +import Pickr from "@simonwep/pickr" + +/* + CSS Color Values +*/ + +export type CLR_VAR = `--${string}` // css variable +export type CLR_HEX = `#${string}` // css hex +export type Color = CLR_HEX | CLR_VAR + +/* + Color Picker +*/ + +export class ColorPicker extends Pickr +{ + ActionSave: ( ActionSave: Color ) => void + ColorReset: ( ) => void + AddButtonReset: ExtraButtonComponent + + constructor( plugin : GistrPlugin, el : HTMLElement, setting : Setting, tip? : string ) + { + const settings : Pickr.Options = + { + el: setting.controlEl.createDiv( { cls: "picker" } ), + theme: "nano", + default: "#FFFFFF", + position: "left-middle", + lockOpacity: false, + components: + { + preview: true, + hue: true, + opacity: true, + interaction: + { + hex: true, + rgba: true, + hsla: true, + input: true, + cancel: true, + save: true, + }, + }, + i18n: + { + "ui:dialog": lng( "pickr_dialog" ), + "btn:swatch": lng( "pickr_swatch" ), + "btn:toggle": ( typeof tip !== "undefined" ) ? tip : lng( "pickr_toggle" ), + "btn:last-color": lng( "pickr_last" ), + "btn:save": lng( "pickr_save" ), + "btn:cancel": lng( "pickr_cancel" ), + "btn:clear": lng( "pickr_clear" ), + } + } + + if ( el.parentElement !== null ) + settings.container = el.parentElement + + super( settings ) + + /* + Colorpicker > Save + */ + + this.ActionSave = ( ActionSave: Color ) => + { + ( + async ( ) => + { + await plugin.saveSettings( ) + } + )( ) + } + + /* + Colorpicker > Reset Color + */ + + this.ColorReset = ( ) => + { + const clr: Color = "#FFFFFF" + this.setColor ( GetColor( clr ) ) + this.ActionSave ( clr ) + } + } +} + +/* + Color > Get +*/ + +export function GetColor( clr: Color ): Color +{ + return bValidCSS( clr ) ? CSS_GetValue( clr ) : clr +} + +/* + Converts colors when converting hsl and rgb +*/ + +export function ConvertColor( str : string ) : string +{ + const strSplit = str.trim( ).replace( /(\d*)%/g, "$1" ).split( " " ) + + const operators: { [ key: string ] : ( n1 : number, n2 : number ) => number } = + { + "+" : ( n1 : number, n2 : number ) : number => Math.max( n1 + n2, 0 ), + "-" : ( n1 : number, n2 : number ) : number => Math.max( n1 - n2, 0 ), + } + + if ( strSplit.length === 3 ) + { + if ( strSplit[ 1 ] in operators ) + { + return `${ operators[ strSplit[ 1 ] ]( parseFloat( strSplit[ 0 ] ), parseFloat( strSplit[ 2 ] ) ) }%` + } + } + + return str +} + +/* + CSS > Get Value +*/ + +export function CSS_GetValue( property: CLR_VAR ): CLR_HEX +{ + + const value = window.getComputedStyle( document.body ).getPropertyValue( property ).trim( ) + + /* + type : hex + #ff0000 + */ + + if ( typeof value === "string" && value.startsWith( "#" ) ) + return `#${ value.trim( ).substring( 1 ) }` + + /* + type : hsl + hsl( 0, 100%, 50% ) + */ + + else if ( value.startsWith( "hsl" ) ) + return `#${ ColorTranslator.toHEXA + ( + value.replace( /ConvertColor\((.*?)\)/g, ( match, capture ) => + ConvertColor( capture ) ) + ).substring( 1 ) }` + + /* + type : rgb + rgb( 255, 0, 0 ) + */ + + else if ( value.startsWith( "rgb" ) ) + return `#${ ColorTranslator.toHEXA + ( + value.replace( /ConvertColor\((.*?)\)/g, ( match, capture ) => + ConvertColor( capture ) ) + ).substring( 1 ) }` + + /* + Unknown type + */ + + else + console.warn( lng( "pickr_dev_unknown", value ) ) + + return `#${ ColorTranslator.toHEXA( value ).substring( 1 ) }` +} + +/* + Check Valid CSS +*/ + +export function bValidCSS( css: string ): css is CLR_VAR +{ + return typeof css === "string" && css.startsWith( "--" ) +} \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000..edd1c037 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1 @@ +export { ColorPicker, GetColor } from 'src/utils/colorpicker' \ No newline at end of file From 592ace190194441e70c480c594f805d401c0f5a8 Mon Sep 17 00:00:00 2001 From: Aetherinox Date: Tue, 12 Mar 2024 19:35:40 -0700 Subject: [PATCH 25/25] chore: update typescript & rollup dependencies --- package-lock.json | 109 ++++------------------------------------------ package.json | 4 +- 2 files changed, 11 insertions(+), 102 deletions(-) diff --git a/package-lock.json b/package-lock.json index 23c021db..9a04f64e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gistr", - "version": "1.4.3", + "version": "1.4.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gistr", - "version": "1.4.3", + "version": "1.4.4", "license": "MIT", "dependencies": { "@aetherinox/noxkit": "^1.1.0", @@ -24,13 +24,12 @@ "@rollup/plugin-typescript": "^11.1.6", "@types/node": "^20.11.24", "@types/semver": "^7.5.8", - "env-cmd": "^10.1.0", "obsidian": "^1.0.0", - "rollup": "^2.32.1", + "rollup": "^2.79.0", "rollup-plugin-define": "^1.0.1", "rollup-plugin-license": "^3.3.1", "tslib": "^2.2.0", - "typescript": "^4.2.4", + "typescript": "^4.9.5", "uuid": "^9.0.1" } }, @@ -101,13 +100,13 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -555,20 +554,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -583,31 +568,6 @@ "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" }, - "node_modules/env-cmd": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-10.1.0.tgz", - "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==", - "dev": true, - "dependencies": { - "commander": "^4.0.0", - "cross-spawn": "^7.0.0" - }, - "bin": { - "env-cmd": "bin/env-cmd.js" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/env-cmd/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -783,12 +743,6 @@ "@types/estree": "*" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, "node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", @@ -939,15 +893,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -1173,27 +1118,6 @@ "randombytes": "^2.1.0" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/smob": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz", @@ -1385,21 +1309,6 @@ "dev": true, "peer": true }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index cff07023..05610814 100644 --- a/package.json +++ b/package.json @@ -34,11 +34,11 @@ "@types/node": "^20.11.24", "@types/semver": "^7.5.8", "obsidian": "^1.0.0", - "rollup": "^2.32.1", + "rollup": "^2.79.0", "rollup-plugin-define": "^1.0.1", "rollup-plugin-license": "^3.3.1", "tslib": "^2.2.0", - "typescript": "^4.2.4", + "typescript": "^4.9.5", "uuid": "^9.0.1" }, "dependencies": {