(Grav GitSync) Automatic Commit from smokephil

This commit is contained in:
smokephil 2024-03-22 22:42:22 +01:00 committed by GitSync
commit 4267db646d
2765 changed files with 462171 additions and 0 deletions

167
plugins/git-sync/.eslintrc Normal file
View file

@ -0,0 +1,167 @@
{
"env": {
"browser": true,
"node": true
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 7,
"sourceType": "module"
},
"rules": {
"accessor-pairs": 2,
"array-bracket-spacing": 0,
"block-scoped-var": 0,
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"camelcase": 0,
"comma-dangle": [2, "never"],
"comma-spacing": [2, { "before": false, "after": true }],
"comma-style": [2, "last"],
"complexity": 0,
"computed-property-spacing": 0,
"consistent-return": 0,
"consistent-this": 0,
"constructor-super": 2,
"curly": [2, "multi-line"],
"default-case": 0,
"dot-location": [2, "property"],
"dot-notation": 0,
"eol-last": 2,
"eqeqeq": [2, "allow-null"],
"func-names": 0,
"func-style": 0,
"generator-star-spacing": [2, { "before": true, "after": true }],
"guard-for-in": 0,
"handle-callback-err": [2, "^(err|error)$" ],
"indent": [2, 4, { "SwitchCase": 1 }],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"linebreak-style": 0,
"lines-around-comment": 0,
"max-nested-callbacks": 0,
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
"new-parens": 2,
"newline-after-var": 0,
"no-alert": 0,
"no-array-constructor": 2,
"no-caller": 2,
"no-catch-shadow": 0,
"no-cond-assign": 2,
"no-console": 0,
"no-constant-condition": 0,
"no-continue": 0,
"no-control-regex": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-div-regex": 0,
"no-dupe-args": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-else-return": 0,
"no-empty": 0,
"no-empty-character-class": 2,
"no-eq-null": 0,
"no-eval": 2,
"no-ex-assign": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-boolean-cast": 2,
"no-extra-parens": 0,
"no-extra-semi": 0,
"no-fallthrough": 2,
"no-floating-decimal": 2,
"no-func-assign": 2,
"no-implied-eval": 2,
"no-inline-comments": 0,
"no-inner-declarations": [2, "functions"],
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
"no-iterator": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-lonely-if": 0,
"no-loop-func": 0,
"no-mixed-requires": 0,
"no-mixed-spaces-and-tabs": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-multiple-empty-lines": [2, { "max": 1 }],
"no-native-reassign": 2,
"no-negated-in-lhs": 2,
"no-nested-ternary": 0,
"no-new": 2,
"no-new-func": 0,
"no-new-object": 2,
"no-new-require": 2,
"no-new-wrappers": 2,
"no-obj-calls": 2,
"no-octal": 2,
"no-octal-escape": 2,
"no-param-reassign": 0,
"no-path-concat": 0,
"no-process-env": 0,
"no-process-exit": 0,
"no-proto": 0,
"no-redeclare": 2,
"no-regex-spaces": 2,
"no-restricted-modules": 0,
"no-return-assign": 2,
"no-script-url": 0,
"no-self-compare": 2,
"no-sequences": 2,
"no-shadow": 0,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-sparse-arrays": 2,
"no-sync": 0,
"no-ternary": 0,
"no-this-before-super": 2,
"no-throw-literal": 2,
"no-trailing-spaces": 2,
"no-undef": 2,
"no-undef-init": 2,
"no-undefined": 0,
"no-underscore-dangle": 0,
"no-unexpected-multiline": 2,
"no-unneeded-ternary": 2,
"no-unreachable": 2,
"no-unused-expressions": 0,
"no-unused-vars": [2, { "vars": "all", "args": "none" }],
"no-use-before-define": 0,
"no-var": 0,
"no-void": 0,
"no-warning-comments": 0,
"no-with": 2,
"object-curly-spacing": 0,
"object-shorthand": 0,
"one-var": [2, { "initialized": "never" }],
"operator-assignment": 0,
"operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
"padded-blocks": 0,
"prefer-const": 0,
"quote-props": 0,
"quotes": [2, "single", "avoid-escape"],
"radix": 2,
"semi": [2, "always"],
"semi-spacing": 0,
"sort-vars": 0,
"keyword-spacing": [2, {"after": true, "overrides": {"throw": { "after": true}, "return": { "before": true }}}],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, "never"],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"] }],
"strict": 0,
"use-isnan": 2,
"valid-jsdoc": 0,
"valid-typeof": 2,
"vars-on-top": 0,
"wrap-iife": [2, "any"],
"wrap-regex": 0,
"yoda": [2, "never"]
}
}

4
plugins/git-sync/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
node_modules
*.js.map
*.css.map
/.idea

View file

