├── assets ├── pull.html ├── style.css ├── fork.html ├── clone.html ├── commit.html ├── settings.html ├── branch.html └── create.html ├── README.md └── GHForPG.js /assets/pull.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 1) Open the desktop version of Pinegrow - this plugin **will not** work with Pinegrow Live 3 | 2) Select File -> Manage libraries & plugins... 4 | 3) Click on "Load plugin" at the bottom 5 | 4) Navigate to the plugin folder and select "GHForPG.js" 6 | 5) I recommend restarting Pinegrow, but this isn't neccessary. 7 | 8 | ## Usage 9 | This plugin will add a new "GitHub" menu to Pinegrow. There are five selections when a project is not open and two additional items when one is open. 10 | 11 | ### Settings 12 | The setting selection opens a modal to add in your user name, email, and personal token for GitHub. These will be stored in a localStorage variable in Pinegrow and should be retained between restarts. There is a "Retrieve Settings" button that will get the user name and email for a project, but not the authorization token. All further actions by the plugin require authorization. 13 | 14 | ## New Repo Controls 15 | ### Create New Repo 16 | This will allow the creation of a new GitHub repo, either from scratch or from an existing project. If the project doesn't exist, a base folder will be created that should then be opened as a Pinegrow project. 17 | 18 | ### Clone Existing Repo 19 | This will create a local copy of a repo from GitHub and link this local copy to the repo on GitHub 20 | 21 | ### Branch Existing Repo 22 | This will create a local copy and a new branch of an existing GitHub repo. 23 | 24 | ### Fork Existing Repo 25 | This will create a local copy and a duplicate of a repo on GitHub from a foreign account into the user's account. 26 | 27 | ## Existing Repo Controls 28 | ### Stage, Commit, and Push Changes 29 | This allows you to select which files are committed to a repo. It flags changed and added files. 30 | 1) Select files and click "Stage files" 31 | 32 | -If you make a mistake you can select a file and click "Unstage files" 33 | 34 | 2) Enter the author email 35 | 3) Enter a commit message 36 | 4) Click on "Commit files" 37 | 5) Click on "Push files" 38 | 39 | ### Pull changes 40 | This will retrieve any changes made to the GitHub repo that aren't present in the local repo. -------------------------------------------------------------------------------- /assets/style.css: -------------------------------------------------------------------------------- 1 | /*From create.html*/ 2 | .gh-error { 3 | color: red; 4 | display: none; 5 | } 6 | 7 | .gh-logged-in { 8 | color: green; 9 | display: none; 10 | } 11 | 12 | .gh-message-area { 13 | width: 100%; 14 | height: 6em; 15 | display: flex; 16 | justify-content: center; 17 | } 18 | 19 | .gh-message-box { 20 | width: 100%; 21 | height: 6em; 22 | border: 1px solid #ccc; 23 | border-radius: 4px; 24 | padding-left: 12px; 25 | padding-right: 12px; 26 | overflow-y: scroll; 27 | } 28 | 29 | /* From clone.html */ 30 | #gh-clone-verification { 31 | visibility: hidden; 32 | margin-left: 20px; 33 | } 34 | 35 | #gh-clone-verification.visible { 36 | visibility: visible; 37 | } 38 | 39 | /* From branch.html */ 40 | #gh-branch-verification { 41 | display: none; 42 | margin-left: 20px; 43 | } 44 | 45 | #gh-branch-verification.visible { 46 | visibility: visible; 47 | } 48 | 49 | /* From commit.html */ 50 | #ghProjectTree { 51 | list-style: none; 52 | padding: 0; 53 | } 54 | 55 | #ghProjectTree input { 56 | margin-right: 10px; 57 | } 58 | 59 | #ghProjectTree ul { 60 | list-style: none; 61 | } 62 | 63 | .gh-commit-error { 64 | color: red; 65 | display: none; 66 | } 67 | 68 | .gh-visible { 69 | display: block; 70 | } 71 | 72 | .gh-hidden { 73 | display: none; 74 | } 75 | 76 | #gh-commit-modal i.folder-icon { 77 | color: black; 78 | } 79 | 80 | #gh-commit-modal li.file { 81 | padding-left: 1em; 82 | } 83 | 84 | .gh-disabled { 85 | color: #ccc; 86 | } 87 | 88 | .gh-ml { 89 | margin-left: 20px; 90 | } 91 | 92 | .gh-mt { 93 | margin-top: 20px; 94 | } 95 | 96 | [data-gh-status]::before{ 97 | content: "*"; 98 | color: white; 99 | } 100 | 101 | [data-gh-status="*modified"]::before { 102 | color: red; 103 | } 104 | 105 | [data-gh-status="*added"]::before { 106 | color: red; 107 | } 108 | 109 | [data-gh-status="modified"]::before { 110 | color: blue; 111 | } 112 | 113 | [data-gh-status="added"]::before { 114 | color: blue; 115 | } 116 | 117 | .gh-red { 118 | color: red; 119 | } 120 | 121 | .gh-blue { 122 | color: blue; 123 | } 124 | 125 | 126 | 127 | /* From fork.html */ 128 | #gh-fork-verification { 129 | visibility: hidden; 130 | margin-left: 20px; 131 | } 132 | -------------------------------------------------------------------------------- /assets/fork.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/clone.html: -------------------------------------------------------------------------------- 1 | 56 | -------------------------------------------------------------------------------- /assets/commit.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/settings.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/branch.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/create.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /GHForPG.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | $('body').one('pinegrow-ready', function (e, pinegrow) { 3 | 4 | //Add a framework id, it should be unique to this framework and version. Best practice is to define the prefix as a variable that can be used throughout the framework. 5 | let framework_id = 'shd_github_for_pinegrow'; 6 | 7 | //Instantiate a new framework 8 | var framework = new PgFramework(framework_id, 'GitHub-for-Pinegrow'); 9 | 10 | // Define a framework type - if you plan on having multiple versions, this should be the same for each version. 11 | framework.type = 'GitHub-for-Pinegrow'; 12 | 13 | //Prevent the activation of multiple versions of the framework - if this should be allowed, change to false 14 | framework.allow_single_type = true; 15 | 16 | //Optional, add a badge to the framework list notify user of new or updated status 17 | //framework.info_badge = 'v1.0.0'; 18 | 19 | //Add a description of the plugin 20 | framework.description = 'Adds GitHub functionality to Pinegrow'; 21 | 22 | //Add a framework author to be displayed with the framework templates 23 | framework.author = 'Robert "Bo" Means'; 24 | 25 | //Add a website "https://pinegrow.com" or mailto "mailto:info@pinegrow.com" link for redirect on author name click 26 | framework.author_link = 'https://robertmeans.net'; 27 | 28 | // Tell Pinegrow about the framework 29 | pinegrow.addFramework(framework); 30 | 31 | //uncomment the line below for debugging - opens devtools on Pinegrow Launch 32 | //require('nw.gui').Window.get().showDevTools(); 33 | 34 | //Establish the base directory for node modules 35 | const frameBase = framework.getBaseUrl(); 36 | 37 | //Load in the Octokit module 38 | const { 39 | Octokit 40 | } = require(crsaMakeFileFromUrl(frameBase + '/node_modules/@octokit/rest/dist-node/index.js')); 41 | 42 | //Load in Git functions from the isomorphic-git library 43 | const Git = require(crsaMakeFileFromUrl(frameBase + '/node_modules/isomorphic-git/index.cjs')); 44 | const http = require(crsaMakeFileFromUrl(frameBase + '/node_modules/isomorphic-git/http/node/index.cjs')); 45 | 46 | //Load in glob for easier file handling 47 | const glob = require(crsaMakeFileFromUrl(frameBase + '/node_modules/fast-glob/out/index.js')); 48 | 49 | //Add in better folder selector 50 | const openFolderExplorer = require(crsaMakeFileFromUrl(frameBase + '/node_modules/nw-programmatic-folder-select/index.js')); 51 | 52 | //load in file management packages 53 | const Path = require('path'); 54 | const fse = require('fs-extra'); 55 | 56 | //Function to poulate settings with existing values, clear settings, save new settings. 57 | let ghManipulateSettingsFields = () => { 58 | //First, let's get all of the form fields and buttons 59 | let userNameField = ghById('gh-user-name'); 60 | let emailField = ghById('gh-user-email'); 61 | let accountTokenField = ghById('gh-token'); 62 | let retrieveSettingsButton = ghById('gh-retrieve-settings'); 63 | let clearSettingsButton = ghById('gh-clear-settings'); 64 | let saveSettingsButton = ghById('gh-save-settings'); 65 | let cancelSettingsButton = ghById('gh-cancel-settings'); 66 | let userErrorMessage = ghById('gh-username-error'); 67 | let emailErrorMessage = ghById('gh-email-error'); 68 | let tokenErrorMessage = ghById('gh-token-error'); 69 | let credentialsErrorMessage = ghById('gh-git-config-missing'); 70 | let projectNotOpenError = ghById('gh-settings-project-error'); 71 | let credentialsNotificationMessage = ghById('gh-settings-retrieved-notification'); 72 | let credentialsMessage = ghById('gh-correct-credentials'); 73 | let configNotification = ghById('gh-settings-config-notification'); 74 | let showToken = ghById('gh-show-token'); 75 | //check if the user has already stored any credentials in local storage, if so add them to the form 76 | if (localStorage.getItem('gh-settings-user-name')) { 77 | userNameField.value = localStorage.getItem('gh-settings-user-name'); 78 | emailField.value = localStorage.getItem('gh-settings-email'); 79 | accountTokenField.value = localStorage.getItem('gh-settings-token'); 80 | } 81 | 82 | //Allow toggle of the token field between plain text and encoded 83 | showToken.addEventListener('click', () => { 84 | accountTokenField.type = showToken.checked ? "text" : "password"; 85 | }); 86 | 87 | //allow user to retrieve settings from gitconfig file 88 | retrieveSettingsButton.addEventListener('click', async () => { 89 | //Throw error if project isn't open 90 | if (!pinegrow.getCurrentProject()) { 91 | projectNotOpenError.style.visibility = 'visible'; 92 | return; 93 | } 94 | let projectDirectory = ghProjectDirectory(); 95 | //Get gitconfig file 96 | let ghCredentials = await ghFetchGitConfig(projectDirectory); 97 | //Throw error if file doesn't exist 98 | if (ghCredentials === false || ghCredentials === undefined) { 99 | credentialsErrorMessage.style.visibility = 'visible'; 100 | return; 101 | } 102 | if (ghCredentials.userName === undefined || ghCredentials.userToken === undefined) { 103 | credentialsErrorMessage.style.visibility = 'visible'; 104 | localStorage.setItem('gh-config-file-error', true); 105 | return; 106 | } 107 | //Set form values to retrieved credentials 108 | credentialsNotificationMessage.style.visibility = 'visible'; 109 | userNameField.value = ghCredentials.userName; 110 | accountTokenField.value = ghCredentials.userToken; 111 | }); 112 | 113 | clearSettingsButton.addEventListener('click', () => { 114 | localStorage.removeItem('gh-settings-user-name'); 115 | localStorage.removeItem('gh-settings-email'); 116 | localStorage.removeItem('gh-settings-token'); 117 | localStorage.removeItem('gh-config-file-error'); 118 | userNameField.value = ''; 119 | emailField.value = ''; 120 | accountTokenField.value = ''; 121 | accountTokenField.type = "password"; 122 | showToken.checked = false; 123 | saveSettingsButton.className = 'btn btn-primary'; 124 | userErrorMessage.style.visibility = 'hidden'; 125 | emailErrorMessage.style.visibility = 'hidden'; 126 | tokenErrorMessage.style.visibility = 'hidden'; 127 | credentialsErrorMessage.style.visibility = 'hidden'; 128 | projectNotOpenError.style.visibility = 'hidden'; 129 | credentialsNotificationMessage.style.visibility = 'hidden'; 130 | credentialsMessage.style.visibility = 'hidden'; 131 | if (pinegrow.getCurrentProject()) { 132 | let projectDirectory = ghProjectDirectory(); 133 | ghDeleteGitConfig(projectDirectory); 134 | } 135 | }); 136 | 137 | saveSettingsButton.addEventListener('click', async () => { 138 | localStorage.setItem('gh-settings-user-name', userNameField.value); 139 | localStorage.setItem('gh-settings-email', emailField.value); 140 | localStorage.setItem('gh-settings-token', accountTokenField.value); 141 | if (emailField.value === '' || emailField.value === null) { 142 | emailErrorMessage.style.visibility = 'visible'; 143 | return; 144 | } 145 | let errorCheck = await ghVerifyGitHubAccount(); 146 | if (true === errorCheck) { 147 | saveSettingsButton.className = 'btn btn-success'; 148 | userErrorMessage.style.visibility = 'hidden'; 149 | emailErrorMessage.style.visibility = 'hidden'; 150 | tokenErrorMessage.style.visibility = 'hidden'; 151 | credentialsErrorMessage.style.visibility = 'hidden'; 152 | projectNotOpenError.style.visibility = 'hidden'; 153 | credentialsNotificationMessage.style.visibility = 'hidden'; 154 | credentialsMessage.style.visibility = 'visible'; 155 | accountTokenField.type = "password"; 156 | if (pinegrow.getCurrentProject()) { 157 | let projectDirectory = ghProjectDirectory(); 158 | if (localStorage.getItem('gh-config-file-error')) { 159 | configNotification.style.visibility = 'visible'; 160 | localStorage.removeItem('gh-config-file-error'); 161 | ghWriteGitConfig(projectDirectory); 162 | } 163 | } 164 | } else if (-1 === errorCheck) { 165 | userErrorMessage.style.visibility = 'visible'; 166 | localStorage.removeItem('gh-settings-user-name'); 167 | credentialsMessage.style.visibility = 'hidden'; 168 | emailErrorMessage.style.visibility = 'hidden'; 169 | tokenErrorMessage.style.visibility = 'hidden'; 170 | credentialsNotificationMessage.style.visibility = 'hidden'; 171 | } else { 172 | tokenErrorMessage.style.visibility = 'visible'; 173 | localStorage.removeItem('gh-settings-token'); 174 | credentialsMessage.style.visibility = 'hidden'; 175 | userErrorMessage.style.visibility = 'hidden'; 176 | emailErrorMessage.style.visibility = 'hidden'; 177 | credentialsNotificationMessage.style.visibility = 'hidden'; 178 | } 179 | }); 180 | 181 | cancelSettingsButton.addEventListener('click', () => { 182 | saveSettingsButton.className = 'btn btn-primary'; 183 | userErrorMessage.style.visibility = 'hidden'; 184 | tokenErrorMessage.style.visibility = 'hidden'; 185 | credentialsMessage.style.visibility = 'hidden'; 186 | projectNotOpenError.style.visibility = 'hidden'; 187 | credentialsNotificationMessage.style.visibility = 'hidden' 188 | accountTokenField.type = "password"; 189 | showToken.checked = false; 190 | }) 191 | }; 192 | 193 | //Function to gather new repo information. 194 | let ghManipulateCreateFields = async () => { 195 | let createModal = ghById('gh-create-modal'); 196 | let closeButton = ghById('gh-create-close-button'); 197 | let repoNameField = ghById('gh-new-repo-name'); 198 | let useExistingControl = ghById('gh-use-existing-folder'); 199 | let repoDescription = ghById('gh-repo-description'); 200 | let repoCommitMessage = ghById('gh-create-commit-message'); 201 | let repoCommitMessageGroup = ghById('gh-create-commit-message-group'); 202 | let repoLicense = ghById('gh-repo-license'); 203 | let repoPrivate = ghById('gh-repo-private'); 204 | let repoInitialize = ghById('gh-auto-init'); 205 | let repoInitializeGroup = ghById('gh-auto-initialize-group'); 206 | let repoNameError = ghById('gh-repo-name-error'); 207 | let newFolderGroup = ghById('gh-create-new-folder-group'); 208 | let folderButton = ghById('gh-get-new-save-folder'); 209 | let folderPath = ghById('gh-new-save-location'); 210 | let folderNameGroup = ghById('gh-new-folder-name-group'); 211 | let folderName = ghById('gh-new-folder-name'); 212 | let useExisting = false; 213 | let correctCredentials = ghById('gh-create-correct-credentials'); 214 | let incorrectCredentials = ghById('gh-create-incorrect-credentials'); 215 | let createMessage = ghById('gh-create-message-box'); 216 | let createNewRepo = ghById('gh-create-repo-button'); 217 | let createResetButton = ghById('gh-create-reset'); 218 | let createPathError = ghById('gh-create-path-error'); 219 | let createLicenseError = ghById('gh-license-error'); 220 | let newFolderNameError = ghById('gh-create-folder-name-error'); 221 | 222 | createMessage.innerHTML = '

