├── .babelrc ├── .editorconfig ├── .gitignore ├── .gitmodules ├── GitHelper.php ├── README.md ├── fields.php ├── gitlog-field.png ├── gitrevisions-field.gif ├── gitrevisions-field.png ├── hooks.php ├── index.css ├── index.js ├── index.php ├── package-lock.json ├── package.json └── src ├── fields ├── git-log.vue └── git-revisions.vue ├── main.js ├── siteMethods └── git.php └── use └── values-push.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env" 4 | ] 5 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = false 9 | insert_final_newline = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Git.php"] 2 | path = Git.php 3 | url = https://github.com/kbjr/Git.php.git 4 | -------------------------------------------------------------------------------- /GitHelper.php: -------------------------------------------------------------------------------- 1 | isInitialized = True; 25 | 26 | $this->user = kirby()->user()->name() ?? kirby()->user()->email();; 27 | $dir = option('wottpal.git.dir'); 28 | $this->path = kirby()->roots()->$dir(); 29 | $this->branch = option('wottpal.git.branch'); 30 | $this->shouldPull = option('wottpal.git.shouldPull'); 31 | $this->shouldPush = option('wottpal.git.shouldPush'); 32 | $this->shouldCommit = option('wottpal.git.shouldCommit'); 33 | $this->userHooks = option('wottpal.git.userHooks'); 34 | $this->gitBin = option('wottpal.git.gitBin'); 35 | $this->windowsMode = option('wottpal.git.windowsMode'); 36 | $this->debug = option('wottpal.git.debug'); 37 | $this->logFile = kirby()->roots()->index() . DS . option('wottpal.git.logFile'); 38 | } 39 | 40 | 41 | /** 42 | * Returns `true` if user-hooks are enabled in the config 43 | * IMPORTANT: It's a security risk to put sensible user-data under version-control. 44 | */ 45 | public function userHooksEnabled() { 46 | if (!$this->isInitialized) $this->initOptions(); 47 | return $this->userHooks; 48 | } 49 | 50 | 51 | /** 52 | * Returns the given path 53 | */ 54 | public function path() { 55 | if (!$this->isInitialized) $this->initOptions(); 56 | return $this->path; 57 | } 58 | 59 | 60 | /** 61 | * Initializes the Git.php repository object. 62 | */ 63 | private function initRepo() 64 | { 65 | if ($this->repo) return true; 66 | if (!$this->isInitialized) $this->initOptions(); 67 | 68 | if (!class_exists("Git")) { 69 | if (file_exists(__DIR__ . DS . 'Git.php' . DS. 'Git.php')) { 70 | require __DIR__ . DS . 'Git.php' . DS. 'Git.php'; 71 | } 72 | } 73 | 74 | if (!class_exists("Git")) { 75 | throw new Exception('Git class not found. Is the Git.php submodule installed?'); 76 | } 77 | 78 | if ($this->gitBin) { 79 | Git::set_bin($this->gitBin); 80 | } 81 | 82 | if ($this->windowsMode) { 83 | Git::windows_mode(); 84 | } 85 | 86 | $this->repo = Git::open($this->path); 87 | 88 | if (!$this->repo->test_git()) { 89 | throw new Exception('System Git could not be found or is not working properly. ' . Git::get_bin()); 90 | // trigger_error('git could not be found or is not working properly. ' . Git::get_bin()); 91 | } 92 | } 93 | 94 | 95 | /** 96 | * Returns the Git.php repository. 97 | */ 98 | public function getRepo() { 99 | if ($this->repo == null) $this->initRepo(); 100 | 101 | return $this->repo; 102 | } 103 | 104 | 105 | /** 106 | * Commits to the repository. 107 | */ 108 | public function commit($message) { 109 | $this->getRepo()->add('-A'); 110 | $this->getRepo()->commit($message); 111 | } 112 | 113 | 114 | /** 115 | * Pushes to the remote repository. 116 | */ 117 | public function push() { 118 | $this->getRepo()->push('origin', $this->branch); 119 | } 120 | 121 | 122 | /** 123 | * Pulls from the remote repository. 124 | */ 125 | public function pull() 126 | { 127 | $this->getRepo()->pull('origin', $this->branch); 128 | } 129 | 130 | 131 | /** 132 | * Returns `true` if there are changes to commit. 133 | */ 134 | public function hasChangesToCommit() { 135 | $result = $this->getRepo()->run('status --porcelain'); 136 | // Didn't show new dirs 137 | // $result = $this->getRepo()->run('ls-files -m'); 138 | return $result !== ''; 139 | } 140 | 141 | 142 | /** 143 | * Called from a Kirby-hook when content got changed. 144 | */ 145 | public function changeHandler($message) 146 | { 147 | if (!$this->isInitialized) $this->initOptions(); 148 | 149 | try { 150 | if ($this->branch) $this->getRepo()->checkout($this->branch); 151 | if ($this->shouldPull) $this->pull(); 152 | if ($this->shouldCommit && $this->hasChangesToCommit()) $this->commit("{$message}\nBy: {$this->user}", true); 153 | if ($this->shouldPush) $this->push(); 154 | 155 | if (!$this->hasChangesToCommit() && $this->shouldCommit) $this->log('Hook fired but no changes'); 156 | elseif ($this->shouldCommit) $this->log('Committed successfully'); 157 | 158 | } catch(Exception $exception) { 159 | $errorMessage = 'Unable to update git: ' . $exception->getMessage(); 160 | 161 | $this->log($errorMessage); 162 | // throw new Exception($errorMessage); 163 | } 164 | } 165 | 166 | 167 | /** 168 | * Creates a new log-message in the log-file. 169 | */ 170 | private function log($message) { 171 | if (!$this->isInitialized) $this->initOptions(); 172 | if (!$this->debug) return; 173 | 174 | $date = date("Y-m-d, H:i", time()); 175 | $message = "[{$date}] {$message}\n"; 176 | 177 | file_put_contents($this->logFile, $message, FILE_APPEND); 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kirby Git 2 | 3 | ![MIT](https://img.shields.io/badge/Kirby-3-green.svg) 4 | [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/wottpal/kirby-anchor-headings/master/LICENSE) 5 | 6 | This is a proof-of-concept of adding automatic version control via hooks to Kirby 3. It's based on [this](https://github.com/blankogmbh/kirby-git-commit-and-push-content) Kirby 2 plugin by @pascalmh and @blankogmbh. 7 | 8 | 9 | ## Usage 10 | 11 | Just put it in your `/site/plugins` folder. If you install it as a submodule or just clone it from Github don't forget to initialize it's submodules `git 12 | submodule update --init --recursive`. 13 | 14 | 15 | ## Options 16 | Set these in your `config.php` prefixed with `wottpal.git.` 17 | 18 | ```php 19 | 'dir' => 'index', // * 20 | 'branch' => 'master', 21 | 'shouldPull' => false, 22 | 'shouldPush' => false, 23 | 'shouldCommit' => false, 24 | 'userHooks' => false, // ** 25 | 'gitBin' => '', 26 | 'windowsMode' => false, 27 | 'debug' => false, 28 | 'logFile' => 'git-log.txt' 29 | ``` 30 | 31 | \* Any value accessible via `kirby()->roots()`. You can for example only have your `content` folder under version-control or as a submodule. 32 | 33 | \*\* Only enable this if you know what you are doing. Putting sensible information (like account-data) under version-control is a security risk. If enabling this you should also remove `site/accounts` from your `.gitignore`. 34 | 35 | ## Log-Field 36 | 37 | A field named `gitLog` is included in the plugin which shows your whole Edit History. By setting `kirbyOnly` to `false` you can include developer-commits in the list as well. 38 | 39 | ```yaml 40 | revisions: 41 | type: gitLog 42 | label: History 43 | limit: 5 44 | kirbyOnly: true 45 | ``` 46 | 47 | ![Git Log Field](gitlog-field.png) 48 | 49 | ## Revisions-Field 50 | 51 | A field named `gitRevisions` is included in the plugin which shows you all commits where the current page was edited and makes it possible to revert changes to a specific commit. 52 | 53 | Important: With the current state you need to define all fieldnames of which you want the content to be changed. 54 | 55 | ```yaml 56 | log: 57 | type: gitRevisions 58 | label: Revisions 59 | fields: 60 | - title 61 | - text 62 | limit: 5 63 | columns: 64 | - author 65 | - hash 66 | - message 67 | ``` 68 | 69 | ![Git Revisions Field](gitrevisions-field.png) 70 | 71 | 72 | # ToDo 73 | 74 | - [ ] More conservative error-handling 75 | - [ ] DRY `fields.php` 76 | - [ ] Improve debug-logging 77 | 78 | 79 | # Contributing 80 | ## Build instructions 81 | 82 | First, install development dependencies: `npm i` 83 | 84 | - Development build: `npm run dev` 85 | - Production build: `npm run build` 86 | -------------------------------------------------------------------------------- /fields.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'computed' => [ 8 | 9 | 'gitLog' => function () { 10 | // Gather all commits and format as valid JSOn 11 | $log = site()->git()->getRepo()->log('{%n \"hash\": \"%h\",%n \"date\": \"%at\",%n \"message\": \"%s\",%n \"author\": \"%an\"},'); 12 | $log = rtrim($log,","); 13 | $log = "[{$log}]"; 14 | $log = json_decode($log, true); 15 | 16 | foreach($log as $idx => $commit) { 17 | 18 | // Format date 19 | $date = $commit['date']; 20 | $log[$idx]['dateFormatted'] = date("Y-m-d, H:i", $date); 21 | 22 | } 23 | 24 | return $log; 25 | } 26 | 27 | ] 28 | ], 29 | 30 | 31 | 'gitRevisions' => [ 32 | 'computed' => [ 33 | 34 | // 'blueprintFields' => function () { 35 | // return $this->model()->blueprint(); 36 | // }, 37 | 38 | 'gitRevisions' => function () { 39 | $gitHelper = site()->git(); 40 | $parent = $this->model(); 41 | 42 | // Get the relative content file path 43 | $contentFile = $parent->contentFile(); 44 | $contentFile = substr($contentFile, strlen($gitHelper->path()) + 1); 45 | 46 | // Get all commits where the content file was modified 47 | $commitFormat = '{%n \"hash\": \"%h\",%n \"date\" : \"%at\"%n,%n \"message\": \"%s\",%n \"author\": \"%an\"},'; 48 | $logCommand = 'log --follow --name-only --pretty=format:"' . $commitFormat . '" -- ' . $contentFile; 49 | $revisions = $gitHelper->getRepo()->run($logCommand); 50 | 51 | // Gather all former paths/filenames 52 | $matches = []; 53 | $pattern = '/\},\s*(.*)\s*/'; 54 | preg_match_all($pattern, $revisions, $matches, PREG_SET_ORDER); 55 | $revisions = preg_replace($pattern, '},' , $revisions); 56 | 57 | // Format as valid Object 58 | $revisions = rtrim($revisions,","); 59 | $revisions = "[{$revisions}]"; 60 | $revisions = json_decode($revisions, true); 61 | 62 | foreach($revisions as $idx => $revision) { 63 | // Map gathered names 64 | $formerContentFile = $matches[$idx][1]; 65 | $revisions[$idx]['path'] = $formerContentFile; 66 | 67 | // Format date 68 | $date = $revision['date']; 69 | $revisions[$idx]['dateFormatted'] = date("Y-m-d, H:i", $date); 70 | 71 | // Get (former) template from filename 72 | $formerTemplate = basename(basename($formerContentFile, ".txt"), ".md"); 73 | $revisions[$idx]['template'] = $formerTemplate; 74 | 75 | // Gather and decode content 76 | $revisionCommand = "show {$revision['hash']}:{$formerContentFile}"; 77 | $revisionContent = $gitHelper->getRepo()->run($revisionCommand); 78 | $revisionContent = Kirby\Data\Txt::decode($revisionContent); 79 | $virtualPage = new Kirby\Cms\Page([ 80 | 'template' => $formerTemplate, 81 | 'content' => $revisionContent, 82 | 'slug' => 'totally-irrelevant' 83 | ]); 84 | $revisionContent = Kirby\Cms\Form::for($virtualPage)->values(); 85 | $revisions[$idx]['content'] = $revisionContent; 86 | } 87 | 88 | return $revisions; 89 | } 90 | ] 91 | ], 92 | 93 | ]; 94 | -------------------------------------------------------------------------------- /gitlog-field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wottpal/kirby-git/1afec185dd7f483c5e843de2734d25a8c72ed06a/gitlog-field.png -------------------------------------------------------------------------------- /gitrevisions-field.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wottpal/kirby-git/1afec185dd7f483c5e843de2734d25a8c72ed06a/gitrevisions-field.gif -------------------------------------------------------------------------------- /gitrevisions-field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wottpal/kirby-git/1afec185dd7f483c5e843de2734d25a8c72ed06a/gitrevisions-field.png -------------------------------------------------------------------------------- /hooks.php: -------------------------------------------------------------------------------- 1 | function ($page) { 10 | site()->git()->changeHandler("Created Page: {$page->id()}"); 11 | }, 12 | 'page.update:after' => function ($newPage,$oldPage) { 13 | site()->git()->changeHandler("Edited Page: {$newPage->id()}"); 14 | }, 15 | 'page.delete:after' => function ($status, $page) { 16 | site()->git()->changeHandler("Deleted Page: {$page->id()}"); 17 | }, 18 | 'page.changeNum:after' => function ($newPage,$oldPage) { 19 | $oldNum = $oldPage->num() ?? "None"; 20 | site()->git()->changeHandler("Sorted Page: {$newPage->id()} ({$oldNum} → {$newPage->num()})"); 21 | }, 22 | 'page.changeSlug:after' => function ($newPage,$oldPage) { 23 | $oldSlug = $oldPage->slug() ?? "None"; 24 | site()->git()->changeHandler("Changed Slug: {$newPage->id()} ({$oldSlug} → {$newPage->slug()})"); 25 | }, 26 | 'page.changeStatus:after' => function ($newPage,$oldPage) { 27 | $oldStatus = $oldPage->status() ?? "None"; 28 | site()->git()->changeHandler("Changed Status: {$newPage->id()} ({$oldStatus} → {$newPage->status()})"); 29 | }, 30 | 'page.changeTemplate:after' => function ($newPage,$oldPage) { 31 | $oldTemplate = $oldPage->template() ?? "None"; 32 | site()->git()->changeHandler("Changed Template: {$newPage->id()} ({$oldTemplate} → {$newPage->template()})"); 33 | }, 34 | 'page.changeTitle:after' => function ($newPage,$oldPage) { 35 | $oldTitle = $oldPage->title() ?? "None"; 36 | site()->git()->changeHandler("Changed Title: {$newPage->id()} ({$oldTitle} → {$newPage->title()})"); 37 | }, 38 | 39 | /** 40 | * File-Hooks 41 | */ 42 | 'file.create:after' => function ($file) { 43 | site()->git()->changeHandler("Uploaded File: {$file->id()}"); 44 | }, 45 | 'file.delete:after' => function ($status, $file) { 46 | site()->git()->changeHandler("Deleted File: {$file->id()}"); 47 | }, 48 | 'file.changeName:after' => function ($newFile,$oldFile) { 49 | $oldFilename = $oldFile->filename() ?? "None"; 50 | site()->git()->changeHandler("Changed Filename: {$newFile->id()} ({$oldFilename} → {$newFile->filename()})"); 51 | }, 52 | 'file.changeSort:after' => function ($newFile,$oldFile) { 53 | $oldSort = $oldFile->sort() ?? "None"; 54 | site()->git()->changeHandler("Sorted File: {$newFile->id()} ({$oldSort} → {$newFile->sort()})"); 55 | }, 56 | 'file.update:after' => function ($newFile,$oldFile) { 57 | site()->git()->changeHandler("Edited File-Metadata: {$newFile->id()}"); 58 | }, 59 | 'file.replace:after' => function ($newFile,$oldFile) { 60 | site()->git()->changeHandler("Replaced File: {$newFile->id()}"); 61 | }, 62 | 63 | /** 64 | * Site-Hooks 65 | */ 66 | 'site.update:after' => function ($newSite,$oldSite) { 67 | site()->git()->changeHandler("Edited Site"); 68 | }, 69 | 70 | /** 71 | * Avatar-Hooks 72 | */ 73 | 'avatar.create:after' => function ($avatar) { 74 | if (!site()->git()->userHooksEnabled()) return; 75 | $name = $avatar->user()->name() ?? $avatar->user()->email(); 76 | site()->git()->changeHandler("Uploaded Avatar: {$name}"); 77 | }, 78 | 'avatar.replace:after' => function ($newAvatar, $oldAvatar) { 79 | if (!site()->git()->userHooksEnabled()) return; 80 | $name = $newAvatar->user()->name() ?? $newAvatar->user()->email(); 81 | site()->git()->changeHandler("Replaced Avatar: {$name}"); 82 | }, 83 | 'avatar.delete:after' => function ($status, $avatar) { 84 | if (!site()->git()->userHooksEnabled()) return; 85 | $name = $avatar->user()->name() ?? $avatar->user()->email(); 86 | site()->git()->changeHandler("Deleted Avatar: {$name}"); 87 | }, 88 | 89 | /** 90 | * User-Hooks 91 | */ 92 | 'user.changeEmail:after' => function ($newUser, $oldUser) { 93 | if (!site()->git()->userHooksEnabled()) return; 94 | $name = $newUser->name() ?? $newUser->email(); 95 | $oldEmail = $oldUser->email() ?? "None"; 96 | site()->git()->changeHandler("Changed E-Mail: {$name} ({$oldEmail} → {$newUser->email()})"); 97 | }, 98 | 'user.changeName:after' => function ($newUser, $oldUser) { 99 | if (!site()->git()->userHooksEnabled()) return; 100 | $name = $newUser->name() ?? $newUser->email(); 101 | $oldName = $oldUser->name() ?? "None"; 102 | $newName = $newUser->name() ?? "None"; 103 | site()->git()->changeHandler("Changed Name: {$name} ({$oldName} → {$newName})"); 104 | }, 105 | 'user.changeLanguage:after' => function ($newUser, $oldUser) { 106 | if (!site()->git()->userHooksEnabled()) return; 107 | $name = $newUser->name() ?? $newUser->email(); 108 | $oldLang = $oldUser->language() ?? "None"; 109 | site()->git()->changeHandler("Changed Language: {$name} ({$oldLang} → {$newUser->language()})"); 110 | }, 111 | 'user.changePassword:after' => function ($newUser, $oldUser) { 112 | if (!site()->git()->userHooksEnabled()) return; 113 | $name = $newUser->name() ?? $newUser->email(); 114 | site()->git()->changeHandler("Changed Password: {$name}"); 115 | }, 116 | 'user.changeRole:after' => function ($newUser, $oldUser) { 117 | if (!site()->git()->userHooksEnabled()) return; 118 | $name = $newUser->name() ?? $newUser->email(); 119 | $oldRole = $oldUser->role() ?? "None"; 120 | site()->git()->changeHandler("Changed Role: {$name} ({$oldRole} → {$newUser->role()})"); 121 | }, 122 | 'user.create:after' => function ($user) { 123 | if (!site()->git()->userHooksEnabled()) return; 124 | $name = $user->name() ?? $user->email(); 125 | site()->git()->changeHandler("Created User: {$name}"); 126 | }, 127 | 'user.update:after' => function ($newUser,$oldUser) { 128 | if (!site()->git()->userHooksEnabled()) return; 129 | $name = $newUser->name() ?? $newUser->email(); 130 | site()->git()->changeHandler("Edited User: {$name}"); 131 | }, 132 | 'user.delete:after' => function ($status, $user ) { 133 | if (!site()->git()->userHooksEnabled()) return; 134 | $name = $user->name() ?? $user->email(); 135 | site()->git()->changeHandler("Deleted User: {$name}"); 136 | }, 137 | 138 | ]; 139 | -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | .k-structure--firstColumnLarger .k-structure-item-content{grid-template-columns:minmax(0,2fr) repeat(auto-fit,minmax(0,1fr))}.k-structure--noAction .k-structure-item-content:hover{background:hsla(0,0%,98%,.75)}.k-structure--noAction .k-structure-item-text{cursor:default}.k-structure--noAction .k-structure-item-text:hover{background:inherit}.k-structure-item-path span:last-of-type{font-weight:600}.k-structure-item-path span:not(:last-of-type){opacity:.75}.k-structure-item-path span:not(:last-of-type):after{opacity:.25;font-weight:400;content:" / "}.k-structure-item-path__none{font-weight:400!important;font-style:italic;color:gray} 2 | .k-structure-item--isSelected p{background:#f0f8ff;pointer-events:none} -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | !function r(o,a,u){function c(e,t){if(!a[e]){if(!o[e]){var i="function"==typeof require&&require;if(!t&&i)return i(e,!0);if(l)return l(e,!0);var s=new Error("Cannot find module '"+e+"'");throw s.code="MODULE_NOT_FOUND",s}var n=a[e]={exports:{}};o[e][0].call(n.exports,function(t){return c(o[e][1][t]||t)},n,n.exports,r,o,a,u)}return a[e].exports}for(var l="function"==typeof require&&require,t=0;tNone')})):(t.commitType="Developer Commit",t.commitSubject=[t.message]),t}).filter(function(t){return"Kirby"==t.authorSource||!e.kirbyOnly})},paginate:function(t){var e=0,i=Math.min(this.log.length,this.limit);t&&(e=t.start-1,i=t.end),this.paginatedLog=this.log.slice(e,i)}}}}(),e.exports.__esModule&&(e.exports=e.exports.default);var s="function"==typeof e.exports?e.exports.options:e.exports;s.render=function(){var e=this,t=e.$createElement,i=e._self._c||t;return i("k-field",e._b({},"k-field",e.$attrs,!1),[e.log.length?i("div",[i("ul",{staticClass:"k-structure k-structure--git k-structure--noAction k-structure--firstColumnLarger"},e._l(e.paginatedLog,function(t){return i("li",{key:t.hash,staticClass:"k-structure-item"},[i("div",{staticClass:"k-structure-item-wrapper"},[i("div",{staticClass:"k-structure-item-content"},[i("p",{staticClass:"k-structure-item-text"},[i("span",{staticClass:"k-structure-item-label"},[e._v(e._s(t.commitType))]),e._v(" "),i("span",{staticClass:"k-structure-item-path",attrs:{title:t.commitSubject.join(" / ")}},e._l(t.commitSubject,function(t){return i("span",{domProps:{innerHTML:e._s(t)}})}))]),e._v(" "),i("p",{staticClass:"k-structure-item-text"},[i("span",{staticClass:"k-structure-item-label"},[e._v("Author")]),e._v(" "),i("span",{attrs:{title:t.author}},[e._v(e._s(t.author))])]),e._v(" "),i("p",{staticClass:"k-structure-item-text"},[i("span",{staticClass:"k-structure-item-label"},[e._v("Date")]),e._v(" "),i("span",{attrs:{title:t.dateFormatted}},[e._v(e._s(t.dateFormatted))])])])])])})),e._v(" "),i("k-pagination",e._b({ref:"pagination",on:{paginate:function(t){e.paginate(t)}}},"k-pagination",e.paginationOptions,!1))],1):i("k-box",[e._v("\n No commits or no repository found.\n ")])],1)},s.staticRenderFns=[]},{}],2:[function(t,e,i){!function(){"use strict";Object.defineProperty(i,"__esModule",{value:!0}),i.default={props:{gitRevisions:Array,fields:{type:Array,default:[]},columns:{type:Array,default:[]},limit:{default:5,type:Number}},data:function(){return{infoColumns:{dateFormatted:"Date"},revisions:[],paginatedRevisions:[]}},created:function(){this.$events.$on("form.change",this.onFormChange),this.$events.$on("form.save",this.onFormSave),this.$events.$on("form.reset",this.onFormReset)},destroyed:function(){this.$events.$off("form.change",this.onFormChange),this.$events.$off("form.save",this.onFormSave),this.$events.$off("form.reset",this.onFormReset)},mounted:function(){this.initInfoColumns(),this.initRevisions(),this.paginate()},computed:{paginationOptions:function(){return{limit:this.limit,align:"center",details:!0,keys:this.revisions.map(function(t){return t.hash}),total:this.revisions.length,hide:!1}}},methods:{onFormChange:function(){},onFormSave:function(){},onFormReset:function(){this.revisions.length&&(this.revisions[0].selected=!0)},initInfoColumns:function(){var t={author:"Author",hash:"Commit-Hash",message:"Commit-Message"},e=!0,i=!1,s=void 0;try{for(var n,r=this.columns[Symbol.iterator]();!(e=(n=r.next()).done);e=!0){var o=n.value;o in t&&(this.infoColumns[o]=t[o])}}catch(t){i=!0,s=t}finally{try{!e&&r.return&&r.return()}finally{if(i)throw s}}},initRevisions:function(){var i=this;console.log(this.gitRevisions),this.revisions=JSON.parse(JSON.stringify(this.gitRevisions)),this.revisions=this.revisions.filter(function(t){var e=Object.keys(t.content).filter(function(t){return-1!==i.fields.indexOf(t)});return 0<(t.updateFields=e).length}),this.revisions=this.revisions.map(function(t){var e=t.message.indexOf("By: ");return-1!=e?(t.author=t.message.substring(e+"By: ".length),t.authorSource="Kirby"):t.authorSource="Git",t}),this.revisions.length&&(this.revisions[0].first=!0,this.revisions[0].selected=!0)},applyRevision:function(t){this.revisions.forEach(function(t){return t.selected=!1}),t.selected=!0,this.$forceUpdate();var e,i,s,n=!0,r=!1,o=void 0;try{for(var a,u=t.updateFields[Symbol.iterator]();!(n=(a=u.next()).done);n=!0){var c=a.value,l=t.content[c];this.$events.$emit("values-push",(s=l,(i=c)in(e={})?Object.defineProperty(e,i,{value:s,enumerable:!0,configurable:!0,writable:!0}):e[i]=s,e))}}catch(t){r=!0,o=t}finally{try{!n&&u.return&&u.return()}finally{if(r)throw o}}},paginate:function(t){var e=0,i=Math.min(this.revisions.length,this.limit);t&&(e=t.start-1,i=t.end),this.paginatedRevisions=this.revisions.slice(e,i)}}}}(),e.exports.__esModule&&(e.exports=e.exports.default);var s="function"==typeof e.exports?e.exports.options:e.exports;s.render=function(){var s=this,t=s.$createElement,n=s._self._c||t;return s.revisions.length?n("k-field",s._b({},"k-field",s.$attrs,!1),[s.revisions.length?n("div",[n("ul",{staticClass:"k-structure k-structure--git"},s._l(s.paginatedRevisions,function(i){return n("li",{key:i.hash,ref:"structureItem",refInFor:!0,staticClass:"k-structure-item",class:{"k-structure-item--isSelected":i.selected},on:{click:function(t){!i.selected&&s.applyRevision(i)}}},[n("div",{staticClass:"k-structure-item-wrapper"},[n("div",{staticClass:"k-structure-item-content"},s._l(s.infoColumns,function(t,e){return n("p",{staticClass:"k-structure-item-text"},[n("span",{staticClass:"k-structure-item-label"},[s._v("\n "+s._s(t)+"\n "),"Date"===t&&i.first?n("span",[s._v("(Latest)")]):s._e()]),s._v(" "),n("span",[s._v(s._s(i[e]))])])}))])])})),s._v(" "),n("k-pagination",s._b({ref:"pagination",on:{paginate:function(t){s.paginate(t)}}},"k-pagination",s.paginationOptions,!1))],1):n("k-box",[s._v("\n No commits or no repository found.\n ")])],1):s._e()},s.staticRenderFns=[]},{}],3:[function(t,e,i){"use strict";var s=o(t(4)),n=o(t(1)),r=o(t(2));function o(t){return t&&t.__esModule?t:{default:t}}panel.plugin("wottpal/git",{use:[s.default],fields:{gitLog:n.default,gitRevisions:r.default}})},{1:1,2:2,4:4}],4:[function(t,e,i){"use strict";Object.defineProperty(i,"__esModule",{value:!0}),i.default=function(t){var e=t.options.components["k-fields-section"];e.options.methods.valuesPush||t.component("k-fields-section",{extends:e,created:function(){this.$events.$on("values-push",this.valuesPush)},destroyed:function(){this.$events.$off("values-push",this.valuesPush)},methods:{valuesPush:function(t){var e=!1;for(var i in t)i in this.values&&(this.values[i]=t[i],e=!0);e&&this.input(this.values)}}})}},{}]},{},[3]); 2 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'dir' => 'index', // or e.g. 'content' 9 | 'branch' => 'master', 10 | 'shouldPull' => false, 11 | 'shouldPush' => false, 12 | 'shouldCommit' => false, 13 | 'userHooks' => false, 14 | 'gitBin' => '', 15 | 'windowsMode' => false, 16 | 'debug' => false, 17 | 'logFile' => 'git-log.txt' 18 | ], 19 | 20 | 'hooks' => require_once __DIR__ . DS . 'hooks.php', 21 | 22 | 'fields' => require_once __DIR__ . DS . 'fields.php', 23 | 24 | 'siteMethods' => [ 25 | 'git' => require_once __DIR__ . DS . 'src' . DS . 'siteMethods/git.php' 26 | ] 27 | 28 | ]); 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kirby-git", 3 | "version": "1.0.0", 4 | "description": "Automatic Version Control for Kirby 3", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/wottpal/kirby-git.git" 9 | }, 10 | "author": "Dennis Kerzig", 11 | "license": "ISC", 12 | "bugs": { 13 | "url": "https://github.com/wottpal/kirby-git/issues" 14 | }, 15 | "homepage": "https://github.com/wottpal/kirby-git#readme", 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1", 18 | "dev": "watchify -vd -p browserify-hmr -e src/main.js -o index.js", 19 | "build": "cross-env NODE_ENV=production browserify -g envify -p [ vueify/plugins/extract-css -o index.css ] -p bundle-collapser/plugin -e src/main.js | uglifyjs -c warnings=false -m > index.js" 20 | }, 21 | "browserify": { 22 | "transform": [ 23 | "babelify", 24 | "vueify" 25 | ] 26 | }, 27 | "devDependencies": { 28 | "babel-core": "^6.26.3", 29 | "babel-plugin-transform-runtime": "^6.23.0", 30 | "babel-preset-env": "^1.7.0", 31 | "babelify": "^8.0.0", 32 | "browserify": "^16.2.2", 33 | "browserify-hmr": "^0.3.6", 34 | "bundle-collapser": "^1.3.0", 35 | "cross-env": "^5.2.0", 36 | "envify": "^4.1.0", 37 | "uglify-js": "^3.4.7", 38 | "vue": "^2.5.17", 39 | "vueify": "^9.4.1", 40 | "watchify": "^3.11.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/fields/git-log.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 146 | 147 | 185 | -------------------------------------------------------------------------------- /src/fields/git-revisions.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 187 | 188 | 196 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import ValuesPush from './use/values-push.js' 2 | import GitLog from './fields/git-log.vue' 3 | import GitRevisions from './fields/git-revisions.vue' 4 | 5 | panel.plugin("wottpal/git", { 6 | use: [ValuesPush], 7 | fields: { 8 | gitLog: GitLog, 9 | gitRevisions: GitRevisions 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /src/siteMethods/git.php: -------------------------------------------------------------------------------- 1 |