@ -0,0 +1,200 @@
# v2.3.2
## 06/03/2021
1. [](#bugfix)
* Better validation for Git Repository value on both Wizard and Backend.
* Prevent malicious commands from being executed in Wizard when "Verifying Authentication, Connection and Branch".
# v2.3.1
## 04/30/2021
1. [](#bugfix)
* Fixed regression where `testRepository` would erroneously pass with invalid credentials [#200](https://github.com/trilbymedia/grav-plugin-git-sync/issues/200)
* Fixed Exception thrown with `bin/plugin git-sync status` command, preventing `sync` [#200](https://github.com/trilbymedia/grav-plugin-git-sync/issues/200)
# v2.3.0
## 04/27/2021
1. [](#new)
* Added new Advanced Git Ignore field where it is possible to specify custom git ignore entries to play along with GitSync [#197](https://github.com/trilbymedia/grav-plugin-git-sync/issues/197) [#117](https://github.com/trilbymedia/grav-plugin-git-sync/issues/117)
* Support `ssh://` protocol and SSH Key authentication ([read more](https://github.com/trilbymedia/grav-plugin-git-sync#ssh--enterprise)) [#110](https://github.com/trilbymedia/grav-plugin-git-sync/issues/110)
1. [](#improved)
* Updated PHP Encryption dependency
1. [](#bugfix)
* Fixed issue with Flex Objects, preventing GitSync's settings to get refreshed `onAdminSave` when "Sync on Page Save" disabled
* Return raw URL for repositories setup with `ssh://` protocol, instead of injecting the password like `git://` and `http://` protocols do [#104](https://github.com/trilbymedia/grav-plugin-git-sync/issues/104)
# v2.2.0
## 04/17/2021
1. [](#improved)
* Better support for branches other than `master`. This includes the transition to `main` from GitHub and the groundwork to support other big providers making the change as announced soon. GitSync is now capable of preset the branch based on the provider selected. You are now also able to specify any custom branch and when testing the repository connection it will also ensure the branch exists and provide feedback if not.
1. [](#bugfix)
* Changing remote branch is now going to properly reference it instead of remaining stuck to `master` [#192](https://github.com/trilbymedia/grav-plugin-git-sync/issues/192), [#183](https://github.com/trilbymedia/grav-plugin-git-sync/issues/183)
* Fixed issue where the Folders to synchronize from the Wizard wouldn't get properly saved [#178](https://github.com/trilbymedia/grav-plugin-git-sync/issues/178)
# v2.1.1
## 07/17/2020
1. [](#new)
* Added `No User` option to allow disabling the username requirement. This is useful for when you have a token and the user is not required. (#166, thanks GwynethLlewelyn)
* Added `passwd` command for programmatically change user/password (use: `bin/plugin git-sync passwd`) (#146)
* Fixed regression wrongly returning the installed Git version and causing all sort of problems, including unrelated histories not kicking off (#61, #168, #171, #173)
* Fixed potential issue where the new feature `no_user` my throw an error
* Fixed issue with autoload
1. [](#bugfix)
* Fixed classes not being loaded in `cli` commands due to Grav changes (#167)
* Updated dependencies / recompiled JS for production
1. [](#improved)
* Bumped modules versions
# v2.1.0
## 03/13/2020
1. [](#new)
* Requires Grav v1.6.0
* Pass phpstan level 2 tests
1. [](#improved)
* Code cleanup
* Added support for Gitea / Gogs webhook secret (#149, thanks @Aisbergg)
# v2.0.5
## 05/06/2019
1. [](#bugfix)
* Fixed validation error with commalist in Folders to Sync field (#141)
# v2.0.4
## 04/22/2019
1. [](#improved)
* urlencode username to allow for special characters (#139)
# v2.0.3
## 03/07/2019
1. [](#bugifx)
* Properly fallback to config message if not there yet (#134)
# v2.0.2
## 02/21/2019
1. [](#improved)
* Fixed InitCommand spelling (#132, thanks @alex-mohemian)
1. [](#bugfix)
* Fixed PHP 5.6 incompatibility introduced by latest release.
# v2.0.1
## 02/19/2019
1. [](#new)
* Added new `init` CLI command (`bin/plugin git-sync init`) (#128, thanks @LeonRyan and @alex-mohemian)
1. [](#improved)
* Allow setting a personalised commit message (#123, thanks @kyed)
* Added better directions for Azure + IIS users for the Git Binary
1. [](#bugfix)
* Fixed `LC_ALL` to use `C` instead of en_US.UTF-8`, to be more flexible (#124, #125, thanks @lambopedia)
# v2.0.0
## 10/15/2018
1. [](#new)
* Added support for new awesome Grav 1.6 Scheduler
* Added logic to display custom nested folders in wizard
* Other than `pages`, it is now possible to enable `config`, `data`, `plugins` and `themes` for synchronization. You can also add any custom folder you have in your `user` (#4, #21, #34, #58, #63, #83)
* Allow users with `admin.pages` permissions to synchronize through quick tray (#79, thanks @apfrod)
* When using Grav as committer, the user email will be now used for the commit (#81, thanks @apfrod)
* Added support for Webhook Secret (Bitbucket does not yet support them) (#72, #73, thanks @pathmissing)
* Added options to turn automatic synchronization on/off with page saves, delete and media changes (#105, thanks @AmauryCarrade)
1. [](#improved)
* Fixed alignment of the git icon in the Wizard (#115)
* Prevent Wizard modal to get canceled when clicking on the overlay background (#115)
* Quick tray icon is now smarter. If GitSync has not been initialized yet, it will take you straight to wizard, otherwise it would perform a synchronization (#115)
* Rearranged blueprint order (thanks @paulhibbitts)
* GitLab: Updated wizard instructions to be inline with the new GitLab UI (#90)
* Tweaked alignment of links in the wizard (#57)
* Properly support local branches that aren't `master` (#56)
* Allow to specify custom local_repository (default, `USER_DIR`) (#95, thanks @Hydraner, also #54, #33, #25)
* Webhook URL is now more robust and secure, by default it is generated with a random value
* Git icon from Admin has been replaced to use the `git` text icon instead of the logo
* Prevent next step if Step 1 and Step 2 are not filled in (#92)
* Added notice in Step 2 explaning what GitSync expect from the repository structure (#92)
1. [](#bugfix)
* Fixed issue where on first initialization the checkout process would error out
* Fixed issue with Pages save.
* Fixed JS error in plugins list
* Fixed nested folders not synchronizing
* Fixed issue where Wizard wouldn't work in case the `admin` path was modified (#27, #94, #77, thanks @pathmissing)
* Fixed webhook generated URL when multi-lang active (#71)
* Resolved issue with untracked/uncommited files at the root of the `sync` folder. (#101, thanks @ScottHamper)
# v1.0.4
## 08/16/2017
1. [](#new)
* CLI: Added `status` command to check config and git (#52, thanks @karfau)
* Allow local branches to be named differently than the remote branches (#48, thanks @denniswebb)
* Added support for new Admin Navigation Tray
1. [](#bugfix)
* Fixed minimum Git required version to support `--all` (#32,#49, thanks @redrohX)
# v1.0.3
## 02/21/2017
1. [](#bugfix)
* Fixed issue with new 'author' option that could trigger errors when settings were not saved. (#23)
* Fixed the 'More Details' button triggering the Modal to close instead of just expanding the details
# v1.0.2
## 02/18/2017
1. [](#new)
* It is now possible to change the committer name. You can choose between Git User, GitSync Committer Name, Grav User Name, Grav User Fullname (#14).
2. [](#improved)
* Added more documentation and description about the support of 2FA and Access Tokens (#16, #19, thanks @OleVik)
* Added 4th Generic Git choice in the wizard for self-hosted and custom git services (Gogs/Gitea) (#7 - #22 - thanks @erlepereira)
1. [](#bugfix)
* Fixed issue preventing the custom Git Binary Path from getting used (#15)
* Fixed issue with Webhook auto-generated URL where it would display double slashes in case of root domain (#15)
* Fixed issue with the modal not properly restoring the tutorial steps of the active selected service
# v1.0.1
## 01/29/2017
1. [](#bugfix)
* Changed default GitSync email for commits
# v1.0.0
## 01/25/2017
1. [](#new)
* Released plugin to stable GPM channel
# v1.0.0-rc.3
## 01/19/2017
1. [](#new)
* Added logger setting to log Git command executions
1. [](#improved)
* Improved Windows compatibility
# v1.0.0-rc.2
## 01/16/2017
1. [](#new)
* Allow to change the path for the `git` binary (#1)
* Added CLI for synchronizing `bin/plugin git-sync sync` (#2)
* More security: Git password will now get encrypted and won't load in admin
1. [](#improved)
* Wizard: Improved Bitbucket explanation about stripping out `user@` from the copied HTTPS url (#3)
1. [](#bugfix)
* Fixed potential issue when retrieving the currently installed git version
* Fixed issue that would not properly hide the password from error messages if the password contained special chars
* Fixed issue preventing the plugin to properly get setup the very first time and causing 401 error (#4)
* Workaround for error thrown when removing the plugin
# v1.0.0-rc.1
## 12/19/2016
1. [](#new)
* Initial Release

201
plugins/git-sync/LICENSE Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,90 @@
![](images/gitsync-logo.png)
**Git Sync** is a Plugin for [Grav CMS](http://github.com/getgrav/grav) that allows to seamlessly synchronize a Git repository with your Grav site, and vice-versa.
Git Sync captures any change that you make on your site and instantly updates your git repository. In the same way, Git Sync supports _webhooks_, allowing to automatically synchronize your site if the repository changes.
Thanks to this powerful bi-directional flow, Git Sync can now turn your site into a collaborative environment where the source of truth is always your git repository and unlimited collaborators and sites can share and contribute to the same content.
## Videos: Setup and Demo
| Up and Running in 2 mins | 2-way Sync Demonstration |
| ------------ | ----------------- |
| [![Up and Running in 2 mins](https://img.youtube.com/vi/avcGP0FAzB8/0.jpg)](https://www.youtube.com/watch?v=avcGP0FAzB8) | [![2-way Sync Demonstration](https://img.youtube.com/vi/3fy78afacyw/0.jpg)](https://www.youtube.com/watch?v=3fy78afacyw) |
## Installation using the GPM (Grav Package Manager)
To install git-sync simply run this command from the Grav root folder
```
bin/gpm install git-sync
```
After having installed the plugin, make sure to go in the plugin settings in order to get the Wizard configuration started.
## Features
<img src="wizard.png" width="500" />
* Easy step-by-step Wizard setup will guide you through a detailed process for setting things up
* Supported hosting services: [GitHub](https://github.com), [BitBucket](https://bitbucket.org), [GitLab](https://gitlab.com) as well as any self-hosted and git service with webhooks support.
* Private repositories
* Basic SSH / Enterprise support (You will need SSH Key properly setup on your machine)
* Synchronize any folder under `user` (pages, themes, config)
* 2FA (Two-Factor Authentication) and Access Token support
* Webhooks support allow for automatic synchronization from the Git Repository with secure Webhook URL auto-generated and support for Webhook Secret (when available)
* Automatically handles simple merges behind the scenes
* Easy one-click button to reset your local changes and restores it to the actual state of the git repository
* Easy one-click button for manual synchronization
* Support for Admin Quick Tray, so you can synchronize from anywhere in Admin
* Ability to customize whether GitSync should synchronize upon save or just manually
* Customize the Committer Name, choose between Git User, GitSync Commiter Name, Grav User Name and Grav user Fullname
* With the built-in Form Process action `gitsync`, you can trigger the synchronization anytime someone submits a post.
* Any 3rd party plugin can integrate with Git Sync and trigger the synchronization through the `gitsync` event.
* Built-in CLI commands to automate synchronizations
* Log any command performed by GitSync to ensure everything runs smoothly or debug potential issues
# Command Line Interface
Git Sync comes with a CLI that allows running synchronizations right within your terminal. This feature is extremely useful in case you'd like to run an autonomous periodic crontab jobs to synchronize with your repository.
To execute the command simply run:
```bash
bin/plugin git-sync sync
```
You can also get a status by running:
```bash
bin/plugin git-sync status
```
Since version 2.1.1 you can now also programmatically change user/password via the `bin/plugin git-sync passwd`. This is useful if you have a container that resets your password, or you have some running scripts that require to programmatically update the password.
# Requirements
In order for the plugin to work, the server needs to run `git` 1.7.1 and above.
The PHP `exec()` and `escapeshellarg()` functions are mandatory. Ensure the options to be enabled in your PHP.
# SSH / Enterprise
Since version v2.3.0, GitSync supports SSH authentication. This means you can omit password altogether and rely on the Repository URL and SSH key on your machine, that you can point to from the Advanced settings in GitSync.
Please note that In order to be able to sparse-checkout and push changes, it is expected you have an ssh-key configured for accessing the repository. This is usually found under `~/.ssh` and it needs to be configured for the same user that runs the web-server.
Point it to the secret (not the public) and make also sure you have strict permissions to the file. (`-rw-------`).
Example: private_key: `/home/www-data/.ssh/id_rsa`
> **IMPORTANT**: SSH keys with passphrase are **NOT** supported. To remove a passphrase, run the `ssh-keygen -p` command and when asked for the new passphrase leave blank and return.
# Known Issues and Resolutions
**Q:** `error: The requested URL returned error: 403 Forbidden while accessing...` [#39](https://github.com/trilbymedia/grav-plugin-git-sync/issues/39)
**A:** This might be caused by your computer having stored in the registry a user/password that might conflict with the one you are intending to use.
[Follow the instructions for resolving the issue...](https://github.com/trilbymedia/grav-plugin-git-sync/issues/39#issuecomment-538867548)
# Sponsored by
This plugin could not have been realized without the sponsorship of [Hibbitts Design](http://www.hibbittsdesign.org/blog/) and the development of [Trilby Media](http://trilby.media).

View file

@ -0,0 +1 @@
import './wizard';

View file

@ -0,0 +1,379 @@
import Settings from 'git-sync';
import request from 'admin/utils/request';
import toastr from 'admin/utils/toastr';
import { config } from 'grav-config';
import $ from 'jquery';
import 'whatwg-fetch';
const GIT_REGEX = /(?:git|ssh|https?|git@[-\w.]+):(\/\/)?(.*?)(\.git)(\/?|\#[-\d\w._]+?)$/;
const WIZARD = $('[data-remodal-id="wizard"]');
const RESET_LOCAL = $('[data-remodal-id="reset-local"]');
const SERVICES = { 'github': 'github.com', 'bitbucket': 'bitbucket.org', 'gitlab': 'gitlab.com', 'allothers': 'allothers.repo' };
const BRANCHES = { 'github': 'main', 'bitbucket': 'master', 'gitlab': 'master', 'allothers': 'master' };
const TEMPLATES = {
REPO_URL: 'https://{placeholder}/getgrav/grav.git'
};
const openWizard = () => {
const modal = WIZARD.remodal({ closeOnConfirm: false });
const previous = WIZARD.find('[data-gitsync-action="previous"]');
const next = WIZARD.find('[data-gitsync-action="next"]');
const save = WIZARD.find('[data-gitsync-action="save"]');
STEP = 0;
WIZARD.find(`form > [class^=step-]:not(.step-${STEP}) > .panel`).hide().removeClass('hidden');
WIZARD.find(`form > [class="step-${STEP}"] > .panel`).show();
next.removeClass('hidden');
previous.addClass('hidden');
save.addClass('hidden');
const webhook = $('[name="data[webhook]"]').val();
const webhook_secret = $('[name="data[webhook_secret]"]').val();
$('[name="gitsync[repository]"]').trigger('change');
$('[name="gitsync[webhook]"]').val(webhook);
$('[name="gitsync[webhook_secret]"]').val(webhook_secret);
$('.gitsync-webhook').text(webhook);
modal.open();
};
const disableButton = (next) => {
next
.attr('disabled', 'disabled')
.addClass('hint--top');
};
const enableButton = (next) => {
next
.attr('disabled', null)
.removeClass('hint--top');
};
let STEP = 0;
let STEPS = 0;
let SERVICE = null;
$(document).on('closed', WIZARD, function(e) {
STEP = 0;
});
$(document).on('click', '[data-gitsync-useraction]', (event) => {
event.preventDefault();
const target = $(event.target).closest('[data-gitsync-useraction]');
const action = target.data('gitsyncUseraction');
const URI = `${config.current_url}.json`;
switch (action) {
case 'wizard':
openWizard();
break;
case 'sync':
const relativeURI = target.data('gitsync-uri');
target.find('i').removeClass('fa-cloud fa-git').addClass('fa-circle-o-notch fa-spin');
request(relativeURI || URI, {
method: 'post',
body: { task: 'synchronize' }
}, () => {
target.find('i').removeClass('fa-circle-o-notch fa-spin').addClass(relativeURI ? 'fa-git' : 'fa-cloud');
});
break;
case 'reset':
const modal = RESET_LOCAL.remodal({ closeOnConfirm: false });
modal.open();
if (!RESET_LOCAL.data('_reset_event_set_')) {
RESET_LOCAL.find('[data-gitsync-action="reset-local"]').one('click', () => {
modal.close();
RESET_LOCAL.data('_reset_event_set_', true);
target.find('i').removeClass('fa-history').addClass('fa-circle-o-notch fa-spin');
request(URI, {
method: 'post',
body: { task: 'resetlocal' }
}, () => {
RESET_LOCAL.data('_reset_event_set_', false);
target.find('i').removeClass('fa-circle-o-notch fa-spin').addClass('fa-history');
});
});
}
break;
}
});
$(document).on('click', '[data-gitsync-action]', (event) => {
event.preventDefault();
const target = $(event.target).closest('[data-gitsync-action]');
const previous = WIZARD.find('[data-gitsync-action="previous"]');
const next = WIZARD.find('[data-gitsync-action="next"]');
const save = WIZARD.find('[data-gitsync-action="save"]');
const action = target.data('gitsyncAction');
const user = $('[name="gitsync[repo_user]"]').val();
const noUser = $('[name="gitsync[no_user]"]').is(':checked');
const password = $('[name="gitsync[repo_password]"]').val();
const repository = $('[name="gitsync[repo_url]"]').val();
const branch = $('[name="gitsync[branch]"]').val();
const webhook = $('[name="gitsync[webhook]"]').val();
const webhook_enabled = $('[name="gitsync[webhook_enabled]"]').is(':checked');
const webhook_secret = $('[name="gitsync[webhook_secret]"]').val();
if (target.attr('disabled')) {
return;
}
let error = [];
if (!user && !noUser) {
error.push('Username is missing.');
}
/*
if (!password) {
error.push('Password is missing.');
}
*/
if (!repository) {
error.push('Repository is missing.');
}
if (['save', 'test'].includes(action)) {
target.find('.fa').removeClass(action === 'test' ? 'fa-plug' : 'fa-check').addClass('fa-spin fa-circle-o-notch');
if (error.length) {
toastr.error(error.join('<br />'));
target.find('.fa').removeClass('fa-spin fa-circle-o-notch').addClass(action === 'test' ? 'fa-plug' : 'fa-check');
return false;
}
}
if (action === 'save') {
const folders = $('[name="gitsync[folders]"]:checked').map((i, item) => item.value);
$('[name="data[repository]"]').val(repository);
$('[name="data[no_user]"]').val(noUser ? '1' : '0');
$('[name="data[user]"]').val(user);
$('[name="data[password]"]').val(password);
$('[name="data[branch]"]').val(branch);
$('[name="data[remote][branch]"]').val(branch);
$('[name="data[webhook]"]').val(webhook);
$(`[name="data[webhook_enabled]"][value="${webhook_enabled ? 1 : 0}"]`).prop('checked', true);
$('[name="data[webhook_secret]"]').val(webhook_secret);
const dataFolders = $('[name="data[folders][]"]');
if (dataFolders && dataFolders[0] && dataFolders[0].selectize) {
dataFolders[0].selectize.setValue(folders.toArray());
}
$('[name="task"][value="save"]').trigger('click');
return false;
}
if (action === 'test') {
const URI = `${config.current_url}.json`;
const test = global.btoa(JSON.stringify({
user: noUser ? '' : user,
password,
repository,
branch
}));
request(URI, {
method: 'post',
body: { test, task: 'testConnection' }
});
target.find('.fa').removeClass('fa-spin fa-circle-o-notch').addClass('fa-plug');
return false;
}
WIZARD.find(`.step-${STEP} > .panel`).slideUp();
STEP += action === 'next' ? +1 : -1;
WIZARD.find(`.step-${STEP} > .panel`).slideDown();
save.addClass('hidden');
if (action === 'next') {
previous.removeClass('hidden');
}
if (STEP <= 0) {
previous.addClass('hidden');
enableButton(next);
}
if (STEP > 0) {
next.removeClass('hidden');
}
if (STEP === 1) {
const selectedRepo = $('[name="gitsync[repository]"]:checked');
if (!selectedRepo.length) {
disableButton(next);
} else {
enableButton(next);
}
}
if (STEP === 2) {
const repoURL = $('[name="gitsync[repo_url]"]').val();
if (!repoURL.length || !branch) {
disableButton(next);
} else {
enableButton(next);
}
}
if (STEP === STEPS) {
next.addClass('hidden');
previous.removeClass('hidden');
save.removeClass('hidden');
}
});
$(document).on('input', '[name="gitsync[no_user]"]', (event) => {
const target = $(event.currentTarget);
const user = $('[name="gitsync[repo_user]"]');
if (target.is(':checked')) {
user
.val('')
.prop('disabled', 'disabled')
.attr('placeholder', '<username not required>');
} else {
user
.prop('disabled', null)
.attr('placeholder', 'Username, not email');
}
});
$(document).on('change', '[name="gitsync[repository]"]', () => {
enableButton(WIZARD.find('[data-gitsync-action="next"]'));
});
$(document).on('input', '[name="gitsync[repo_url]"]', (event) => {
const target = $(event.currentTarget);
const value = target.val();
const isGitURL = GIT_REGEX.test(value);
const next = WIZARD.find('[data-gitsync-action="next"]');
target.removeClass('invalid');
if (!isGitURL) {
target.addClass('invalid');
}
if (isGitURL && value.length) {
enableButton(next);
} else {
disableButton(next);
}
});
$(document).on('keyup', '[data-gitsync-uribase] [name="gitsync[webhook]"]', (event) => {
const target = $(event.currentTarget);
const value = target.val();
$('.gitsync-webhook').text(value);
});
$(document).on('keyup', '[data-gitsync-uribase] [name="gitsync[webhook_secret]"]', (event) => {
$('[data-gitsync-uribase] [name="gitsync[webhook_enabled]"]').trigger('change');
});
$(document).on('change', '[data-gitsync-uribase] [name="gitsync[webhook_enabled]"]', (event) => {
const target = $(event.currentTarget);
const checked = target.is(':checked');
const secret = $('[name="gitsync[webhook_secret]"]').val();
target.closest('.webhook-secret-wrapper').find('label:last-child')[checked ? 'removeClass' : 'addClass']('hidden');
$('.gitsync-webhook-secret').html(!checked || !secret.length ? '<em>leave empty</em>' : `<code>${secret}</code>`);
});
$(document).on('change', '[name="gitsync[repository]"]', (event) => {
const target = $(event.target);
if (!target.is(':checked')) {
return;
}
SERVICE = target.val();
Object.keys(SERVICES).forEach((service) => {
WIZARD.find(`.hidden-step-${service}`)[service === SERVICE ? 'removeClass' : 'addClass']('hidden');
if (service === SERVICE) {
WIZARD.find('.webhook-secret-wrapper')[service === 'bitbucket' ? 'addClass' : 'removeClass']('hidden');
WIZARD
.find('input[name="gitsync[repo_url]"][placeholder]')
.attr('placeholder', TEMPLATES.REPO_URL.replace(/\{placeholder\}/, SERVICES[service]))
.end()
.find('input[name="gitsync[branch]"]')
.attr('placeholder', BRANCHES[service])
.val(BRANCHES[service]);
}
});
});
$(document).on('click', '[data-access-tokens-details]', (event) => {
event.preventDefault();
const button = $(event.currentTarget);
const panel = button.closest('.access-tokens').find('.access-tokens-details');
panel.slideToggle(250, () => {
const isVisible = panel.is(':visible');
const icon = button.find('.fa');
icon.removeClass('fa-chevron-down fa-chevron-up').addClass(`fa-chevron-${isVisible ? 'up' : 'down'}`);
});
});
const showNotices = (element) => {
const target = $(element);
const selection = target.val().replace(/\//g, '-');
const column = target.closest('.columns').find('.column:last');
column.find('[class*="description-"]').addClass('hidden');
column.find(`.description-${selection}`).removeClass('hidden').hide().fadeIn({
duration: 250
});
};
$(document).on('input', '[data-remodal-id="wizard"] .step-4 input[type="checkbox"]', (event) => {
const target = $(event.currentTarget);
if (!target.is(':checked')) {
return;
}
showNotices(target);
});
$(document).on('mouseenter', '[data-remodal-id="wizard"] .step-4 .info-desc', (event) => {
const target = $(event.currentTarget).siblings('input[type="checkbox"]');
showNotices(target);
});
$(document).on('mouseleave', '[data-remodal-id="wizard"] .step-4 label', (event) => {
const target = $(event.currentTarget);
const container = target.closest('.columns');
const column = container.find('.column:last-child');
column.find('[class*="description-"]').addClass('hidden');
});
$(document).on('mouseleave', '[data-remodal-id="wizard"] .columns .column:first-child', (event) => {
const target = $(event.currentTarget);
const column = target.siblings('.column');
column.find('[class*="description-"]').addClass('hidden');
});
$(document).ready(() => {
STEPS = WIZARD.find('[class^="step-"]').length - 1;
WIZARD.wrapInner('<form></form>');
RESET_LOCAL.wrapInner('<form></form>');
if (WIZARD.length && (Settings.first_time || !Settings.git_installed)) {
openWizard();
}
});
export default Settings;

View file

@ -0,0 +1,289 @@
name: Git Sync
type: plugin
slug: git-sync
version: 2.3.2
description: Allows to synchronize portions of Grav with Git Repositories (GitHub, BitBucket, GitLab)
icon: git
author:
name: Trilby Media, LLC
email: hello@trilby.media
url: http://trilby.media
homepage: http://trilby.media
keywords: grav, plugin, git, sync, github, bitbucket, gitlab
issues: https://github.com/trilbymedia/grav-plugin-git-sync/issues
docs: https://github.com/trilbymedia/grav-plugin-git-sync
license: MIT
dependencies:
- { name: grav, version: '>=1.6.0' }
- { name: form, version: '>=2.16.3' }
form:
validation: strict
fields:
Basic:
type: section
title: Basic Settings
underline: true
enabled:
type: toggle
label: Plugin Status
highlight: 1
default: 0
options:
1: Enabled
0: Disabled
validate:
type: bool
folders:
type: select
multiple: true
label: Folders to Sync
classes: fancy
description: Removing folders after they have been synced may cause undesired results.
default:
- pages
options:
- pages
- themes
- plugins
- config
- data
selectize:
create: true
validate:
type: commalist
Sync:
type: section
title: Automatic Synchronization Settings
underline: true
SyncNotice:
type: hidden
markdown: true
text: |
! To improve the speed of saving pages you can disable automatic sync. Then, changes to a page will not be sent to the remote repository on every save. To sync your changes to the repository tap the GitSync button (<i class="fa fa-git"></i>) in the top left of the Administration Panel, or use the below Scheduler option to add the GitSync Syncronization Job to the Scheduler (<strong>Grav 1.6 required</strong>).
sync.on_save:
type: toggle
label: Sync on Page Save
help: Sync with the remote directory when a page is saved through the admin
default: 1
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
sync.on_delete:
type: toggle
label: Sync on Page Delete
help: Sync with the remote directory when a page is deleted through the admin
default: 1
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
sync.on_media:
type: toggle
label: Sync on Media Changes
help: Sync with the remote directory when a media is uploaded or deleted through the admin immediately (instead of only syncing when the page is saved)
default: 1
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
sync.cron_enable:
type: toggle
label: Add Sync to Scheduler
help: Add GitSync Job to the Scheduler so it can automatically perform synchronization at the given time
default: 0
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
sync.cron_at:
type: cron
label: Run Sync at
help: When should the Scheduler run the automatic GitSync synchronization job
default: '0 12,23 * * *'
Repo:
type: section
title: Git Repository Settings
underline: true
local_repository:
type: hidden
multiple: false
size: medium
label: Local Repository Path
repository:
type: text
label: Git Repository
placeholder: https://github.com/user/repository.git
no_user:
type: toggle
label: User not required
highlight: 0
default: 0
options:
1: Enabled
0: Disabled
description: With this setting enabled, the user can be left blank and it will be ignored from the authentication. Useful when only needing access tokens `token@host` rather than `user:password@host`
user:
type: text
label: Git User
placeholder: Username, not email
autocomplete: off
password:
type: enc-password
label: Git Password or Token
placeholder: Your Git Password or Token
description: Enter your password or token to encrypt and securely store it, then save the settings. It will not show up here for security reasons.
autocomplete: off
webhook:
type: text
label: Repository Web Hook URL
placeholder: /_git-sync
data-default@: '\Grav\Plugin\GitSyncPlugin::generateRandomWebhook'
webhook_enabled:
type: toggle
label: Web Hook Secret
highlight: 1
default: 0
options:
1: Enabled
0: Disabled
description: With this setting enabled, only authorized webhook calls will be able to trigger a synchronization (recommended)
webhook_secret:
type: text
label: Repository Web Hook Secret
placeholder: Your Web Hook Secret
data-default@: '\Grav\Plugin\GitSyncPlugin::generateWebhookSecret'
description: You can either use this randomly generated string or enter your own secret. <br /> **Bitbucket** does not yet support Webhook Secrets.
markdown: true
Advanced:
type: section
title: Advanced Git Settings
underline: true
branch:
type: text
default: master
label: Local Branch
placeholder: master
remote.name:
type: text
default: origin
label: Remote Name
placeholder: origin
remote.branch:
type: text
default: master
label: Remote Branch
placeholder: master
git.author:
type: select
default: gituser
label: Commits Author
options:
gituser: Use Git User Name
gitsync: Use GitSync Committer Name
gravuser: Use Grav User Name
gravfull: Use Grav User Full Name
git.message:
type: text
default: (Grav GitSync) Automatic Commit
label: Commit message
placeholder: (Grav GitSync) Automatic Commit
help: You can use {{pageTitle}} or {{pageRoute}} in your message as placeholders for the title or route of the page being saved
git.name:
type: text
default: GitSync
label: Committer Name
placeholder: GitSync
git.email:
type: text
default: git-sync@trilby.media
label: Committer Email
placeholder: git-sync@trilby.media
git.bin:
type: text
default: git
label: Git Binary Path
help: If the default `git` command doesn't work on your machine or if you want to specify a custom path, do it in here
placeholder: /usr/bin/git
git.ignore:
type: textarea
label: Git Ignore
help: Add custom git ignore rules to go along with GitSync. One per line
rows: 6
placeholder: |
node_modules
/.idea
git.private_key:
type: text
label: Private SSH Key
placeholder: ~/.ssh/id_rsa
markdown: true
description: >
In order to be able to sparse-checkout and push changes, it is expected you have an ssh-key configured for accessing the repository. This is usually found under `~/.ssh` and it needs to be configured for the same user that runs the web-server. <br />
<br />
Point it to the secret (not the public) and make also sure you have strict permissions to the file. (`-rw-------`). <br />
<br />
Example: `private_key: /home/www-data/.ssh/id_rsa`<br />
<br />
**IMPORTANT**: SSH keys with passphrase are __NOT__ supported. To remove a passphrase, run the `ssh-keygen -p` command and when asked for the new passphrase leave blank and return.
logging:
type: toggle
default: 0
label: Log Git Commands
help: Logs git commands. Useful to debug and troubleshoot git execution
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
Actions:
type: section
title: Actions
underline: true
_wizard:
type: git-wizard
label: Text Variable
help: Text to add to the top of a page

View file

@ -0,0 +1,177 @@
<?php
namespace Grav\Plugin\GitSync;
use Grav\Common\Grav;
use Grav\Common\Plugin;
use Grav\Common\Utils;
use Grav\Plugin\Admin\AdminBaseController;
class AdminController extends AdminBaseController
{
protected $action;
protected $target;
protected $active;
protected $plugin;
protected $task_prefix = 'task';
/** @var GitSync */
public $git;
/**
* @param Plugin $plugin
*/
public function __construct(Plugin $plugin)
{
$this->grav = Grav::instance();
$this->active = false;
$uri = $this->grav['uri'];
$this->plugin = $plugin;
$post = !empty($_POST) ? $_POST : [];
$this->post = $this->getPost($post);
// Ensure the controller should be running
if (Utils::isAdminPlugin()) {
$routeDetails = $this->grav['admin']->getRouteDetails();
$target = array_pop($routeDetails);
$this->git = new GitSync();
// return null if this is not running
if ($target !== $plugin->name) {
return;
}
$this->action = !empty($this->post['action']) ? $this->post['action'] : $uri->param('action');
$this->target = $target;
$this->active = true;
$this->admin = Grav::instance()['admin'];
$task = !empty($post['task']) ? $post['task'] : $uri->param('task');
if ($task && ($this->target === $plugin->name || $uri->route() === '/lessons')) {
$this->task = $task;
$this->active = true;
}
}
}
public function taskTestConnection()
{
$post = $this->post;
$test = base64_decode($post['test']) ?: null;
$data = $test ? json_decode($test, false) : new \stdClass();
try {
$testResult = Helper::testRepository($data->user, $data->password, $data->repository, $data->branch);
if (!empty($testResult)) {
echo json_encode([
'status' => 'success',
'message' => 'The connection to the repository has been successful.'
]);
} else {
echo json_encode([
'status' => 'error',
'message' => 'Branch "' . $data->branch .'" not found in the repository.'
]);
}
} catch (\Exception $e) {
$invalid = str_replace($data->password, '{password}', $e->getMessage());
echo json_encode([
'status' => 'error',
'message' => $invalid
]);
}
exit;
}
public function taskSynchronize()
{
try {
$this->plugin->synchronize();
echo json_encode([
'status' => 'success',
'message' => 'GitSync has successfully synchronized with the repository.'
]);
} catch (\Exception $e) {
$invalid = str_replace($this->git->getConfig('password', null), '{password}', $e->getMessage());
echo json_encode([
'status' => 'error',
'message' => $invalid
]);
}
exit;
}
public function taskResetLocal()
{
try {
$this->plugin->reset();
echo json_encode([
'status' => 'success',
'message' => 'GitSync has successfully reset your local changes and synchronized with the repository.'
]);
} catch (\Exception $e) {
$invalid = str_replace($this->git->getConfig('password', null), '{password}', $e->getMessage());
echo json_encode([
'status' => 'error',
'message' => $invalid
]);
}
exit;
}
/**
* Performs a task or action on a post or target.
*
* @return bool
*/
public function execute()
{
$params = [];
// Handle Task & Action
if ($this->post && $this->task) {
// validate nonce
if (!$this->validateNonce()) {
return false;
}
$method = $this->task_prefix . ucfirst($this->task);
} elseif ($this->target) {
if (!$this->action) {
return false;
}
$method = strtolower($this->action) . ucfirst($this->target);
} else {
return false;
}
if (!method_exists($this, $method)) {
return false;
}
$success = $this->{$method}(...$params);
// Grab redirect parameter.
$redirect = $this->post['_redirect'] ?? null;
unset($this->post['_redirect']);
// Redirect if requested.
if ($redirect) {
$this->setRedirect($redirect);
}
return $success;
}
/**
* @return bool
*/
public function isActive()
{
return (bool) $this->active;
}
}

View file

@ -0,0 +1,536 @@
<?php
namespace Grav\Plugin\GitSync;
use Grav\Common\Grav;
use Grav\Common\Plugin;
use Grav\Common\Utils;
use http\Exception\RuntimeException;
use RocketTheme\Toolbox\File\File;
use SebastianBergmann\Git\Git;
class GitSync extends Git
{
/** @var static */
static public $instance;
/** @var Grav */
protected $grav;
/** @var Plugin */
protected $plugin;
/** @var array */
protected $config;
/** @var string */
protected $repositoryPath;
/** @var string|null */
private $user;
/** @var string|null */
private $password;
public function __construct()
{
$this->grav = Grav::instance();
$this->config = $this->grav['config']->get('plugins.git-sync');
$this->repositoryPath = isset($this->config['local_repository']) && $this->config['local_repository'] ? $this->config['local_repository'] : USER_DIR;
parent::__construct($this->repositoryPath);
static::$instance = $this;
$this->user = isset($this->config['no_user']) && $this->config['no_user'] ? '' : ($this->config['user'] ?? null);
$this->password = $this->config['password'] ?? null;
unset($this->config['user'], $this->config['password']);
}
/**
* @return static
*/
public static function instance()
{
if (null === static::$instance) {
static::$instance = new static;
}
return static::$instance;
}
/**
* @return string|null
*/
public function getUser()
{
return $this->user;
}
/**
* @return string|null
*/
public function getPassword()
{
return $this->password;
}
/**
* @param array $config
*/
public function setConfig($config)
{
$this->config = $config;
$this->user = $this->config['user'];
$this->password = $this->config['password'];
}
/**
* @return array
*/
public function getRuntimeInformation()
{
$result = [
'repositoryPath' => $this->repositoryPath,
'username' => $this->user,
'password' => $this->password
];
foreach ($this->config as $key => $item) {
if (is_array($item)) {
$count = count($item);
$arr = $item;
if ($count === 0) {// empty array, could still be associative
$arr = '[]';
} else if (isset($item[0])) {// fast check for plain array with numeric keys
$arr = '[\'' . implode('\', \'', $item) . '\']';
}
$result[$key] = $arr;
} else {
$result[$key] = $item;
}
}
return $result;
}
/**
* @param string $url
* @return string[]
*/
public function testRepository($url, $branch)
{
if (!preg_match(Helper::GIT_REGEX, $url)) {
throw new \RuntimeException("Git Repository value does not match the supported format.");
}
$branch = $branch ? '"' . $branch . '"' : '';
return $this->execute("ls-remote \"{$url}\" {$branch}");
}
/**
* @return bool
*/
public function initializeRepository()
{
if (!Helper::isGitInitialized()) {
$branch = $this->getRemote('branch', null);
$local_branch = $this->getConfig('branch', $branch);
$this->execute('init');
$this->execute('checkout -b ' . $local_branch, true);
}
$this->enableSparseCheckout();
return true;
}
/**
* @param string|null $name
* @param string|null $email
* @return bool
*/
public function setUser($name = null, $email = null)
{
$name = $this->getConfig('git', $name)['name'];
$email = $this->getConfig('git', $email)['email'];
$privateKey = $this->getGitConfig('private_key', null);
$this->execute("config user.name \"{$name}\"");
$this->execute("config user.email \"{$email}\"");
if ($privateKey) {
$this->execute('config core.sshCommand "ssh -i ' . $privateKey . ' -F /dev/null"');
} else {
$this->execute('config --unset core.sshCommand');
}
return true;
}
/**
* @param string|null $name
* @return bool
*/
public function hasRemote($name = null)
{
$name = $this->getRemote('name', $name);
try {
/** @var string $version */
$version = Helper::isGitInstalled(true);
// remote get-url 'name' supported from 2.7.0 and above
if (version_compare($version, '2.7.0', '>=')) {
$command = "remote get-url \"{$name}\"";
} else {
$command = "config --get remote.{$name}.url";
}
$this->execute($command);
} catch (\Exception $e) {
return false;
}
return true;
}
public function enableSparseCheckout()
{
$folders = $this->config['folders'];
$this->execute('config core.sparsecheckout true');
$sparse = [];
foreach ($folders as $folder) {
$sparse[] = $folder . '/';
$sparse[] = $folder . '/*';
}
$file = File::instance(rtrim($this->repositoryPath, '/') . '/.git/info/sparse-checkout');
$file->save(implode("\r\n", $sparse));
$file->free();
$ignore = ['/*'];
foreach ($folders as $folder) {
$folder = rtrim($folder,'/');
$nested = substr_count($folder, '/');
if ($nested) {
$subfolders = explode('/', $folder);
$nested_tracking = '';
foreach ($subfolders as $index => $subfolder) {
$last = $index === (count($subfolders) - 1);
$nested_tracking .= $subfolder . '/';
if (!in_array('!/' . $nested_tracking, $ignore, true)) {
$ignore[] = rtrim($nested_tracking . (!$last ? '*' : ''), '/');
$ignore[] = rtrim('!/' . $nested_tracking, '/');
}
}
} else {
$ignore[] = '!/' . $folder;
}
}
$ignoreEntries = explode("\n", $this->getGitConfig('ignore', ''));
$ignore = array_merge($ignore, $ignoreEntries);
$file = File::instance(rtrim($this->repositoryPath, '/') . '/.gitignore');
$file->save(implode("\r\n", $ignore));
$file->free();
}
/**
* @param string|null $alias
* @param string|null $url
* @param bool $authenticated
* @return string[]
*/
public function addRemote($alias = null, $url = null, $authenticated = false)
{
$alias = $this->getRemote('name', $alias);
$url = $this->getConfig('repository', $url);
if ($authenticated) {
$user = $this->user ?? '';
$password = $this->password ? Helper::decrypt($this->password) : '';
$url = Helper::prepareRepository($user, $password, $url);
}
$command = $this->hasRemote($alias) ? 'set-url' : 'add';
return $this->execute("remote {$command} {$alias} \"{$url}\"");
}
/**
* @return string[]
*/
public function add()
{
/** @var string $version */
$version = Helper::isGitInstalled(true);
$add = 'add';
// With the introduction of customizable paths,
// it appears that the add command should always
// add everything that is not committed to ensure
// there are no orphan changes left behind
/*
$folders = $this->config['folders'];
$paths = [];
foreach ($folders as $folder) {
$paths[] = $folder;
}
*/
$paths = ['.'];
if (version_compare($version, '2.0', '<')) {
$add .= ' --all';
}
return $this->execute($add . ' ' . implode(' ', $paths));
}
/**
* @param string $message
* @return string[]
*/
public function commit($message = '(Grav GitSync) Automatic Commit')
{
$authorType = $this->getGitConfig('author', 'gituser');
if (defined('GRAV_CLI') && in_array($authorType, ['gravuser', 'gravfull'])) {
$authorType = 'gituser';
}
// get message from config, it any, or stick to the default one
$config = $this->getConfig('git', null);
$message = $config['message'] ?? $message;
// get Page Title and Route from Post
$uri = $this->grav['uri'];
$page_title = $uri->post('data.header.title');
$page_route = $uri->post('data.route');
$pageTitle = $page_title ?: 'NO TITLE FOUND';
$pageRoute = $page_route ?: 'NO ROUTE FOUND';
// include page title and route in the message, if placeholders exist
$message = str_replace('{{pageTitle}}', $pageTitle, $message);
/** @var string $message */
$message = str_replace('{{pageRoute}}', $pageRoute, $message);
switch ($authorType) {
case 'gitsync':
$user = $this->getConfig('git', null)['name'];
$email = $this->getConfig('git', null)['email'];
break;
case 'gravuser':
$user = $this->grav['session']->user->username;
$email = $this->grav['session']->user->email;
break;
case 'gravfull':
$user = $this->grav['session']->user->fullname;
$email = $this->grav['session']->user->email;
break;
case 'gituser':
default:
$user = $this->user;
$email = $this->getConfig('git', null)['email'];
break;
}
$author = $user . ' <' . $email . '>';
$author = '--author="' . $author . '"';
$message .= ' from ' . $user;
$this->add();
return $this->execute('commit ' . $author . ' -m ' . escapeshellarg($message));
}
/**
* @param string|null $name
* @param string|null $branch
* @return string[]
*/
public function fetch($name = null, $branch = null)
{
$name = $this->getRemote('name', $name);
$branch = $this->getRemote('branch', $branch);
return $this->execute("fetch {$name} {$branch}");
}
/**
* @param string|null $name
* @param string|null $branch
* @return string[]
*/
public function pull($name = null, $branch = null)
{
$name = $this->getRemote('name', $name);
$branch = $this->getRemote('branch', $branch);
/** @var string $version */
$version = Helper::isGitInstalled(true);
$unrelated_histories = '--allow-unrelated-histories';
// --allow-unrelated-histories starts at 2.9.0
if (version_compare($version, '2.9.0', '<')) {
$unrelated_histories = '';
}
return $this->execute("pull {$unrelated_histories} -X theirs {$name} {$branch}");
}
/**
* @param string|null $name
* @param string|null $branch
* @return string[]
*/
public function push($name = null, $branch = null)
{
$name = $this->getRemote('name', $name);
$branch = $this->getRemote('branch', $branch);
$local_branch = $this->getConfig('branch', null);
return $this->execute("push {$name} {$local_branch}:{$branch}");
}
/**
* @param string|null $name
* @param string|null $branch
* @return bool
*/
public function sync($name = null, $branch = null)
{
$name = $this->getRemote('name', $name);
$branch = $this->getRemote('branch', $branch);
$this->addRemote(null, null, true);
$this->fetch($name, $branch);
$this->pull($name, $branch);
$this->push($name, $branch);
$this->addRemote();
return true;
}
/**
* @return string[]
*/
public function reset()
{
return $this->execute('reset --hard HEAD');
}
/**
* @return bool
*/
public function isWorkingCopyClean()
{
$message = 'nothing to commit';
$output = $this->execute('status');
return strpos($output[count($output) - 1], $message) === 0;
}
/**
* @return bool
*/
public function hasChangesToCommit()
{
$folders = $this->config['folders'];
$paths = [];
foreach ($folders as $folder) {
$folder = explode('/', $folder);
$paths[] = array_shift($folder);
}
$message = 'nothing to commit';
$output = $this->execute('status ' . implode(' ', $paths));
return strpos($output[count($output) - 1], $message) !== 0;
}
/**
* @param string $command
* @param bool $quiet
* @return string[]
*/
public function execute($command, $quiet = false)
{
try {
$bin = Helper::getGitBinary($this->getGitConfig('bin', 'git'));
/** @var string $version */
$version = Helper::isGitInstalled(true);
// -C <path> supported from 1.8.5 and above
if (version_compare($version, '1.8.5', '>=')) {
$command = $bin . ' -C ' . escapeshellarg($this->repositoryPath) . ' ' . $command;
} else {
$command = 'cd ' . $this->repositoryPath . ' && ' . $bin . ' ' . $command;
}
$command .= ' 2>&1';
if (DIRECTORY_SEPARATOR === '/') {
$command = 'LC_ALL=C ' . $command;
}
if ($this->getConfig('logging', false)) {
$log_command = Helper::preventReadablePassword($command, $this->password ?? '');
$this->grav['log']->notice('gitsync[command]: ' . $log_command);
exec($command, $output, $returnValue);
$log_output = Helper::preventReadablePassword(implode("\n", $output), $this->password ?? '');
$this->grav['log']->notice('gitsync[output]: ' . $log_output);
} else {
exec($command, $output, $returnValue);
}
if ($returnValue !== 0 && $returnValue !== 5 && !$quiet) {
throw new \RuntimeException(implode("\r\n", $output));
}
return $output;
} catch (\RuntimeException $e) {
$message = $e->getMessage();
$message = Helper::preventReadablePassword($message, $this->password ?? '');
// handle scary messages
if (Utils::contains($message, 'remote: error: cannot lock ref')) {
$message = 'GitSync: An error occurred while trying to synchronize. This could mean GitSync is already running. Please try again.';
}
throw new \RuntimeException($message);
}
}
/**
* @param string $type
* @param mixed $value
* @return mixed
*/
public function getGitConfig($type, $value)
{
return $this->config['git'][$type] ?? $value;
}
/**
* @param string $type
* @param mixed $value
* @return mixed
*/
public function getRemote($type, $value)
{
return $value ?: ($this->config['remote'][$type] ?? $value);
}
/**
* @param string $type
* @param mixed $value
* @return mixed
*/
public function getConfig($type, $value)
{
return $value ?: ($this->config[$type] ?? $value);
}
}

View file

@ -0,0 +1,158 @@
<?php
namespace Grav\Plugin\GitSync;
use Defuse\Crypto\Crypto;
use Grav\Common\Config\Config;
use Grav\Common\Grav;
use Grav\Common\Utils;
use SebastianBergmann\Git\RuntimeException;
class Helper
{
/** @var string */
private static $hash = '594ef69d-6c29-45f7-893a-f1b4342687d3';
/** @var string */
const GIT_REGEX = '/(?:git|ssh|https?|git@[-\w.]+):(\/\/)?(.*?)(\.git)(\/?|\#[-\d\w._]+?)$/';
/**
* Checks if the user/ folder is already initialized
*
* @return bool
*/
public static function isGitInitialized()
{
return file_exists(rtrim(USER_DIR, '/') . '/.git');
}
/**
* @param bool $version
* @return bool|string
*/
public static function isGitInstalled($version = false)
{
$bin = Helper::getGitBinary();
exec($bin . ' --version', $output, $returnValue);
$installed = $returnValue === 0;
if ($version && $output) {
$output = explode(' ', array_shift($output));
$versions = array_filter($output, static function($item) {
return version_compare($item, '0.0.1', '>=');
});
$installed = array_shift($versions);
}
return $installed;
}
/**
* @param bool $override
* @return string
*/
public static function getGitBinary($override = false)
{
/** @var Config $grav */
$config = Grav::instance()['config'];
return $override ?: $config->get('plugins.git-sync.git.bin', 'git');
}
/**
* @param string $user
* @param string $password
* @param string $repository
* @return string
*/
public static function prepareRepository($user, $password, $repository)
{
$user = $user ? urlencode($user) . ':' : '';
$password = urlencode($password);
if (Utils::startsWith($repository, 'ssh://')) {
return $repository;
}
return str_replace('://', "://${user}${password}@", $repository);
}
/**
* @param string $user
* @param string $password
* @param string $repository
* @return string[]
*/
public static function testRepository($user, $password, $repository, $branch)
{
$git = new GitSync();
$repository = self::prepareRepository($user, $password, $repository);
try {
return $git->testRepository($repository, $branch);
} catch (RuntimeException $e) {
return [$e->getMessage()];
}
}
/**
* @param string $password
* @return string
* @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException
*/
public static function encrypt($password)
{
return 'gitsync-' . Crypto::encryptWithPassword($password, self::$hash);
}
/**
* @param string $enc_password
* @return string
*/
public static function decrypt($enc_password)
{
if (strpos($enc_password, 'gitsync-') === 0) {
$enc_password = substr($enc_password, 8);
return Crypto::decryptWithPassword($enc_password, self::$hash);
}
return $enc_password;
}
/**
* @return bool
*/
public static function synchronize()
{
if (!self::isGitInstalled() || !self::isGitInitialized()) {
return true;
}
$git = new GitSync();
if ($git->hasChangesToCommit()) {
$git->commit();
}
// synchronize with remote
$git->sync();
return true;
}
/**
* @param string $str
* @param string $password
* @return string
*/
public static function preventReadablePassword($str, $password)
{
$encoded = urlencode(self::decrypt($password));
return str_replace($encoded, '{password}', $str);
}
}

View file

@ -0,0 +1,45 @@
<?php namespace Grav\Plugin\Console;
use Grav\Console\ConsoleCommand;
use Grav\Plugin\GitSync\GitSync;
/**
* Class InitCommand
*
* @package Grav\Plugin\Console
*/
class InitCommand extends ConsoleCommand
{
protected function configure()
{
$this
->setName('init')
->setDescription('Initializes your git repository')
->setHelp('The <info>init</info> command runs the same git commands as the onAdminAfterSave function. Use this to manually initialize git-sync (useful for automated deployments).')
;
}
protected function serve()
{
require_once __DIR__ . '/../vendor/autoload.php';
$plugin = new GitSync();
$repository = $plugin->getConfig('repository', false);
$this->output->writeln('');
if (!$repository) {
$this->output->writeln('<red>ERROR:</red> No repository has been configured!');
}
$this->output->writeln('Initializing <cyan>' . $repository . '</cyan>');
$this->output->write('Starting initialization...');
$plugin->initializeRepository();
$plugin->setUser();
$plugin->addRemote();
$this->output->writeln('completed.');
}
}

View file

@ -0,0 +1,91 @@
<?php
namespace Grav\Plugin\Console;
use Grav\Console\ConsoleCommand;
use Grav\Plugin\GitSync\GitSync;
use Grav\Plugin\GitSync\Helper;
use Grav\Common\Grav;
use RocketTheme\Toolbox\File\YamlFile;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputOption;
/**
* Class LogCommand
*
* @package Grav\Plugin\Console
*/
class PasswdCommand extends ConsoleCommand
{
/** @var array */
protected $options = [];
protected function configure()
{
$this
->setName('passwd')
->setDescription('Allows to change the user and/or password programmatically')
->addOption(
'user',
'u',
InputOption::VALUE_REQUIRED,
'The username. Use empty double quotes if you need an empty username.'
)
->addOption(
'password',
'p',
InputOption::VALUE_REQUIRED,
"The password."
)
->setHelp('The <info>%command.name%</info> command allows to change the user and/or password. Useful when running automated scripts or needing to programmatically set them without admin access.')
;
}
protected function serve()
{
require_once __DIR__ . '/../vendor/autoload.php';
$grav = Grav::instance();
$config = $grav['config'];
$locator = $grav['locator'];
$filename = 'config://plugins/git-sync.yaml';
$file = YamlFile::instance($locator->findResource($filename, true, true));
$this->options = [
'user' => $this->input->getOption('user'),
'password' => $this->input->getOption('password')
];
if ($this->options['password'] !== null) {
$this->options['password'] = Helper::encrypt($this->options['password']);
}
$user = $this->options['user'] !== null ? $this->options['user'] : $config->get('plugins.git-sync.user');
$password = $this->options['password'] !== null ? $this->options['password'] : $config->get('plugins.git-sync.password');
$config->set('plugins.git-sync.user', $user);
$config->set('plugins.git-sync.password', $password);
$content = $grav['config']->get('plugins.git-sync');
$file->save($content);
$file->free();
$this->output->writeln('');
$this->output->writeln('<green>User / Password updated.</green>');
$this->output->writeln('');
}
private function console_header($readable, $cmd = '', $remote_action = false)
{
$this->output->writeln(
"<yellow>$readable</yellow>" . ($cmd ? "(<info>$cmd</info>)" : ''). ($remote_action ? '...' : '')
);
}
private function console_log($lines, $password)
{
foreach ($lines as $line) {
$this->output->writeln(' ' . Helper::preventReadablePassword($line, $password));
}
}
}

View file

@ -0,0 +1,149 @@
<?php
namespace Grav\Plugin\Console;
use Grav\Console\ConsoleCommand;
use Grav\Plugin\GitSync\GitSync;
use Grav\Plugin\GitSync\Helper;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputOption;
/**
* Class LogCommand
*
* @package Grav\Plugin\Console
*/
class StatusCommand extends ConsoleCommand
{
protected function configure()
{
$this
->setName('status')
->setDescription('Checks the status of plugin config, git and git workspace. No files get modified!')
->addOption(
'fetch', 'f',
InputOption::VALUE_NONE,
'additionally do a git fetch to look updates (changes not files in workspace)'
)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command checks if the plugin is usable the way it has been configured.
While doing this it prints the available information for your inspection.
<comment>No files in the workspace are modified when running this test.</comment>
The <info>--fetch</info> option can be used to see differences between the remote in the <info>git status</info> (last check)
It also returns with an error code and a helpful message when something is not normal:
<error>100</error> : <info>git</info> binary not working as expected
<error>50</error> : <info>repositoryFolder</info> and git workspace root do not match
<error>10</error> : <info>repository</info> is not configured
<error>5</error> : state of workspace not clean
<error>1</error> : Some checks can throw a <info>RuntimeException</info> which is not caught, read the message for details
EOF
)
;
}
protected function serve()
{
require_once __DIR__ . '/../vendor/autoload.php';
$plugin = new GitSync();
$this->output->writeln('');
$this->console_header('plugin runtime information:');
$info = $plugin->getRuntimeInformation();
$info['isGitInitialized'] = Helper::isGitInitialized();
$info['gitVersion'] = Helper::isGitInstalled(true);
ksort($info);
dump($info);
if (!Helper::isGitInstalled()) {
throw new RuntimeException('git binary not found', 100);
}
$this->console_header('detect git workspace root:');
$git_root = $plugin->execute('rev-parse --show-toplevel');
$this->console_log($git_root, '');
if (rtrim($info['repositoryPath'], '/') !== rtrim($git_root[0], '/')) {
throw new RuntimeException('git root and repositoryPath do not match', 50);
}
// needed to prevent output in logs:
$password = Helper::decrypt($plugin->getPassword() ?? '');
$this->console_header('local git config:');
$this->console_log(
$plugin->execute('config --local -l'), $password
);
$this->console_header(
'Testing connection to repository', 'git ls-remote', true
);
$repository = $plugin->getConfig('repository', false);
if (!$repository) {
throw new RuntimeException('No repository has been configured', 10);
}
$testRepository = $plugin->testRepository(
Helper::prepareRepository(
$plugin->getUser() ?? '',
$password,
$repository),
$plugin->getRemote('branch', null),
);
$this->console_log($testRepository, $password);
$fetched = false;
if ($this->input->getOption('fetch')) {
$remote = $plugin->getRemote('name', '');
$this->console_header(
'Looking for updates', "git fetch $remote", true
);
$this->console_log($plugin->fetch($remote), $password);
$fetched = true;
}
$this->console_header(
'Checking workspace status', 'git status', true
);
$git_status = $plugin->execute('status');
$this->console_log($git_status, $password);
if (!$plugin->isWorkingCopyClean()) {
throw new RuntimeException('Working state is not clean.', 5);
}
if ($fetched) {
$uptodate = strpos($git_status[1], 'branch is up-to-date with') > 0;
if ($uptodate) {
$this->console_header(
'Congrats: You should be able to run the <info>sync</info> command without problems!'
);
} else {
$this->output->writeln('<yellow>You are not in sync!</yellow>');
$this->output->writeln('Take a look at the output of git status to see more details.');
$this->output->writeln('In most cases the <info>sync</info> command is able to fix this.');
}
} else {
$this->console_header('Looks good: use <info>--fetch</info> option to check for updates.');
}
}
private function console_header($readable, $cmd = '', $remote_action = false)
{
$this->output->writeln(
"<yellow>$readable</yellow>" . ($cmd ? "(<info>$cmd</info>)" : ''). ($remote_action ? '...' : '')
);
}
private function console_log($lines, $password)
{
foreach ($lines as $line) {
$this->output->writeln(' ' . Helper::preventReadablePassword($line, $password));
}
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace Grav\Plugin\Console;
use Grav\Console\ConsoleCommand;
use Grav\Plugin\GitSync\GitSync;
/**
* Class LogCommand
*
* @package Grav\Plugin\Console
*/
class SyncCommand extends ConsoleCommand
{
protected function configure()
{
$this
->setName('sync')
->setDescription('Performs a synchronization of your site')
->setHelp('The <info>sync</info> command performs a synchronization of your site. Useful if you want to run a periodic crontab job to automate it.')
;
}
protected function serve()
{
require_once __DIR__ . '/../vendor/autoload.php';
$plugin = new GitSync();
$repository = $plugin->getConfig('repository', false);
$this->output->writeln('');
if (!$repository) {
$this->output->writeln('<red>ERROR:</red> No repository has been configured');
}
$this->output->writeln('Synchronizing with <cyan>' . $repository . '</cyan>');
if ($plugin->hasChangesToCommit()) {
$this->output->writeln('Changes detected, adding and committing...');
$plugin->add();
$plugin->commit();
}
$this->output->write('Starting Synchronization...');
$plugin->sync();
$this->output->writeln('completed.');
}
}

View file

@ -0,0 +1,28 @@
{
"require": {
"php": ">=7.1.3",
"ext-json": "*",
"ext-openssl": "*",
"sebastian/git": "^2.1",
"defuse/php-encryption": "^2.0"
},
"replace": {
"paragonie/random_compat": "9.99.99"
},
"autoload": {
"psr-4": {
"Grav\\Plugin\\GitSync\\": "classes/",
"Grav\\Plugin\\Console\\": "cli/"
},
"classmap": [
"git-sync.php"
]
},
"license": "Apache-2.0",
"authors": [
{
"name": "Trilby Media, LLC",
"email": "devs@trilbymedia.com"
}
]
}

139
plugins/git-sync/composer.lock generated Normal file
View file

@ -0,0 +1,139 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "5e875c300e0af3cf13a009bb9c591afd",
"packages": [
{
"name": "defuse/php-encryption",
"version": "v2.3.1",
"source": {
"type": "git",
"url": "https://github.com/defuse/php-encryption.git",
"reference": "77880488b9954b7884c25555c2a0ea9e7053f9d2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/defuse/php-encryption/zipball/77880488b9954b7884c25555c2a0ea9e7053f9d2",
"reference": "77880488b9954b7884c25555c2a0ea9e7053f9d2",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"paragonie/random_compat": ">= 2",
"php": ">=5.6.0"
},
"require-dev": {
"phpunit/phpunit": "^4|^5|^6|^7|^8|^9"
},
"bin": [
"bin/generate-defuse-key"
],
"type": "library",
"autoload": {
"psr-4": {
"Defuse\\Crypto\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Hornby",
"email": "taylor@defuse.ca",
"homepage": "https://defuse.ca/"
},
{
"name": "Scott Arciszewski",
"email": "info@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "Secure PHP Encryption Library",
"keywords": [
"aes",
"authenticated encryption",
"cipher",
"crypto",
"cryptography",
"encrypt",
"encryption",
"openssl",
"security",
"symmetric key cryptography"
],
"support": {
"issues": "https://github.com/defuse/php-encryption/issues",
"source": "https://github.com/defuse/php-encryption/tree/v2.3.1"
},
"time": "2021-04-09T23:57:26+00:00"
},
{
"name": "sebastian/git",
"version": "2.1.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/git.git",
"reference": "815bbbc963cf35e5413df195aa29df58243ecd24"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/git/zipball/815bbbc963cf35e5413df195aa29df58243ecd24",
"reference": "815bbbc963cf35e5413df195aa29df58243ecd24",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Simple wrapper for Git",
"homepage": "http://www.github.com/sebastianbergmann/git",
"keywords": [
"git"
],
"support": {
"issues": "https://github.com/sebastianbergmann/git/issues",
"source": "https://github.com/sebastianbergmann/git/tree/master"
},
"abandoned": true,
"time": "2017-01-23T20:57:12+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.1.3",
"ext-json": "*",
"ext-openssl": "*"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
}

View file

@ -0,0 +1,22 @@
@font-face {
font-family: 'gitsync';
src:
url('../fonts/gitsync.ttf?ivrc6k') format('truetype'),
url('../fonts/gitsync.woff?ivrc6k') format('woff'),
url('../fonts/gitsync.svg?ivrc6k#gitsync') format('svg');
font-weight: normal;
font-style: normal;
}
.fa.fa-git,
.fa.fa-git-square {
font-family: 'gitsync' !important;
vertical-align: middle;
}
.fa-git-square:before {
content: "\e900" !important;
}
.fa-git:before {
content: "\e901" !important;
}

View file

@ -0,0 +1,137 @@
[data-remodal-id="wizard"] .button[disabled], [data-remodal-id="wizard"] .button[disabled]:hover, [data-remodal-id="reset-local"] .button[disabled], [data-remodal-id="reset-local"] .button[disabled]:hover {
background: #ccc;
cursor: default; }
[data-remodal-id="wizard"] .button[disabled]:active, [data-remodal-id="reset-local"] .button[disabled]:active {
margin: 0; }
[data-remodal-id="wizard"] form [class^="step-"] a:not(.button):not([target]), [data-remodal-id="reset-local"] form [class^="step-"] a:not(.button):not([target]) {
vertical-align: middle;
color: #5591c7; }
[data-remodal-id="wizard"] form [class^="step-"] a:not(.button):not([target]):hover, [data-remodal-id="reset-local"] form [class^="step-"] a:not(.button):not([target]):hover {
color: #366188; }
[data-remodal-id="wizard"] .step-1 a, [data-remodal-id="reset-local"] .step-1 a {
vertical-align: middle; }
[data-remodal-id="wizard"] .access-tokens h3, [data-remodal-id="reset-local"] .access-tokens h3 {
margin-top: 1rem; }
[data-remodal-id="wizard"] .access-tokens a, [data-remodal-id="reset-local"] .access-tokens a {
vertical-align: inherit !important; }
[data-remodal-id="wizard"] .access-tokens-details > p, [data-remodal-id="reset-local"] .access-tokens-details > p {
margin-top: 0; }
[data-remodal-id="wizard"] .access-tokens-details > div > ul, [data-remodal-id="reset-local"] .access-tokens-details > div > ul {
margin-bottom: 0; }
[data-remodal-id="wizard"] .center, [data-remodal-id="reset-local"] .center {
text-align: center; }
[data-remodal-id="wizard"] h1, [data-remodal-id="reset-local"] h1 {
margin-bottom: 0;
padding-top: 0.5rem;
border-top: 3px solid transparent; }
[data-remodal-id="wizard"] .wizard-padding, [data-remodal-id="reset-local"] .wizard-padding {
padding: 0 3rem; }
[data-remodal-id="wizard"] .wizard-padding p, [data-remodal-id="reset-local"] .wizard-padding p {
padding: 0; }
[data-remodal-id="wizard"] input[disabled], [data-remodal-id="reset-local"] input[disabled] {
background: #efefef;
border-color: #ddd; }
[data-remodal-id="wizard"] input.invalid, [data-remodal-id="reset-local"] input.invalid {
border-color: #f4516d;
color: #f4516d; }
[data-remodal-id="wizard"] label.disabled, [data-remodal-id="reset-local"] label.disabled {
color: #ccc; }
[data-remodal-id="wizard"] label img, [data-remodal-id="reset-local"] label img {
max-width: 100px;
display: inline-block;
vertical-align: middle;
margin-left: 0.5rem; }
[data-remodal-id="wizard"] label a, [data-remodal-id="reset-local"] label a {
margin-left: 0.5rem; }
[data-remodal-id="wizard"] .columns, [data-remodal-id="reset-local"] .columns {
display: flex;
align-items: center;
justify-content: center; }
[data-remodal-id="wizard"] .columns .column, [data-remodal-id="reset-local"] .columns .column {
flex: 1; }
[data-remodal-id="wizard"] .columns .column:first-child, [data-remodal-id="reset-local"] .columns .column:first-child {
width: 35%;
flex: none;
border-right: 1px solid #ddd;
margin-right: 2rem; }
[data-remodal-id="wizard"] .step-1 div.column:nth-child(1), [data-remodal-id="reset-local"] .step-1 div.column:nth-child(1) {
flex-grow: 1.5;
width: 15%; }
[data-remodal-id="wizard"] .step-1 div.column:nth-child(1) > label:nth-child(4), [data-remodal-id="reset-local"] .step-1 div.column:nth-child(1) > label:nth-child(4) {
width: 110%; }
[data-remodal-id="wizard"] .step-1 div.column:nth-child(2), [data-remodal-id="reset-local"] .step-1 div.column:nth-child(2) {
margin-left: 25px; }
[data-remodal-id="wizard"] .step-1 div.column:nth-child(2) > label:nth-child(1) > input:nth-child(1), [data-remodal-id="wizard"] .step-1 div.column:nth-child(2) > label:nth-child(2) > input:nth-child(1), [data-remodal-id="reset-local"] .step-1 div.column:nth-child(2) > label:nth-child(1) > input:nth-child(1), [data-remodal-id="reset-local"] .step-1 div.column:nth-child(2) > label:nth-child(2) > input:nth-child(1) {
width: 100%; }
[data-remodal-id="wizard"] .step-1 .no_user, [data-remodal-id="reset-local"] .step-1 .no_user {
float: right;
font-size: .8rem;
padding: 0; }
[data-remodal-id="wizard"] .step-1 .no_user input, [data-remodal-id="reset-local"] .step-1 .no_user input {
margin-right: 0; }
[data-remodal-id="wizard"] .step-2 .info, [data-remodal-id="reset-local"] .step-2 .info {
margin: 0.2rem 0;
padding: 0.5rem 1.5rem; }
[data-remodal-id="wizard"] .step-2 ol, [data-remodal-id="wizard"] .step-2 ul, [data-remodal-id="reset-local"] .step-2 ol, [data-remodal-id="reset-local"] .step-2 ul {
padding-left: 1rem; }
[data-remodal-id="wizard"] .step-4 .info, [data-remodal-id="reset-local"] .step-4 .info {
font-size: 100%;
margin: 0.2rem 0; }
[data-remodal-id="wizard"] .step-4 .alert, [data-remodal-id="wizard"] .step-4 .warning, [data-remodal-id="reset-local"] .step-4 .alert, [data-remodal-id="reset-local"] .step-4 .warning {
padding: 0.5rem 1.5rem; }
[data-remodal-id="wizard"] .step-4 .fa.fa-warning, [data-remodal-id="reset-local"] .step-4 .fa.fa-warning {
color: #ff7d3b;
font-size: 1.2rem; }
[data-remodal-id="wizard"] .step-4 .wizard-padding label > *, [data-remodal-id="reset-local"] .step-4 .wizard-padding label > * {
vertical-align: middle; }
[data-remodal-id="wizard"] .step-4 .info-desc, [data-remodal-id="reset-local"] .step-4 .info-desc {
color: #0091ff;
float: right;
margin-right: .5rem;
font-size: 1.2rem; }
[data-remodal-id="wizard"] .step-4 hr, [data-remodal-id="reset-local"] .step-4 hr {
margin: .5rem 0; }
#admin-main [data-grav-field="git-wizard"] {
margin: 0 1rem; }
#admin-main [data-grav-field="git-wizard"] .danger.button-bar {
margin: 0;
padding: 0;
background: transparent; }
#admin-main [data-grav-field="git-wizard"] .button {
top: 0 !important;
-webkit-transform: translateY(0) !important;
-moz-transform: translateY(0) !important;
-ms-transform: translateY(0) !important;
-o-transform: translateY(0) !important;
transform: translateY(0) !important; }
/*# sourceMappingURL=git-sync.css.map */

View file

@ -0,0 +1,12 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="gitsync" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="git-square" horiz-adv-x="878" d="M332.571 203.428c0-30.857-28-37.714-53.143-37.714-24.571 0-61.143 4-61.143 36 0 31.429 30.857 36.571 56 36.571 24 0 58.286-4 58.286-34.857zM312 469.714c0-28.571-11.429-48.571-42.286-48.571-31.429 0-44 18.286-44 48s11.429 51.429 44 51.429c29.143 0 42.286-24 42.286-50.857zM406.857 512.571v71.429c-24.571-9.143-50.857-16.571-77.143-16.571-18.857 10.857-40.571 16.571-62.857 16.571-65.143 0-116.571-48-116.571-114.286 0-35.429 23.429-84.571 58.857-96.571v-1.714c-18.286-8-21.714-30.286-21.714-48.571 0-18.857 6.857-34.286 23.429-44v-1.714c-38.857-12.571-64.571-37.143-64.571-79.429 0-72.571 69.143-93.143 129.714-93.143 73.143 0 128 26.857 128 107.429 0 57.143-52 74.286-99.429 82.857-16 2.857-43.429 14.286-43.429 34.286 0 18.857 10.286 26.857 28 29.714 58.286 11.429 95.429 56.571 95.429 116.571 0 10.286-2.286 20-5.714 29.714 9.143 2.286 18.857 4.571 28 7.429zM440.571 273.143h78.286c-1.143 15.429-1.143 31.429-1.143 46.857v221.143c0 13.143 0 26.286 1.143 39.429h-78.286c1.714-13.143 1.714-27.429 1.714-40.571v-224c0-14.286 0-28.571-1.714-42.857zM731.429 282.286v69.143c-11.429-8-25.143-12-38.857-12-25.714 0-30.286 25.714-30.286 46.857v128.571h29.714c10.286 0 20-1.143 30.286-1.143v66.857h-60c0 19.429-1.143 38.857 1.714 58.286h-80c1.714-10.286 2.286-20.571 2.286-31.429v-26.857h-34.286v-66.857c6.857 0.571 13.714 1.714 21.143 1.714 4 0 8.571-0.571 13.143-0.571v-1.143h-1.143v-124c0-61.714 9.143-121.143 84.571-121.143 21.143 0 42.857 3.429 61.714 13.714zM528 685.714c0 26.857-20 52-48 52s-48.571-24.571-48.571-52c0-26.857 21.143-50.857 48.571-50.857s48 24.571 48 50.857zM877.714 713.143v-548.571c0-90.857-73.714-164.571-164.571-164.571h-548.571c-90.857 0-164.571 73.714-164.571 164.571v548.571c0 90.857 73.714 164.571 164.571 164.571h548.571c90.857 0 164.571-73.714 164.571-164.571z" />
<glyph unicode="&#xe901;" glyph-name="git" d="M340 85.714c0 50.286-55.429 57.143-94.286 57.143-40.571 0-90.286-8.571-90.286-59.429 0-51.429 58.857-57.714 98.286-57.714 41.714 0 86.286 10.286 86.286 60zM306.286 517.143c0 42.857-20.571 81.714-68 81.714-52.571 0-70.857-34.857-70.857-82.857 0-47.429 20.571-77.143 70.857-77.143 49.714 0 68 32 68 78.286zM460 702.286v-115.429c-14.857-5.143-29.714-9.143-45.143-12.571 5.714-15.429 9.143-31.429 9.143-48 0-96.571-59.429-170.286-154.286-188-28.571-5.714-45.143-17.714-45.143-48.571 0-87.429 230.857-28 230.857-189.143 0-130.857-88.571-173.714-207.429-173.714-97.714 0-209.143 32.571-209.143 150.286 0 68.571 41.714 108 104 128.571v2.286c-26.286 16-38.286 41.143-38.286 72 0 29.143 6.286 65.143 36 78.286v2.286c-57.714 19.429-95.429 98.857-95.429 156.571 0 106.857 82.857 185.143 188.571 185.143 35.429 0 70.857-9.143 101.714-26.857 42.857 0 85.143 11.429 124.571 26.857zM641.714 198.857h-126.857c2.286 25.714 2.286 50.857 2.286 76.571v348c0 24.571 0.571 49.143-2.286 73.143h126.857c-2.857-23.429-2.286-47.429-2.286-70.857v-350.286c0-25.714 0-50.857 2.286-76.571zM985.143 325.714v-112c-30.286-16.571-65.143-22.286-99.429-22.286-122.286 0-136.571 96.571-136.571 196v200.571h1.143v2.286c-7.429 0-14.286 1.143-21.143 1.143-11.429 0-22.857-1.714-33.714-3.429v108.571h54.857v43.429c0 17.143-0.571 34.286-3.429 50.857h129.714c-4.571-31.429-3.429-62.857-3.429-94.286h97.714v-108.571c-16.571 0-33.143 2.286-49.143 2.286h-48.571v-208.571c0-33.714 7.429-74.857 49.714-74.857 22.286 0 44 6.286 62.286 18.857zM656 866.857c0-42.857-33.143-82.857-77.143-82.857-45.143 0-78.857 39.429-78.857 82.857 0 44 33.143 84 78.857 84 45.143 0 77.143-41.143 77.143-84z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,490 @@
<?php
namespace Grav\Plugin;
use Composer\Autoload\ClassLoader;
use Grav\Common\Config\Config;
use Grav\Common\Data\Data;
use Grav\Common\Grav;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Plugin;
use Grav\Common\Scheduler\Scheduler;
use Grav\Plugin\GitSync\AdminController;
use Grav\Plugin\GitSync\GitSync;
use Grav\Plugin\GitSync\Helper;
use RocketTheme\Toolbox\Event\Event;
/**
* Class GitSyncPlugin
*
* @package Grav\Plugin
*/
class GitSyncPlugin extends Plugin
{
/** @var AdminController|null */
protected $controller;
/** @var GitSync */
protected $git;
/**
* @return array
*/
public static function getSubscribedEvents()
{
return [
'onPluginsInitialized' => [
['autoload', 100000],
['onPluginsInitialized', 1000]
],
'onPageInitialized' => ['onPageInitialized', 0],
'onFormProcessed' => ['onFormProcessed', 0],
'onSchedulerInitialized' => ['onSchedulerInitialized', 0]
];
}
/**
* [onPluginsInitialized:100000] Composer autoload.
*
* @return ClassLoader
*/
public function autoload() : ClassLoader
{
return require __DIR__ . '/vendor/autoload.php';
}
/**
* @return string
*/
public static function generateWebhookSecret()
{
return static::generateHash(24);
}
/**
* @return string
*/
public static function generateRandomWebhook()
{
return '/_git-sync-' . static::generateHash(6);
}
/**
* Initialize the plugin
*/
public function onPluginsInitialized()
{
$this->enable(['gitsync' => ['synchronize', 0]]);
$this->init();
if ($this->isAdmin()) {
$this->enable([
'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
'onTwigSiteVariables' => ['onTwigSiteVariables', 0],
'onAdminMenu' => ['onAdminMenu', 0],
'onAdminSave' => ['onAdminSave', 0],
'onAdminAfterSave' => ['onAdminAfterSave', 0],
'onAdminAfterSaveAs' => ['onAdminAfterSaveAs', 0],
'onAdminAfterDelete' => ['onAdminAfterDelete', 0],
'onAdminAfterAddMedia' => ['onAdminAfterMedia', 0],
'onAdminAfterDelMedia' => ['onAdminAfterMedia', 0],
]);
return;
}
$config = $this->config->get('plugins.' . $this->name);
$route = $this->grav['uri']->route();
$webhook = $config['webhook'] ?? false;
$secret = $config['webhook_secret'] ?? false;
$enabled = $config['webhook_enabled'] ?? false;
if ($route === $webhook && $_SERVER['REQUEST_METHOD'] === 'POST') {
if ($secret && $enabled) {
if (!$this->isRequestAuthorized($secret)) {
http_response_code(401);
header('Content-Type: application/json');
echo json_encode([
'status' => 'error',
'message' => 'Unauthorized request'
]);
exit;
}
}
try {
$this->synchronize();
header('Content-Type: application/json');
echo json_encode([
'status' => 'success',
'message' => 'GitSync completed the synchronization'
]);
} catch (\Exception $e) {
http_response_code(500);
header('Content-Type: application/json');
echo json_encode([
'status' => 'error',
'message' => 'GitSync failed to synchronize'
]);
}
exit;
}
}
/**
* Returns true if the request contains a valid signature or token
* @param string $secret local secret
* @return bool whether or not the request is authorized
*/
public function isRequestAuthorized($secret)
{
if (isset($_SERVER['HTTP_X_HUB_SIGNATURE'])) {
$payload = file_get_contents('php://input') ?: '';
return $this->isGithubSignatureValid($secret, $_SERVER['HTTP_X_HUB_SIGNATURE'], $payload);
}
if (isset($_SERVER['HTTP_X_GITLAB_TOKEN'])) {
return $this->isGitlabTokenValid($secret, $_SERVER['HTTP_X_GITLAB_TOKEN']);
} else {
$payload = file_get_contents('php://input');
return $this->isGiteaSecretValid($secret, $payload);
}
return false;
}
/**
* Hashes the webhook request body with the client secret and
* checks if it matches the webhook signature header
* @param string $secret The webhook secret
* @param string $signatureHeader The signature of the webhook request
* @param string $payload The webhook request body
* @return bool Whether the signature is valid or not
*/
public function isGithubSignatureValid($secret, $signatureHeader, $payload)
{
[$algorithm, $signature] = explode('=', $signatureHeader);
return $signature === hash_hmac($algorithm, $payload, $secret);
}
/**
* Returns true if given Gitlab token matches secret
* @param string $secret local secret
* @param string $token token received from Gitlab webhook request
* @return bool whether or not secret and token match
*/
public function isGitlabTokenValid($secret, $token)
{
return $secret === $token;
}
/**
* Returns true if secret contained in the payload matches the client
* secret
* @param string $secret The webhook secret
* @param string $payload The webhook request body
* @return boolean Whether the client secret matches the payload secret or
* not
*/
public function isGiteaSecretValid($secret, $payload)
{
$payload = json_decode($payload, true);
if (!empty($payload) && isset($payload['secret'])) {
return $secret === $payload['secret'];
}
return false;
}
public function onAdminMenu()
{
$base = rtrim($this->grav['base_url'], '/') . '/' . trim($this->grav['admin']->base, '/');
$options = [
'hint' => Helper::isGitInitialized() ? 'Synchronize GitSync' : 'Configure GitSync',
'class' => 'gitsync-sync',
'location' => 'pages',
'route' => Helper::isGitInitialized() ? 'admin' : 'admin/plugins/git-sync',
'icon' => 'fa-' . $this->grav['plugins']->get('git-sync')->blueprints()->get('icon')
];
if (Helper::isGitInstalled()) {
if (Helper::isGitInitialized()) {
$options['data'] = [
'gitsync-useraction' => 'sync',
'gitsync-uri' => $base . '/plugins/git-sync'
];
}
$this->grav['twig']->plugins_quick_tray['GitSync'] = $options;
}
}
public function init()
{
if ($this->isAdmin()) {
/** @var AdminController controller */
$this->controller = new AdminController($this);
$this->git = &$this->controller->git;
} else {
$this->git = new GitSync();
}
}
/**
* @return bool
*/
public function synchronize()
{
if (!Helper::isGitInstalled() || !Helper::isGitInitialized()) {
return true;
}
$this->grav->fireEvent('onGitSyncBeforeSynchronize');
if ($this->git->hasChangesToCommit()) {
$this->git->commit();
}
// synchronize with remote
$this->git->sync();
$this->grav->fireEvent('onGitSyncAfterSynchronize');
return true;
}
public function onSchedulerInitialized(Event $event)
{
/** @var Config $config */
$config = Grav::instance()['config'];
$run_at = $config->get('plugins.git-sync.sync.cron_at', '0 12,23 * * *');
if ($config->get('plugins.git-sync.sync.cron_enable', false)) {
/** @var Scheduler $scheduler */
$scheduler = $event['scheduler'];
$job = $scheduler->addFunction('Grav\Plugin\GitSync\Helper::synchronize', [], 'GitSync');
$job->at($run_at);
}
}
/**
* @return bool
*/
public function reset()
{
if (!Helper::isGitInstalled() || !Helper::isGitInitialized()) {
return true;
}
$this->grav->fireEvent('onGitSyncBeforeReset');
$this->git->reset();
$this->grav->fireEvent('onGitSyncAfterReset');
return true;
}
/**
* Add current directory to twig lookup paths.
*/
public function onTwigTemplatePaths()
{
$this->grav['twig']->twig_paths[] = __DIR__ . '/templates';
}
/**
* Set needed variables to display cart.
*
* @return bool
*/
public function onTwigSiteVariables()
{
// workaround for admin plugin issue that doesn't properly unsubscribe events upon plugin uninstall
if (!class_exists(Helper::class)) {
return false;
}
$user = $this->grav['user'];
if (!$user->authenticated) {
return false;
}
$settings = [
'first_time' => !Helper::isGitInitialized(),
'git_installed' => Helper::isGitInstalled()
];
$this->grav['twig']->twig_vars['git_sync'] = $settings;
$adminPath = trim($this->grav['admin']->base, '/');
if ($this->grav['uri']->path() === "/$adminPath/plugins/git-sync") {
$this->grav['assets']->addCss('plugin://git-sync/css-compiled/git-sync.css');
} else {
$this->grav['assets']->addInlineJs('var GitSync = ' . json_encode($settings) . ';');
}
$this->grav['assets']->addJs('plugin://git-sync/js/vendor.js', ['loading' => 'defer', 'priority' => 0]);
$this->grav['assets']->addJs('plugin://git-sync/js/app.js', ['loading' => 'defer', 'priority' => 0]);
$this->grav['assets']->addCss('plugin://git-sync/css-compiled/git-sync-icon.css');
return true;
}
public function onPageInitialized()
{
if ($this->controller && $this->controller->isActive()) {
$this->controller->execute();
$this->controller->redirect();
}
}
/**
* @param Event $event
* @return Data|true
*/
public function onAdminSave(Event $event)
{
$obj = $event['object'];
$adminPath = trim($this->grav['admin']->base, '/');
$isPluginRoute = $this->grav['uri']->path() === "/$adminPath/plugins/" . $this->name;
if ($obj instanceof Data) {
if (!$isPluginRoute || !Helper::isGitInstalled()) {
return true;
}
// empty password, keep current one or encrypt if haven't already
$password = $obj->get('password', false);
if (!$password) { // set to !()
$current_password = $this->git->getPassword();
// password exists but was never encrypted
if ($current_password && strpos($current_password, 'gitsync-') !== 0) {
$current_password = Helper::encrypt($current_password);
}
} else {
// password is getting changed
$current_password = Helper::encrypt($password);
}
$obj->set('password', $current_password);
}
return $obj;
}
/**
* @param Event $event
*/
public function onAdminAfterSave(Event $event)
{
$obj = $event['object'];
$adminPath = trim($this->grav['admin']->base, '/');
$uriPath = $this->grav['uri']->path();
$isPluginRoute = $uriPath === "/$adminPath/plugins/" . $this->name;
if ($obj instanceof PageInterface && !$this->grav['config']->get('plugins.git-sync.sync.on_save', true)) {
return;
}
if ($obj instanceof Data) {
$folders = $this->git->getConfig('folders', $event['object']->get('folders', []));
$data_type = preg_replace('#^/' . preg_quote($adminPath, '#') . '/#', '', $uriPath);
$data_type = explode('/', $data_type);
$data_type = array_shift($data_type);
if (null === $data_type || !Helper::isGitInstalled() || (!$isPluginRoute && !in_array($this->getFolderMapping($data_type), $folders, true))) {
return;
}
if ($isPluginRoute) {
$this->git->setConfig($obj->toArray());
// initialize git if not done yet
$this->git->initializeRepository();
// set committer and remote data
$this->git->setUser();
$this->git->addRemote();
}
}
$this->synchronize();
}
public function onAdminAfterSaveAs()
{
if ($this->grav['config']->get('plugins.git-sync.sync.on_save', true))
{
$this->synchronize();
}
}
public function onAdminAfterDelete()
{
if ($this->grav['config']->get('plugins.git-sync.sync.on_delete', true))
{
$this->synchronize();
}
}
public function onAdminAfterMedia()
{
if ($this->grav['config']->get('plugins.git-sync.sync.on_media', true))
{
$this->synchronize();
}
}
/**
* @param Event $event
*/
public function onFormProcessed(Event $event)
{
$action = $event['action'];
if ($action === 'gitsync') {
$this->synchronize();
}
}
/**
* @param string $data_type
* @return string|null
*/
public function getFolderMapping($data_type)
{
switch ($data_type) {
case 'user':
return 'accounts';
case 'themes':
return 'config';
case 'config':
case 'data':
case 'plugins':
case 'pages':
return $data_type;
}
return null;
}
/**
* @param int $len
* @return string
*/
protected static function generateHash(int $len): string
{
$bytes = openssl_random_pseudo_bytes($len, $isStrong);
if ($bytes === false) {
throw new \RuntimeException('Could not generate hash');
}
if ($isStrong === false) {
// It's ok not to be strong [EA].
$isStrong = true;
}
return bin2hex($bytes);
}
}

View file

@ -0,0 +1 @@
enabled: true

View file

@ -0,0 +1,2 @@
#!/bin/sh
gosass -input scss/ -output css/ -sourcemap -watch -style compressed

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="bitbucket" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="863.566px" height="267.72px" viewBox="0 0 863.566 267.72" style="enable-background:new 0 0 863.566 267.72;"
xml:space="preserve">
<style type="text/css">
.st0{fill:#205081;}
</style>
<g>
<g>
<g>
<g>
<path class="st0" d="M432.851,136.489c-9.223,0-18.545,2.275-24.859,4.641v-34.558c0-1.738-1.414-3.146-3.15-3.146h-18.628
c-1.736,0-3.13,1.408-3.13,3.146v119.01c0,1.51,1.086,2.826,2.577,3.092c11.492,2.096,25.048,2.83,33.187,2.83
c34.984,0,42.139-14.87,42.139-37.211v-26.049C460.984,147.162,451.519,136.489,432.851,136.489z M419.926,209.871
c-4.717,0-8.624-0.182-11.935-0.571v-46.559c5.13-2.299,12.346-4.616,18.132-4.616c7.058,0,9.938,2.766,9.938,9.568v27.313
C436.061,204.862,433.568,209.871,419.926,209.871z"/>
<path class="st0" d="M546.674,138.31H528.07c-1.736,0-3.146,1.413-3.146,3.127v58.95c-6.71,3.401-16.155,6.929-21.777,6.929
c-4.193,0-5.742-1.549-5.742-5.746v-60.133c0-1.714-1.432-3.127-3.19-3.127h-18.583c-1.762,0-3.154,1.413-3.154,3.127v62.509
c0,18.283,7.195,27.559,21.369,27.559c9.872,0,23.308-3.392,34.652-8.726l1.372,4.645c0.409,1.344,1.617,2.265,3.028,2.265
l13.775-0.004c1.741,0,3.153-1.412,3.153-3.147v-85.1C549.827,139.723,548.415,138.31,546.674,138.31z"/>
<path class="st0" d="M621.62,210.423c-0.121-0.877-0.633-1.647-1.33-2.147c-0.732-0.489-1.636-0.653-2.493-0.46
c-5.337,1.216-10.939,1.874-16.195,1.874c-11.798,0-15.213-3.005-15.213-13.394v-24.6c0-10.397,3.415-13.402,15.213-13.402
c2.806,0,9.794,0.403,16.195,1.869c0.857,0.196,1.761,0.034,2.493-0.471c0.719-0.484,1.227-1.261,1.33-2.142l2.212-14.727
c0.22-1.552-0.694-3.03-2.212-3.474c-7.175-2.112-15.583-2.861-20.549-2.861c-28.132,0-39.604,10.246-39.604,35.392v24.231
c0,25.137,11.472,35.392,39.604,35.392c6.826,0,14.7-1.096,20.549-2.855c1.518-0.449,2.432-1.938,2.212-3.483L621.62,210.423z"
/>
<path class="st0" d="M749.865,136.489c-24.967,0-38.149,12.434-38.149,35.941v23.499c0,24.265,12.364,35.574,38.889,35.574
c10.792,0,22.304-1.892,31.566-5.171c1.472-0.53,2.353-2.075,2.045-3.621l-2.761-13.316c-0.184-0.855-0.716-1.61-1.452-2.05
c-0.755-0.459-1.655-0.58-2.491-0.337c-8.567,2.414-16.687,3.597-24.907,3.597c-13.556,0-15.97-4.393-15.97-13.587v-3.217
h46.213c1.755,0,3.148-1.393,3.148-3.156v-17.13C785.995,148.248,774.524,136.489,749.865,136.489z M736.635,173.262v-3.019
c0-8.77,4.279-12.857,13.419-12.857c10.1,0,12.117,4.928,12.117,12.857v3.019H736.635z"/>
<path class="st0" d="M302.116,138.269H283.51c-1.739,0-3.154,1.418-3.154,3.148v85.085c0,1.745,1.415,3.152,3.154,3.152h18.605
c1.779,0,3.168-1.407,3.168-3.152v-85.085C305.284,139.687,303.895,138.269,302.116,138.269z"/>
<path class="st0" d="M302.116,103.416H283.51c-1.739,0-3.154,1.428-3.154,3.157v16.029c0,1.763,1.415,3.159,3.154,3.159h18.605
c1.779,0,3.168-1.396,3.168-3.159v-16.029C305.284,104.844,303.895,103.416,302.116,103.416z"/>
<g>
<path class="st0" d="M255.251,164.776c7.711-4.242,11.059-10.188,11.059-19.331v-12.513c0-18.323-10.995-27.217-33.572-27.217
h-45.939c-1.721,0-3.133,1.408-3.133,3.145v117.642c0,1.745,1.412,3.152,3.133,3.152h49.213
c21.917,0,34.493-10.643,34.493-29.211v-12.922C270.504,176.444,264.881,168.172,255.251,164.776z M209.308,127.347h20.691
c9.366,0,10.676,4.087,10.676,8.463v10.721c0,6.972-3.255,9.95-10.86,9.95h-4.474c-1.762,0-3.174,1.417-3.174,3.157v14.781
c0,1.751,1.412,3.161,3.174,3.161h8.093c8.119,0,11.413,3.14,11.413,10.849v9.097c0,7.843-3.374,10.488-13.415,10.488h-22.122
V127.347z"/>
</g>
<path class="st0" d="M372.022,211.568c-0.123-0.886-0.612-1.666-1.347-2.166c-0.741-0.489-1.659-0.652-2.518-0.433
c-3.291,0.82-6.441,1.268-8.872,1.268c-4.602,0-6.646-2.003-6.646-6.476v-45.085h18.074c1.74,0,3.148-1.408,3.148-3.151v-14.069
c0-1.734-1.408-3.148-3.148-3.148h-18.074v-21.676c0-0.917-0.41-1.779-1.083-2.372c-0.694-0.606-1.617-0.88-2.52-0.749
l-18.605,2.59c-1.554,0.225-2.713,1.556-2.713,3.129v19.078h-10.145c-1.737,0-3.152,1.413-3.152,3.148v14.069
c0,1.743,1.415,3.151,3.152,3.151h10.145v48.007c0,16.472,8.646,24.835,25.738,24.835c4.868,0,13.128-1.127,18.526-2.999
c1.451-0.499,2.311-1.938,2.085-3.454L372.022,211.568z"/>
<path class="st0" d="M847.374,211.564c-0.122-0.886-0.632-1.666-1.373-2.168c-0.733-0.487-1.657-0.652-2.493-0.427
c-3.314,0.816-6.441,1.268-8.893,1.268c-4.582,0-6.665-2.009-6.665-6.481v-45.085h18.096c1.757,0,3.144-1.417,3.144-3.146
v-14.069c0-1.734-1.387-3.149-3.144-3.149H827.95v-21.68c0-0.919-0.368-1.78-1.066-2.371c-0.695-0.603-1.613-0.877-2.514-0.745
l-18.607,2.59c-1.553,0.22-2.722,1.551-2.722,3.113v19.093h-10.138c-1.722,0-3.147,1.414-3.147,3.149v14.069
c0,1.729,1.426,3.146,3.147,3.146h10.138v48.013c0,16.466,8.671,24.829,25.746,24.829c4.887,0,13.125-1.126,18.524-2.997
c1.433-0.501,2.326-1.934,2.104-3.454L847.374,211.564z"/>
</g>
</g>
</g>
</g>
<path class="st0" d="M708.825,224.391l-24.88-41.074l24.001-40.449c0.578-0.975,0.589-2.184,0.028-3.166
c-0.561-0.984-1.605-1.592-2.737-1.592h-20.553c-1.126,0-2.166,0.602-2.729,1.576l-22.832,39.584v-72.678
c0-1.741-1.411-3.15-3.15-3.15h-18.606c-1.739,0-3.15,1.409-3.15,3.15v119.911c0,1.74,1.411,3.15,3.15,3.15h18.606
c1.739,0,3.15-1.41,3.15-3.15v-39.244l24.295,40.654c0.568,0.951,1.596,1.535,2.704,1.535h20.173c0.006,0,0.014,0,0.02,0
c1.74,0,3.15-1.412,3.15-3.15C709.466,225.58,709.228,224.92,708.825,224.391z"/>
<path class="st0" d="M89.219,94.963v0.002V94.963c-33.675,0-61.172,9.053-61.172,20.294c0,2.961,7.343,45.415,10.256,62.251
c1.306,7.551,20.818,18.621,50.9,18.621l0.031-0.09v0.09c30.081,0,49.593-11.07,50.899-18.621
c2.913-16.836,10.256-59.29,10.256-62.251C150.39,104.017,122.894,94.963,89.219,94.963z M89.219,182.49
c-10.739,0-19.445-8.707-19.445-19.445c0-10.74,8.706-19.445,19.445-19.445c10.739,0,19.445,8.705,19.445,19.445
C108.664,173.783,99.958,182.49,89.219,182.49z M89.208,121.53c-21.636-0.035-39.169-3.794-39.162-8.398
c0.007-4.604,17.554-8.307,39.19-8.272c21.636,0.034,39.169,3.793,39.162,8.398C128.39,117.863,110.844,121.564,89.208,121.53z"/>
<path class="st0" d="M133.185,194.382c-0.93,0-1.675,0.658-1.675,0.658s-15.064,11.929-42.29,11.93
c-27.227-0.001-42.29-11.93-42.29-11.93s-0.746-0.658-1.675-0.658c-1.111,0-2.164,0.746-2.164,2.393
c0,0.174,0.017,0.348,0.049,0.518c2.338,12.514,4.046,21.393,4.346,22.744c2.041,9.205,20.048,16.151,41.733,16.152l0,0h0.001h0.001
l0,0c21.686-0.001,39.692-6.947,41.733-16.152c0.3-1.352,2.008-10.23,4.346-22.744c0.032-0.17,0.049-0.344,0.049-0.518
C135.349,195.128,134.295,194.382,133.185,194.382z"/>
<circle class="st0" cx="89.2" cy="163.039" r="9.745"/>
</svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 2002 542" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g transform="matrix(1,0,0,1,-199.492,-929.431)">
<g id="Layer-1" transform="matrix(4.16667,0,0,4.16667,0,0)">
<path d="M140.375,278.65L100.019,278.65C98.978,278.65 98.133,279.495 98.133,280.537L98.133,300.267C98.133,301.308 98.978,302.156 100.019,302.156L115.762,302.156L115.762,326.67C115.762,326.67 112.227,327.875 102.454,327.875C90.924,327.875 74.817,323.662 74.817,288.243C74.817,252.818 91.589,248.157 107.335,248.157C120.965,248.157 126.837,250.556 130.573,251.713C131.747,252.072 132.833,250.903 132.833,249.862L137.335,230.798C137.335,230.31 137.17,229.723 136.614,229.325C135.097,228.242 125.84,223.063 102.454,223.063C75.513,223.063 47.878,234.525 47.878,289.625C47.878,344.726 79.518,352.937 106.18,352.937C128.256,352.937 141.648,343.504 141.648,343.504C142.2,343.199 142.26,342.427 142.26,342.074L142.26,280.537C142.26,279.495 141.416,278.65 140.375,278.65" style="fill:rgb(14,13,13);fill-rule:nonzero;"/>
<path d="M348.353,229.665C348.353,228.615 347.521,227.768 346.48,227.768L323.757,227.768C322.719,227.768 321.875,228.615 321.875,229.665C321.875,229.67 321.881,273.578 321.881,273.578L286.462,273.578L286.462,229.665C286.462,228.615 285.626,227.768 284.587,227.768L261.866,227.768C260.832,227.768 259.989,228.615 259.989,229.665L259.989,348.568C259.989,349.617 260.832,350.471 261.866,350.471L284.587,350.471C285.626,350.471 286.462,349.617 286.462,348.568L286.462,297.709L321.881,297.709C321.881,297.709 321.82,348.564 321.82,348.568C321.82,349.617 322.663,350.471 323.702,350.471L346.478,350.471C347.519,350.471 348.351,349.617 348.353,348.568L348.353,229.665Z" style="fill:rgb(14,13,13);fill-rule:nonzero;"/>
<path d="M183.254,245.268C183.254,237.086 176.694,230.475 168.601,230.475C160.516,230.475 153.951,237.086 153.951,245.268C153.951,253.442 160.516,260.072 168.601,260.072C176.694,260.072 183.254,253.442 183.254,245.268" style="fill:rgb(14,13,13);fill-rule:nonzero;"/>
<path d="M181.629,323.486L181.629,268.6C181.629,267.558 180.788,266.706 179.749,266.706L157.098,266.706C156.059,266.706 155.129,267.778 155.129,268.819L155.129,347.455C155.129,349.765 156.569,350.453 158.433,350.453L178.841,350.453C181.08,350.453 181.629,349.353 181.629,347.418L181.629,323.486Z" style="fill:rgb(14,13,13);fill-rule:nonzero;"/>
<path d="M434.71,266.885L412.161,266.885C411.127,266.885 410.285,267.738 410.285,268.786L410.285,327.088C410.285,327.088 404.557,331.28 396.426,331.28C388.296,331.28 386.138,327.59 386.138,319.629L386.138,268.786C386.138,267.738 385.298,266.885 384.263,266.885L361.378,266.885C360.345,266.885 359.499,267.738 359.499,268.786L359.499,323.479C359.499,347.125 372.678,352.91 390.808,352.91C405.681,352.91 417.673,344.694 417.673,344.694C417.673,344.694 418.244,349.024 418.502,349.537C418.761,350.049 419.434,350.567 420.161,350.567L434.72,350.502C435.753,350.502 436.599,349.649 436.599,348.604L436.592,268.786C436.592,267.738 435.749,266.885 434.71,266.885" style="fill:rgb(14,13,13);fill-rule:nonzero;"/>
<path d="M487.445,331.207C479.624,330.969 474.319,327.42 474.319,327.42L474.319,289.766C474.319,289.766 479.552,286.558 485.973,285.984C494.093,285.257 501.918,287.71 501.918,307.08C501.918,327.506 498.386,331.537 487.445,331.207M496.339,264.214C483.532,264.214 474.821,269.928 474.821,269.928L474.821,229.665C474.821,228.615 473.982,227.768 472.946,227.768L450.159,227.768C449.123,227.768 448.281,228.615 448.281,229.665L448.281,348.568C448.281,349.617 449.123,350.471 450.162,350.471L465.971,350.471C466.683,350.471 467.222,350.103 467.621,349.461C468.013,348.822 468.581,343.978 468.581,343.978C468.581,343.978 477.898,352.809 495.538,352.809C516.246,352.809 528.122,342.305 528.122,305.654C528.122,269.003 509.155,264.214 496.339,264.214" style="fill:rgb(14,13,13);fill-rule:nonzero;"/>
<path d="M246.935,266.695L229.891,266.695C229.891,266.695 229.866,244.182 229.866,244.177C229.866,243.324 229.426,242.899 228.44,242.899L205.212,242.899C204.309,242.899 203.825,243.296 203.825,244.164L203.825,267.433C203.825,267.433 192.184,270.243 191.397,270.47C190.615,270.698 190.038,271.421 190.038,272.283L190.038,286.905C190.038,287.957 190.877,288.805 191.915,288.805L203.825,288.805L203.825,323.984C203.825,350.113 222.154,352.679 234.521,352.679C240.171,352.679 246.932,350.865 248.047,350.453C248.723,350.205 249.116,349.506 249.116,348.748L249.134,332.662C249.134,331.613 248.248,330.763 247.249,330.763C246.256,330.763 243.716,331.168 241.099,331.168C232.728,331.168 229.891,327.275 229.891,322.236C229.891,317.201 229.89,288.805 229.89,288.805L246.935,288.805C247.974,288.805 248.817,287.957 248.817,286.905L248.817,268.59C248.817,267.54 247.974,266.695 246.935,266.695" style="fill:rgb(14,13,13);fill-rule:nonzero;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="1202px" height="455px" viewBox="0 0 1202 455" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
<title>wm_no_bg</title>
<desc>Created with Sketch.</desc>
<defs>
<path id="path-1" d="M0,1173.3333 L1999.99995,1173.3333 L1999.99995,0 L0,0 L0,1173.3333 L0,1173.3333 Z"></path>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="gitlab_logo" sketch:type="MSLayerGroup" transform="translate(-439.000000, -360.000000)">
<g id="g10" transform="translate(1000.000000, 587.333300) scale(1, -1) translate(-1000.000000, -587.333300) translate(0.000000, 0.333300)">
<g id="g12" transform="translate(1316.014500, 505.244121)" fill="#8C929D" sketch:type="MSShapeGroup">
<path d="M22.6666661,162.666663 L0.835999979,162.666663 L0.905333311,0.178666662 L89.2199978,0.178666662 L89.2199978,20.2719995 L22.7359994,20.2719995 L22.6666661,162.666663 L22.6666661,162.666663 Z" id="path14"></path>
</g>
<g id="g16">
<g id="g18-Clipped">
<mask id="mask-2" sketch:name="path22" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="path22"></g>
<g id="g18" mask="url(#mask-2)">
<g transform="translate(438.666658, 358.666658)">
<g id="g24" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(977.327440, 143.284396)">
<path d="M73.3333315,31.9999992 C67.8759983,26.294666 58.6973319,20.5879995 46.2933322,20.5879995 C29.6719993,20.5879995 22.9746661,28.7746659 22.9746661,39.4426657 C22.9746661,55.5666653 34.1373325,63.2573318 57.9533319,63.2573318 C62.4186651,63.2573318 69.6119983,62.7613318 73.3333315,62.0173318 L73.3333315,31.9999992 L73.3333315,31.9999992 Z M50.7586654,130.48533 C33.1293325,130.48533 16.9599996,124.235997 4.34266656,113.83333 L12.0586664,100.467997 C20.9893328,105.677331 31.9053325,110.887997 47.5333321,110.887997 C65.394665,110.887997 73.3333315,101.707997 73.3333315,86.3279978 L73.3333315,78.3893314 C69.8599983,79.1333314 62.6666651,79.6306647 58.2013319,79.6306647 C19.9973328,79.6306647 0.647999984,66.234665 0.647999984,38.2013324 C0.647999984,13.1466663 16.0279996,0.494666654 39.3466657,0.494666654 C55.0546653,0.494666654 70.1079982,7.68933314 75.3173315,19.3479995 L79.2866647,3.47199991 L94.6679976,3.47199991 L94.6679976,86.5759978 C94.6679976,112.871997 83.2559979,130.48533 50.7586654,130.48533 L50.7586654,130.48533 Z" id="path26" fill="#8C929D" sketch:type="MSShapeGroup"></path>
</g>
<g id="g28" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(1099.766904, 143.128930)">
<path d="M42.6666656,19.9999995 C34.4799991,19.9999995 27.2853327,20.9919995 21.8279995,23.4733327 L21.8279995,90.8253311 L21.8279995,98.6386642 C29.2706659,104.841331 38.4493324,109.306664 50.1093321,109.306664 C71.1946649,109.306664 79.3813313,94.4226643 79.3813313,70.3586649 C79.3813313,36.1253324 66.2333317,19.9999995 42.6666656,19.9999995 M51.841332,130.64133 C32.3306659,130.64133 21.8279995,117.367997 21.8279995,117.367997 L21.8279995,138.330663 L21.7586661,166.114663 L11.9159997,166.114663 L0.425333323,166.114663 L0.494666654,7.59599981 C11.1613331,3.13066659 25.7973327,0.65066665 41.6746656,0.65066665 C82.3586646,0.65066665 101.955997,26.6973327 101.955997,71.5986649 C101.955997,107.073331 83.8426646,130.64133 51.841332,130.64133" id="path30" fill="#8C929D" sketch:type="MSShapeGroup"></path>
</g>
<g id="g32" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(584.042117, 143.630796)">
<path d="M78.6666647,147.999996 C98.0159975,147.999996 110.419997,141.550663 118.606664,135.099997 L127.987997,151.350663 C115.20133,162.563996 98.0026642,168.590662 79.6586647,168.590662 C33.2693325,168.590662 0.771999981,140.30933 0.771999981,83.2533313 C0.771999981,23.4666661 35.8306658,0.147999996 75.9373314,0.147999996 C96.0319976,0.147999996 113.149331,4.86133321 124.311997,9.57466643 L123.854664,73.4533315 L123.854664,80.9893313 L123.854664,93.5479977 L64.3173317,93.5479977 L64.3173317,73.4533315 L102.271997,73.4533315 L102.729331,24.9559994 C97.7679976,22.4746661 89.0853311,20.4906662 77.4266647,20.4906662 C45.1773322,20.4906662 23.5946661,40.7773323 23.5946661,83.5013312 C23.5946661,126.91333 45.9213322,147.999996 78.6666647,147.999996" id="path34" fill="#8C929D" sketch:type="MSShapeGroup"></path>
</g>
<g id="g36" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(793.569045, 142.577463)">
<path d="M22.6666661,166.666662 L1.33199997,166.666662 L1.4013333,139.378663 L1.4013333,128.214663 L1.4013333,121.707997 L1.4013333,110.353331 L1.4013333,45.3586655 L1.4013333,45.1106655 C1.4013333,18.8146662 12.813333,1.2013333 45.3106655,1.2013333 C49.7999988,1.2013333 54.193332,1.60933329 58.4586652,2.38399994 L58.4586652,21.5426661 C55.3706653,21.0693328 52.0746654,20.7999995 48.5359988,20.7999995 C30.6746659,20.7999995 22.7359994,29.9786659 22.7359994,45.3586655 L22.7359994,110.353331 L58.4586652,110.353331 L58.4586652,128.214663 L22.7359994,128.214663 L22.6666661,166.666662 L22.6666661,166.666662 Z" id="path38" fill="#8C929D" sketch:type="MSShapeGroup"></path>
</g>
<path d="M740.766646,146.755996 L762.101312,146.755996 L762.101312,270.793327 L740.766646,270.793327 L740.766646,146.755996 L740.766646,146.755996 Z" id="path40" fill="#8C929D" sketch:type="MSShapeGroup"></path>
<path d="M740.766646,287.909326 L762.101312,287.909326 L762.101312,309.243992 L740.766646,309.243992 L740.766646,287.909326 L740.766646,287.909326 Z" id="path42" fill="#8C929D" sketch:type="MSShapeGroup"></path>
<g id="g44" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(0.532000, 0.774933)">
<path d="M491.999988,194.666662 L464.441322,279.481326 L409.82399,447.578655 C407.014656,456.226655 394.778657,456.226655 391.96799,447.578655 L337.349325,279.481326 L155.982663,279.481326 L101.362664,447.578655 C98.5533309,456.226655 86.3173312,456.226655 83.5066646,447.578655 L28.8893326,279.481326 L1.33199997,194.666662 C-1.18266664,186.930662 1.57199996,178.455996 8.1519998,173.674662 L246.665327,0.385333324 L485.179988,173.674662 C491.759988,178.455996 494.513321,186.930662 491.999988,194.666662" id="path46" fill="#FC6D26" sketch:type="MSShapeGroup"></path>
</g>
<g id="g48" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(155.197863, 1.160267)">
<path d="M91.9999977,0 L91.9999977,0 L182.683995,279.095993 L1.31599997,279.095993 L91.9999977,0 L91.9999977,0 Z" id="path50" fill="#E24329" sketch:type="MSShapeGroup"></path>
</g>
<g id="g52" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(247.197860, 1.160267)">
<g id="path54"></g>
</g>
<g id="g56" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(28.531199, 1.160800)">
<path d="M218.666661,0 L127.982663,279.09466 L0.890666644,279.09466 L218.666661,0 L218.666661,0 Z" id="path58" fill="#FC6D26" sketch:type="MSShapeGroup"></path>
</g>
<g id="g60" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(247.197860, 1.160800)">
<g id="path62"></g>
</g>
<g id="g64" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(0.088533, 0.255867)">
<path d="M29.3333326,279.999993 L29.3333326,279.999993 L1.77466662,195.185328 C-0.738666648,187.449329 2.01466662,178.974662 8.59599979,174.194662 L247.109327,0.905333311 L29.3333326,279.999993 L29.3333326,279.999993 Z" id="path66" fill="#FCA326" sketch:type="MSShapeGroup"></path>
</g>
<g id="g68" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(247.197860, 1.160267)">
<g id="path70"></g>
</g>
<g id="g72" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(29.421866, 280.255593)">
<path d="M0,0 L127.091997,0 L72.4733315,168.097329 C69.6626649,176.746662 57.4266652,176.746662 54.617332,168.097329 L0,0 L0,0 Z" id="path74" fill="#E24329" sketch:type="MSShapeGroup"></path>
</g>
<g id="g76" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(247.197860, 1.160800)">
<path d="M0,0 L90.6839977,279.09466 L217.775995,279.09466 L0,0 L0,0 Z" id="path78" fill="#FC6D26" sketch:type="MSShapeGroup"></path>
</g>
<g id="g80" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(246.307061, 0.255867)">
<path d="M218.666661,279.999993 L218.666661,279.999993 L246.225327,195.185328 C248.73866,187.449329 245.985327,178.974662 239.403994,174.194662 L0.890666644,0.905333311 L218.666661,279.999993 L218.666661,279.999993 Z" id="path82" fill="#FCA326" sketch:type="MSShapeGroup"></path>
</g>
<g id="g84" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(336.973725, 280.255593)">
<path d="M127.999997,0 L0.907999977,0 L55.5266653,168.097329 C58.3373319,176.746662 70.5733316,176.746662 73.3826648,168.097329 L127.999997,0 L127.999997,0 Z" id="path86" fill="#E24329" sketch:type="MSShapeGroup"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 272.96 114.01"
version="1.1"
id="svg10"
sodipodi:docname="gitlogo.svg"
inkscape:version="0.92.0 r15299">
<metadata
id="metadata16">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs14" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1070"
inkscape:window-height="735"
id="namedview12"
showgrid="false"
inkscape:zoom="1.0807445"
inkscape:cx="115.66102"
inkscape:cy="55.154427"
inkscape:window-x="2350"
inkscape:window-y="328"
inkscape:window-maximized="0"
inkscape:current-layer="svg10" />
<path
fill="#f05133"
d="M111.78,51.977,62.035,2.2381c-2.8622-2.8648-7.5082-2.8648-10.374,0l-10.329,10.33,13.102,13.102c3.0459-1.0284,6.5371-0.33888,8.9639,2.0884,2.4394,2.4424,3.124,5.9634,2.0698,9.0195l12.628,12.628c3.0551-1.0528,6.58-0.37262,9.0195,2.0712,3.4106,3.4096,3.4106,8.9345,0,12.345-3.4111,3.4116-8.936,3.4116-12.349,0-2.5645-2.5665-3.1988-6.3345-1.8999-9.4942l-11.777-11.777-0.001,30.991c0.8315,0.41162,1.6162,0.961,2.3091,1.6509,3.4096,3.4092,3.4096,8.9331,0,12.348-3.4106,3.4091-8.938,3.4091-12.345,0-3.4101-3.4146-3.4101-8.9385,0-12.348,0.84275-0.84125,1.8179-1.478,2.8584-1.9048v-31.279c-1.041-0.425-2.015-1.057-2.859-1.905-2.583-2.581-3.2051-6.372-1.8804-9.5439l-12.916-12.918-34.106,34.105c-2.8657,2.867-2.8657,7.513,0,10.378l49.742,49.739c2.8638,2.8648,7.5082,2.8648,10.376,0l49.512-49.504c2.8648-2.8662,2.8648-7.5136,0-10.379"
id="path8" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:46.82492828px;line-height:27.43648148px;font-family:'Helvetica Rounded LT Std';-inkscape-font-specification:'Helvetica Rounded LT Std';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#999999;fill-opacity:1;stroke:none;stroke-width:1.0974592px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
x="128.30931"
y="69.483566"
id="text20"
transform="scale(0.9595033,1.0422059)"><tspan
sodipodi:role="line"
id="tspan18"
x="128.30931"
y="69.483566"
style="fill:#999999;stroke-width:1.0974592px;font-family: 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;font-weight: 300">Others</tspan></text>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

196
plugins/git-sync/js/app.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,44 @@
{
"name": "trilby-grav-plugin-gitsync",
"version": "2.1.0",
"description": "Git Sync Grav Plugin",
"main": "app/main.js",
"scripts": {
"watch": "webpack --watch --progress --color --mode development --config webpack.conf.js",
"dev": "webpack --progress --mode development --colors --config webpack.conf.js",
"prod": "webpack --mode production --config webpack.conf.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/trilbymedia/grav-plugin-git-sync.git"
},
"author": "Trilby Media, LLC",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/trilbymedia/grav-plugin-git-sync/issues"
},
"homepage": "https://github.com/trilbymedia/grav-plugin-git-sync#readme",
"devDependencies": {
"@babel/core": "^7.14.3",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-proposal-json-strings": "^7.14.2",
"@babel/plugin-proposal-object-rest-spread": "^7.14.4",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.14.4",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.2",
"css-loader": "^5.2.6",
"eslint": "^7.27.0",
"eslint-loader": "^4.0.2",
"exports-loader": "^3.0.0",
"imports-loader": "^3.0.0",
"json-loader": "^0.5.7",
"style-loader": "^2.0.0",
"uglifyjs-webpack-plugin": "^2.2.0",
"webpack": "^5.38.1",
"webpack-cli": "^4.7.0",
"whatwg-fetch": "^3.6.2"
}
}

View file

@ -0,0 +1,8 @@
$page-background: #f7f7f7;
$content-text: #666;
$header-background: $page-background;
$calendar-text: $content-text;
$calendar-details-background: #dcdcdc;
$calendar-details-background-alt: #cecece;
$calendar-day-background: #ffffff;
$toast-danger-background: #C62828;

View file

@ -0,0 +1,7 @@
// Vendors
@import "vendor/bourbon/bourbon";
// Load Template Configuration
@import "configuration/colors";
@import "plugin/wizard";

View file

@ -0,0 +1,191 @@
[data-remodal-id="wizard"], [data-remodal-id="reset-local"] {
.button[disabled] {
&, &:hover {
background: #ccc;
cursor: default;
}
&:active {
margin: 0;
}
}
form [class^="step-"] a:not(.button):not([target]) {
vertical-align: middle;
color: #5591c7;
&:hover {
color: #366188;
}
}
.step-1 a {
vertical-align: middle;
}
.access-tokens {
h3 {
margin-top: 1rem;
}
a {
vertical-align: inherit !important;
}
}
.access-tokens-details {
> p {
margin-top: 0;
}
> div > ul {
margin-bottom: 0;
}
}
.center {
text-align: center;
}
h1 {
margin-bottom: 0;
padding-top: 0.5rem;
border-top: 3px solid transparent;
}
.wizard-padding {
padding: 0 3rem;
p {
padding: 0;
}
}
input[disabled] {
background: #efefef;
border-color: #ddd;
}
input.invalid {
border-color: #f4516d;
color: #f4516d;
}
label {
&.disabled {
color: #ccc;
}
img {
max-width: 100px;
display: inline-block;
vertical-align: middle;
margin-left: 0.5rem;
}
a {
margin-left: 0.5rem;
}
}
.columns {
display: flex;
align-items: center;
justify-content: center;
.column {
flex: 1;
&:first-child {
width: 35%;
flex: none;
border-right: 1px solid #ddd;
margin-right: 2rem;
}
}
}
.step-1 {
div.column:nth-child(1) {
flex-grow: 1.5;
width: 15%;
> label:nth-child(4) {
width: 110%;
}
}
div.column:nth-child(2) {
margin-left: 25px;
}
div.column:nth-child(2) > label:nth-child(1) > input:nth-child(1), div.column:nth-child(2) > label:nth-child(2) > input:nth-child(1) {
width: 100%;
}
.no_user {
float: right;
font-size: .8rem;
padding: 0;
input {
margin-right: 0;
}
}
}
.step-2 {
.info {
margin: 0.2rem 0;
padding: 0.5rem 1.5rem;
}
ol, ul {
padding-left: 1rem;
}
}
.step-4 {
.info {
font-size: 100%;
margin: 0.2rem 0;
}
.alert, .warning {
padding: 0.5rem 1.5rem;
}
.fa.fa-warning {
color: #ff7d3b;
font-size: 1.2rem;
}
.wizard-padding label > * {
vertical-align: middle;
}
.info-desc {
color: #0091ff;
float: right;
margin-right: .5rem;
font-size: 1.2rem;
}
hr {
margin: .5rem 0;
}
}
}
#admin-main [data-grav-field="git-wizard"] {
margin: 0 1rem;
.danger.button-bar {
margin: 0;
padding: 0;
background: transparent;
}
.button {
top: 0 !important;
@include transform(translateY(0) !important);
}
}

View file

@ -0,0 +1,411 @@
// The following features have been deprecated and will be removed in the next MAJOR version release
@mixin inline-block {
display: inline-block;
@warn "The inline-block mixin is deprecated and will be removed in the next major version release";
}
@mixin button ($style: simple, $base-color: #4294f0, $text-size: inherit, $padding: 7px 18px) {
@if type-of($style) == string and type-of($base-color) == color {
@include buttonstyle($style, $base-color, $text-size, $padding);
}
@if type-of($style) == string and type-of($base-color) == number {
$padding: $text-size;
$text-size: $base-color;
$base-color: #4294f0;
@if $padding == inherit {
$padding: 7px 18px;
}
@include buttonstyle($style, $base-color, $text-size, $padding);
}
@if type-of($style) == color and type-of($base-color) == color {
$base-color: $style;
$style: simple;
@include buttonstyle($style, $base-color, $text-size, $padding);
}
@if type-of($style) == color and type-of($base-color) == number {
$padding: $text-size;
$text-size: $base-color;
$base-color: $style;
$style: simple;
@if $padding == inherit {
$padding: 7px 18px;
}
@include buttonstyle($style, $base-color, $text-size, $padding);
}
@if type-of($style) == number {
$padding: $base-color;
$text-size: $style;
$base-color: #4294f0;
$style: simple;
@if $padding == #4294f0 {
$padding: 7px 18px;
}
@include buttonstyle($style, $base-color, $text-size, $padding);
}
&:disabled {
cursor: not-allowed;
opacity: 0.5;
}
@warn "The button mixin is deprecated and will be removed in the next major version release";
}
// Selector Style Button
@mixin buttonstyle($type, $b-color, $t-size, $pad) {
// Grayscale button
@if $type == simple and $b-color == grayscale($b-color) {
@include simple($b-color, true, $t-size, $pad);
}
@if $type == shiny and $b-color == grayscale($b-color) {
@include shiny($b-color, true, $t-size, $pad);
}
@if $type == pill and $b-color == grayscale($b-color) {
@include pill($b-color, true, $t-size, $pad);
}
@if $type == flat and $b-color == grayscale($b-color) {
@include flat($b-color, true, $t-size, $pad);
}
// Colored button
@if $type == simple {
@include simple($b-color, false, $t-size, $pad);
}
@else if $type == shiny {
@include shiny($b-color, false, $t-size, $pad);
}
@else if $type == pill {
@include pill($b-color, false, $t-size, $pad);
}
@else if $type == flat {
@include flat($b-color, false, $t-size, $pad);
}
}
// Simple Button
@mixin simple($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
$color: hsl(0, 0, 100%);
$border: adjust-color($base-color, $saturation: 9%, $lightness: -14%);
$inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%);
$stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%);
$text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%);
@if is-light($base-color) {
$color: hsl(0, 0, 20%);
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
}
@if $grayscale == true {
$border: grayscale($border);
$inset-shadow: grayscale($inset-shadow);
$stop-gradient: grayscale($stop-gradient);
$text-shadow: grayscale($text-shadow);
}
border: 1px solid $border;
border-radius: 3px;
box-shadow: inset 0 1px 0 0 $inset-shadow;
color: $color;
display: inline-block;
font-size: $textsize;
font-weight: bold;
@include linear-gradient ($base-color, $stop-gradient);
padding: $padding;
text-decoration: none;
text-shadow: 0 1px 0 $text-shadow;
background-clip: padding-box;
&:hover:not(:disabled) {
$base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%);
$inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%);
$stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%);
@if $grayscale == true {
$base-color-hover: grayscale($base-color-hover);
$inset-shadow-hover: grayscale($inset-shadow-hover);
$stop-gradient-hover: grayscale($stop-gradient-hover);
}
@include linear-gradient ($base-color-hover, $stop-gradient-hover);
box-shadow: inset 0 1px 0 0 $inset-shadow-hover;
cursor: pointer;
}
&:active:not(:disabled),
&:focus:not(:disabled) {
$border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%);
$inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%);
@if $grayscale == true {
$border-active: grayscale($border-active);
$inset-shadow-active: grayscale($inset-shadow-active);
}
border: 1px solid $border-active;
box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active;
}
}
// Shiny Button
@mixin shiny($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
$color: hsl(0, 0, 100%);
$border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81);
$border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122);
$fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46);
$inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12);
$second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33);
$text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114);
$third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48);
@if is-light($base-color) {
$color: hsl(0, 0, 20%);
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
}
@if $grayscale == true {
$border: grayscale($border);
$border-bottom: grayscale($border-bottom);
$fourth-stop: grayscale($fourth-stop);
$inset-shadow: grayscale($inset-shadow);
$second-stop: grayscale($second-stop);
$text-shadow: grayscale($text-shadow);
$third-stop: grayscale($third-stop);
}
@include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%);
border: 1px solid $border;
border-bottom: 1px solid $border-bottom;
border-radius: 5px;
box-shadow: inset 0 1px 0 0 $inset-shadow;
color: $color;
display: inline-block;
font-size: $textsize;
font-weight: bold;
padding: $padding;
text-align: center;
text-decoration: none;
text-shadow: 0 -1px 1px $text-shadow;
&:hover:not(:disabled) {
$first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18);
$second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51);
$third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66);
$fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63);
@if $grayscale == true {
$first-stop-hover: grayscale($first-stop-hover);
$second-stop-hover: grayscale($second-stop-hover);
$third-stop-hover: grayscale($third-stop-hover);
$fourth-stop-hover: grayscale($fourth-stop-hover);
}
@include linear-gradient(top, $first-stop-hover 0%,
$second-stop-hover 50%,
$third-stop-hover 50%,
$fourth-stop-hover 100%);
cursor: pointer;
}
&:active:not(:disabled),
&:focus:not(:disabled) {
$inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122);
@if $grayscale == true {
$inset-shadow-active: grayscale($inset-shadow-active);
}
box-shadow: inset 0 0 20px 0 $inset-shadow-active;
}
}
// Pill Button
@mixin pill($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
$color: hsl(0, 0, 100%);
$border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%);
$border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%);
$border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%);
$inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%);
$stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%);
$text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%);
@if is-light($base-color) {
$color: hsl(0, 0, 20%);
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
}
@if $grayscale == true {
$border-bottom: grayscale($border-bottom);
$border-sides: grayscale($border-sides);
$border-top: grayscale($border-top);
$inset-shadow: grayscale($inset-shadow);
$stop-gradient: grayscale($stop-gradient);
$text-shadow: grayscale($text-shadow);
}
border: 1px solid $border-top;
border-color: $border-top $border-sides $border-bottom;
border-radius: 16px;
box-shadow: inset 0 1px 0 0 $inset-shadow;
color: $color;
display: inline-block;
font-size: $textsize;
font-weight: normal;
line-height: 1;
@include linear-gradient ($base-color, $stop-gradient);
padding: $padding;
text-align: center;
text-decoration: none;
text-shadow: 0 -1px 1px $text-shadow;
background-clip: padding-box;
&:hover:not(:disabled) {
$base-color-hover: adjust-color($base-color, $lightness: -4.5%);
$border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%);
$border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%);
$border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%);
$inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%);
$stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%);
$text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%);
@if $grayscale == true {
$base-color-hover: grayscale($base-color-hover);
$border-bottom: grayscale($border-bottom);
$border-sides: grayscale($border-sides);
$border-top: grayscale($border-top);
$inset-shadow-hover: grayscale($inset-shadow-hover);
$stop-gradient-hover: grayscale($stop-gradient-hover);
$text-shadow-hover: grayscale($text-shadow-hover);
}
@include linear-gradient ($base-color-hover, $stop-gradient-hover);
background-clip: padding-box;
border: 1px solid $border-top;
border-color: $border-top $border-sides $border-bottom;
box-shadow: inset 0 1px 0 0 $inset-shadow-hover;
cursor: pointer;
text-shadow: 0 -1px 1px $text-shadow-hover;
}
&:active:not(:disabled),
&:focus:not(:disabled) {
$active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%);
$border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%);
$border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%);
$inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%);
$text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%);
@if $grayscale == true {
$active-color: grayscale($active-color);
$border-active: grayscale($border-active);
$border-bottom-active: grayscale($border-bottom-active);
$inset-shadow-active: grayscale($inset-shadow-active);
$text-shadow-active: grayscale($text-shadow-active);
}
background: $active-color;
border: 1px solid $border-active;
border-bottom: 1px solid $border-bottom-active;
box-shadow: inset 0 0 6px 3px $inset-shadow-active;
text-shadow: 0 -1px 1px $text-shadow-active;
}
}
// Flat Button
@mixin flat($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
$color: hsl(0, 0, 100%);
@if is-light($base-color) {
$color: hsl(0, 0, 20%);
}
background-color: $base-color;
border-radius: 3px;
border: 0;
color: $color;
display: inline-block;
font-size: $textsize;
font-weight: bold;
padding: $padding;
text-decoration: none;
background-clip: padding-box;
&:hover:not(:disabled){
$base-color-hover: adjust-color($base-color, $saturation: 4%, $lightness: 5%);
@if $grayscale == true {
$base-color-hover: grayscale($base-color-hover);
}
background-color: $base-color-hover;
cursor: pointer;
}
&:active:not(:disabled),
&:focus:not(:disabled) {
$base-color-active: adjust-color($base-color, $saturation: -4%, $lightness: -5%);
@if $grayscale == true {
$base-color-active: grayscale($base-color-active);
}
background-color: $base-color-active;
cursor: pointer;
}
}
// Flexible grid
@function flex-grid($columns, $container-columns: $fg-max-columns) {
$width: $columns * $fg-column + ($columns - 1) * $fg-gutter;
$container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
@return percentage($width / $container-width);
@warn "The flex-grid function is deprecated and will be removed in the next major version release";
}
// Flexible gutter
@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) {
$container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
@return percentage($gutter / $container-width);
@warn "The flex-gutter function is deprecated and will be removed in the next major version release";
}
@function grid-width($n) {
@return $n * $gw-column + ($n - 1) * $gw-gutter;
@warn "The grid-width function is deprecated and will be removed in the next major version release";
}
@function golden-ratio($value, $increment) {
@return modular-scale($increment, $value, $ratio: $golden);
@warn "The golden-ratio function is deprecated and will be removed in the next major version release. Please use the modular-scale function, instead.";
}
@mixin box-sizing($box) {
@include prefixer(box-sizing, $box, webkit moz spec);
@warn "The box-sizing mixin is deprecated and will be removed in the next major version release. This property can now be used un-prefixed.";
}

View file

@ -0,0 +1,87 @@
// Bourbon 4.2.7
// http://bourbon.io
// Copyright 2011-2015 thoughtbot, inc.
// MIT License
@import "settings/prefixer";
@import "settings/px-to-em";
@import "settings/asset-pipeline";
@import "functions/assign-inputs";
@import "functions/contains";
@import "functions/contains-falsy";
@import "functions/is-length";
@import "functions/is-light";
@import "functions/is-number";
@import "functions/is-size";
@import "functions/px-to-em";
@import "functions/px-to-rem";
@import "functions/shade";
@import "functions/strip-units";
@import "functions/tint";
@import "functions/transition-property-name";
@import "functions/unpack";
@import "functions/modular-scale";
@import "helpers/convert-units";
@import "helpers/directional-values";
@import "helpers/font-source-declaration";
@import "helpers/gradient-positions-parser";
@import "helpers/linear-angle-parser";
@import "helpers/linear-gradient-parser";
@import "helpers/linear-positions-parser";
@import "helpers/linear-side-corner-parser";
@import "helpers/radial-arg-parser";
@import "helpers/radial-positions-parser";
@import "helpers/radial-gradient-parser";
@import "helpers/render-gradients";
@import "helpers/shape-size-stripper";
@import "helpers/str-to-num";
@import "css3/animation";
@import "css3/appearance";
@import "css3/backface-visibility";
@import "css3/background";
@import "css3/background-image";
@import "css3/border-image";
@import "css3/calc";
@import "css3/columns";
@import "css3/filter";
@import "css3/flex-box";
@import "css3/font-face";
@import "css3/font-feature-settings";
@import "css3/hidpi-media-query";
@import "css3/hyphens";
@import "css3/image-rendering";
@import "css3/keyframes";
@import "css3/linear-gradient";
@import "css3/perspective";
@import "css3/placeholder";
@import "css3/radial-gradient";
@import "css3/selection";
@import "css3/text-decoration";
@import "css3/transform";
@import "css3/transition";
@import "css3/user-select";
@import "addons/border-color";
@import "addons/border-radius";
@import "addons/border-style";
@import "addons/border-width";
@import "addons/buttons";
@import "addons/clearfix";
@import "addons/ellipsis";
@import "addons/font-stacks";
@import "addons/hide-text";
@import "addons/margin";
@import "addons/padding";
@import "addons/position";
@import "addons/prefixer";
@import "addons/retina-image";
@import "addons/size";
@import "addons/text-inputs";
@import "addons/timing-functions";
@import "addons/triangle";
@import "addons/word-wrap";
@import "bourbon-deprecated-upcoming";

View file

@ -0,0 +1,26 @@
@charset "UTF-8";
/// Provides a quick method for targeting `border-color` on specific sides of a box. Use a `null` value to skip a side.
///
/// @param {Arglist} $vals
/// List of arguments
///
/// @example scss - Usage
/// .element {
/// @include border-color(#a60b55 #76cd9c null #e8ae1a);
/// }
///
/// @example css - CSS Output
/// .element {
/// border-left-color: #e8ae1a;
/// border-right-color: #76cd9c;
/// border-top-color: #a60b55;
/// }
///
/// @require {mixin} directional-property
///
/// @output `border-color`
@mixin border-color($vals...) {
@include directional-property(border, color, $vals...);
}

View file

@ -0,0 +1,48 @@
@charset "UTF-8";
/// Provides a quick method for targeting `border-radius` on both corners on the side of a box.
///
/// @param {Number} $radii
/// List of arguments
///
/// @example scss - Usage
/// .element-one {
/// @include border-top-radius(5px);
/// }
///
/// .element-two {
/// @include border-left-radius(3px);
/// }
///
/// @example css - CSS Output
/// .element-one {
/// border-top-left-radius: 5px;
/// border-top-right-radius: 5px;
/// }
///
/// .element-two {
/// border-bottom-left-radius: 3px;
/// border-top-left-radius: 3px;
/// }
///
/// @output `border-radius`
@mixin border-top-radius($radii) {
border-top-left-radius: $radii;
border-top-right-radius: $radii;
}
@mixin border-right-radius($radii) {
border-bottom-right-radius: $radii;
border-top-right-radius: $radii;
}
@mixin border-bottom-radius($radii) {
border-bottom-left-radius: $radii;
border-bottom-right-radius: $radii;
}
@mixin border-left-radius($radii) {
border-bottom-left-radius: $radii;
border-top-left-radius: $radii;
}

View file

@ -0,0 +1,25 @@
@charset "UTF-8";
/// Provides a quick method for targeting `border-style` on specific sides of a box. Use a `null` value to skip a side.
///
/// @param {Arglist} $vals
/// List of arguments
///
/// @example scss - Usage
/// .element {
/// @include border-style(dashed null solid);
/// }
///
/// @example css - CSS Output
/// .element {
/// border-bottom-style: solid;
/// border-top-style: dashed;
/// }
///
/// @require {mixin} directional-property
///
/// @output `border-style`
@mixin border-style($vals...) {
@include directional-property(border, style, $vals...);
}

View file

@ -0,0 +1,25 @@
@charset "UTF-8";
/// Provides a quick method for targeting `border-width` on specific sides of a box. Use a `null` value to skip a side.
///
/// @param {Arglist} $vals
/// List of arguments
///
/// @example scss - Usage
/// .element {
/// @include border-width(1em null 20px);
/// }
///
/// @example css - CSS Output
/// .element {
/// border-bottom-width: 20px;
/// border-top-width: 1em;
/// }
///
/// @require {mixin} directional-property
///
/// @output `border-width`
@mixin border-width($vals...) {
@include directional-property(border, width, $vals...);
}

View file

@ -0,0 +1,64 @@
@charset "UTF-8";
/// Generates variables for all buttons. Please note that you must use interpolation on the variable: `#{$all-buttons}`.
///
/// @example scss - Usage
/// #{$all-buttons} {
/// background-color: #f00;
/// }
///
/// #{$all-buttons-focus},
/// #{$all-buttons-hover} {
/// background-color: #0f0;
/// }
///
/// #{$all-buttons-active} {
/// background-color: #00f;
/// }
///
/// @example css - CSS Output
/// button,
/// input[type="button"],
/// input[type="reset"],
/// input[type="submit"] {
/// background-color: #f00;
/// }
///
/// button:focus,
/// input[type="button"]:focus,
/// input[type="reset"]:focus,
/// input[type="submit"]:focus,
/// button:hover,
/// input[type="button"]:hover,
/// input[type="reset"]:hover,
/// input[type="submit"]:hover {
/// background-color: #0f0;
/// }
///
/// button:active,
/// input[type="button"]:active,
/// input[type="reset"]:active,
/// input[type="submit"]:active {
/// background-color: #00f;
/// }
///
/// @require assign-inputs
///
/// @type List
///
/// @todo Remove double assigned variables (Lines 5962) in v5.0.0
$buttons-list: 'button',
'input[type="button"]',
'input[type="reset"]',
'input[type="submit"]';
$all-buttons: assign-inputs($buttons-list);
$all-buttons-active: assign-inputs($buttons-list, active);
$all-buttons-focus: assign-inputs($buttons-list, focus);
$all-buttons-hover: assign-inputs($buttons-list, hover);
$all-button-inputs: $all-buttons;
$all-button-inputs-active: $all-buttons-active;
$all-button-inputs-focus: $all-buttons-focus;
$all-button-inputs-hover: $all-buttons-hover;

View file

@ -0,0 +1,25 @@
@charset "UTF-8";
/// Provides an easy way to include a clearfix for containing floats.
///
/// @link http://cssmojo.com/latest_new_clearfix_so_far/
///
/// @example scss - Usage
/// .element {
/// @include clearfix;
/// }
///
/// @example css - CSS Output
/// .element::after {
/// clear: both;
/// content: "";
/// display: table;
/// }
@mixin clearfix {
&::after {
clear: both;
content: "";
display: table;
}
}

View file

@ -0,0 +1,30 @@
@charset "UTF-8";
/// Truncates text and adds an ellipsis to represent overflow.
///
/// @param {Number} $width [100%]
/// Max-width for the string to respect before being truncated
///
/// @example scss - Usage
/// .element {
/// @include ellipsis;
/// }
///
/// @example css - CSS Output
/// .element {
/// display: inline-block;
/// max-width: 100%;
/// overflow: hidden;
/// text-overflow: ellipsis;
/// white-space: nowrap;
/// word-wrap: normal;
/// }
@mixin ellipsis($width: 100%) {
display: inline-block;
max-width: $width;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal;
}

View file

@ -0,0 +1,31 @@
@charset "UTF-8";
/// Georgia font stack.
///
/// @type List
$georgia: "Georgia", "Cambria", "Times New Roman", "Times", serif;
/// Helvetica font stack.
///
/// @type List
$helvetica: "Helvetica Neue", "Helvetica", "Roboto", "Arial", sans-serif;
/// Lucida Grande font stack.
///
/// @type List
$lucida-grande: "Lucida Grande", "Tahoma", "Verdana", "Arial", sans-serif;
/// Monospace font stack.
///
/// @type List
$monospace: "Bitstream Vera Sans Mono", "Consolas", "Courier", monospace;
/// Verdana font stack.
///
/// @type List
$verdana: "Verdana", "Geneva", sans-serif;

View file

@ -0,0 +1,27 @@
/// Hides the text in an element, commonly used to show an image. Some elements will need block-level styles applied.
///
/// @link http://zeldman.com/2012/03/01/replacing-the-9999px-hack-new-image-replacement
///
/// @example scss - Usage
/// .element {
/// @include hide-text;
/// }
///
/// @example css - CSS Output
/// .element {
/// overflow: hidden;
/// text-indent: 101%;
/// white-space: nowrap;
/// }
///
/// @todo Remove height argument in v5.0.0
@mixin hide-text($height: null) {
overflow: hidden;
text-indent: 101%;
white-space: nowrap;
@if $height {
@warn "The `hide-text` mixin has changed and no longer requires a height. The height argument will no longer be accepted in v5.0.0";
}
}

View file

@ -0,0 +1,26 @@
@charset "UTF-8";
/// Provides a quick method for targeting `margin` on specific sides of a box. Use a `null` value to skip a side.
///
/// @param {Arglist} $vals
/// List of arguments
///
/// @example scss - Usage
/// .element {
/// @include margin(null 10px 3em 20vh);
/// }
///
/// @example css - CSS Output
/// .element {
/// margin-bottom: 3em;
/// margin-left: 20vh;
/// margin-right: 10px;
/// }
///
/// @require {mixin} directional-property
///
/// @output `margin`
@mixin margin($vals...) {
@include directional-property(margin, false, $vals...);
}

View file

@ -0,0 +1,26 @@
@charset "UTF-8";
/// Provides a quick method for targeting `padding` on specific sides of a box. Use a `null` value to skip a side.
///
/// @param {Arglist} $vals
/// List of arguments
///
/// @example scss - Usage
/// .element {
/// @include padding(12vh null 10px 5%);
/// }
///
/// @example css - CSS Output
/// .element {
/// padding-bottom: 10px;
/// padding-left: 5%;
/// padding-top: 12vh;
/// }
///
/// @require {mixin} directional-property
///
/// @output `padding`
@mixin padding($vals...) {
@include directional-property(padding, false, $vals...);
}

View file

@ -0,0 +1,48 @@
@charset "UTF-8";
/// Provides a quick method for setting an elements position. Use a `null` value to skip a side.
///
/// @param {Position} $position [relative]
/// A CSS position value
///
/// @param {Arglist} $coordinates [null null null null]
/// List of values that correspond to the 4-value syntax for the edges of a box
///
/// @example scss - Usage
/// .element {
/// @include position(absolute, 0 null null 10em);
/// }
///
/// @example css - CSS Output
/// .element {
/// left: 10em;
/// position: absolute;
/// top: 0;
/// }
///
/// @require {function} is-length
/// @require {function} unpack
@mixin position($position: relative, $coordinates: null null null null) {
@if type-of($position) == list {
$coordinates: $position;
$position: relative;
}
$coordinates: unpack($coordinates);
$offsets: (
top: nth($coordinates, 1),
right: nth($coordinates, 2),
bottom: nth($coordinates, 3),
left: nth($coordinates, 4)
);
position: $position;
@each $offset, $value in $offsets {
@if is-length($value) {
#{$offset}: $value;
}
}
}

View file

@ -0,0 +1,66 @@
@charset "UTF-8";
/// A mixin for generating vendor prefixes on non-standardized properties.
///
/// @param {String} $property
/// Property to prefix
///
/// @param {*} $value
/// Value to use
///
/// @param {List} $prefixes
/// Prefixes to define
///
/// @example scss - Usage
/// .element {
/// @include prefixer(border-radius, 10px, webkit ms spec);
/// }
///
/// @example css - CSS Output
/// .element {
/// -webkit-border-radius: 10px;
/// -moz-border-radius: 10px;
/// border-radius: 10px;
/// }
///
/// @require {variable} $prefix-for-webkit
/// @require {variable} $prefix-for-mozilla
/// @require {variable} $prefix-for-microsoft
/// @require {variable} $prefix-for-opera
/// @require {variable} $prefix-for-spec
@mixin prefixer($property, $value, $prefixes) {
@each $prefix in $prefixes {
@if $prefix == webkit {
@if $prefix-for-webkit {
-webkit-#{$property}: $value;
}
} @else if $prefix == moz {
@if $prefix-for-mozilla {
-moz-#{$property}: $value;
}
} @else if $prefix == ms {
@if $prefix-for-microsoft {
-ms-#{$property}: $value;
}
} @else if $prefix == o {
@if $prefix-for-opera {
-o-#{$property}: $value;
}
} @else if $prefix == spec {
@if $prefix-for-spec {
#{$property}: $value;
}
} @else {
@warn "Unrecognized prefix: #{$prefix}";
}
}
}
@mixin disable-prefix-for-all() {
$prefix-for-webkit: false !global;
$prefix-for-mozilla: false !global;
$prefix-for-microsoft: false !global;
$prefix-for-opera: false !global;
$prefix-for-spec: false !global;
}

View file

@ -0,0 +1,25 @@
@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $retina-suffix: _2x, $asset-pipeline: $asset-pipeline) {
@if $asset-pipeline {
background-image: image-url("#{$filename}.#{$extension}");
} @else {
background-image: url("#{$filename}.#{$extension}");
}
@include hidpi {
@if $asset-pipeline {
@if $retina-filename {
background-image: image-url("#{$retina-filename}.#{$extension}");
} @else {
background-image: image-url("#{$filename}#{$retina-suffix}.#{$extension}");
}
} @else {
@if $retina-filename {
background-image: url("#{$retina-filename}.#{$extension}");
} @else {
background-image: url("#{$filename}#{$retina-suffix}.#{$extension}");
}
}
background-size: $background-size;
}
}

View file

@ -0,0 +1,51 @@
@charset "UTF-8";
/// Sets the `width` and `height` of the element.
///
/// @param {List} $size
/// A list of at most 2 size values.
///
/// If there is only a single value in `$size` it is used for both width and height. All units are supported.
///
/// @example scss - Usage
/// .first-element {
/// @include size(2em);
/// }
///
/// .second-element {
/// @include size(auto 10em);
/// }
///
/// @example css - CSS Output
/// .first-element {
/// width: 2em;
/// height: 2em;
/// }
///
/// .second-element {
/// width: auto;
/// height: 10em;
/// }
///
/// @todo Refactor in 5.0.0 to use a comma-separated argument
@mixin size($value) {
$width: nth($value, 1);
$height: $width;
@if length($value) > 1 {
$height: nth($value, 2);
}
@if is-size($height) {
height: $height;
} @else {
@warn "`#{$height}` is not a valid length for the `$height` parameter in the `size` mixin.";
}
@if is-size($width) {
width: $width;
} @else {
@warn "`#{$width}` is not a valid length for the `$width` parameter in the `size` mixin.";
}
}

View file

@ -0,0 +1,113 @@
@charset "UTF-8";
/// Generates variables for all text-based inputs. Please note that you must use interpolation on the variable: `#{$all-text-inputs}`.
///
/// @example scss - Usage
/// #{$all-text-inputs} {
/// border: 1px solid #f00;
/// }
///
/// #{$all-text-inputs-focus},
/// #{$all-text-inputs-hover} {
/// border: 1px solid #0f0;
/// }
///
/// #{$all-text-inputs-active} {
/// border: 1px solid #00f;
/// }
///
/// @example css - CSS Output
/// input[type="color"],
/// input[type="date"],
/// input[type="datetime"],
/// input[type="datetime-local"],
/// input[type="email"],
/// input[type="month"],
/// input[type="number"],
/// input[type="password"],
/// input[type="search"],
/// input[type="tel"],
/// input[type="text"],
/// input[type="time"],
/// input[type="url"],
/// input[type="week"],
/// textarea {
/// border: 1px solid #f00;
/// }
///
/// input[type="color"]:focus,
/// input[type="date"]:focus,
/// input[type="datetime"]:focus,
/// input[type="datetime-local"]:focus,
/// input[type="email"]:focus,
/// input[type="month"]:focus,
/// input[type="number"]:focus,
/// input[type="password"]:focus,
/// input[type="search"]:focus,
/// input[type="tel"]:focus,
/// input[type="text"]:focus,
/// input[type="time"]:focus,
/// input[type="url"]:focus,
/// input[type="week"]:focus,
/// textarea:focus,
/// input[type="color"]:hover,
/// input[type="date"]:hover,
/// input[type="datetime"]:hover,
/// input[type="datetime-local"]:hover,
/// input[type="email"]:hover,
/// input[type="month"]:hover,
/// input[type="number"]:hover,
/// input[type="password"]:hover,
/// input[type="search"]:hover,
/// input[type="tel"]:hover,
/// input[type="text"]:hover,
/// input[type="time"]:hover,
/// input[type="url"]:hover,
/// input[type="week"]:hover,
/// textarea:hover {
/// border: 1px solid #0f0;
/// }
///
/// input[type="color"]:active,
/// input[type="date"]:active,
/// input[type="datetime"]:active,
/// input[type="datetime-local"]:active,
/// input[type="email"]:active,
/// input[type="month"]:active,
/// input[type="number"]:active,
/// input[type="password"]:active,
/// input[type="search"]:active,
/// input[type="tel"]:active,
/// input[type="text"]:active,
/// input[type="time"]:active,
/// input[type="url"]:active,
/// input[type="week"]:active,
/// textarea:active {
/// border: 1px solid #00f;
/// }
///
/// @require assign-inputs
///
/// @type List
$text-inputs-list: 'input[type="color"]',
'input[type="date"]',
'input[type="datetime"]',
'input[type="datetime-local"]',
'input[type="email"]',
'input[type="month"]',
'input[type="number"]',
'input[type="password"]',
'input[type="search"]',
'input[type="tel"]',
'input[type="text"]',
'input[type="time"]',
'input[type="url"]',
'input[type="week"]',
'input:not([type])',
'textarea';
$all-text-inputs: assign-inputs($text-inputs-list);
$all-text-inputs-active: assign-inputs($text-inputs-list, active);
$all-text-inputs-focus: assign-inputs($text-inputs-list, focus);
$all-text-inputs-hover: assign-inputs($text-inputs-list, hover);

View file

@ -0,0 +1,34 @@
@charset "UTF-8";
/// CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie)
///
/// Timing functions are the same as demoed here: http://jqueryui.com/resources/demos/effect/easing.html
///
/// @type cubic-bezier
$ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530);
$ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190);
$ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220);
$ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060);
$ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715);
$ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035);
$ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335);
$ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045);
$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940);
$ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000);
$ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000);
$ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000);
$ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000);
$ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000);
$ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000);
$ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275);
$ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955);
$ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000);
$ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000);
$ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000);
$ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950);
$ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000);
$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860);
$ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550);

View file

@ -0,0 +1,63 @@
@mixin triangle($size, $color, $direction) {
$width: nth($size, 1);
$height: nth($size, length($size));
$foreground-color: nth($color, 1);
$background-color: if(length($color) == 2, nth($color, 2), transparent);
height: 0;
width: 0;
@if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) {
$width: $width / 2;
$height: if(length($size) > 1, $height, $height/2);
@if $direction == up {
border-bottom: $height solid $foreground-color;
border-left: $width solid $background-color;
border-right: $width solid $background-color;
} @else if $direction == right {
border-bottom: $width solid $background-color;
border-left: $height solid $foreground-color;
border-top: $width solid $background-color;
} @else if $direction == down {
border-left: $width solid $background-color;
border-right: $width solid $background-color;
border-top: $height solid $foreground-color;
} @else if $direction == left {
border-bottom: $width solid $background-color;
border-right: $height solid $foreground-color;
border-top: $width solid $background-color;
}
} @else if ($direction == up-right) or ($direction == up-left) {
border-top: $height solid $foreground-color;
@if $direction == up-right {
border-left: $width solid $background-color;
} @else if $direction == up-left {
border-right: $width solid $background-color;
}
} @else if ($direction == down-right) or ($direction == down-left) {
border-bottom: $height solid $foreground-color;
@if $direction == down-right {
border-left: $width solid $background-color;
} @else if $direction == down-left {
border-right: $width solid $background-color;
}
} @else if ($direction == inset-up) {
border-color: $background-color $background-color $foreground-color;
border-style: solid;
border-width: $height $width;
} @else if ($direction == inset-down) {
border-color: $foreground-color $background-color $background-color;
border-style: solid;
border-width: $height $width;
} @else if ($direction == inset-right) {
border-color: $background-color $background-color $background-color $foreground-color;
border-style: solid;
border-width: $width $height;
} @else if ($direction == inset-left) {
border-color: $background-color $foreground-color $background-color $background-color;
border-style: solid;
border-width: $width $height;
}
}

View file

@ -0,0 +1,29 @@
@charset "UTF-8";
/// Provides an easy way to change the `word-wrap` property.
///
/// @param {String} $wrap [break-word]
/// Value for the `word-break` property.
///
/// @example scss - Usage
/// .wrapper {
/// @include word-wrap(break-word);
/// }
///
/// @example css - CSS Output
/// .wrapper {
/// overflow-wrap: break-word;
/// word-break: break-all;
/// word-wrap: break-word;
/// }
@mixin word-wrap($wrap: break-word) {
overflow-wrap: $wrap;
word-wrap: $wrap;
@if $wrap == break-word {
word-break: break-all;
} @else {
word-break: $wrap;
}
}

View file

@ -0,0 +1,43 @@
// http://www.w3.org/TR/css3-animations/#the-animation-name-property-
// Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties.
@mixin animation($animations...) {
@include prefixer(animation, $animations, webkit moz spec);
}
@mixin animation-name($names...) {
@include prefixer(animation-name, $names, webkit moz spec);
}
@mixin animation-duration($times...) {
@include prefixer(animation-duration, $times, webkit moz spec);
}
@mixin animation-timing-function($motions...) {
// ease | linear | ease-in | ease-out | ease-in-out
@include prefixer(animation-timing-function, $motions, webkit moz spec);
}
@mixin animation-iteration-count($values...) {
// infinite | <number>
@include prefixer(animation-iteration-count, $values, webkit moz spec);
}
@mixin animation-direction($directions...) {
// normal | alternate
@include prefixer(animation-direction, $directions, webkit moz spec);
}
@mixin animation-play-state($states...) {
// running | paused
@include prefixer(animation-play-state, $states, webkit moz spec);
}
@mixin animation-delay($times...) {
@include prefixer(animation-delay, $times, webkit moz spec);
}
@mixin animation-fill-mode($modes...) {
// none | forwards | backwards | both
@include prefixer(animation-fill-mode, $modes, webkit moz spec);
}

View file

@ -0,0 +1,3 @@
@mixin appearance($value) {
@include prefixer(appearance, $value, webkit moz ms o spec);
}

View file

@ -0,0 +1,3 @@
@mixin backface-visibility($visibility) {
@include prefixer(backface-visibility, $visibility, webkit spec);
}

View file

@ -0,0 +1,42 @@
//************************************************************************//
// Background-image property for adding multiple background images with
// gradients, or for stringing multiple gradients together.
//************************************************************************//
@mixin background-image($images...) {
$webkit-images: ();
$spec-images: ();
@each $image in $images {
$webkit-image: ();
$spec-image: ();
@if (type-of($image) == string) {
$url-str: str-slice($image, 1, 3);
$gradient-type: str-slice($image, 1, 6);
@if $url-str == "url" {
$webkit-image: $image;
$spec-image: $image;
}
@else if $gradient-type == "linear" {
$gradients: _linear-gradient-parser($image);
$webkit-image: map-get($gradients, webkit-image);
$spec-image: map-get($gradients, spec-image);
}
@else if $gradient-type == "radial" {
$gradients: _radial-gradient-parser($image);
$webkit-image: map-get($gradients, webkit-image);
$spec-image: map-get($gradients, spec-image);
}
}
$webkit-images: append($webkit-images, $webkit-image, comma);
$spec-images: append($spec-images, $spec-image, comma);
}
background-image: $webkit-images;
background-image: $spec-images;
}

View file

@ -0,0 +1,55 @@
//************************************************************************//
// Background property for adding multiple backgrounds using shorthand
// notation.
//************************************************************************//
@mixin background($backgrounds...) {
$webkit-backgrounds: ();
$spec-backgrounds: ();
@each $background in $backgrounds {
$webkit-background: ();
$spec-background: ();
$background-type: type-of($background);
@if $background-type == string or $background-type == list {
$background-str: if($background-type == list, nth($background, 1), $background);
$url-str: str-slice($background-str, 1, 3);
$gradient-type: str-slice($background-str, 1, 6);
@if $url-str == "url" {
$webkit-background: $background;
$spec-background: $background;
}
@else if $gradient-type == "linear" {
$gradients: _linear-gradient-parser("#{$background}");
$webkit-background: map-get($gradients, webkit-image);
$spec-background: map-get($gradients, spec-image);
}
@else if $gradient-type == "radial" {
$gradients: _radial-gradient-parser("#{$background}");
$webkit-background: map-get($gradients, webkit-image);
$spec-background: map-get($gradients, spec-image);
}
@else {
$webkit-background: $background;
$spec-background: $background;
}
}
@else {
$webkit-background: $background;
$spec-background: $background;
}
$webkit-backgrounds: append($webkit-backgrounds, $webkit-background, comma);
$spec-backgrounds: append($spec-backgrounds, $spec-background, comma);
}
background: $webkit-backgrounds;
background: $spec-backgrounds;
}

View file

@ -0,0 +1,59 @@
@mixin border-image($borders...) {
$webkit-borders: ();
$spec-borders: ();
@each $border in $borders {
$webkit-border: ();
$spec-border: ();
$border-type: type-of($border);
@if $border-type == string or list {
$border-str: if($border-type == list, nth($border, 1), $border);
$url-str: str-slice($border-str, 1, 3);
$gradient-type: str-slice($border-str, 1, 6);
@if $url-str == "url" {
$webkit-border: $border;
$spec-border: $border;
}
@else if $gradient-type == "linear" {
$gradients: _linear-gradient-parser("#{$border}");
$webkit-border: map-get($gradients, webkit-image);
$spec-border: map-get($gradients, spec-image);
}
@else if $gradient-type == "radial" {
$gradients: _radial-gradient-parser("#{$border}");
$webkit-border: map-get($gradients, webkit-image);
$spec-border: map-get($gradients, spec-image);
}
@else {
$webkit-border: $border;
$spec-border: $border;
}
}
@else {
$webkit-border: $border;
$spec-border: $border;
}
$webkit-borders: append($webkit-borders, $webkit-border, comma);
$spec-borders: append($spec-borders, $spec-border, comma);
}
-webkit-border-image: $webkit-borders;
border-image: $spec-borders;
border-style: solid;
}
//Examples:
// @include border-image(url("image.png"));
// @include border-image(url("image.png") 20 stretch);
// @include border-image(linear-gradient(45deg, orange, yellow));
// @include border-image(linear-gradient(45deg, orange, yellow) stretch);
// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round);
// @include border-image(radial-gradient(top, cover, orange, yellow, orange));

View file

@ -0,0 +1,4 @@
@mixin calc($property, $value) {
#{$property}: -webkit-calc(#{$value});
#{$property}: calc(#{$value});
}

View file

@ -0,0 +1,47 @@
@mixin columns($arg: auto) {
// <column-count> || <column-width>
@include prefixer(columns, $arg, webkit moz spec);
}
@mixin column-count($int: auto) {
// auto || integer
@include prefixer(column-count, $int, webkit moz spec);
}
@mixin column-gap($length: normal) {
// normal || length
@include prefixer(column-gap, $length, webkit moz spec);
}
@mixin column-fill($arg: auto) {
// auto || length
@include prefixer(column-fill, $arg, webkit moz spec);
}
@mixin column-rule($arg) {
// <border-width> || <border-style> || <color>
@include prefixer(column-rule, $arg, webkit moz spec);
}
@mixin column-rule-color($color) {
@include prefixer(column-rule-color, $color, webkit moz spec);
}
@mixin column-rule-style($style: none) {
// none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid
@include prefixer(column-rule-style, $style, webkit moz spec);
}
@mixin column-rule-width ($width: none) {
@include prefixer(column-rule-width, $width, webkit moz spec);
}
@mixin column-span($arg: none) {
// none || all
@include prefixer(column-span, $arg, webkit moz spec);
}
@mixin column-width($length: auto) {
// auto || length
@include prefixer(column-width, $length, webkit moz spec);
}

View file

@ -0,0 +1,4 @@
@mixin filter($function: none) {
// <filter-function> [<filter-function]* | none
@include prefixer(filter, $function, webkit spec);
}

View file

@ -0,0 +1,287 @@
// CSS3 Flexible Box Model and property defaults
// Custom shorthand notation for flexbox
@mixin box($orient: inline-axis, $pack: start, $align: stretch) {
@include display-box;
@include box-orient($orient);
@include box-pack($pack);
@include box-align($align);
}
@mixin display-box {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox; // IE 10
display: box;
}
@mixin box-orient($orient: inline-axis) {
// horizontal|vertical|inline-axis|block-axis|inherit
@include prefixer(box-orient, $orient, webkit moz spec);
}
@mixin box-pack($pack: start) {
// start|end|center|justify
@include prefixer(box-pack, $pack, webkit moz spec);
-ms-flex-pack: $pack; // IE 10
}
@mixin box-align($align: stretch) {
// start|end|center|baseline|stretch
@include prefixer(box-align, $align, webkit moz spec);
-ms-flex-align: $align; // IE 10
}
@mixin box-direction($direction: normal) {
// normal|reverse|inherit
@include prefixer(box-direction, $direction, webkit moz spec);
-ms-flex-direction: $direction; // IE 10
}
@mixin box-lines($lines: single) {
// single|multiple
@include prefixer(box-lines, $lines, webkit moz spec);
}
@mixin box-ordinal-group($int: 1) {
@include prefixer(box-ordinal-group, $int, webkit moz spec);
-ms-flex-order: $int; // IE 10
}
@mixin box-flex($value: 0) {
@include prefixer(box-flex, $value, webkit moz spec);
-ms-flex: $value; // IE 10
}
@mixin box-flex-group($int: 1) {
@include prefixer(box-flex-group, $int, webkit moz spec);
}
// CSS3 Flexible Box Model and property defaults
// Unified attributes for 2009, 2011, and 2012 flavours.
// 2009 - display (box | inline-box)
// 2011 - display (flexbox | inline-flexbox)
// 2012 - display (flex | inline-flex)
@mixin display($value) {
// flex | inline-flex
@if $value == "flex" {
// 2009
display: -webkit-box;
display: -moz-box;
display: box;
// 2012
display: -webkit-flex;
display: -moz-flex;
display: -ms-flexbox; // 2011 (IE 10)
display: flex;
} @else if $value == "inline-flex" {
display: -webkit-inline-box;
display: -moz-inline-box;
display: inline-box;
display: -webkit-inline-flex;
display: -moz-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
} @else {
display: $value;
}
}
// 2009 - box-flex (integer)
// 2011 - flex (decimal | width decimal)
// 2012 - flex (integer integer width)
@mixin flex($value) {
// Grab flex-grow for older browsers.
$flex-grow: nth($value, 1);
// 2009
@include prefixer(box-flex, $flex-grow, webkit moz spec);
// 2011 (IE 10), 2012
@include prefixer(flex, $value, webkit moz ms spec);
}
// 2009 - box-orient ( horizontal | vertical | inline-axis | block-axis)
// - box-direction (normal | reverse)
// 2011 - flex-direction (row | row-reverse | column | column-reverse)
// 2012 - flex-direction (row | row-reverse | column | column-reverse)
@mixin flex-direction($value: row) {
// Alt values.
$value-2009: $value;
$value-2011: $value;
$direction: normal;
@if $value == row {
$value-2009: horizontal;
} @else if $value == "row-reverse" {
$value-2009: horizontal;
$direction: reverse;
} @else if $value == column {
$value-2009: vertical;
} @else if $value == "column-reverse" {
$value-2009: vertical;
$direction: reverse;
}
// 2009
@include prefixer(box-orient, $value-2009, webkit moz spec);
@include prefixer(box-direction, $direction, webkit moz spec);
// 2012
@include prefixer(flex-direction, $value, webkit moz spec);
// 2011 (IE 10)
-ms-flex-direction: $value;
}
// 2009 - box-lines (single | multiple)
// 2011 - flex-wrap (nowrap | wrap | wrap-reverse)
// 2012 - flex-wrap (nowrap | wrap | wrap-reverse)
@mixin flex-wrap($value: nowrap) {
// Alt values
$alt-value: $value;
@if $value == nowrap {
$alt-value: single;
} @else if $value == wrap {
$alt-value: multiple;
} @else if $value == "wrap-reverse" {
$alt-value: multiple;
}
@include prefixer(box-lines, $alt-value, webkit moz spec);
@include prefixer(flex-wrap, $value, webkit moz ms spec);
}
// 2009 - TODO: parse values into flex-direction/flex-wrap
// 2011 - TODO: parse values into flex-direction/flex-wrap
// 2012 - flex-flow (flex-direction || flex-wrap)
@mixin flex-flow($value) {
@include prefixer(flex-flow, $value, webkit moz spec);
}
// 2009 - box-ordinal-group (integer)
// 2011 - flex-order (integer)
// 2012 - order (integer)
@mixin order($int: 0) {
// 2009
@include prefixer(box-ordinal-group, $int, webkit moz spec);
// 2012
@include prefixer(order, $int, webkit moz spec);
// 2011 (IE 10)
-ms-flex-order: $int;
}
// 2012 - flex-grow (number)
@mixin flex-grow($number: 0) {
@include prefixer(flex-grow, $number, webkit moz spec);
-ms-flex-positive: $number;
}
// 2012 - flex-shrink (number)
@mixin flex-shrink($number: 1) {
@include prefixer(flex-shrink, $number, webkit moz spec);
-ms-flex-negative: $number;
}
// 2012 - flex-basis (number)
@mixin flex-basis($width: auto) {
@include prefixer(flex-basis, $width, webkit moz spec);
-ms-flex-preferred-size: $width;
}
// 2009 - box-pack (start | end | center | justify)
// 2011 - flex-pack (start | end | center | justify)
// 2012 - justify-content (flex-start | flex-end | center | space-between | space-around)
@mixin justify-content($value: flex-start) {
// Alt values.
$alt-value: $value;
@if $value == "flex-start" {
$alt-value: start;
} @else if $value == "flex-end" {
$alt-value: end;
} @else if $value == "space-between" {
$alt-value: justify;
} @else if $value == "space-around" {
$alt-value: distribute;
}
// 2009
@include prefixer(box-pack, $alt-value, webkit moz spec);
// 2012
@include prefixer(justify-content, $value, webkit moz ms o spec);
// 2011 (IE 10)
-ms-flex-pack: $alt-value;
}
// 2009 - box-align (start | end | center | baseline | stretch)
// 2011 - flex-align (start | end | center | baseline | stretch)
// 2012 - align-items (flex-start | flex-end | center | baseline | stretch)
@mixin align-items($value: stretch) {
$alt-value: $value;
@if $value == "flex-start" {
$alt-value: start;
} @else if $value == "flex-end" {
$alt-value: end;
}
// 2009
@include prefixer(box-align, $alt-value, webkit moz spec);
// 2012
@include prefixer(align-items, $value, webkit moz ms o spec);
// 2011 (IE 10)
-ms-flex-align: $alt-value;
}
// 2011 - flex-item-align (auto | start | end | center | baseline | stretch)
// 2012 - align-self (auto | flex-start | flex-end | center | baseline | stretch)
@mixin align-self($value: auto) {
$value-2011: $value;
@if $value == "flex-start" {
$value-2011: start;
} @else if $value == "flex-end" {
$value-2011: end;
}
// 2012
@include prefixer(align-self, $value, webkit moz spec);
// 2011 (IE 10)
-ms-flex-item-align: $value-2011;
}
// 2011 - flex-line-pack (start | end | center | justify | distribute | stretch)
// 2012 - align-content (flex-start | flex-end | center | space-between | space-around | stretch)
@mixin align-content($value: stretch) {
$value-2011: $value;
@if $value == "flex-start" {
$value-2011: start;
} @else if $value == "flex-end" {
$value-2011: end;
} @else if $value == "space-between" {
$value-2011: justify;
} @else if $value == "space-around" {
$value-2011: distribute;
}
// 2012
@include prefixer(align-content, $value, webkit moz spec);
// 2011 (IE 10)
-ms-flex-line-pack: $value-2011;
}

View file

@ -0,0 +1,24 @@
@mixin font-face(
$font-family,
$file-path,
$weight: normal,
$style: normal,
$asset-pipeline: $asset-pipeline,
$file-formats: eot woff2 woff ttf svg) {
$font-url-prefix: font-url-prefixer($asset-pipeline);
@font-face {
font-family: $font-family;
font-style: $style;
font-weight: $weight;
src: font-source-declaration(
$font-family,
$file-path,
$asset-pipeline,
$file-formats,
$font-url-prefix
);
}
}

View file

@ -0,0 +1,4 @@
@mixin font-feature-settings($settings...) {
@if length($settings) == 0 { $settings: none; }
@include prefixer(font-feature-settings, $settings, webkit moz ms spec);
}

View file

@ -0,0 +1,10 @@
// HiDPI mixin. Default value set to 1.3 to target Google Nexus 7 (http://bjango.com/articles/min-device-pixel-ratio/)
@mixin hidpi($ratio: 1.3) {
@media only screen and (-webkit-min-device-pixel-ratio: $ratio),
only screen and (min--moz-device-pixel-ratio: $ratio),
only screen and (-o-min-device-pixel-ratio: #{$ratio}/1),
only screen and (min-resolution: round($ratio * 96dpi)),
only screen and (min-resolution: $ratio * 1dppx) {
@content;
}
}

View file

@ -0,0 +1,4 @@
@mixin hyphens($hyphenation: none) {
// none | manual | auto
@include prefixer(hyphens, $hyphenation, webkit moz ms spec);
}

View file

@ -0,0 +1,14 @@
@mixin image-rendering ($mode:auto) {
@if ($mode == crisp-edges) {
-ms-interpolation-mode: nearest-neighbor; // IE8+
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
}
@else {
image-rendering: $mode;
}
}

View file

@ -0,0 +1,36 @@
// Adds keyframes blocks for supported prefixes, removing redundant prefixes in the block's content
@mixin keyframes($name) {
$original-prefix-for-webkit: $prefix-for-webkit;
$original-prefix-for-mozilla: $prefix-for-mozilla;
$original-prefix-for-microsoft: $prefix-for-microsoft;
$original-prefix-for-opera: $prefix-for-opera;
$original-prefix-for-spec: $prefix-for-spec;
@if $original-prefix-for-webkit {
@include disable-prefix-for-all();
$prefix-for-webkit: true !global;
@-webkit-keyframes #{$name} {
@content;
}
}
@if $original-prefix-for-mozilla {
@include disable-prefix-for-all();
$prefix-for-mozilla: true !global;
@-moz-keyframes #{$name} {
@content;
}
}
$prefix-for-webkit: $original-prefix-for-webkit !global;
$prefix-for-mozilla: $original-prefix-for-mozilla !global;
$prefix-for-microsoft: $original-prefix-for-microsoft !global;
$prefix-for-opera: $original-prefix-for-opera !global;
$prefix-for-spec: $original-prefix-for-spec !global;
@if $original-prefix-for-spec {
@keyframes #{$name} {
@content;
}
}
}

View file

@ -0,0 +1,38 @@
@mixin linear-gradient($pos, $g1, $g2: null,
$g3: null, $g4: null,
$g5: null, $g6: null,
$g7: null, $g8: null,
$g9: null, $g10: null,
$fallback: null) {
// Detect what type of value exists in $pos
$pos-type: type-of(nth($pos, 1));
$pos-spec: null;
$pos-degree: null;
// If $pos is missing from mixin, reassign vars and add default position
@if ($pos-type == color) or (nth($pos, 1) == "transparent") {
$g10: $g9; $g9: $g8; $g8: $g7; $g7: $g6; $g6: $g5;
$g5: $g4; $g4: $g3; $g3: $g2; $g2: $g1; $g1: $pos;
$pos: null;
}
@if $pos {
$positions: _linear-positions-parser($pos);
$pos-degree: nth($positions, 1);
$pos-spec: nth($positions, 2);
}
$full: $g1, $g2, $g3, $g4, $g5, $g6, $g7, $g8, $g9, $g10;
// Set $g1 as the default fallback color
$fallback-color: nth($g1, 1);
// If $fallback is a color use that color as the fallback color
@if (type-of($fallback) == color) or ($fallback == "transparent") {
$fallback-color: $fallback;
}
background-color: $fallback-color;
background-image: -webkit-linear-gradient($pos-degree $full); // Safari 5.1+, Chrome
background-image: unquote("linear-gradient(#{$pos-spec}#{$full})");
}

View file

@ -0,0 +1,8 @@
@mixin perspective($depth: none) {
// none | <length>
@include prefixer(perspective, $depth, webkit moz spec);
}
@mixin perspective-origin($value: 50% 50%) {
@include prefixer(perspective-origin, $value, webkit moz spec);
}

View file

@ -0,0 +1,8 @@
@mixin placeholder {
$placeholders: ":-webkit-input" ":-moz" "-moz" "-ms-input";
@each $placeholder in $placeholders {
&:#{$placeholder}-placeholder {
@content;
}
}
}

View file

@ -0,0 +1,39 @@
// Requires Sass 3.1+
@mixin radial-gradient($g1, $g2,
$g3: null, $g4: null,
$g5: null, $g6: null,
$g7: null, $g8: null,
$g9: null, $g10: null,
$pos: null,
$shape-size: null,
$fallback: null) {
$data: _radial-arg-parser($g1, $g2, $pos, $shape-size);
$g1: nth($data, 1);
$g2: nth($data, 2);
$pos: nth($data, 3);
$shape-size: nth($data, 4);
$full: $g1, $g2, $g3, $g4, $g5, $g6, $g7, $g8, $g9, $g10;
// Strip deprecated cover/contain for spec
$shape-size-spec: _shape-size-stripper($shape-size);
// Set $g1 as the default fallback color
$first-color: nth($full, 1);
$fallback-color: nth($first-color, 1);
@if (type-of($fallback) == color) or ($fallback == "transparent") {
$fallback-color: $fallback;
}
// Add Commas and spaces
$shape-size: if($shape-size, "#{$shape-size}, ", null);
$pos: if($pos, "#{$pos}, ", null);
$pos-spec: if($pos, "at #{$pos}", null);
$shape-size-spec: if(($shape-size-spec != " ") and ($pos == null), "#{$shape-size-spec}, ", "#{$shape-size-spec} ");
background-color: $fallback-color;
background-image: -webkit-radial-gradient(#{$pos}#{$shape-size}#{$full});
background-image: radial-gradient(#{$shape-size-spec}#{$pos-spec}#{$full});
}

View file

@ -0,0 +1,42 @@
@charset "UTF-8";
/// Outputs the spec and prefixed versions of the `::selection` pseudo-element.
///
/// @param {Bool} $current-selector [false]
/// If set to `true`, it takes the current element into consideration.
///
/// @example scss - Usage
/// .element {
/// @include selection(true) {
/// background-color: #ffbb52;
/// }
/// }
///
/// @example css - CSS Output
/// .element::-moz-selection {
/// background-color: #ffbb52;
/// }
///
/// .element::selection {
/// background-color: #ffbb52;
/// }
@mixin selection($current-selector: false) {
@if $current-selector {
&::-moz-selection {
@content;
}
&::selection {
@content;
}
} @else {
::-moz-selection {
@content;
}
::selection {
@content;
}
}
}

View file

@ -0,0 +1,19 @@
@mixin text-decoration($value) {
// <text-decoration-line> || <text-decoration-style> || <text-decoration-color>
@include prefixer(text-decoration, $value, moz);
}
@mixin text-decoration-line($line: none) {
// none || underline || overline || line-through
@include prefixer(text-decoration-line, $line, moz);
}
@mixin text-decoration-style($style: solid) {
// solid || double || dotted || dashed || wavy
@include prefixer(text-decoration-style, $style, moz webkit);
}
@mixin text-decoration-color($color: currentColor) {
// currentColor || <color>
@include prefixer(text-decoration-color, $color, moz);
}

View file

@ -0,0 +1,15 @@
@mixin transform($property: none) {
// none | <transform-function>
@include prefixer(transform, $property, webkit moz ms o spec);
}
@mixin transform-origin($axes: 50%) {
// x-axis - left | center | right | length | %
// y-axis - top | center | bottom | length | %
// z-axis - length
@include prefixer(transform-origin, $axes, webkit moz ms o spec);
}
@mixin transform-style($style: flat) {
@include prefixer(transform-style, $style, webkit moz ms o spec);
}

View file

@ -0,0 +1,71 @@
// Shorthand mixin. Supports multiple parentheses-deliminated values for each variable.
// Example: @include transition (all 2s ease-in-out);
// @include transition (opacity 1s ease-in 2s, width 2s ease-out);
// @include transition-property (transform, opacity);
@mixin transition($properties...) {
// Fix for vendor-prefix transform property
$needs-prefixes: false;
$webkit: ();
$moz: ();
$spec: ();
// Create lists for vendor-prefixed transform
@each $list in $properties {
@if nth($list, 1) == "transform" {
$needs-prefixes: true;
$list1: -webkit-transform;
$list2: -moz-transform;
$list3: ();
@each $var in $list {
$list3: join($list3, $var);
@if $var != "transform" {
$list1: join($list1, $var);
$list2: join($list2, $var);
}
}
$webkit: append($webkit, $list1);
$moz: append($moz, $list2);
$spec: append($spec, $list3);
} @else {
$webkit: append($webkit, $list, comma);
$moz: append($moz, $list, comma);
$spec: append($spec, $list, comma);
}
}
@if $needs-prefixes {
-webkit-transition: $webkit;
-moz-transition: $moz;
transition: $spec;
} @else {
@if length($properties) >= 1 {
@include prefixer(transition, $properties, webkit moz spec);
} @else {
$properties: all 0.15s ease-out 0s;
@include prefixer(transition, $properties, webkit moz spec);
}
}
}
@mixin transition-property($properties...) {
-webkit-transition-property: transition-property-names($properties, "webkit");
-moz-transition-property: transition-property-names($properties, "moz");
transition-property: transition-property-names($properties, false);
}
@mixin transition-duration($times...) {
@include prefixer(transition-duration, $times, webkit moz spec);
}
@mixin transition-timing-function($motions...) {
// ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier()
@include prefixer(transition-timing-function, $motions, webkit moz spec);
}
@mixin transition-delay($times...) {
@include prefixer(transition-delay, $times, webkit moz spec);
}

View file

@ -0,0 +1,3 @@
@mixin user-select($value: none) {
@include prefixer(user-select, $value, webkit moz ms spec);
}

View file

@ -0,0 +1,11 @@
@function assign-inputs($inputs, $pseudo: null) {
$list: ();
@each $input in $inputs {
$input: unquote($input);
$input: if($pseudo, $input + ":" + $pseudo, $input);
$list: append($list, $input, comma);
}
@return $list;
}

View file

@ -0,0 +1,20 @@
@charset "UTF-8";
/// Checks if a list does not contains a value.
///
/// @access private
///
/// @param {List} $list
/// The list to check against.
///
/// @return {Bool}
@function contains-falsy($list) {
@each $item in $list {
@if not $item {
@return true;
}
}
@return false;
}

View file

@ -0,0 +1,26 @@
@charset "UTF-8";
/// Checks if a list contains a value(s).
///
/// @access private
///
/// @param {List} $list
/// The list to check against.
///
/// @param {List} $values
/// A single value or list of values to check for.
///
/// @example scss - Usage
/// contains($list, $value)
///
/// @return {Bool}
@function contains($list, $values...) {
@each $value in $values {
@if type-of(index($list, $value)) != "number" {
@return false;
}
}
@return true;
}

View file

@ -0,0 +1,11 @@
@charset "UTF-8";
/// Checks for a valid CSS length.
///
/// @param {String} $value
@function is-length($value) {
@return type-of($value) != "null" and (str-slice($value + "", 1, 4) == "calc"
or index(auto inherit initial 0, $value)
or (type-of($value) == "number" and not(unitless($value))));
}

View file

@ -0,0 +1,21 @@
@charset "UTF-8";
/// Programatically determines whether a color is light or dark.
///
/// @link http://robots.thoughtbot.com/closer-look-color-lightness
///
/// @param {Color (Hex)} $color
///
/// @example scss - Usage
/// is-light($color)
///
/// @return {Bool}
@function is-light($hex-color) {
$-local-red: red(rgba($hex-color, 1));
$-local-green: green(rgba($hex-color, 1));
$-local-blue: blue(rgba($hex-color, 1));
$-local-lightness: ($-local-red * 0.2126 + $-local-green * 0.7152 + $-local-blue * 0.0722) / 255;
@return $-local-lightness > 0.6;
}

View file

@ -0,0 +1,11 @@
@charset "UTF-8";
/// Checks for a valid number.
///
/// @param {Number} $value
///
/// @require {function} contains
@function is-number($value) {
@return contains("0" "1" "2" "3" "4" "5" "6" "7" "8" "9" 0 1 2 3 4 5 6 7 8 9, $value);
}

View file

@ -0,0 +1,13 @@
@charset "UTF-8";
/// Checks for a valid CSS size.
///
/// @param {String} $value
///
/// @require {function} contains
/// @require {function} is-length
@function is-size($value) {
@return is-length($value)
or contains("fill" "fit-content" "min-content" "max-content", $value);
}

View file

@ -0,0 +1,69 @@
// Scaling Variables
$golden: 1.618;
$minor-second: 1.067;
$major-second: 1.125;
$minor-third: 1.2;
$major-third: 1.25;
$perfect-fourth: 1.333;
$augmented-fourth: 1.414;
$perfect-fifth: 1.5;
$minor-sixth: 1.6;
$major-sixth: 1.667;
$minor-seventh: 1.778;
$major-seventh: 1.875;
$octave: 2;
$major-tenth: 2.5;
$major-eleventh: 2.667;
$major-twelfth: 3;
$double-octave: 4;
$modular-scale-ratio: $perfect-fourth !default;
$modular-scale-base: em($em-base) !default;
@function modular-scale($increment, $value: $modular-scale-base, $ratio: $modular-scale-ratio) {
$v1: nth($value, 1);
$v2: nth($value, length($value));
$value: $v1;
// scale $v2 to just above $v1
@while $v2 > $v1 {
$v2: ($v2 / $ratio); // will be off-by-1
}
@while $v2 < $v1 {
$v2: ($v2 * $ratio); // will fix off-by-1
}
// check AFTER scaling $v2 to prevent double-counting corner-case
$double-stranded: $v2 > $v1;
@if $increment > 0 {
@for $i from 1 through $increment {
@if $double-stranded and ($v1 * $ratio) > $v2 {
$value: $v2;
$v2: ($v2 * $ratio);
} @else {
$v1: ($v1 * $ratio);
$value: $v1;
}
}
}
@if $increment < 0 {
// adjust $v2 to just below $v1
@if $double-stranded {
$v2: ($v2 / $ratio);
}
@for $i from $increment through -1 {
@if $double-stranded and ($v1 / $ratio) < $v2 {
$value: $v2;
$v2: ($v2 / $ratio);
} @else {
$v1: ($v1 / $ratio);
$value: $v1;
}
}
}
@return $value;
}

View file

@ -0,0 +1,13 @@
// Convert pixels to ems
// eg. for a relational value of 12px write em(12) when the parent is 16px
// if the parent is another value say 24px write em(12, 24)
@function em($pxval, $base: $em-base) {
@if not unitless($pxval) {
$pxval: strip-units($pxval);
}
@if not unitless($base) {
$base: strip-units($base);
}
@return ($pxval / $base) * 1em;
}

View file

@ -0,0 +1,15 @@
// Convert pixels to rems
// eg. for a relational value of 12px write rem(12)
// Assumes $em-base is the font-size of <html>
@function rem($pxval) {
@if not unitless($pxval) {
$pxval: strip-units($pxval);
}
$base: $em-base;
@if not unitless($base) {
$base: strip-units($base);
}
@return ($pxval / $base) * 1rem;
}

View file

@ -0,0 +1,24 @@
@charset "UTF-8";
/// Mixes a color with black.
///
/// @param {Color} $color
///
/// @param {Number (Percentage)} $percent
/// The amount of black to be mixed in.
///
/// @example scss - Usage
/// .element {
/// background-color: shade(#ffbb52, 60%);
/// }
///
/// @example css - CSS Output
/// .element {
/// background-color: #664a20;
/// }
///
/// @return {Color}
@function shade($color, $percent) {
@return mix(#000, $color, $percent);
}

View file

@ -0,0 +1,17 @@
@charset "UTF-8";
/// Strips the unit from a number.
///
/// @param {Number (With Unit)} $value
///
/// @example scss - Usage
/// $dimension: strip-units(10em);
///
/// @example css - CSS Output
/// $dimension: 10;
///
/// @return {Number (Unitless)}
@function strip-units($value) {
@return ($value / ($value * 0 + 1));
}

View file

@ -0,0 +1,24 @@
@charset "UTF-8";
/// Mixes a color with white.
///
/// @param {Color} $color
///
/// @param {Number (Percentage)} $percent
/// The amount of white to be mixed in.
///
/// @example scss - Usage
/// .element {
/// background-color: tint(#6ecaa6, 40%);
/// }
///
/// @example css - CSS Output
/// .element {
/// background-color: #a8dfc9;
/// }
///
/// @return {Color}
@function tint($color, $percent) {
@return mix(#fff, $color, $percent);
}

View file

@ -0,0 +1,22 @@
// Return vendor-prefixed property names if appropriate
// Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background
//************************************************************************//
@function transition-property-names($props, $vendor: false) {
$new-props: ();
@each $prop in $props {
$new-props: append($new-props, transition-property-name($prop, $vendor), comma);
}
@return $new-props;
}
@function transition-property-name($prop, $vendor: false) {
// put other properties that need to be prefixed here aswell
@if $vendor and $prop == transform {
@return unquote('-'+$vendor+'-'+$prop);
}
@else {
@return $prop;
}
}

View file

@ -0,0 +1,27 @@
@charset "UTF-8";
/// Converts shorthand to the 4-value syntax.
///
/// @param {List} $shorthand
///
/// @example scss - Usage
/// .element {
/// margin: unpack(1em 2em);
/// }
///
/// @example css - CSS Output
/// .element {
/// margin: 1em 2em 1em 2em;
/// }
@function unpack($shorthand) {
@if length($shorthand) == 1 {
@return nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1);
} @else if length($shorthand) == 2 {
@return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 1) nth($shorthand, 2);
} @else if length($shorthand) == 3 {
@return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 3) nth($shorthand, 2);
} @else {
@return $shorthand;
}
}

View file

@ -0,0 +1,21 @@
//************************************************************************//
// Helper function for str-to-num fn.
// Source: http://sassmeister.com/gist/9647408
//************************************************************************//
@function _convert-units($number, $unit) {
$strings: "px", "cm", "mm", "%", "ch", "pica", "in", "em", "rem", "pt", "pc", "ex", "vw", "vh", "vmin", "vmax", "deg", "rad", "grad", "turn";
$units: 1px, 1cm, 1mm, 1%, 1ch, 1pica, 1in, 1em, 1rem, 1pt, 1pc, 1ex, 1vw, 1vh, 1vmin, 1vmax, 1deg, 1rad, 1grad, 1turn;
$index: index($strings, $unit);
@if not $index {
@warn "Unknown unit `#{$unit}`.";
@return false;
}
@if type-of($number) != "number" {
@warn "`#{$number} is not a number`";
@return false;
}
@return $number * nth($units, $index);
}

View file

@ -0,0 +1,96 @@
@charset "UTF-8";
/// Directional-property mixins are shorthands for writing properties like the following
///
/// @ignore You can also use `false` instead of `null`.
///
/// @param {List} $vals
/// List of directional values
///
/// @example scss - Usage
/// .element {
/// @include border-style(dotted null);
/// @include margin(null 0 10px);
/// }
///
/// @example css - CSS Output
/// .element {
/// border-bottom-style: dotted;
/// border-top-style: dotted;
/// margin-bottom: 10px;
/// margin-left: 0;
/// margin-right: 0;
/// }
///
/// @require {function} contains-falsy
///
/// @return {List}
@function collapse-directionals($vals) {
$output: null;
$a: nth($vals, 1);
$b: if(length($vals) < 2, $a, nth($vals, 2));
$c: if(length($vals) < 3, $a, nth($vals, 3));
$d: if(length($vals) < 2, $a, nth($vals, if(length($vals) < 4, 2, 4)));
@if $a == 0 { $a: 0; }
@if $b == 0 { $b: 0; }
@if $c == 0 { $c: 0; }
@if $d == 0 { $d: 0; }
@if $a == $b and $a == $c and $a == $d { $output: $a; }
@else if $a == $c and $b == $d { $output: $a $b; }
@else if $b == $d { $output: $a $b $c; }
@else { $output: $a $b $c $d; }
@return $output;
}
/// Output directional properties, for instance `margin`.
///
/// @access private
///
/// @param {String} $pre
/// Prefix to use
/// @param {String} $suf
/// Suffix to use
/// @param {List} $vals
/// List of values
///
/// @require {function} collapse-directionals
/// @require {function} contains-falsy
@mixin directional-property($pre, $suf, $vals) {
// Property Names
$top: $pre + "-top" + if($suf, "-#{$suf}", "");
$bottom: $pre + "-bottom" + if($suf, "-#{$suf}", "");
$left: $pre + "-left" + if($suf, "-#{$suf}", "");
$right: $pre + "-right" + if($suf, "-#{$suf}", "");
$all: $pre + if($suf, "-#{$suf}", "");
$vals: collapse-directionals($vals);
@if contains-falsy($vals) {
@if nth($vals, 1) { #{$top}: nth($vals, 1); }
@if length($vals) == 1 {
@if nth($vals, 1) { #{$right}: nth($vals, 1); }
} @else {
@if nth($vals, 2) { #{$right}: nth($vals, 2); }
}
@if length($vals) == 2 {
@if nth($vals, 1) { #{$bottom}: nth($vals, 1); }
@if nth($vals, 2) { #{$left}: nth($vals, 2); }
} @else if length($vals) == 3 {
@if nth($vals, 3) { #{$bottom}: nth($vals, 3); }
@if nth($vals, 2) { #{$left}: nth($vals, 2); }
} @else if length($vals) == 4 {
@if nth($vals, 3) { #{$bottom}: nth($vals, 3); }
@if nth($vals, 4) { #{$left}: nth($vals, 4); }
}
} @else {
#{$all}: $vals;
}
}

View file

@ -0,0 +1,43 @@
// Used for creating the source string for fonts using @font-face
// Reference: http://goo.gl/Ru1bKP
@function font-url-prefixer($asset-pipeline) {
@if $asset-pipeline == true {
@return font-url;
} @else {
@return url;
}
}
@function font-source-declaration(
$font-family,
$file-path,
$asset-pipeline,
$file-formats,
$font-url) {
$src: ();
$formats-map: (
eot: "#{$file-path}.eot?#iefix" format("embedded-opentype"),
woff2: "#{$file-path}.woff2" format("woff2"),
woff: "#{$file-path}.woff" format("woff"),
ttf: "#{$file-path}.ttf" format("truetype"),
svg: "#{$file-path}.svg##{$font-family}" format("svg")
);
@each $key, $values in $formats-map {
@if contains($file-formats, $key) {
$file-path: nth($values, 1);
$font-format: nth($values, 2);
@if $asset-pipeline == true {
$src: append($src, font-url($file-path) $font-format, comma);
} @else {
$src: append($src, url($file-path) $font-format, comma);
}
}
}
@return $src;
}

Some files were not shown because too many files have changed in this diff Show more