No message at this time

'; 223 | 224 | useExistingControl.addEventListener('click', () => { 225 | if (pinegrow.getCurrentProject()) { 226 | if (useExistingControl.checked) { 227 | newFolderGroup.style.display = 'none'; 228 | folderNameGroup.style.display = 'none'; 229 | repoCommitMessageGroup.style.display = 'block'; 230 | repoInitializeGroup.style.display = 'none'; 231 | useExisting = true; 232 | let currentProject = pinegrow.getCurrentProject(); 233 | let sanitizedName = ghSanitizeRepoName(currentProject.name) 234 | repoNameField.value = sanitizedName; 235 | } else { 236 | newFolderGroup.style.display = 'block'; 237 | folderNameGroup.style.display = 'block'; 238 | repoCommitMessageGroup.style.display = 'none'; 239 | repoInitializeGroup.style.display = 'block'; 240 | useExisting = false; 241 | repoNameField.value = ''; 242 | } 243 | } else { 244 | createMessage.innerHTML = '

You must have a project open to use this option.

'; 245 | useExistingControl.checked = false; 246 | } 247 | }); 248 | 249 | repoNameField.addEventListener('input', () => { 250 | if (!useExistingControl.checked) { 251 | folderName.value = repoNameField.value; 252 | } 253 | }); 254 | 255 | folderButton.addEventListener('click', () => { 256 | openFolderExplorer(window, (selection) => { 257 | folderPath.innerHTML = selection; 258 | }); 259 | }); 260 | 261 | createResetButton.addEventListener('click', () => { 262 | folderPath.innerHTML = ''; 263 | createModal.querySelectorAll('.gh-error').forEach(el => { 264 | el.style.display = 'none'; 265 | }); 266 | createModal.querySelector('.gh-logged-in').style.display = 'none'; 267 | createMessage.innerHTML = '

No message at this time

'; 268 | createNewRepo.disabled = false; 269 | }) 270 | 271 | createNewRepo.addEventListener('click', async () => { 272 | let errorCheck = await ghVerifyGitHubAccount(); 273 | if (!errorCheck) { 274 | createMessage.innerHTML = '

Please check your credentials.

'; 275 | incorrectCredentials.style.display = "block"; 276 | return; 277 | } else { 278 | correctCredentials.style.display = "block"; 279 | } 280 | if (repoNameField.value === '' || repoNameField.value === undefined) { 281 | createMessage.innerHTML = '

Please add a repo name.

'; 282 | repoNameError.style.display = 'block'; 283 | return; 284 | } 285 | if (repoLicense.value === '') { 286 | createMessage.innerHTML = '

Please select a license.

'; 287 | createLicenseError.style.display = 'block'; 288 | return; 289 | } 290 | if (folderPath.innerHTML === '' && !useExisting) { 291 | createMessage.innerHTML = '

Please add a path to save your local repo.

'; 292 | createPathError.style.display = 'block'; 293 | return; 294 | } 295 | if ((folderName.value === '' || folderName.value === undefined) && !useExisting) { 296 | createMessage.innerHTML = '

Please add a name for your local repo folder.

