├── dist ├── css │ └── tool.css ├── mix-manifest.json └── js │ └── tool.js ├── resources ├── sass │ └── tool.scss ├── js │ ├── tool.js │ └── components │ │ └── Tool.vue └── views │ ├── navigation.blade.php │ └── changelog.blade.php ├── webpack.mix.js ├── .gitignore ├── README.md ├── routes ├── web.php └── api.php ├── src ├── Entry.php ├── Http │ ├── Controllers │ │ ├── Api │ │ │ └── ChangelogController.php │ │ └── ChangelogController.php │ └── Middleware │ │ └── Authorize.php ├── NovaChangelog.php ├── Providers │ └── Tool.php └── Changelog.php ├── .github └── FUNDING.yml ├── composer.json ├── docker-compose.yml ├── package.json └── .devcontainer └── devcontainer.json /dist/css/tool.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/sass/tool.scss: -------------------------------------------------------------------------------- 1 | // Nova Tool CSS 2 | -------------------------------------------------------------------------------- /dist/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/tool.js": "/js/tool.js", 3 | "/css/tool.css": "/css/tool.css" 4 | } -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix') 2 | 3 | mix.setPublicPath('dist') 4 | .js('resources/js/tool.js', 'js') 5 | .sass('resources/sass/tool.scss', 'css') 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor 3 | /node_modules 4 | package-lock.json 5 | composer.phar 6 | composer.lock 7 | phpunit.xml 8 | .phpunit.result.cache 9 | .DS_Store 10 | Thumbs.db 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Changelog for Laravel 2 | 3 | ![Changelog for Laravel masthead image.](https://repository-images.githubusercontent.com/189888584/d096b800-f298-11e9-977e-e6e3925132ae) 4 | 5 | Easily integrate and display your changelog in your app. 6 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | { 2 | router.addRoutes([ 3 | { 4 | name: 'laravel-changelog', 5 | path: '/laravel-changelog', 6 | component: require('./components/Tool'), 7 | }, 8 | ]) 9 | }) 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: mikebronner # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: #mikebronner 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | custom: # Replace with a single custom sponsorship URL 10 | -------------------------------------------------------------------------------- /resources/views/navigation.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Change Log 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Http/Controllers/Api/ChangelogController.php: -------------------------------------------------------------------------------- 1 | entries; 14 | } 15 | 16 | public function show(string $version) : Entry 17 | { 18 | return (new Changelog) 19 | ->find($version); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | Change Log 5 | 6 | @foreach ($entries as $entry) 7 | @if ($entry->details) 8 |

9 | {{ $entry->version }} 10 | 11 | {{ $entry->date }} 12 | 13 |