'; 297 | newFolderNameError.style.display = 'block'; 298 | return; 299 | } 300 | createMessage.innerHTML = ""; 301 | let repoArgs = { 302 | "name": repoNameField.value 303 | }; 304 | repoArgs['description'] = repoDescription.value; 305 | repoArgs['license_template'] = repoLicense.value; 306 | 307 | if (repoPrivate.checked) { 308 | repoArgs['private'] = true; 309 | } 310 | 311 | if (repoInitialize.checked) { 312 | repoArgs['auto_init'] = true; 313 | } 314 | 315 | let repoCreated = await ghCreateRepo(repoArgs); 316 | ghStatusUpdate(repoCreated.status, createMessage, 'New Repo created.'); 317 | 318 | if (!useExisting && repoCreated.status == "201") { 319 | let owner = localStorage.getItem('gh-settings-user-name'); 320 | let newFolder = folderName.value; 321 | let dir = Path.join(folderPath.innerHTML, newFolder); 322 | let repo = "https://github.com/" + owner + "/" + repoNameField.value; 323 | const onAuth = () => ({ 324 | username: localStorage.getItem('gh-settings-user-name'), 325 | password: localStorage.getItem('gh-settings-token') 326 | }); 327 | await ghClone(onAuth, repo, dir, createMessage, ''); 328 | let content = '_pgbackup'; 329 | let ignoreFile = Path.join(dir, '.gitignore'); 330 | try { 331 | fse.writeFileSync(ignoreFile, content); 332 | } catch (err) { 333 | console.error(err); 334 | } 335 | let projectData = { 336 | repo: repoNameField.value, 337 | owner: owner, 338 | branch: 'main' 339 | }; 340 | ghCreateJsonFile(projectData, dir, 'githubinfo.json'); 341 | ghWriteGitConfig(dir); 342 | } else if (useExisting && repoCreated.status == "201") { 343 | let owner = localStorage.getItem('gh-settings-user-name'); 344 | 345 | let projectDirectory = ghProjectDirectory(); 346 | await Git.init({ 347 | fs: fse, 348 | dir: projectDirectory, 349 | defaultBranch: 'main' 350 | }); 351 | let projectData = { 352 | repo: repoNameField.value, 353 | owner: owner, 354 | branch: 'main' 355 | }; 356 | let commitMessage = (repoCommitMessage.value) ? repoCommitMessage.value : undefined; 357 | ghCreateJsonFile(projectData, projectDirectory, 'githubinfo.json'); 358 | let remoteUrl = 'https://github.com/' + owner + '/' + repoNameField.value; 359 | let newAccountConfigValues = `[remote "origin"] 360 | fetch = +refs/heads/*:refs/remotes/origin/* 361 | url = ${remoteUrl} 362 | [branch "main"] 363 | merge = refs/heads/main 364 | remote = origin` 365 | await ghWriteConfig(newAccountConfigValues, projectDirectory); 366 | await ghWriteGitConfig(projectDirectory); 367 | let uploadStatus = await ghUploadToRepoNew(projectDirectory, projectData, 'main', commitMessage); 368 | ghStatusUpdate(uploadStatus, createMessage, 'Files uploaded'); 369 | } 370 | createNewRepo.disabled = true; 371 | }); 372 | 373 | closeButton.addEventListener('click', () => { 374 | createResetButton.click(); 375 | }) 376 | }; 377 | 378 | //Function to gather existing repo information. 379 | let ghManipulateCloneFields = async () => { 380 | let repoOwner = ghById('gh-existing-repo-owner'); 381 | let repoName = ghById('gh-existing-repo-name'); 382 | let branchName = ghById('gh-clone-branch-name'); 383 | let folderButton = ghById('gh-get-save-folder'); 384 | let saveLocation = ghById('gh-save-location'); 385 | let folderName = ghById('gh-clone-folder-name'); 386 | let folderVerification = ghById('gh-clone-verification'); 387 | let folderOkayButton = ghById('gh-clone-folder-okay'); 388 | let folderCancelButton = ghById('gh-clone-folder-cancel'); 389 | let clearButton = ghById('gh-clone-clear'); 390 | let cloneButton = ghById('gh-get-existing-repo-button'); 391 | let loginError = ghById('gh-clone-log-error'); 392 | let logCheck = await ghVerifyGitHubAccount(); 393 | if (logCheck !== true) { 394 | loginError.style.visibility = 'visible'; 395 | } else { 396 | loginError.style.visibility = 'hidden'; 397 | } 398 | let cloneMessage = ghById('gh-clone-message-box'); 399 | cloneMessage.innerHTML = '

No message at this time

'; 400 | folderButton.addEventListener('click', () => { 401 | openFolderExplorer(window, (selection) => { 402 | saveLocation.innerHTML = selection; 403 | }); 404 | }); 405 | 406 | //Redo error checking? 407 | cloneButton.addEventListener('click', async () => { 408 | if (logCheck === true && folderName.value && saveLocation.innerHTML != '' && repoOwner.value && repoName.value) { 409 | let cloneBranch = (branchName.value) ? branchName.value : ''; 410 | cloneMessage.innerHTML = ""; 411 | let newFolder = folderName.value; 412 | let dir = Path.join(saveLocation.innerHTML, newFolder); 413 | let dirCheck = fse.existsSync(dir); 414 | if (dirCheck) { 415 | folderVerification.classList.add('visible'); 416 | cloneMessage.innerHTML = "

Please verify existing folder overwrite.

"; 417 | } else { 418 | let url = "https://github.com/" + repoOwner.value + "/" + repoName.value + '.git'; 419 | const onAuth = () => ({ 420 | username: localStorage.getItem('gh-settings-user-name'), 421 | password: localStorage.getItem('gh-settings-token') 422 | }); 423 | let cloneRepo = await ghClone(onAuth, url, dir, cloneMessage, cloneBranch); 424 | if (cloneRepo) { 425 | let successMessage = document.createElement('p'); 426 | successMessage.innerHTML = 'Repo successfully cloned.'; 427 | cloneMessage.appendChild(successMessage); 428 | let projectData = { 429 | repo: repoName.value, 430 | owner: owner, 431 | branch: 'main' 432 | }; 433 | ghCreateJsonFile(projectData, dir, 'githubinfo.json'); 434 | ghWriteGitConfig(dir); 435 | let content = '_pgbackup'; 436 | let ignoreFile = Path.join(dir, '.gitignore'); 437 | try { 438 | const gitIgnore = fse.writeFileSync(ignoreFile, content); 439 | } catch (err) { 440 | console.error(err); 441 | } 442 | } 443 | } 444 | } else { 445 | cloneMessage.innerHTML = '

Check that your credentials are valid and you have filled out all of the fields.

'; 446 | } 447 | }); 448 | 449 | folderOkayButton.addEventListener('click', async () => { 450 | cloneMessage.innerHTML = ''; 451 | let newFolder = folderName.value 452 | let dir = Path.join(saveLocation.innerHTML, newFolder); 453 | let cloneBranch = (branchName.value) ? branchName.value : ''; 454 | let url = "https://github.com/" + repoOwner.value + "/" + repoName.value + '.git'; 455 | const onAuth = () => ({ 456 | username: localStorage.getItem('gh-settings-user-name'), 457 | password: localStorage.getItem('gh-settings-token') 458 | }); 459 | await ghClone(onAuth, url, dir, cloneMessage, cloneBranch); 460 | if (cloneRepo) { 461 | let successMessage = document.createElement('p'); 462 | successMessage.innerHTML = 'Repo successfully cloned.'; 463 | cloneMessage.appendChild(successMessage); 464 | let projectData = { 465 | repo: repoName.value, 466 | owner: owner, 467 | branch: 'main' 468 | }; 469 | ghCreateJsonFile(projectData, dir, 'githubinfo.json'); 470 | ghWriteGitConfig(dir); 471 | let ignoreFile = Path.join(dir, '.gitignore'); 472 | try { 473 | const gitIgnore = fse.writeFileSync(ignoreFile, content); 474 | } catch (err) { 475 | console.error(err); 476 | } 477 | } 478 | }); 479 | 480 | folderCancelButton.addEventListener('click', () => { 481 | folderVerification.classList.remove('visible'); 482 | cloneMessage.innerHTML = ""; 483 | }); 484 | 485 | clearButton.addEventListener('click', () => { 486 | repoOwner.value = ''; 487 | repoName.value = ''; 488 | saveLocation.innerHTML = ''; 489 | folderName.value = ''; 490 | cloneMessage.innerHTML = ''; 491 | if (folderVerification.classList.contains('visible')) folderVerification.classList.remove('visible') 492 | }); 493 | }; 494 | 495 | //Function to gather branch info 496 | let ghManipulateBranchFields = async () => { 497 | let branchRepoOwner = ghById('gh-branch-repo-owner'); 498 | let branchRepoName = ghById('gh-branch-repo-name'); 499 | let branchBranchName = ghById('gh-branch-branch-name'); 500 | let branchNewName = ghById('gh-branch-new-name'); 501 | let branchFolderButton = ghById('gh-branch-get-save-folder'); 502 | let branchFolderPath = ghById('gh-branch-save-location'); 503 | let branchFolderName = ghById('gh-branch-folder-name'); 504 | let branchVerification = ghById('gh-branch-verification'); 505 | let branchOkay = ghById('gh-branch-folder-okay'); 506 | let branchCancel = ghById('gh-branch-folder-cancel'); 507 | let branchMessage = ghById('gh-branch-message-box'); 508 | let branchSubmit = ghById('gh-branch-submit'); 509 | let branchReset = ghById('gh-branch-reset-button'); 510 | let branchClose = ghById('gh-branch-close'); 511 | let correctCredentials = ghById('gh-branch-correct-credentials'); 512 | let incorrectCredentials = ghById('gh-branch-incorrect-credentials'); 513 | 514 | branchMessage.innerHTML = '

No message at this time.

'; 515 | 516 | let errorCheck = await ghVerifyGitHubAccount(); 517 | if (errorCheck) { 518 | correctCredentials.style.display = 'block'; 519 | incorrectCredentials.style.display = 'none'; 520 | } else { 521 | correctCredentials.style.display = 'none'; 522 | incorrectCredentials.style.display = 'block'; 523 | } 524 | 525 | let octokit = await ghCreateOctokitInstance(); 526 | 527 | branchNewName.addEventListener('input', () => branchFolderName.value = branchNewName.value); 528 | 529 | branchFolderButton.addEventListener('click', () => { 530 | openFolderExplorer(window, (selection) => { 531 | branchFolderPath.innerHTML = selection; 532 | }); 533 | }); 534 | 535 | branchReset.addEventListener('click', () => { 536 | branchFolderPath.innerHTML = ''; 537 | branchMessage.innerHTML = ''; 538 | if (branchVerification.classList.contains('visible')) branchVerification.classList.remove('visible'); 539 | }); 540 | 541 | branchClose.addEventListener('click', () => branchReset.click()); 542 | 543 | branchSubmit.addEventListener('click', async () => { 544 | if (errorCheck && branchRepoOwner.value && branchRepoName.value && branchNewName.value && (branchFolderPath.innerHTML != '' || branchFolderPath.innerHTML != undefined) && branchFolderName.value) { 545 | let branchBranch = (branchBranchName.value) ? branchBranchName.value : ''; 546 | branchMessage.innerHTML = ''; 547 | let newFolder = branchFolderName.value; 548 | let dir = Path.join(branchFolderPath.innerHTML, newFolder); 549 | let dirCheck = fse.existsSync(dir); 550 | if (dirCheck) { 551 | branchVerification.classList.add('visible'); 552 | branchMessage.innerHTML('

Please verify existing folder overwrite.

'); 553 | } else { 554 | let cloneAndBranch = await ghCloneAndBranch(dir, branchMessage, branchBranch); 555 | } 556 | } else { 557 | cloneMessage.innerHTML = '

Check that your credentials are valid and you have filled out all of the fields.

'; 558 | } 559 | }); 560 | 561 | branchOkay.addEventListener('click', async () => { 562 | let branchBranch = (branchBranchName.value) ? branchBranchName.value : ''; 563 | branchMessage.innerHTML = ''; 564 | let newFolder = branchFolderName.value; 565 | let dir = Path.join(branchFolderPath.innerHTML, newFolder); 566 | let cloneAndBranch = await ghCloneAndBranch(dir, branchMessage, branchBranch); 567 | }); 568 | 569 | branchCancel.addEventListener('click', function () { 570 | branchVerification.classList.remove('visible'); 571 | branchMessage.innerHTML = ""; 572 | }); 573 | 574 | let ghCloneAndBranch = async (dir, branchMessage, branchBranch) => { 575 | let url = "https://github.com/" + branchRepoOwner.value + "/" + branchRepoName.value + '.git'; 576 | const onAuth = () => ({ 577 | username: localStorage.getItem('gh-settings-user-name'), 578 | password: localStorage.getItem('gh-settings-token') 579 | }); 580 | let baseRepoCreate = await ghClone(onAuth, url, dir, branchMessage, branchBranch); 581 | if (baseRepoCreate) { 582 | let successMessage = document.createElement('p'); 583 | successMessage.innerHTML = 'Repo successfully cloned.'; 584 | branchMessage.appendChild(successMessage); 585 | let currentBranch = (branchBranchName.value === '' || branchBranchName.value === undefined) ? 'main' : branchBranchName.value; 586 | let args = { 587 | octokit: octokit, 588 | owner: branchRepoOwner.value, 589 | repo: branchRepoName.value, 590 | currentBranch, 591 | newBranch: branchNewName.value 592 | } 593 | let currentSha = await ghGetCurrentCommit(args); 594 | 595 | let createBranch = await ghCreateRef(args, currentSha.commitSha); 596 | branchMessage.innerHTML = '

Branch created and reference updated.

'; 597 | let projectData = { 598 | repo: args.repo, 599 | owner: args.owner, 600 | branch: args.newBranch 601 | } 602 | ghCreateJsonFile(projectData, dir, 'githubinfo.json'); 603 | ghWriteGitConfig(dir); 604 | } 605 | } 606 | } 607 | 608 | let ghManipulateForkFields = async () => { 609 | let closeButton = ghById('gh-fork-close'); 610 | let forkOwner = ghById('gh-fork-repo-owner'); 611 | let forkNameField = ghById('gh-fork-repo-name'); 612 | let forkSaveFolderButton = ghById('gh-fork-get-save-folder'); 613 | let forkSaveLocation = ghById('gh-fork-save-location'); 614 | let forkNewFolderName = ghById('gh-fork-folder-name'); 615 | let forkFolderVerification = ghById('gh-fork-verification') 616 | let forkOkayButton = ghById('gh-fork-folder-okay'); 617 | let forkReset = ghById('gh-fork-reset-button'); 618 | let forkButton = ghById('gh-fork-button'); 619 | let loginError = ghById('gh-fork-log-error'); 620 | let forkMessage = ghById('gh-fork-message-box'); 621 | let mainOwner = ''; 622 | 623 | let logCheck = await ghVerifyGitHubAccount(); 624 | 625 | if (logCheck !== true) { 626 | loginError.style.visibility = 'visible'; 627 | } else { 628 | loginError.style.visibility = 'hidden'; 629 | mainOwner = localStorage.getItem('gh-settings-user-name') 630 | } 631 | 632 | forkMessage.innerHTML = '

No message at this time

'; 633 | 634 | forkSaveFolderButton.addEventListener('click', () => { 635 | openFolderExplorer(window, (selection) => { 636 | forkSaveLocation.innerHTML = selection; 637 | }); 638 | }); 639 | 640 | forkNameField.addEventListener('input', () => forkNewFolderName.value = forkNameField.value); 641 | 642 | forkReset.addEventListener('click', () => { 643 | forkMessage.innerHTML = '

No message at this time

'; 644 | }); 645 | 646 | closeButton.addEventListener('click', () => { 647 | forkReset.click(); 648 | }); 649 | 650 | forkButton.addEventListener('click', async () => { 651 | if (logCheck !== true) return; 652 | if (forkOwner.value === '' || forkNameField === '') return; 653 | 654 | let octokit = await ghCreateOctokitInstance(); 655 | 656 | let newFork = await octokit.rest.repos.createFork({ 657 | owner: forkOwner.value, 658 | repo: forkNameField.value 659 | }); 660 | ghStatusUpdate(newFork.status, forkMessage, 'Repo forked - creating local clone.'); 661 | 662 | let dir = Path.join(forkSaveLocation.innerHTML, forkNewFolderName); 663 | let dirCheck = fse.existsSync(dir); 664 | if (dirCheck) { 665 | forkFolderVerification.classList.add('visible'); 666 | cloneMessage.innerHTML = "

Please verify existing folder overwrite.

"; 667 | } else { 668 | let url = "https://github.com/" + mainOwner + "/" + forkNameField.value + '.git'; 669 | const onAuth = () => ({ 670 | username: mainOwner, 671 | password: localStorage.getItem('gh-settings-token') 672 | }); 673 | let forkRepo = await ghClone(onAuth, url, dir, forkMessage, ''); 674 | if (forkRepo) { 675 | let successMessage = document.createElement('p'); 676 | successMessage.innerHTML = 'Local copy created'; 677 | forkMessage.appendChild(successMessage); 678 | let projectData = { 679 | repo: forkNameField.value, 680 | owner: mainOwner, 681 | branch: 'main' 682 | }; 683 | ghCreateJsonFile(projectData, dir, 'githubinfo.json'); 684 | ghWriteGitConfig(dir); 685 | let content = '_pgbackup'; 686 | let ignoreFile = Path.join(dir, '.gitignore'); 687 | try { 688 | const gitIgnore = fse.writeFileSync(ignoreFile, content); 689 | } catch (err) { 690 | console.error(err); 691 | } 692 | } 693 | } 694 | 695 | forkOkayButton.addEventListener('click', async () => { 696 | cloneMessage.innerHTML = ''; 697 | let forkDir = Path.join(forkSaveLocation.innerHTML, forkNewFolderName); 698 | let url = "https://github.com/" + mainOwner + "/" + forkNameField.value + '.git'; 699 | const onAuth = () => ({ 700 | username: mainOwner, 701 | password: localStorage.getItem('gh-settings-token') 702 | }); 703 | let forkRepo = await ghClone(onAuth, url, forkDir, forkMessage, ''); 704 | if (forkRepo) { 705 | let successMessage = document.createElement('p'); 706 | successMessage.innerHTML = 'Local copy created'; 707 | forkMessage.appendChild(successMessage); 708 | let projectData = { 709 | repo: forkNameField.value, 710 | owner: mainOwner, 711 | branch: 'main' 712 | }; 713 | ghCreateJsonFile(projectData, forkDir, 'githubinfo.json'); 714 | ghWriteGitConfig(forkDir); 715 | let content = '_pgbackup'; 716 | let ignoreFile = Path.join(forkDir, '.gitignore'); 717 | try { 718 | const gitIgnore = fse.writeFileSync(ignoreFile, content); 719 | } catch (err) { 720 | console.error(err); 721 | } 722 | } 723 | }); 724 | }) 725 | }; 726 | 727 | //Function to gather staging and commit information. 728 | let ghManipulateCommitFields = () => { 729 | let projectDirectory = ghProjectDirectory(); 730 | let gitStatus = ghById('gh-commit-git-status-box'); 731 | let statusButton = ghById('gh-status-button'); 732 | let commitMessageBox = ghById('gh-commit-message-box'); 733 | let directoryHead = ghById('ghProjectTree'); 734 | let directoryFolders = directoryHead.querySelectorAll('[data-gh-type="folder"]'); 735 | directoryFolders.forEach(folder => { 736 | let folderIcon = folder.querySelector('i'); 737 | folderIcon.addEventListener('click', function (evt) { 738 | folderIcon.classList.contains('icon-right') ? folderIcon.classList.replace('icon-right', 'icon-down') : folderIcon.classList.replace('icon-down', 'icon-right'); 739 | let fileList = folder.querySelector('ul'); 740 | fileList.classList.contains('gh-hidden') ? fileList.classList.replace('gh-hidden', 'gh-visible') : fileList.classList.replace('gh-visible', 'gh-hidden'); 741 | }) 742 | let folderInput = folder.querySelector('input'); 743 | folderInput.addEventListener('click', (evt) => { 744 | let fileList = folder.querySelector('ul'); 745 | let fileInputs = fileList.querySelectorAll('li > input'); 746 | let checkStatus = folderInput.checked; 747 | fileInputs.forEach(input => { 748 | input.checked = checkStatus ? true : false; 749 | }); 750 | }); 751 | }); 752 | let messageError = ghById('gh-commit-message-error'); 753 | let emailError = ghById('gh-commit-email-error'); 754 | let credentialsError = ghById('gh-commit-credentials-error'); 755 | let commitMessage = ghById('gh-commit-message'); 756 | let authorEmail = ghById('gh-commit-email'); 757 | let unstageButton = ghById('gh-unstage-button'); 758 | let stageButton = ghById('gh-stage-button'); 759 | let commitButton = ghById('gh-commit-button'); 760 | let pushButton = ghById('gh-push-button'); 761 | let currentCommitStatus = localStorage.getItem('gh-commited'); 762 | if (currentCommitStatus === true) { 763 | commitButton.disabled = false; 764 | commitMessage.disabled = false; 765 | authorEmail.disabled = false; 766 | } 767 | 768 | 769 | statusButton.addEventListener('click', async () => { 770 | let stagedFiles = await ghStagedFiles(); 771 | gitStatus.innerHTML = '