14 |
15 |
16 | {!! $entry->details !!} 17 |
18 |
19 | @endif 20 | @endforeach 21 | @endsection 22 | -------------------------------------------------------------------------------- /src/Http/Controllers/ChangelogController.php: -------------------------------------------------------------------------------- 1 | entries; 14 | 15 | return view("laravel-changelog::changelog") 16 | ->with(compact("entries")); 17 | // return (new Changelog) 18 | // ->entries; 19 | } 20 | 21 | public function show(string $version) : Entry 22 | { 23 | return (new Changelog) 24 | ->find($version); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "genealabs/laravel-changelog", 3 | "description": "A Laravel Nova tool.", 4 | "keywords": [ 5 | "laravel", 6 | "nova" 7 | ], 8 | "license": "MIT", 9 | "require": { 10 | "illuminate/filesystem": "^10.0|^11.0", 11 | "illuminate/support": "^10.0|^11.0", 12 | "jenssegers/model": "^1.5", 13 | "spatie/laravel-markdown": "^2.4" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "GeneaLabs\\LaravelChangelog\\": "src/" 18 | } 19 | }, 20 | "extra": { 21 | "laravel": { 22 | "providers": [ 23 | "GeneaLabs\\LaravelChangelog\\Providers\\Tool" 24 | ] 25 | } 26 | }, 27 | "config": { 28 | "sort-packages": true 29 | }, 30 | "minimum-stability": "dev", 31 | "prefer-stable": true 32 | } 33 | -------------------------------------------------------------------------------- /src/NovaChangelog.php: -------------------------------------------------------------------------------- 1 | latestVersion; 27 | 28 | return view('laravel-changelog::navigation') 29 | ->with(compact( 30 | "latestVersion" 31 | )); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Http/Middleware/Authorize.php: -------------------------------------------------------------------------------- 1 | first([$this, 'matchesTool']); 18 | 19 | return optional($tool)->authorize($request) 20 | ? $next($request) 21 | : abort(403); 22 | } 23 | 24 | /** 25 | * Determine whether this tool belongs to the package. 26 | * 27 | * @param \Laravel\Nova\Tool $tool 28 | * @return bool 29 | */ 30 | public function matchesTool($tool) 31 | { 32 | return $tool instanceof LaravelChangelog; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # For more information: https://laravel.com/docs/sail 2 | version: '3' 3 | 4 | services: 5 | laravel.test: 6 | image: ghcr.io/mikebronner/sail/php-8.2:latest 7 | extra_hosts: 8 | - 'host.docker.internal:host-gateway' 9 | ports: 10 | - '${APP_PORT:-80}:80' 11 | - '${VITE_PORT:-5173}:${VITE_PORT:-5173}' 12 | environment: 13 | WWWUSER: '${WWWUSER}' 14 | LARAVEL_SAIL: 1 15 | XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}' 16 | XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}' 17 | volumes: 18 | - '.:/var/www/html' 19 | networks: 20 | - sail 21 | 22 | networks: 23 | sail: 24 | driver: bridge 25 | 26 | volumes: 27 | sail-meilisearch: 28 | driver: local 29 | sail-minio: 30 | driver: local 31 | sail-pgsql: 32 | driver: local 33 | sail-redis: 34 | driver: local 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 7 | "watch-poll": "npm run watch -- --watch-poll", 8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "prod": "npm run production", 10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" 11 | }, 12 | "devDependencies": { 13 | "cross-env": "^5.0.0", 14 | "laravel-mix": "^1.0" 15 | }, 16 | "dependencies": { 17 | "vue": "^2.5.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Providers/Tool.php: -------------------------------------------------------------------------------- 1 | loadViewsFrom(__DIR__.'/../../resources/views', 'laravel-changelog'); 12 | 13 | $this->app->booted(function () { 14 | $this->routes(); 15 | }); 16 | } 17 | 18 | protected function routes() : void 19 | { 20 | $namespace = 'GeneaLabs\LaravelChangelog\Http\Controllers'; 21 | 22 | if ($this->app->routesAreCached()) { 23 | return; 24 | } 25 | 26 | app("router") 27 | ->middleware(['nova']) 28 | ->prefix('genealabs/laravel-changelog/api') 29 | ->namespace($namespace . "\Api") 30 | ->group(__DIR__ . '/../../routes/api.php'); 31 | 32 | app("router") 33 | ->middleware(['web']) 34 | ->namespace($namespace) 35 | ->group(__DIR__ . '/../../routes/web.php'); 36 | } 37 | 38 | public function register() : void 39 | { 40 | // 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /resources/js/components/Tool.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 60 | 61 | 64 | -------------------------------------------------------------------------------- /src/Changelog.php: -------------------------------------------------------------------------------- 1 | reject(function ($path): bool { 19 | return is_dir($path); 20 | }) 21 | ->filter(function ($existingFileName): bool { 22 | return Str::lower($existingFileName) === Str::lower($this->fileName); 23 | }) 24 | ->first(); 25 | 26 | if (! $changelog) { 27 | return ""; 28 | } 29 | 30 | return file_get_contents(base_path($changelog)); 31 | } 32 | 33 | protected function parseChangelog() : Collection 34 | { 35 | $changelog = $this->getChangelog(); 36 | $versions = collect(explode("\n## ", $changelog)); 37 | $versions->shift(); 38 | 39 | return $versions 40 | ->reject(function ($entry): bool { 41 | return Str::startsWith($entry, "Unreleased"); 42 | }) 43 | ->map(function ($entry): Entry { 44 | $matches = []; 45 | preg_match("/\[(.*?)\].*?(\d{4}-\d{2}-\d{2})?\n(.*)?/s", $entry, $matches); 46 | $entry = new Entry; 47 | $entry->date = data_get($matches, 2) 48 | ?? ""; 49 | $entry->version = data_get($matches, 1) 50 | ?? ""; 51 | $entry->details = Str::markdown(data_get($matches, 3) ?? ""); 52 | 53 | return $entry; 54 | }); 55 | } 56 | 57 | public function find(string $version) : Entry 58 | { 59 | return $this 60 | ->entries 61 | ->filter(function ($entry) use ($version) { 62 | return version_compare($entry->version, $version, "=="); 63 | }) 64 | ->first(); 65 | } 66 | 67 | public function load(?string $filename = "CHANGELOG.md"): self 68 | { 69 | $this->fileName = $filename; 70 | 71 | return $this; 72 | } 73 | 74 | public function getEntriesAttribute() : Collection 75 | { 76 | return $this 77 | ->parseChangelog() 78 | ->sort(function ($entry1, $entry2) { 79 | return version_compare($entry1->version, $entry2->version, ">") 80 | ? -1 81 | : 1; 82 | }) 83 | ->values(); 84 | } 85 | 86 | public function getLatestVersionEntryAttribute() : Entry 87 | { 88 | return $this 89 | ->entries 90 | ->filter(function ($entry) { 91 | return version_compare($entry->version, "0.0.1", ">=") >= 0 92 | && $entry->date; 93 | }) 94 | ->first(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // https://aka.ms/devcontainer.json 2 | { 3 | "name": "Existing Docker Compose (Extend)", 4 | "dockerComposeFile": [ 5 | "../docker-compose.yml" 6 | ], 7 | "features": { 8 | "ghcr.io/devcontainers/features/sshd:1": { 9 | "version": "latest" 10 | } 11 | }, 12 | "service": "laravel.test", 13 | "workspaceFolder": "/var/www/html", 14 | "customizations": { 15 | "vscode": { 16 | "settings": {}, 17 | "extensions": [ 18 | "aaron-bond.better-comments", 19 | "adrianwilczynski.alpine-js-intellisense", 20 | "AlexArthurs.todo-pusher", 21 | "amiralizadeh9480.laravel-extra-intellisense", 22 | "austenc.laravel-blade-spacer", 23 | "beyondcode.tinkerwell", 24 | "bmewburn.vscode-intelephense-client", 25 | "bradlc.vscode-tailwindcss", 26 | "christian-kohler.npm-intellisense", 27 | "christian-kohler.path-intellisense", 28 | "cierra.livewire-vscode", 29 | "codecov.codecov", 30 | "codingyu.laravel-goto-view", 31 | "davidanson.vscode-markdownlint", 32 | "davidbwaters.macos-modern-theme", 33 | "eamodio.gitlens", 34 | "editorconfig.editorconfig", 35 | "ericcheng.codesongclear", 36 | "faelv.composer-companion", 37 | "file-icons.file-icons", 38 | "foxundermoon.shell-format", 39 | "georgykurian.laravel-ide-helper", 40 | "github.codespaces", 41 | "GitHub.copilot-chat", 42 | "GitHub.copilot-nightly", 43 | "GitHub.copilot", 44 | "github.vscode-github-actions", 45 | "github.vscode-pull-request-github", 46 | "Gruntfuggly.todo-tree", 47 | "heissenbergerlab.php-array-from-json", 48 | "heybourn.headwind", 49 | "huibizhang.codesnap-plus", 50 | "irongeek.vscode-env", 51 | "kencocaceo.customvscodeuicss", 52 | "m4ns0ur.base64", 53 | "maciejdems.add-to-gitignore", 54 | "mahmoudshahin.laravel-routes", 55 | "markis.code-coverage", 56 | "martybegood.single-editor-tabs", 57 | "mechatroner.rainbow-csv", 58 | "mehedidracula.php-namespace-resolver", 59 | "mhutchie.git-graph", 60 | "mikestead.dotenv", 61 | "mohamedbenhida.laravel-intellisense", 62 | "mrmlnc.vscode-duplicate", 63 | "naoray.laravel-goto-components", 64 | "oderwat.indent-rainbow", 65 | "pcbowers.alpine-intellisense", 66 | "recca0120.vscode-phpunit", 67 | "redhat.vscode-yaml", 68 | "rifi2k.format-html-in-php", 69 | "shevaua.phpcs", 70 | "shufo.vscode-blade-formatter", 71 | "sperovita.alpinejs-syntax-highlight", 72 | "streetsidesoftware.code-spell-checker", 73 | "syler.ignore", 74 | "teabyii.ayu", 75 | "usernamehw.errorlens", 76 | "vincaslt.highlight-matching-tag", 77 | "WakaTime.vscode-wakatime", 78 | "withfig.fig", 79 | "xdebug.php-debug" 80 | ] 81 | } 82 | }, 83 | "remoteUser": "sail", 84 | "postCreateCommand": "sudo chown -R 1000:1000 /var/www/html", 85 | // "forwardPorts": [ 86 | // ], 87 | // "portsAttributes": { 88 | // }, 89 | "mounts": [ 90 | "source=${localEnv:HOME}/.wakatime.cfg,target=/home/sail/.wakatime.cfg,type=bind,consistency=delegated" 91 | ] 92 | // "runServices": [], 93 | // "shutdownAction": "none", 94 | } -------------------------------------------------------------------------------- /dist/js/tool.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){n(1),e.exports=n(11)},function(e,t,n){Nova.booting(function(e,t,r){t.addRoutes([{name:"laravel-changelog",path:"/laravel-changelog",component:n(2)}])})},function(e,t,n){var r=n(8)(n(9),n(10),!1,function(e){n(3)},"data-v-4e88d959",null);e.exports=r.exports},function(e,t,n){var r=n(4);"string"==typeof r&&(r=[[e.i,r,""]]),r.locals&&(e.exports=r.locals);n(6)("446af118",r,!0,{})},function(e,t,n){(e.exports=n(5)(!1)).push([e.i,"",""])},function(e,t){e.exports=function(e){var t=[];return t.toString=function(){return this.map(function(t){var n=function(e,t){var n=e[1]||"",r=e[3];if(!r)return n;if(t&&"function"==typeof btoa){var o=(i=r,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(i))))+" */"),a=r.sources.map(function(e){return"/*# sourceURL="+r.sourceRoot+e+" */"});return[n].concat(a).concat([o]).join("\n")}var i;return[n].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+n+"}":n}).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var r={},o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;o0&&void 0!==arguments[0]?arguments[0]:"",t=this;Nova.request().get("/genealabs/laravel-changelog/api/entries/"+e).then(function(e){console.log(e.data),t.changelog=e.data,t.nextLink=e.next,t.previousLink=e.previous,t.isLoading=!1}).catch(function(e){console.error(e,e.data)})}}}},function(e,t){e.exports={render:function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",[n("h1",{staticClass:"mb-6"},[e._v("Change Log")]),e._v(" "),n("loading-view",{attrs:{loading:e.isLoading}},e._l(e.changelog,function(t,r){return n("div",{key:r},[t.details?n("div",[n("h2",{staticClass:"mb-2"},[n("span",{domProps:{textContent:e._s(t.version)}}),e._v(" "),n("span",{staticClass:"text-70 font-normal ml-6",domProps:{textContent:e._s(t.date)}})]),e._v(" "),n("card",{staticClass:"mb-8 p-4"},[n("p",{staticClass:"details",domProps:{innerHTML:e._s(t.details)}})])],1):e._e()])}),0)],1)},staticRenderFns:[]}},function(e,t){}]); --------------------------------------------------------------------------------