Currently Staged Files:

' + stagedFiles; 772 | }); 773 | 774 | stageButton.addEventListener('click', async () => { 775 | let filesToStage = ghGetFiles(directoryHead); 776 | let stageFiles = await ghStageFiles(filesToStage); 777 | if (stageFiles) { 778 | let stagedFiles = await ghStagedFiles(); 779 | gitStatus.innerHTML = '

Currently Staged Files:

' + stagedFiles; 780 | localStorage.setItem('gh-commited', true); 781 | commitButton.disabled = false; 782 | commitMessage.disabled = false; 783 | authorEmail.disabled = false; 784 | } 785 | }); 786 | 787 | unstageButton.addEventListener('click', async () => { 788 | let filesToUnstage = ghGetFiles(directoryHead); 789 | let unstageFiles = await ghUnstageFiles(filesToUnstage); 790 | if (unstageFiles) { 791 | let stagedFiles = await ghStagedFiles(); 792 | gitStatus.innerHTML = '

Currently Staged Files:

' + stagedFiles; 793 | } 794 | }); 795 | 796 | commitButton.addEventListener('click', async () => { 797 | let projectDirectory = ghProjectDirectory(); 798 | if (commitMessage.value === '') { 799 | messageError.style.display = "block"; 800 | return; 801 | } 802 | let errorCheck = await ghVerifyGitHubAccount(); 803 | if (!errorCheck) { 804 | credentialsError.style.display = "block"; 805 | return; 806 | } 807 | if (authorEmail.value === '') { 808 | emailError.style.display = "block"; 809 | return; 810 | } 811 | let sha = await Git.commit({ 812 | fs: fse, 813 | dir: projectDirectory, 814 | author: { 815 | name: localStorage.getItem('gh-settings-user-name'), 816 | email: authorEmail.value 817 | }, 818 | message: commitMessage.value 819 | }); 820 | if (sha) { 821 | let content = { 822 | "committed": true 823 | }; 824 | let filename = "githubinfo.json"; 825 | let appendCommit = ghAppendJsonFile(content, projectDirectory, filename); 826 | } 827 | commitMessageBox.innerHTML = "

Files committed by " + localStorage.getItem('gh-settings-user-name') + " as SHA " + sha + "

"; 828 | }); 829 | 830 | pushButton.addEventListener('click', async () => { 831 | let projectDirectory = ghProjectDirectory(); 832 | let errorCheck = await ghVerifyGitHubAccount(); 833 | if (!errorCheck) { 834 | credentialsError.classList = 'gh-visible gh-error'; 835 | return; 836 | } 837 | let jsonData = ghReadJsonFile(projectDirectory, 'githubinfo.json'); 838 | if (!jsonData.committed) { 839 | commitMessageBox.innerHTML = "

No files committed. Please make a commit and try again.

"; 840 | return; 841 | } 842 | const onAuth = () => ({ 843 | username: localStorage.getItem('gh-settings-user-name'), 844 | password: localStorage.getItem('gh-settings-token') 845 | }); 846 | let pushRequest = await Git.push({ 847 | fs: fse, 848 | http, 849 | dir: projectDirectory, 850 | onAuth, 851 | author: { 852 | name: localStorage.getItem('gh-settings-user-name') 853 | } 854 | }); 855 | let content = { 856 | "committed": false 857 | }; 858 | ghAppendJsonFile(content, projectDirectory, 'githubinfo.json'); 859 | }); 860 | 861 | let cancelButton = ghById('gh-commit-cancel'); 862 | let closeButton = ghById('gh-commit-x'); 863 | let resetModal = () => { 864 | let commitDynamicContainer = ghById('gh-commit-dynamic-container'); 865 | if (commitDynamicContainer) commitDynamicContainer.remove(); 866 | messageError.style.display = "none"; 867 | credentialsError.style.display = "none"; 868 | emailError.style.display = "none"; 869 | gitStatus.innerHTML = ''; 870 | commitMessage.value = ''; 871 | authorEmail.value = ''; 872 | }; 873 | [cancelButton, closeButton].forEach(button => { 874 | button.addEventListener('click', resetModal); 875 | }); 876 | cancelButton.addEventListener('click', () => { 877 | closeButton.removeEventListener('click', resetModal); 878 | }); 879 | closeButton.addEventListener('click', () => { 880 | cancelButton.removeEventListener('click', resetModal); 881 | }); 882 | }; 883 | 884 | let ghManipulatePullFields = () => { 885 | let projectDirectory = ghProjectDirectory(); 886 | let messageArea = ghById('gh-pull-message-box'); 887 | let pullButton = ghById('gh-pull-button'); 888 | let closeX = ghById('gh-pull-x'); 889 | let cancelButton = ghById('gh-pull-cancel'); 890 | let pullIt = () => { 891 | return Git.pull({ 892 | fs: fse, 893 | http, 894 | dir: projectDirectory, 895 | singleBranch: true 896 | }) 897 | .then(() => messageArea.innerHTML = ('Pull successful')) 898 | .catch(err => { 899 | console.error(err); 900 | }); 901 | }; 902 | pullButton.addEventListener('click', pullIt); 903 | closeX.addEventListener('click', () => { 904 | cancelButton.click(); 905 | }); 906 | cancelButton.addEventListener('click', () => { 907 | messageArea.innerHTML = ""; 908 | }) 909 | 910 | } 911 | 912 | //Function to add all the initial field listeners to each modal 913 | let ghAddFieldsListeners = () => { 914 | ghManipulateSettingsFields(); 915 | ghManipulateCreateFields(); 916 | ghManipulateCloneFields(); 917 | ghManipulateBranchFields(); 918 | ghManipulateForkFields(); 919 | } 920 | 921 | //Function to add the click listeners to the initial menu items 922 | let ghAddModalListener = ({ 923 | targetId, 924 | modalName 925 | }) => { 926 | //Adds the click listener to the settings menu item 927 | let modalItem = ghById(targetId); 928 | let modalId = '#gh-' + modalName + '-modal'; 929 | 930 | modalItem.addEventListener('click', () => $(modalId).modal('show')); 931 | }; 932 | 933 | 934 | let ghGetFileAsBASE64 = (filePath) => fse.readFile(filePath, 'base64'); 935 | 936 | let ghSetBranchToCommit = ({ 937 | octokit, 938 | owner, 939 | repo, 940 | currentBranch 941 | }, commitSha) => octokit.git.updateRef({ 942 | owner, 943 | repo, 944 | ref: `heads/${currentBranch}`, 945 | sha: commitSha 946 | }); 947 | 948 | 949 | //HTML for the main menu 950 | let $menu = $(` 951 | 962 | `); 963 | 964 | //Adds the main GitHub menu to Pinegrow upon open 965 | pinegrow.addPluginControlToTopbar(framework, $menu, true, function () { 966 | ghAddStyling(); 967 | ghAddTheModals(); 968 | }); 969 | 970 | // Check if we are opening another project in a new window 971 | if (pinegrow.getCurrentProject()) { 972 | addToGHMenu(); 973 | } 974 | 975 | 976 | //Adds project specific GitHub menu items 977 | //Replaced anonymous callback function with 'addToGHMenu' to solve problem with opening 978 | //project in a new window not triggering menu addition 979 | pinegrow.addEventHandler('on_project_loaded', addToGHMenu); 980 | //Removes extra menu items on project close 981 | pinegrow.addEventHandler('on_project_closed', removeFromGHMenu); 982 | 983 | //Function to add the styling to the page 984 | function ghAddStyling() { 985 | let styleLink = document.createElement('link'); 986 | styleLink.setAttribute('rel', 'stylesheet'); 987 | let styleFile = framework.getResourceUrl('./assets/style.css'); 988 | styleLink.setAttribute('href', styleFile); 989 | const theApp = ghById('pgapp'); 990 | theApp.appendChild(styleLink); 991 | } 992 | 993 | 994 | //Function to add modals to the page 995 | async function ghAddModal({ 996 | targetId, 997 | modalName 998 | }) { 999 | let modalDiv = document.createElement('div'); 1000 | let modalId = modalName + 'ModalContainer'; 1001 | modalDiv.setAttribute('id', modalId); 1002 | const theApp = ghById('pgapp'); 1003 | theApp.appendChild(modalDiv); 1004 | let modalFile = crsaMakeFileFromUrl(frameBase + '/assets/' + modalName + '.html'); 1005 | let settingsModalContainer = ghById(modalId); 1006 | settingsModalContainer.innerHTML = await ghFetchHtmlFragment(modalFile); 1007 | ghAddModalListener({ 1008 | targetId: targetId, 1009 | modalName: modalName 1010 | }); 1011 | } 1012 | 1013 | async function ghAddTheModals() { 1014 | let initialModals = [{ 1015 | targetId: 'gh-create-repo', 1016 | modalName: 'create' 1017 | }, 1018 | { 1019 | targetId: 'gh-clone-repo', 1020 | modalName: 'clone' 1021 | }, 1022 | { 1023 | targetId: 'gh-branch-repo', 1024 | modalName: 'branch' 1025 | }, 1026 | { 1027 | targetId: 'gh-fork-repo', 1028 | modalName: 'fork' 1029 | }, 1030 | { 1031 | targetId: 'gh-settings', 1032 | modalName: 'settings' 1033 | } 1034 | ]; 1035 | await Promise.all(initialModals.map((modal) => { 1036 | ghAddModal(modal); 1037 | })); 1038 | ghAddFieldsListeners(); 1039 | } 1040 | 1041 | async function addToGHMenu() { 1042 | // first check existence of additional menu to avoid double entries to the GH Menu 1043 | if (!ghById('stage-changes')) { 1044 | let targetMenu = ghById('gh-dropdown'); 1045 | let newItem = document.createDocumentFragment(); 1046 | let listOne = document.createElement('li'); 1047 | listOne.innerHTML = 'Stage, Commit, and Push Changes' 1048 | newItem.appendChild(listOne); 1049 | let listTwo = document.createElement('li'); 1050 | listTwo.innerHTML = 'Pull changes'; 1051 | newItem.appendChild(listTwo); 1052 | // rjs: using namedItem is more robust then using hardcoded index-number 1053 | // rjs: this namedItem needs an id on the element
in the menu 1054 | let menuDivider = targetMenu.children.namedItem('ruler-one'); 1055 | targetMenu.insertBefore(newItem, menuDivider); 1056 | let stageModal = { 1057 | targetId: 'gh-stage-changes', 1058 | modalName: 'commit' 1059 | }; 1060 | await ghAddModal(stageModal); 1061 | ghAddStageListener(stageModal); 1062 | await ghCreateRepoModal(); 1063 | await ghTestStatus(); 1064 | ghManipulateCommitFields(); 1065 | let pullModal = { 1066 | targetId: 'gh-pull-changes', 1067 | modalName: 'pull' 1068 | }; 1069 | await ghAddModal(pullModal); 1070 | ghManipulatePullFields(); 1071 | } 1072 | } 1073 | 1074 | function removeFromGHMenu(pagenull, project) { 1075 | ghById('gh-stage-changes').remove(); 1076 | ghById('gh-pull-changes').remove(); 1077 | ghById('commitModalContainer').remove(); 1078 | } 1079 | 1080 | async function ghVerifyGitHubAccount() { 1081 | if (localStorage.getItem('gh-settings-token') && localStorage.getItem('gh-settings-user-name')) { 1082 | //instantiates octokit object with token authorization 1083 | let octokit = await ghCreateOctokitInstance(); 1084 | let userName = localStorage.getItem('gh-settings-user-name'); 1085 | return octokit.users.getAuthenticated() 1086 | .then(isAuthenticated => isAuthenticated.data.login) 1087 | .then(returnedName => (userName === returnedName) ? true : -1) 1088 | .catch(err => console.error(err)); 1089 | } 1090 | } 1091 | 1092 | async function ghCreateRepo(repoArgs) { 1093 | //instantiates octokit object with token authorization 1094 | let octokit = await ghCreateOctokitInstance(); 1095 | return octokit.rest.repos.createForAuthenticatedUser(repoArgs).catch(err => { 1096 | console.error(err); 1097 | return { 1098 | "status": "error" 1099 | }; 1100 | }); 1101 | } 1102 | 1103 | async function ghCreateRepoModal() { 1104 | let ghProject = await ghGetDirectory(); 1105 | let directoryContainer = document.getElementById('ghDirectoryContainer'); 1106 | directoryContainer.innerHTML = ''; 1107 | const element = document.createElement('div'); 1108 | element.setAttribute('class', 'gh-file-selection'); 1109 | element.setAttribute('id', 'gh-commit-dynamic-container'); 1110 | const ghContent = document.createElement('div'); 1111 | ghContent.setAttribute('class', 'gh-project-directory gh-project-list'); 1112 | const title = document.createElement('h2'); 1113 | title.textContent = ghProject.name; 1114 | ghContent.appendChild(title); 1115 | const target = document.createElement('ul'); 1116 | target.setAttribute('id', 'ghProjectTree'); 1117 | ghContent.appendChild(target); 1118 | element.appendChild(ghContent); 1119 | directoryContainer.appendChild(element); 1120 | await ghCreateDirectory(ghProject.children); 1121 | } 1122 | 1123 | async function ghCreateDirectory(ghProjectChildren) { 1124 | let cleanProject = await ghCleanChildren(ghProjectChildren); 1125 | let projectDirectory = ghProjectDirectory(); 1126 | const projectTree = (targetElement, children) => { 1127 | children.forEach(async function (child) { 1128 | if (child.type === 'directory') { 1129 | let dirSet = document.createElement('li'); 1130 | dirSet.setAttribute('draggable', 'draggable'); 1131 | dirSet.className = 'project-item folder folder-closed'; 1132 | let folderIcon = document.createElement('i'); 1133 | folderIcon.className = 'folder-icon icon icon-right'; 1134 | dirSet.append(folderIcon); 1135 | let dirSelect = document.createElement('input'); 1136 | dirSelect.setAttribute('type', 'checkbox'); 1137 | dirSet.append(dirSelect); 1138 | dirSet.append(document.createTextNode(child.name)); 1139 | dirSet.setAttribute('data-gh-file-name', child.name); 1140 | dirSet.setAttribute('data-gh-url', child.path); 1141 | dirSet.setAttribute('data-gh-type', 'folder'); 1142 | let subDir = document.createElement('ul'); 1143 | subDir.className = 'gh-hidden'; 1144 | dirSet.append(subDir); 1145 | targetElement.append(dirSet); 1146 | projectTree(subDir, child.children); 1147 | return; 1148 | } 1149 | let fileWrap = document.createElement('li'); 1150 | let fileStatus = await ghGitStatus(projectDirectory, child.path); 1151 | let disabled = fileStatus.staged || fileStatus.ignored; 1152 | fileWrap.className = 'project-item file'; 1153 | if (disabled) fileWrap.classList.add('gh-disabled'); 1154 | fileWrap.setAttribute('data-gh-type', 'file'); 1155 | fileWrap.setAttribute('data-gh-file-name', child.name); 1156 | fileWrap.setAttribute('data-gh-url', child.path); 1157 | fileWrap.setAttribute('data-gh-status', fileStatus.status); 1158 | if (!fileStatus.ignored) { 1159 | let fileSelect = document.createElement('input'); 1160 | fileSelect.setAttribute('type', 'checkbox'); 1161 | fileWrap.append(fileSelect); 1162 | } 1163 | fileWrap.append(document.createTextNode(child.name)); 1164 | targetElement.append(fileWrap); 1165 | }); 1166 | }; 1167 | const ghDiv = ghById('ghProjectTree'); 1168 | projectTree(ghDiv, cleanProject); 1169 | } 1170 | 1171 | function ghGetDirectory() { 1172 | let project = pinegrow.getCurrentProject(); 1173 | let path = project.root.path; 1174 | return ghDirTree(path); 1175 | } 1176 | 1177 | function ghGetFilePath(fileName) { 1178 | return project.getAbsolutePath(fileName); 1179 | } 1180 | 1181 | function ghGetRelativeUrl(fileName) { 1182 | return project.getAbsolutePath(fileName); 1183 | } 1184 | 1185 | function ghCreateJsonFile(content, path, name) { 1186 | let jsonData = JSON.stringify(content, null, 2); 1187 | let jsonFile = Path.join(path, name); 1188 | try { 1189 | fse.writeFileSync(jsonFile, jsonData); 1190 | } catch (err) { 1191 | console.error(err); 1192 | } 1193 | } 1194 | 1195 | function ghReadJsonFile(path, filename) { 1196 | let jsonFile = crsaMakeFileFromUrl(Path.join(path, filename)); 1197 | if (fse.existsSync(jsonFile)) { 1198 | try { 1199 | let projectInfo = fse.readJsonSync(jsonFile); 1200 | return projectInfo; 1201 | } catch (err) { 1202 | console.error(err); 1203 | } 1204 | } 1205 | return false; 1206 | } 1207 | 1208 | function ghAppendJsonFile(content, path, filename) { 1209 | let jsonFile = crsaMakeFileFromUrl(Path.join(path, filename)); 1210 | let projectInfo = fse.readJsonSync(jsonFile); 1211 | let newInfo = Object.assign(projectInfo, content); 1212 | let jsonData = JSON.stringify(newInfo, null, 2); 1213 | try { 1214 | fse.writeFileSync(jsonFile, jsonData); 1215 | } catch (err) { 1216 | console.error(err); 1217 | } 1218 | } 1219 | 1220 | async function ghClone(onAuth, url, dir, messageBox, cloneBranch) { 1221 | try { 1222 | let args = { 1223 | fs: fse, 1224 | http, 1225 | dir, 1226 | onAuth, 1227 | url, 1228 | onProgress(evt) { 1229 | //console.log(evt); 1230 | let update = document.createElement('p'); 1231 | let total = (evt.total === undefined) ? '' : '/' + evt.total; 1232 | update.innerHTML = ('' + evt.phase + ' ' + evt.loaded + total); 1233 | messageBox.appendChild(update); 1234 | }, 1235 | onMessage(evt) { 1236 | //console.log(evt); 1237 | }, 1238 | onAuthSuccess(evt) { 1239 | let update = document.createElement('p'); 1240 | update.innerHTML = 'Authorization successful'; 1241 | messageBox.appendChild(update); 1242 | }, 1243 | onAuthFailure(evt) { 1244 | let update = document.createElement('p'); 1245 | update.innerHTML = 'Authorization error, please check token and repo privacy settings.'; 1246 | messageBox.appendChild(update); 1247 | } 1248 | }; 1249 | if (cloneBranch != '') { 1250 | args.ref = cloneBranch; 1251 | args.singleBranch = true; 1252 | } 1253 | const gitClone = await Git.clone(args); 1254 | return true; 1255 | } catch (error) { 1256 | let update = document.createElement('p'); 1257 | update.innerHTML = error.data.response; 1258 | messageBox.appendChild(update); 1259 | fse.rmdir(dir, (err) => { 1260 | if (err) { 1261 | throw err; 1262 | } else { 1263 | console.error('removed directory', err); 1264 | } 1265 | }) 1266 | } 1267 | } 1268 | 1269 | async function ghUploadToRepo(projectDirectory, projectData, branch = 'main', commitMessage = 'commit initial files') { 1270 | let octokit = await ghCreateOctokitInstance(); 1271 | await ghDelayer(400); 1272 | let args = { 1273 | octokit: octokit, 1274 | owner: projectData.owner, 1275 | repo: projectData.repo, 1276 | currentBranch: branch, 1277 | message: commitMessage 1278 | } 1279 | let currentCommit = await ghGetCurrentCommit(args); 1280 | //does this need further work? 1281 | let gitIgnoreFiles = await ghGetIgnoredFiles(projectDirectory); 1282 | let unfilteredFilesPaths = await glob.sync(projectDirectory + '/**/*', { 1283 | ignore: ['**/.git/**'] 1284 | }); 1285 | let filesPaths = ghFilterFiles(unfilteredFilesPaths, gitIgnoreFiles); 1286 | let filesBlobs = await Promise.all(filesPaths.map(file => ghCreateBlobForFile(args, file))); 1287 | let pathsForBlobs = await Promise.all(filesPaths.map(fullPath => Path.relative(projectDirectory, fullPath))); 1288 | let newTree = await ghCreateNewTree(args, filesBlobs, pathsForBlobs, currentCommit.treeSha); 1289 | let newCommit = await ghCreateNewCommit(args, newTree.sha, currentCommit.commitSha); 1290 | if (newCommit.status === 201) { 1291 | await ghSetBranchToCommit(args, newCommit.data.sha); 1292 | } 1293 | return newCommit.status; 1294 | } 1295 | 1296 | async function ghGetCurrentCommit({ 1297 | octokit, 1298 | owner, 1299 | repo, 1300 | currentBranch 1301 | }) { 1302 | let refData = await octokit.git.getRef({ 1303 | owner, 1304 | repo, 1305 | ref: `heads/${currentBranch}`, 1306 | }); 1307 | let commitSha = refData.data.object.sha; 1308 | let commitData = await octokit.git.getCommit({ 1309 | owner, 1310 | repo, 1311 | commit_sha: commitSha, 1312 | }); 1313 | return { 1314 | commitSha, 1315 | treeSha: commitData.data.tree.sha, 1316 | } 1317 | } 1318 | 1319 | async function ghCreateBlobForFile({ 1320 | octokit, 1321 | owner, 1322 | repo 1323 | }, filePath) { 1324 | let content = await ghGetFileAsBASE64(filePath); 1325 | let blobData = await octokit.git.createBlob({ 1326 | owner, 1327 | repo, 1328 | content, 1329 | encoding: 'base64', 1330 | }); 1331 | return blobData.data; 1332 | } 1333 | 1334 | async function ghCreateNewTree({ 1335 | octokit, 1336 | owner, 1337 | repo 1338 | }, blobs, paths, parentTreeSha) { 1339 | let tree = blobs.map((blob, index) => { 1340 | let sha = blob.sha; 1341 | return { 1342 | path: paths[index], 1343 | mode: '100644', 1344 | type: 'blob', 1345 | sha 1346 | }; 1347 | }); 1348 | let { 1349 | data 1350 | } = await octokit.git.createTree({ 1351 | owner, 1352 | repo, 1353 | tree, 1354 | base_tree: parentTreeSha 1355 | }); 1356 | return data; 1357 | } 1358 | 1359 | async function ghCreateNewCommit({ 1360 | octokit, 1361 | owner, 1362 | repo, 1363 | message 1364 | }, currentTreeSha, currentCommitSha) { 1365 | return octokit.git.createCommit({ 1366 | owner, 1367 | repo, 1368 | message, 1369 | tree: currentTreeSha, 1370 | parents: [currentCommitSha], 1371 | }); 1372 | } 1373 | 1374 | async function ghGetIgnoredFiles(projectDirectory) { 1375 | let theFile = crsaMakeFileFromUrl(Path.join(projectDirectory, '.gitignore')); 1376 | if (fse.existsSync(theFile)) { 1377 | try { 1378 | return fse.readFile(theFile, 'utf-8'); 1379 | } catch (err) { 1380 | console.error(err); 1381 | } 1382 | } 1383 | return false; 1384 | } 1385 | 1386 | function ghFilterFiles(unfilteredFiles, ignoredFiles) { 1387 | let filteredFiles = unfilteredFiles; 1388 | if (!ignoredFiles) return filteredFiles; 1389 | ignoredFiles = ignoredFiles.split(','); 1390 | ignoredFiles.forEach((ignoredFile) => { 1391 | filteredFiles = filteredFiles.filter(file => !file.includes(ignoredFile)); 1392 | }); 1393 | return filteredFiles; 1394 | } 1395 | 1396 | async function ghFilterChildren(childFiles, ignoredFiles) { 1397 | if (!ignoredFiles) return childFiles; 1398 | ignoredFiles = ignoredFiles.split(','); 1399 | return childFiles.filter(file => !ignoredFiles.includes(file.name)); 1400 | } 1401 | 1402 | function ghDelayer(ms) { 1403 | return new Promise((resolve, reject) => { 1404 | setTimeout(() => { 1405 | resolve(); 1406 | }, ms) 1407 | }); 1408 | } 1409 | 1410 | function ghStatusUpdate(status, target, customMessage) { 1411 | let update = document.createElement('p'); 1412 | let updateElement; 1413 | switch (status) { 1414 | case 201: 1415 | updateElement = document.createTextNode('Status: 201 Operation successful ' + customMessage); 1416 | update.appendChild(updateElement); 1417 | break; 1418 | case 304: 1419 | updateElement = document.createTextNode('Status: 304 Repo exists and was not modified'); 1420 | update.appendChild(updateElement); 1421 | break; 1422 | case 400: 1423 | updateElement = document.createTextNode('Status: 400 Bad request. Check arguments for special characters'); 1424 | update.appendChild(updateElement); 1425 | break; 1426 | case 401: 1427 | updateElement = document.createTextNode('Status: 401 Authentication failed'); 1428 | update.appendChild(updateElement); 1429 | break; 1430 | case 403: 1431 | updateElement = document.createTextNode('Status: 403 Forbidden. Check token options'); 1432 | update.appendChild(updateElement); 1433 | break; 1434 | case 404: 1435 | updateElement = document.createTextNode('Status: 404 Resource not found.'); 1436 | update.appendChild(updateElement); 1437 | break; 1438 | case 422: 1439 | updateElement = document.createTextNode('Status: 422 Repo validation failed.'); 1440 | update.appendChild(updateElement); 1441 | break; 1442 | case "error": 1443 | updateElement = document.createTextNode('Status: Repo creation failed with unknown error - possibly the chosen name already exists?'); 1444 | update.appendChild(updateElement); 1445 | break; 1446 | default: 1447 | updateElement = document.createTextNode('No message returned'); 1448 | update.appendChild(updateElement); 1449 | } 1450 | target.appendChild(update); 1451 | } 1452 | 1453 | function ghCreateRef({ 1454 | octokit, 1455 | owner, 1456 | repo, 1457 | newBranch 1458 | }, sha) { 1459 | let ref = 'refs/heads/' + newBranch; 1460 | let args = { 1461 | owner, 1462 | repo, 1463 | ref, 1464 | sha 1465 | } 1466 | return octokit.rest.git.createRef(args); 1467 | } 1468 | 1469 | async function ghCleanChildren(projectChildren) { 1470 | let projectDirectory = ghProjectDirectory(); 1471 | let ignoredFiles = await ghGetIgnoredFiles(projectDirectory); 1472 | return ghFilterChildren(projectChildren, ignoredFiles); 1473 | } 1474 | 1475 | function ghProjectDirectory() { 1476 | let currentProject = pinegrow.getCurrentProject(); 1477 | return crsaMakeFileFromUrl(currentProject.getUrl()); 1478 | } 1479 | 1480 | function ghSafeReadDirSync(path) { 1481 | let dirData = {}; 1482 | try { 1483 | dirData = fse.readdirSync(path); 1484 | } catch (ex) { 1485 | if (ex.code == "EACCES" || ex.code == "EPERM") { 1486 | //User does not have permissions, ignore directory 1487 | return null; 1488 | } else throw ex; 1489 | } 1490 | return dirData; 1491 | } 1492 | 1493 | function ghDirTree(path) { 1494 | let name = Path.basename(path); 1495 | let item = { 1496 | path, 1497 | name 1498 | }; 1499 | 1500 | let stats; 1501 | 1502 | try { 1503 | stats = fse.statSync(path); 1504 | } catch (err) { 1505 | console.error(err); 1506 | return null; 1507 | } 1508 | 1509 | if (stats.isFile()) { 1510 | item.type = 'file'; 1511 | } else if (stats.isDirectory()) { 1512 | let dirData = ghSafeReadDirSync(path); 1513 | if (dirData === null) return null; 1514 | 1515 | item.type = 'directory'; 1516 | 1517 | item.children = dirData 1518 | .map(child => ghDirTree(Path.join(path, child))) 1519 | .filter(e => !!e); 1520 | } else { 1521 | return null; 1522 | } 1523 | return item; 1524 | } 1525 | 1526 | async function ghStageCommits(filesToCommit, commitMessage) { 1527 | let octokit = await ghCreateOctokitInstance(); 1528 | let projectDirectory = ghProjectDirectory(); 1529 | let jsonData = await ghReadJsonFile(projectDirectory, 'githubinfo.json'); 1530 | let args = { 1531 | octokit: octokit, 1532 | owner: jsonData.owner, 1533 | repo: jsonData.repo, 1534 | currentBranch: jsonData.branch, 1535 | message: commitMessage 1536 | } 1537 | let currentCommit = await ghGetCurrentCommit(args); 1538 | let filesBlobs = await Promise.all(filesToCommit.map(file => ghCreateBlobForFile(args, file))); 1539 | let pathsForBlobs = await Promise.all(filesToCommit.map(fullPath => Path.relative(projectDirectory, fullPath))); 1540 | let newTree = await ghCreateNewTree(args, filesBlobs, pathsForBlobs, currentCommit.tree); 1541 | let newCommit = await ghCreateNewCommit(args, newTree.sha, currentCommit.commitSha); 1542 | if (newCommit.status === 201) { 1543 | await ghSetBranchToCommit(args, newCommit.data.sha); 1544 | } 1545 | return newCommit.status; 1546 | } 1547 | 1548 | function ghStageFiles(filePaths) { 1549 | let projectDirectory = ghProjectDirectory(); 1550 | filePaths.forEach(async (path) => { 1551 | let relativePath = Path.relative(projectDirectory, path); 1552 | await Git.add({ 1553 | fs: fse, 1554 | dir: projectDirectory, 1555 | filepath: relativePath 1556 | }); 1557 | }); 1558 | return true; 1559 | } 1560 | 1561 | async function ghUploadToRepoNew(projectDirectory, projectData, branch = 'main', commitMessage = 'commit initial files') { 1562 | let filesPaths = await glob.sync(projectDirectory + '/**/*', { 1563 | ignore: ['**/.git/**'] 1564 | }); 1565 | filesPaths.forEach(async (path) => { 1566 | let relativePath = Path.relative(projectDirectory, path); 1567 | let ignored = await ghGitStatus(projectDirectory, path); 1568 | if (!ignored[ignored]) { 1569 | await Git.add({ 1570 | fs: fse, 1571 | dir: projectDirectory, 1572 | filepath: relativePath 1573 | }); 1574 | } 1575 | }); 1576 | await Git.commit({ 1577 | fs: fse, 1578 | dir: projectDirectory, 1579 | author: { 1580 | name: localStorage.getItem('gh-settings-user-name'), 1581 | email: localStorage.getItem('gh-settings-email') 1582 | }, 1583 | message: commitMessage 1584 | }); 1585 | const onAuth = () => ({ 1586 | username: localStorage.getItem('gh-settings-user-name'), 1587 | password: localStorage.getItem('gh-settings-token') 1588 | }); 1589 | await Git.push({ 1590 | fs: fse, 1591 | http, 1592 | dir: projectDirectory, 1593 | onAuth, 1594 | force: true, 1595 | author: { 1596 | name: localStorage.getItem('gh-settings-user-name') 1597 | } 1598 | }); 1599 | } 1600 | 1601 | function ghUnstageFiles(filePaths) { 1602 | let projectDirectory = ghProjectDirectory(); 1603 | filePaths.forEach(async (path) => { 1604 | let relativePath = Path.relative(projectDirectory, path); 1605 | await Git.remove({ 1606 | fs: fse, 1607 | dir: projectDirectory, 1608 | filepath: relativePath 1609 | }); 1610 | }); 1611 | return true; 1612 | } 1613 | 1614 | async function ghGitStatus(projectDirectory, filePath) { 1615 | let fileName = Path.relative(projectDirectory, filePath); 1616 | let fileStatus = await Git.status({ 1617 | fs: fse, 1618 | dir: projectDirectory, 1619 | filepath: fileName 1620 | }); 1621 | let unstagedChanges = ['*modified', '*deleted', '*added', '*unmodified', '*absent', '*undeleted', '*undeletemodified']; 1622 | let stagedChanges = ['modified', 'deleted', 'added']; 1623 | let unstaged = unstagedChanges.includes(fileStatus); 1624 | let staged = stagedChanges.includes(fileStatus); 1625 | let ignored = fileStatus === 'ignored' ? true : false; 1626 | return { 1627 | status: fileStatus, 1628 | unstaged: unstaged, 1629 | staged: staged, 1630 | ignored: ignored 1631 | }; 1632 | } 1633 | 1634 | async function ghStagedFiles() { 1635 | let projectDirectory = ghProjectDirectory(); 1636 | let status = await Git.listFiles({ 1637 | fs: fse, 1638 | dir: projectDirectory 1639 | }); 1640 | let returnedFiles = []; 1641 | for (let file of status) { 1642 | if (await ghStagedCallback(file)) { 1643 | returnedFiles.push(file); 1644 | } 1645 | } 1646 | return returnedFiles.join(", "); 1647 | } 1648 | 1649 | async function ghStagedCallback(file) { 1650 | let projectDirectory = ghProjectDirectory(); 1651 | let pathedFile = Path.join(projectDirectory, file); 1652 | let testFile = await ghGitStatus(projectDirectory, pathedFile); 1653 | return testFile.staged; 1654 | } 1655 | 1656 | async function ghTestStatus() { 1657 | let stagedFiles = await ghStagedFiles(); 1658 | if (stagedFiles.length > 0) { 1659 | localStorage.setItem('gh-commited', true); 1660 | } else { 1661 | localStorage.setItem('gh-commited', false); 1662 | } 1663 | } 1664 | 1665 | //Allows single point creation of octokit instance 1666 | function ghCreateOctokitInstance() { 1667 | try { 1668 | return new Octokit({ 1669 | type: 'token', 1670 | auth: localStorage.getItem('gh-settings-token'), 1671 | }); 1672 | } catch (err) { 1673 | console.error(err); 1674 | return err; 1675 | } 1676 | } 1677 | 1678 | //Adds function to bring in modal files 1679 | function ghFetchHtmlFragment(htmlLocation) { 1680 | try { 1681 | return fse.readFileSync(htmlLocation, 'utf-8'); 1682 | } catch (e) { 1683 | console.error(e); 1684 | } 1685 | } 1686 | 1687 | //make selecting fields easier by providing shorthand 1688 | function ghById(id) { 1689 | return document.getElementById(id); 1690 | } 1691 | 1692 | //get selected files from list 1693 | function ghGetFiles(targetList) { 1694 | let returnedFiles = []; 1695 | let selectedFiles = targetList.querySelectorAll('input[type="checkbox"]:checked:not(:disabled)'); 1696 | selectedFiles.forEach(file => { 1697 | let fileParent = file.parentNode; 1698 | if (fileParent.getAttribute('type') != 'folder') { 1699 | returnedFiles.push(fileParent.getAttribute("data-gh-url")); 1700 | } 1701 | }); 1702 | return returnedFiles; 1703 | } 1704 | 1705 | //add user settings to the config file 1706 | function ghWriteGitConfig(projectDirectory) { 1707 | let userName = localStorage.getItem('gh-settings-user-name'); 1708 | let email = localStorage.getItem('gh-settings-email'); 1709 | let token = localStorage.getItem('gh-settings-token'); 1710 | let configValues = new Map([ 1711 | ['user.email', email], 1712 | ['user.name', userName], 1713 | ['author.name', userName], 1714 | ['author.email', email], 1715 | ['github.user', userName], 1716 | ['github.token', token] 1717 | ]); 1718 | return ghSetConfig(configValues, projectDirectory); 1719 | } 1720 | 1721 | async function ghSetConfig(configValues, projectDirectory) { 1722 | for await (let [key, value] of configValues) { 1723 | let writeValue = await Git.setConfig({ 1724 | fs: fse, 1725 | dir: projectDirectory, 1726 | path: key, 1727 | value: value, 1728 | append: true 1729 | }); 1730 | } 1731 | } 1732 | 1733 | async function ghWriteConfig(configValues, projectDirectory) { 1734 | let fileName = crsaMakeFileFromUrl(Path.join(projectDirectory, '.git', 'config')); 1735 | let fileCheck = fse.existsSync(fileName); 1736 | if (!fileCheck) return; 1737 | try { 1738 | fse.appendFileSync(fileName, configValues); 1739 | } catch (err) { 1740 | console.error(err); 1741 | } 1742 | } 1743 | 1744 | //check if the gitconfig file exists 1745 | async function ghFetchGitConfig(projectDirectory) { 1746 | let fileName = crsaMakeFileFromUrl(Path.join(projectDirectory, '.git', 'config')); 1747 | let fileCheck = fse.existsSync(fileName); 1748 | if (!fileCheck) return; 1749 | return ghReadGitConfig(projectDirectory); 1750 | } 1751 | 1752 | //Read the gitconfig file and return the credentials 1753 | async function ghReadGitConfig(projectDirectory) { 1754 | let ghCredentials = {}; 1755 | ghCredentials.userName = await Git.getConfig({ 1756 | fs: fse, 1757 | dir: projectDirectory, 1758 | path: 'github.user' 1759 | }); 1760 | ghCredentials.userToken = await Git.getConfig({ 1761 | fs: fse, 1762 | dir: projectDirectory, 1763 | path: 'github.token' 1764 | }); 1765 | return ghCredentials; 1766 | } 1767 | 1768 | //delete the config file 1769 | function ghDeleteGitConfig(projectDirectory) { 1770 | let theFile = crsaMakeFileFromUrl(Path.join(projectDirectory, '.gitconfig')); 1771 | fse.remove(theFile, (err) => { 1772 | if (err) return console.error(err); 1773 | }); 1774 | } 1775 | 1776 | function ghSanitizeRepoName(name) { 1777 | let regex = /[A-Za-z0-9-_. ]/; 1778 | let sanitizedName = name.split('').filter(letter => regex.test(letter)); 1779 | let spaces = / /g; 1780 | let fullSani = sanitizedName.join('').replace(spaces, "-"); 1781 | return fullSani; 1782 | } 1783 | 1784 | function ghAddStageListener({ 1785 | targetId, 1786 | modalName 1787 | }) { 1788 | let modalItem = ghById(targetId); 1789 | let modalId = '#gh-' + modalName + '-modal'; 1790 | modalItem.addEventListener('click', async () => { 1791 | await ghCreateRepoModal(); 1792 | await ghTestStatus(); 1793 | await ghManipulateCommitFields(); 1794 | $(modalId).modal('show'); 1795 | }) 1796 | } 1797 | }); 1798 | }); --------------------------------------------------------------------------------