├── .gitignore
├── README.md
├── composer.json
├── config
└── nova-blog.php
├── dist
├── css
│ ├── post-form.css
│ └── tool.css
├── js
│ ├── post-form.js
│ └── tool.js
└── mix-manifest.json
├── package.json
├── resources
├── js
│ ├── components
│ │ ├── Tool.vue
│ │ └── post-form
│ │ │ ├── Create.vue
│ │ │ ├── DefaultField.vue
│ │ │ ├── Edit.vue
│ │ │ └── FieldSet.vue
│ ├── post-form.js
│ └── tool.js
├── sass
│ ├── post-form.scss
│ └── tool.scss
└── views
│ └── navigation.blade.php
├── routes
└── api.php
├── src
├── Actions
│ ├── PublishPost.php
│ └── SaveDraftPost.php
├── BlogResponder.php
├── Bootstrap
│ └── Blog.php
├── FieldSet.php
├── Http
│ ├── Controllers
│ │ ├── BlogBaseController.php
│ │ ├── DashboardController.php
│ │ ├── MigrationController.php
│ │ ├── ResetController.php
│ │ └── UninstallController.php
│ └── Middleware
│ │ └── Authorize.php
├── Metrics
│ └── Posts
│ │ ├── NewPosts.php
│ │ └── PostsTrend.php
├── Migrations
│ ├── CategoryMigration.php
│ ├── CommentMigration.php
│ ├── ForeignKeyMigration.php
│ ├── Migrator.php
│ ├── NovaPendingTrixAttachmentsMigration.php
│ ├── NovaTrixAttachmentsMigration.php
│ ├── PostCategoryPivotMigration.php
│ ├── PostMigration.php
│ └── TagMigration.php
├── Models
│ ├── Category.php
│ ├── Comment.php
│ ├── Image.php
│ ├── Post.php
│ └── Tag.php
├── NovaBlogTool.php
├── Observers
│ └── ImageObserver.php
├── Policies
│ ├── CategoryPolicy.php
│ ├── CommentPolicy.php
│ ├── PostPolicy.php
│ └── TagPolicy.php
├── PostForm.php
├── Processors
│ └── StoreImage.php
├── Resources
│ ├── Category.php
│ ├── Comment.php
│ ├── Post.php
│ ├── Resource.php
│ └── Tag.php
├── Scopes
│ └── PublishedScope.php
├── ToolServiceProvider.php
└── Traits
│ └── Sluggable.php
└── webpack.mix.js
/.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 | # Nova Blog Tool.
2 |
3 | This tool allow you to create a simple blog for your website using Laravel Nova.
4 |
5 | ## Installation
6 |
7 | You can install the package via composer:
8 |
9 | ```bash
10 | composer require digitalcloud/nova-blog-tool
11 | ```
12 |
13 | ## Usage
14 |
15 | You must register the tool with Nova. This is typically done in the tools method of the NovaServiceProvider, in app/Providers/NovaServiceProvider.php.
16 |
17 | ```php
18 |
19 | use DigitalCloud\NovaBlogTool\NovaBlogTool;
20 | // ....
21 |
22 | public function tools()
23 | {
24 | return [
25 | // ...
26 | new NovaBlogTool(),
27 | // ...
28 | ];
29 | }
30 |
31 | ```
32 |
33 | ## Images
34 |
35 | 
36 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "digitalcloud/nova-blog-tool",
3 | "description": "A Laravel Nova tool.",
4 | "keywords": [
5 | "laravel",
6 | "nova"
7 | ],
8 | "license": "MIT",
9 | "require": {
10 | "php": ">=7.1.0",
11 | "spatie/laravel-sluggable": "^2.1",
12 | "spatie/laravel-translatable": "*",
13 | "spatie/nova-tags-field": "*",
14 | "digitalcloud/multilingual-nova": "^1.0",
15 | "infinety-es/nova-filemanager": "^1.1",
16 | "fourstacks/nova-checkboxes": "^0.1.0",
17 | "yassi/nova-custom-form": "dev-master"
18 | },
19 | "autoload": {
20 | "psr-4": {
21 | "DigitalCloud\\NovaBlogTool\\": "src/"
22 | }
23 | },
24 | "extra": {
25 | "laravel": {
26 | "providers": [
27 | "DigitalCloud\\NovaBlogTool\\ToolServiceProvider",
28 | "Yassi\\NovaCustomForm\\NovaCustomFormServiceProvider"
29 | ]
30 | }
31 | },
32 | "config": {
33 | "sort-packages": true
34 | },
35 | "minimum-stability": "dev",
36 | "prefer-stable": true
37 | }
38 |
--------------------------------------------------------------------------------
/config/nova-blog.php:
--------------------------------------------------------------------------------
1 | [
5 | 'users' => [
6 | 'model' => env('BLOG_USER_MODEL', App\User::class),
7 | ],
8 |
9 | 'posts' => [
10 | 'search' => ['id', 'title', 'content'],
11 | ],
12 |
13 | 'categories' => [
14 | 'search' => ['id', 'name', 'description'],
15 | ],
16 |
17 | 'comments' => [
18 | 'search' => ['id', 'name'],
19 | ],
20 |
21 | 'tags' => [
22 | 'search' => ['id', 'name'],
23 | ],
24 | ],
25 |
26 | 'user_model' => env('BLOG_USER_MODEL', App\User::class),
27 | ];
28 |
--------------------------------------------------------------------------------
/dist/css/post-form.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DigitalCloud/nova-blog-tool/49ad4bd80a85a1acee7e1b42782d9f58ac92c8e6/dist/css/post-form.css
--------------------------------------------------------------------------------
/dist/css/tool.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DigitalCloud/nova-blog-tool/49ad4bd80a85a1acee7e1b42782d9f58ac92c8e6/dist/css/tool.css
--------------------------------------------------------------------------------
/dist/js/tool.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports;
11 | /******/ }
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.l = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // define getter function for harmony exports
37 | /******/ __webpack_require__.d = function(exports, name, getter) {
38 | /******/ if(!__webpack_require__.o(exports, name)) {
39 | /******/ Object.defineProperty(exports, name, {
40 | /******/ configurable: false,
41 | /******/ enumerable: true,
42 | /******/ get: getter
43 | /******/ });
44 | /******/ }
45 | /******/ };
46 | /******/
47 | /******/ // getDefaultExport function for compatibility with non-harmony modules
48 | /******/ __webpack_require__.n = function(module) {
49 | /******/ var getter = module && module.__esModule ?
50 | /******/ function getDefault() { return module['default']; } :
51 | /******/ function getModuleExports() { return module; };
52 | /******/ __webpack_require__.d(getter, 'a', getter);
53 | /******/ return getter;
54 | /******/ };
55 | /******/
56 | /******/ // Object.prototype.hasOwnProperty.call
57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58 | /******/
59 | /******/ // __webpack_public_path__
60 | /******/ __webpack_require__.p = "";
61 | /******/
62 | /******/ // Load entry module and return exports
63 | /******/ return __webpack_require__(__webpack_require__.s = 40);
64 | /******/ })
65 | /************************************************************************/
66 | /******/ ({
67 |
68 | /***/ 2:
69 | /***/ (function(module, exports) {
70 |
71 | /* globals __VUE_SSR_CONTEXT__ */
72 |
73 | // IMPORTANT: Do NOT use ES2015 features in this file.
74 | // This module is a runtime utility for cleaner component module output and will
75 | // be included in the final webpack user bundle.
76 |
77 | module.exports = function normalizeComponent (
78 | rawScriptExports,
79 | compiledTemplate,
80 | functionalTemplate,
81 | injectStyles,
82 | scopeId,
83 | moduleIdentifier /* server only */
84 | ) {
85 | var esModule
86 | var scriptExports = rawScriptExports = rawScriptExports || {}
87 |
88 | // ES6 modules interop
89 | var type = typeof rawScriptExports.default
90 | if (type === 'object' || type === 'function') {
91 | esModule = rawScriptExports
92 | scriptExports = rawScriptExports.default
93 | }
94 |
95 | // Vue.extend constructor export interop
96 | var options = typeof scriptExports === 'function'
97 | ? scriptExports.options
98 | : scriptExports
99 |
100 | // render functions
101 | if (compiledTemplate) {
102 | options.render = compiledTemplate.render
103 | options.staticRenderFns = compiledTemplate.staticRenderFns
104 | options._compiled = true
105 | }
106 |
107 | // functional template
108 | if (functionalTemplate) {
109 | options.functional = true
110 | }
111 |
112 | // scopedId
113 | if (scopeId) {
114 | options._scopeId = scopeId
115 | }
116 |
117 | var hook
118 | if (moduleIdentifier) { // server build
119 | hook = function (context) {
120 | // 2.3 injection
121 | context =
122 | context || // cached call
123 | (this.$vnode && this.$vnode.ssrContext) || // stateful
124 | (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
125 | // 2.2 with runInNewContext: true
126 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
127 | context = __VUE_SSR_CONTEXT__
128 | }
129 | // inject component styles
130 | if (injectStyles) {
131 | injectStyles.call(this, context)
132 | }
133 | // register component module identifier for async chunk inferrence
134 | if (context && context._registeredComponents) {
135 | context._registeredComponents.add(moduleIdentifier)
136 | }
137 | }
138 | // used by ssr in case component is cached and beforeCreate
139 | // never gets called
140 | options._ssrRegister = hook
141 | } else if (injectStyles) {
142 | hook = injectStyles
143 | }
144 |
145 | if (hook) {
146 | var functional = options.functional
147 | var existing = functional
148 | ? options.render
149 | : options.beforeCreate
150 |
151 | if (!functional) {
152 | // inject component registration as beforeCreate hook
153 | options.beforeCreate = existing
154 | ? [].concat(existing, hook)
155 | : [hook]
156 | } else {
157 | // for template-only hot-reload because in that case the render fn doesn't
158 | // go through the normalizer
159 | options._injectStyles = hook
160 | // register for functioal component in vue file
161 | options.render = function renderWithStyleInjection (h, context) {
162 | hook.call(context)
163 | return existing(h, context)
164 | }
165 | }
166 | }
167 |
168 | return {
169 | esModule: esModule,
170 | exports: scriptExports,
171 | options: options
172 | }
173 | }
174 |
175 |
176 | /***/ }),
177 |
178 | /***/ 40:
179 | /***/ (function(module, exports, __webpack_require__) {
180 |
181 | __webpack_require__(41);
182 | __webpack_require__(45);
183 | module.exports = __webpack_require__(46);
184 |
185 |
186 | /***/ }),
187 |
188 | /***/ 41:
189 | /***/ (function(module, exports, __webpack_require__) {
190 |
191 | Nova.booting(function (Vue, router) {
192 | router.addRoutes([{
193 | name: 'nova-blog-tool',
194 | path: '/nova-blog-tool',
195 | component: __webpack_require__(42)
196 | }]);
197 | });
198 |
199 | /***/ }),
200 |
201 | /***/ 42:
202 | /***/ (function(module, exports, __webpack_require__) {
203 |
204 | var disposed = false
205 | var normalizeComponent = __webpack_require__(2)
206 | /* script */
207 | var __vue_script__ = __webpack_require__(43)
208 | /* template */
209 | var __vue_template__ = __webpack_require__(44)
210 | /* template functional */
211 | var __vue_template_functional__ = false
212 | /* styles */
213 | var __vue_styles__ = null
214 | /* scopeId */
215 | var __vue_scopeId__ = null
216 | /* moduleIdentifier (server only) */
217 | var __vue_module_identifier__ = null
218 | var Component = normalizeComponent(
219 | __vue_script__,
220 | __vue_template__,
221 | __vue_template_functional__,
222 | __vue_styles__,
223 | __vue_scopeId__,
224 | __vue_module_identifier__
225 | )
226 | Component.options.__file = "resources/js/components/Tool.vue"
227 |
228 | /* hot reload */
229 | if (false) {(function () {
230 | var hotAPI = require("vue-hot-reload-api")
231 | hotAPI.install(require("vue"), false)
232 | if (!hotAPI.compatible) return
233 | module.hot.accept()
234 | if (!module.hot.data) {
235 | hotAPI.createRecord("data-v-68ff5483", Component.options)
236 | } else {
237 | hotAPI.reload("data-v-68ff5483", Component.options)
238 | }
239 | module.hot.dispose(function (data) {
240 | disposed = true
241 | })
242 | })()}
243 |
244 | module.exports = Component.exports
245 |
246 |
247 | /***/ }),
248 |
249 | /***/ 43:
250 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
251 |
252 | "use strict";
253 | Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
254 | //
255 | //
256 | //
257 | //
258 | //
259 | //
260 | //
261 | //
262 | //
263 | //
264 | //
265 | //
266 | //
267 | //
268 | //
269 | //
270 | //
271 | //
272 | //
273 | //
274 | //
275 | //
276 | //
277 | //
278 | //
279 | //
280 | //
281 | //
282 | //
283 | //
284 | //
285 | //
286 | //
287 | //
288 | //
289 | //
290 |
291 | /* harmony default export */ __webpack_exports__["default"] = ({
292 | data: function data() {
293 | return {
294 | installed: null,
295 | messages: [],
296 | error: false
297 | };
298 | },
299 | mounted: function mounted() {
300 | this.installationCheck();
301 | },
302 |
303 |
304 | methods: {
305 | installationCheck: function installationCheck() {
306 | var _this = this;
307 |
308 | Nova.request().get("/nova-vendor/nova-blog-tool/check-migrations").then(function (response) {
309 | return _this.installed = response.data.installed;
310 | }).catch(function () {
311 | return _this.error = true;
312 | });
313 | },
314 | install: function install() {
315 | var _this2 = this;
316 |
317 | Nova.request().get("/nova-vendor/nova-blog-tool/migrate-tables").then(function (response) {
318 | return _this2.messages = response.data.messages;
319 | }).then(function () {
320 | return _this2.reloadPage();
321 | }).then(function () {
322 | return _this2.installationCheck();
323 | }).catch(function (error) {
324 | return _this2.error = true;
325 | });
326 | },
327 | resetContent: function resetContent() {
328 | var _this3 = this;
329 |
330 | this.resetMessages();
331 |
332 | Nova.request().get("/nova-vendor/nova-blog-tool/reset-content").then(function (response) {
333 | return _this3.messages = response.data.messages;
334 | }).then(function () {
335 | setTimeout(function () {
336 | _this3.messages = [];
337 | }, 2000);
338 | }).catch(function (error) {
339 | return _this3.error = true;
340 | });
341 | },
342 | deleteTables: function deleteTables() {
343 | var _this4 = this;
344 |
345 | this.resetMessages();
346 |
347 | Nova.request().get("/nova-vendor/nova-blog-tool/uninstall").then(function (response) {
348 | return _this4.messages = response.data.messages;
349 | }).then(function () {
350 | return _this4.reloadPage();
351 | }).catch(function (error) {
352 | return _this4.error = true;
353 | });
354 | },
355 | resetMessages: function resetMessages() {
356 | this.messages = [];
357 | },
358 | reloadPage: function reloadPage() {
359 | setTimeout(function () {
360 | window.location.reload();
361 | }, 1000);
362 | }
363 | }
364 | });
365 |
366 | /***/ }),
367 |
368 | /***/ 44:
369 | /***/ (function(module, exports, __webpack_require__) {
370 |
371 | var render = function() {
372 | var _vm = this
373 | var _h = _vm.$createElement
374 | var _c = _vm._self._c || _h
375 | return _c(
376 | "div",
377 | [
378 | _c("heading", { staticClass: "mb-6" }, [_vm._v("Blog")]),
379 | _vm._v(" "),
380 | _c(
381 | "card",
382 | [
383 | _vm.messages.length !== 0
384 | ? [
385 | _c("div", { staticClass: "px-4 py-4 " }, [
386 | _c(
387 | "div",
388 | {
389 | staticClass:
390 | "mx-2 my-2 px-6 py-8 relative bg-80 text-20 rounded-lg"
391 | },
392 | [
393 | _c(
394 | "div",
395 | { staticClass: "absolute pin-t pin-r mr-2 mt-2 pr-1" },
396 | [
397 | _c(
398 | "button",
399 | {
400 | staticClass: "text-20 font-bold",
401 | on: { click: _vm.resetMessages }
402 | },
403 | [_c("strong", [_vm._v("X")])]
404 | )
405 | ]
406 | ),
407 | _vm._v(" "),
408 | _vm._l(_vm.messages, function(message, index) {
409 | return _c("div", { key: index }, [
410 | _c("p", [_vm._v(_vm._s(message))])
411 | ])
412 | })
413 | ],
414 | 2
415 | )
416 | ])
417 | ]
418 | : _vm._e(),
419 | _vm._v(" "),
420 | _c(
421 | "div",
422 | { staticClass: "px-6 py-8 text-80" },
423 | [
424 | _vm.installed
425 | ? [
426 | _c("h4", { staticClass: "mb-4" }, [
427 | _vm._v("Blog is currently installed and active.")
428 | ]),
429 | _vm._v(" "),
430 | _c(
431 | "button",
432 | {
433 | staticClass: "btn btn-default btn-danger",
434 | on: { click: _vm.deleteTables }
435 | },
436 | [_vm._v("Uninstall and delete blog tables")]
437 | ),
438 | _vm._v(" "),
439 | _c(
440 | "button",
441 | {
442 | staticClass:
443 | "btn btn-default bg-80 text-20 font-normal",
444 | on: { click: _vm.resetContent }
445 | },
446 | [_vm._v("Reset blog content")]
447 | )
448 | ]
449 | : [
450 | _c("p", [
451 | _c("span", { staticClass: "pr-4" }, [
452 | _vm._v("Blog DB tables not found.")
453 | ]),
454 | _vm._v(" "),
455 | _c(
456 | "button",
457 | {
458 | staticClass: "btn btn-default btn-primary",
459 | on: { click: _vm.install }
460 | },
461 | [_vm._v("Install")]
462 | )
463 | ])
464 | ]
465 | ],
466 | 2
467 | )
468 | ],
469 | 2
470 | )
471 | ],
472 | 1
473 | )
474 | }
475 | var staticRenderFns = []
476 | render._withStripped = true
477 | module.exports = { render: render, staticRenderFns: staticRenderFns }
478 | if (false) {
479 | module.hot.accept()
480 | if (module.hot.data) {
481 | require("vue-hot-reload-api") .rerender("data-v-68ff5483", module.exports)
482 | }
483 | }
484 |
485 | /***/ }),
486 |
487 | /***/ 45:
488 | /***/ (function(module, exports) {
489 |
490 | // removed by extract-text-webpack-plugin
491 |
492 | /***/ }),
493 |
494 | /***/ 46:
495 | /***/ (function(module, exports) {
496 |
497 | // removed by extract-text-webpack-plugin
498 |
499 | /***/ })
500 |
501 | /******/ });
--------------------------------------------------------------------------------
/dist/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/js/post-form.js": "/js/post-form.js",
3 | "/js/tool.js": "/js/tool.js",
4 | "/css/tool.css": "/css/tool.css",
5 | "/css/post-form.css": "/css/post-form.css"
6 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/resources/js/components/Tool.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Blog
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | Blog is currently installed and active.
22 |
23 |
24 |
25 |
26 |
27 |
28 | Blog DB tables not found.
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
100 |
--------------------------------------------------------------------------------
/resources/js/components/post-form/Create.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{__('New')}} {{ singularName }}
5 |
87 |
88 |
89 |
90 |
203 |
204 |
--------------------------------------------------------------------------------
/resources/js/components/post-form/DefaultField.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ fieldLabel }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{ firstError }}
15 |
16 |
17 |
18 | {{ field.helpText }}
19 |
20 |
21 |
22 |
23 |
24 |
55 |
--------------------------------------------------------------------------------
/resources/js/components/post-form/Edit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{__('Edit')}} {{ singularName }}
4 |
5 |
6 |
152 |
153 |
154 |
155 |
156 |
322 |
--------------------------------------------------------------------------------
/resources/js/components/post-form/FieldSet.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
18 |
19 |
20 |
21 |
22 |
23 |
38 |
--------------------------------------------------------------------------------
/resources/js/post-form.js:
--------------------------------------------------------------------------------
1 | Nova.booting((Vue, router) => {
2 | Vue.component('post-form-create', require('./components/post-form/Create'))
3 | Vue.component('post-form-edit', require('./components/post-form/Edit'))
4 | Vue.component('default-field', require('./components/post-form/DefaultField'))
5 | Vue.component('field-set', require('./components/post-form/FieldSet'))
6 | })
7 |
--------------------------------------------------------------------------------
/resources/js/tool.js:
--------------------------------------------------------------------------------
1 | Nova.booting((Vue, router) => {
2 | router.addRoutes([
3 | {
4 | name: 'nova-blog-tool',
5 | path: '/nova-blog-tool',
6 | component: require('./components/Tool'),
7 | },
8 | ])
9 | })
10 |
--------------------------------------------------------------------------------
/resources/sass/post-form.scss:
--------------------------------------------------------------------------------
1 | // Nova Tool CSS
2 |
--------------------------------------------------------------------------------
/resources/sass/tool.scss:
--------------------------------------------------------------------------------
1 | // Nova Tool CSS
2 |
--------------------------------------------------------------------------------
/resources/views/navigation.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
10 |
13 |
14 |
15 | -
16 |
17 | Dashboard
18 |
19 |
20 | -
21 |
27 | Categories
28 |
29 |
30 | -
31 |
37 | Posts
38 |
39 |
40 | -
41 |
47 | Comments
48 |
49 |
50 | -
51 |
57 | Tags
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/routes/api.php:
--------------------------------------------------------------------------------
1 | published = true;
29 | $model->status = 'published';
30 | $model->save();
31 | } catch (Exception $e) {
32 | $this->markAsFailed($model, $e);
33 | return Action::danger('It not worked!');
34 | }
35 | }
36 | return Action::message('Post Published!');
37 | }
38 |
39 | /**
40 | * Get the fields available on the action.
41 | *
42 | * @return array
43 | */
44 | public function fields()
45 | {
46 | return [];
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Actions/SaveDraftPost.php:
--------------------------------------------------------------------------------
1 | published = false;
29 | $model->status = 'draft';
30 | $model->save();
31 |
32 | } catch (Exception $e) {
33 | $this->markAsFailed($model, $e);
34 | return Action::danger('It not worked!');
35 | }
36 | }
37 | return Action::message('Post Saved!');
38 | }
39 |
40 | /**
41 | * Get the fields available on the action.
42 | *
43 | * @return array
44 | */
45 | public function fields()
46 | {
47 | return [];
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/BlogResponder.php:
--------------------------------------------------------------------------------
1 | name = $name;
48 | $this->label = $label;
49 | $this->position = $position;
50 | $this->class = $class;
51 | $this->style = $style;
52 | parent::__construct($this->prepareFields($fields));
53 | }
54 |
55 | /**
56 | * Prepare the panel for JSON serialization.
57 | *
58 | * @return array
59 | */
60 | public function jsonSerialize()
61 | {
62 | return [
63 | 'component' => $this->component,
64 | 'name' => $this->name,
65 | 'label' => $this->label,
66 | 'position' => $this->position,
67 | 'prefixComponent' => true,
68 | 'panel' => $this->panel,
69 | 'indexName' => $this->name,
70 | 'fields' => $this->data,
71 | 'class' => $this->class,
72 | 'style' => $this->style
73 | ];
74 | }
75 |
76 | /**
77 | * Prepare the given fields.
78 | *
79 | * @param \Closure|array $fields
80 | * @return array
81 | */
82 | protected function prepareFields($fields)
83 | {
84 | return collect(is_callable($fields) ? $fields() : $fields)->each(function ($field) {
85 | if($field instanceof Field) {
86 | $field->withMeta(['fieldSet' => $this->name]);
87 | }
88 | })->all();
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/Http/Controllers/BlogBaseController.php:
--------------------------------------------------------------------------------
1 | migrations = $migrator->getMigrations();
32 | }
33 |
34 | /**
35 | * Undocumented function.
36 | *
37 | * @return \Illuminate\Http\JsonResponse
38 | */
39 | public function execute() : JsonResponse
40 | {
41 | $this->processTask();
42 |
43 | return response()->json(['messages' => $this->messages], 200);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Http/Controllers/DashboardController.php:
--------------------------------------------------------------------------------
1 | json([
13 | 'installed' => Blog::isInstalled(),
14 | ], 200);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/Http/Controllers/MigrationController.php:
--------------------------------------------------------------------------------
1 | migrations as $tableName => $migrationClass) {
13 | if (Schema::hasTable($tableName)) {
14 | $this->messages[] = BlogResponder::tableAlreadyCreated($tableName);
15 | }
16 |
17 | try {
18 | $migrationClass->up();
19 | $this->messages[] = BlogResponder::migrationSuccess($tableName);
20 | } catch (\Exception $e) {
21 | $this->messages[] = BlogResponder::migrationFailure($tableName);
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Http/Controllers/ResetController.php:
--------------------------------------------------------------------------------
1 | migrations as $tableName => $migrationClass) {
14 | if (! Schema::hasTable($tableName)) {
15 | $this->messages[] = BlogResponder::resetTableNotFound($tableName);
16 | }
17 |
18 | try {
19 | $this->truncateTable($tableName);
20 | $this->messages[] = BlogResponder::resetSuccess($tableName);
21 | } catch (\Exception $e) {
22 | $this->messages[] = BlogResponder::resetFailure($tableName);
23 | }
24 | }
25 | }
26 |
27 | protected function truncateTable($tableName)
28 | {
29 | DB::statement('SET FOREIGN_KEY_CHECKS=0;');
30 | DB::table($tableName)->truncate();
31 | DB::statement('SET FOREIGN_KEY_CHECKS=1;');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Http/Controllers/UninstallController.php:
--------------------------------------------------------------------------------
1 | migrations as $tableName => $migrationClass) {
14 | if (! Schema::hasTable($tableName)) {
15 | $this->messages[] = BlogResponder::deleteTableNotFound($tableName);
16 | }
17 |
18 | try {
19 | $this->deleteTable($migrationClass);
20 | $this->messages[] = BlogResponder::deleteSuccess($tableName);
21 | } catch (\Exception $e) {
22 | $this->messages[] = BlogResponder::deleteFailure($tableName);
23 | }
24 | }
25 | }
26 |
27 | protected function deleteTable($migrationClass)
28 | {
29 | DB::statement('SET FOREIGN_KEY_CHECKS=0;');
30 | $migrationClass->down();
31 | DB::statement('SET FOREIGN_KEY_CHECKS=1;');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Http/Middleware/Authorize.php:
--------------------------------------------------------------------------------
1 | authorize($request) ? $next($request) : abort(403);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Metrics/Posts/NewPosts.php:
--------------------------------------------------------------------------------
1 | count($request, Post::class);
19 | }
20 |
21 | /**
22 | * Get the ranges available for the metric.
23 | * @return array
24 | */
25 | public function ranges()
26 | {
27 | return [
28 | 1 => 'Since Yesterday',
29 | 7 => 'Since Last Week',
30 | 30 => 'Since Last Month',
31 | 90 => 'Since 3 Months Ago',
32 | 180 => 'Since 6 Months Ago',
33 | 365 => 'Since Last Year',
34 | ];
35 | }
36 |
37 | /**
38 | * Determine for how many minutes the metric should be cached.
39 | * @return \DateTimeInterface|\DateInterval|float|int
40 | */
41 | public function cacheFor()
42 | {
43 | return now()->addMinutes(10);
44 | }
45 |
46 | /**
47 | * Get the URI key for the metric.
48 | * @return string
49 | */
50 | public function uriKey()
51 | {
52 | return 'new-posts';
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Metrics/Posts/PostsTrend.php:
--------------------------------------------------------------------------------
1 | countByDays($request, Post::class)->showLatestValue();
19 | }
20 |
21 | /**
22 | * Get the ranges available for the metric.
23 | * @return array
24 | */
25 | public function ranges()
26 | {
27 | return [
28 | 1 => 'Today',
29 | 2 => 'Yesterday',
30 | 7 => 'Last 7 Days',
31 | 14 => 'Last 14 Days',
32 | 28 => 'Last 28 Days',
33 | 30 => 'Last 30 Days',
34 | 90 => 'Last 90 Days',
35 | 180 => 'Last 180 Days',
36 | 365 => 'Last Year',
37 | ];
38 | }
39 |
40 | /**
41 | * Get the URI key for the metric.
42 | * @return string
43 | */
44 | public function uriKey()
45 | {
46 | return 'posts-per-day';
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Migrations/CategoryMigration.php:
--------------------------------------------------------------------------------
1 | increments('id');
14 | $table->unsignedInteger('parent_id')->nullable();
15 | $table->json('name');
16 | $table->json('description')->nullable();
17 | $table->string('slug')->unique();
18 | $table->softDeletes();
19 | $table->timestamps();
20 | });
21 | }
22 |
23 | public function down()
24 | {
25 | Schema::dropIfExists('categories');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Migrations/CommentMigration.php:
--------------------------------------------------------------------------------
1 | increments('id');
14 | $table->unsignedInteger('post_id');
15 | $table->unsignedInteger('user_id');
16 | $table->json('body');
17 | $table->timestamps();
18 | $table->softDeletes();
19 | });
20 | }
21 |
22 | public function down()
23 | {
24 | Schema::dropIfExists('comments');
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Migrations/ForeignKeyMigration.php:
--------------------------------------------------------------------------------
1 | foreign('user_id')->references('id')->on('users');
14 | });
15 |
16 | Schema::table('comments', function (Blueprint $table) {
17 | $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
18 | $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
19 | });
20 | }
21 |
22 | public function down()
23 | {
24 | Schema::table('posts', function (Blueprint $table) {
25 | $table->dropForeign(['user_id']);
26 | });
27 |
28 | Schema::table('comments', function (Blueprint $table) {
29 | $table->dropForeign(['post_id']);
30 | $table->dropForeign(['user_id']);
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Migrations/Migrator.php:
--------------------------------------------------------------------------------
1 | new CategoryMigration,
11 | 'posts' => new PostMigration,
12 | 'comments' => new CommentMigration,
13 | 'tags' => new TagMigration,
14 | 'post_category' => new PostCategoryPivotMigration,
15 | 'nova_pending_trix_attachments' => new NovaPendingTrixAttachmentsMigration(),
16 | 'nova_trix_attachments' => new NovaTrixAttachmentsMigration(),
17 | ];
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Migrations/NovaPendingTrixAttachmentsMigration.php:
--------------------------------------------------------------------------------
1 | increments('id');
14 | $table->string('draft_id')->index();
15 | $table->string('attachment');
16 | $table->string('disk');
17 | $table->timestamps();
18 | });
19 | }
20 |
21 | public function down()
22 | {
23 | Schema::dropIfExists('nova_pending_trix_attachments');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Migrations/NovaTrixAttachmentsMigration.php:
--------------------------------------------------------------------------------
1 | increments('id');
14 | $table->string('attachable_type');
15 | $table->unsignedInteger('attachable_id');
16 | $table->string('attachment');
17 | $table->string('disk');
18 | $table->string('url')->index();
19 | $table->timestamps();
20 |
21 | $table->index(['attachable_type', 'attachable_id']);
22 | });
23 | }
24 |
25 | public function down()
26 | {
27 | Schema::dropIfExists('nova_trix_attachments');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Migrations/PostCategoryPivotMigration.php:
--------------------------------------------------------------------------------
1 | increments('id');
14 | $table->unsignedInteger('post_id');
15 | $table->unsignedInteger('category_id');
16 | });
17 | }
18 |
19 | public function down()
20 | {
21 | Schema::dropIfExists('post_category');
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Migrations/PostMigration.php:
--------------------------------------------------------------------------------
1 | increments('id');
14 | $table->unsignedInteger('parent_id')->nullable();
15 | $table->json('title')->unique();
16 | $table->json('content');
17 | $table->json('category')->nullable();
18 | $table->string('slug')->unique();
19 | $table->unsignedInteger('menu_order')->default(0);
20 |
21 | $table->string('status')->default('draft');
22 | $table->boolean('published')->default(false);
23 | $table->boolean('featured')->default(false);
24 |
25 | $table->timestamp('post_date')->useCurrent();
26 | $table->timestamp('modified_date')->nullable();
27 | $table->timestamp('publish_date')->nullable();
28 | $table->timestamp('scheduled_for')->useCurrent();
29 |
30 | $table->string('featured_image')->nullable();
31 | $table->softDeletes();
32 | $table->timestamps();
33 | });
34 | }
35 |
36 | public function down()
37 | {
38 | Schema::dropIfExists('posts');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Migrations/TagMigration.php:
--------------------------------------------------------------------------------
1 | increments('id');
14 | $table->json('name');
15 | $table->json('slug');
16 | $table->string('type')->nullable();
17 | $table->integer('order_column')->nullable();
18 | $table->softDeletes();
19 | $table->timestamps();
20 | });
21 |
22 | Schema::create('taggables', function (Blueprint $table) {
23 | $table->increments('id');
24 | $table->integer('tag_id')->unsigned();
25 | $table->integer('taggable_id')->unsigned();
26 | $table->string('taggable_type');
27 |
28 | $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
29 | });
30 | }
31 |
32 | public function down()
33 | {
34 | Schema::dropIfExists('tags');
35 | Schema::dropIfExists('taggables');
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Models/Category.php:
--------------------------------------------------------------------------------
1 | belongsToMany(Post::class, 'post_category', 'post_id', 'category_id');
34 | }
35 |
36 | /**
37 | * Get the route key for the model.
38 | *
39 | * @return string
40 | */
41 | public function getRouteKeyName()
42 | {
43 | return 'slug';
44 | }
45 |
46 | public function setAttribute($key, $value)
47 | {
48 | if (in_array($key, $this->translatable) && ! is_array($value)) {
49 | return $this->setTranslation($key, app()->getLocale(), $value);
50 | }
51 | return parent::setAttribute($key, $value);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Models/Comment.php:
--------------------------------------------------------------------------------
1 | 'integer',
33 | 'user_id' => 'integer',
34 | 'body' => 'string',
35 | ];
36 |
37 | /**
38 | * The attributes that should be mutated to dates.
39 | * @var array
40 | */
41 | protected $dates = [
42 | 'created_at',
43 | 'updated_at',
44 | 'deleted_at',
45 | ];
46 |
47 | /**
48 | * @return BelongsTo
49 | */
50 | public function post(): BelongsTo
51 | {
52 | return $this->belongsTo(Post::class);
53 | }
54 |
55 | /**
56 | * @return BelongsTo
57 | */
58 | public function author(): BelongsTo
59 | {
60 | return $this->belongsTo(config('nova-blog.user_model'), 'user_id');
61 | }
62 |
63 | public function setAttribute($key, $value)
64 | {
65 | if (in_array($key, $this->translatable) && ! is_array($value)) {
66 | return $this->setTranslation($key, app()->getLocale(), $value);
67 | }
68 | return parent::setAttribute($key, $value);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Models/Image.php:
--------------------------------------------------------------------------------
1 | filename);
30 | }
31 |
32 | /**
33 | * Get image thumbnail's link.
34 | * @return string
35 | */
36 | public function getThumbnailLinkAttribute()
37 | {
38 | return url('uploads/images/'.config('nova-blog.image_settings.disk').$this->thumbnail);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Models/Post.php:
--------------------------------------------------------------------------------
1 | 'boolean',
51 | 'scheduled_for' => 'datetime:Y-m-d H:i:s',
52 | ];
53 |
54 | /**
55 | * The attributes that should be mutated to dates.
56 | * @var array
57 | */
58 | protected $dates = [
59 | 'scheduled_for',
60 | 'deleted_at',
61 | 'created_at',
62 | 'updated_at',
63 | ];
64 |
65 | /**
66 | * Published mutator.
67 | * @return bool
68 | */
69 | // public function getPublishedAttribute()
70 | // {
71 | // return now() > $this->scheduled_for;
72 | // }
73 |
74 | /**
75 | * The "booting" method of the model.
76 | *
77 | * @return void
78 | */
79 | protected static function boot()
80 | {
81 | parent::boot();
82 |
83 | self::updating(function($model){
84 | // if($model->published) {
85 | // $model->status = 'published';
86 | // }
87 | // if($model->status != 'published') {
88 | // $model->published = 0;
89 | // }
90 | });
91 |
92 | self::creating(function($model){
93 | if($model->published) {
94 | $model->status = 'published';
95 | }
96 | });
97 |
98 | self::created(function($model){
99 | $model->category()->sync($model->queuedCategories, true);
100 | $model->queuedCategories = [];
101 | });
102 |
103 | static::addGlobalScope(new PublishedScope());
104 | }
105 |
106 | function setCategoryAttribute($value){
107 | if (! $this->exists) {
108 | $this->queuedCategories = $value;
109 | return;
110 | }
111 |
112 | $this->category()->sync($value, true);
113 | }
114 |
115 | function getCategoryAttribute(){
116 | return implode(',', $this->category()->pluck('category_id')->toArray());
117 | }
118 |
119 | /**
120 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
121 | */
122 | public function author(): BelongsTo
123 | {
124 | return $this->belongsTo(config('nova-blog.resources.users.model'), 'user_id');
125 | }
126 |
127 | /**
128 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
129 | */
130 | // public function featured_image(): BelongsTo
131 | // {
132 | // return $this->belongsTo(Image::class, 'image_id');
133 | // }
134 |
135 | /**
136 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
137 | */
138 | public function category(): BelongsToMany
139 | {
140 | return $this->belongsToMany(Category::class, 'post_category');
141 | }
142 |
143 | /**
144 | * @return \Illuminate\Database\Eloquent\Relations\HasMany
145 | */
146 | public function comments(): HasMany
147 | {
148 | return $this->hasMany(Comment::class);
149 | }
150 |
151 | /**
152 | * Get the route key for the model.
153 | *
154 | * @return string
155 | */
156 | public function getRouteKeyName()
157 | {
158 | return 'slug';
159 | }
160 |
161 | public function setAttribute($key, $value)
162 | {
163 | if (in_array($key, $this->translatable) && ! is_array($value)) {
164 | return $this->setTranslation($key, app()->getLocale(), $value);
165 | }
166 | return parent::setAttribute($key, $value);
167 | }
168 |
169 | public function tags(): MorphToMany
170 | {
171 | return $this->morphToMany(
172 | Tag::class,
173 | 'taggable',
174 | 'taggables',
175 | 'taggable_id',
176 | 'tag_id'
177 | );
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/Models/Tag.php:
--------------------------------------------------------------------------------
1 | 'string',
30 | 'description' => 'string',
31 | 'tagged_count' => 'integer',
32 | ];
33 |
34 | /**
35 | * The attributes that should be mutated to dates.
36 | * @var array
37 | */
38 | protected $dates = [
39 | 'created_at',
40 | 'updated_at',
41 | ];
42 |
43 | /**
44 | * @return BelongsToMany
45 | */
46 |
47 | public function posts(): MorphToMany
48 | {
49 | return $this->morphedByMany(
50 | Post::class,
51 | 'taggable',
52 | 'taggables',
53 | 'tag_id',
54 | 'taggable_id'
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/NovaBlogTool.php:
--------------------------------------------------------------------------------
1 | delete($image->filename);
18 | Storage::disk(config('nova-blog.image_settings.disk'))->delete(config('nova-blog.image_settings.path_thumb').$image->thumbnail);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Policies/CategoryPolicy.php:
--------------------------------------------------------------------------------
1 | can('view category');
21 | }
22 |
23 | public function create(User $user)
24 | {
25 | return $user->can('create category');
26 | }
27 |
28 | public function update(User $user, Category $category)
29 | {
30 | return $user->can('update category');
31 | }
32 |
33 | public function delete(User $user, Category $category)
34 | {
35 | return $user->can('delete category');
36 | }
37 |
38 | public function restore(User $user, Category $category)
39 | {
40 | return $user->can('restore category');
41 | }
42 |
43 | public function forceDelete(User $user, Category $category)
44 | {
45 | return $user->can('force delete category');
46 | }
47 |
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/Policies/CommentPolicy.php:
--------------------------------------------------------------------------------
1 | can('view comment');
21 | }
22 |
23 | public function create(User $user)
24 | {
25 | return $user->can('create comment');
26 | }
27 |
28 | public function update(User $user, Comment $comment)
29 | {
30 | return $user->can('update comment');
31 | }
32 |
33 | public function delete(User $user, Comment $comment)
34 | {
35 | return $user->can('delete comment');
36 | }
37 |
38 | public function restore(User $user, Comment $comment)
39 | {
40 | return $user->can('restore comment');
41 | }
42 |
43 | public function forceDelete(User $user, Comment $comment)
44 | {
45 | return $user->can('force delete comment');
46 | }
47 |
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/Policies/PostPolicy.php:
--------------------------------------------------------------------------------
1 | can('view post');
21 | }
22 |
23 | public function create(User $user)
24 | {
25 | return $user->can('create post');
26 | }
27 |
28 | public function update(User $user, Post $post)
29 | {
30 | return $user->can('update post');
31 | }
32 |
33 | public function delete(User $user, Post $post)
34 | {
35 | return $user->can('delete post');
36 | }
37 |
38 | public function restore(User $user, Post $post)
39 | {
40 | return $user->can('restore post');
41 | }
42 |
43 | public function forceDelete(User $user, Post $post)
44 | {
45 | return $user->can('force delete post');
46 | }
47 |
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/Policies/TagPolicy.php:
--------------------------------------------------------------------------------
1 | can('view tag');
21 | }
22 |
23 | public function create(User $user)
24 | {
25 | return $user->can('create tag');
26 | }
27 |
28 | public function update(User $user, Tag $tag)
29 | {
30 | return $user->can('update tag');
31 | }
32 |
33 | public function delete(User $user, Tag $tag)
34 | {
35 | return $user->can('delete tag');
36 | }
37 |
38 | public function restore(User $user, Tag $tag)
39 | {
40 | return $user->can('restore tag');
41 | }
42 |
43 | public function forceDelete(User $user, Tag $tag)
44 | {
45 | return $user->can('force delete tag');
46 | }
47 |
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/PostForm.php:
--------------------------------------------------------------------------------
1 | featured_image;
15 |
16 | $file_extension = $image_file->getClientOriginalExtension();
17 |
18 | $new_filename = str_random(8).'_'.time().''.str_random(32);
19 |
20 | $filename = $new_filename.'.'.$file_extension;
21 |
22 | $filename_thumb = $new_filename.'.'.$file_extension;
23 |
24 | Storage::disk(config('nova-blog.image_settings.disk'))
25 | ->put(
26 | config('nova-blog.image_settings.path').$filename,
27 | (string) file_get_contents($image_file)
28 | );
29 | $image_thumb = ImageManagerStatic::make($image_file)
30 | ->resize(
31 | config('nova-blog.image_thumb_settings.width'),
32 | config('nova-blog.image_thumb_settings.height')
33 | )->save($filename_thumb);
34 | Storage::disk(config('nova-blog.image_settings.disk'))
35 | ->put(
36 | config('nova-blog.image_settings.path_thumb').$filename_thumb,
37 | $image_thumb
38 | );
39 |
40 | //@todo Check is image uploaded with the post or alone in Image page.
41 | // $image_model = new Image;
42 | // $image_model->title = $image_file->getClientOriginalName();
43 | // $image_model->filename = $filename;
44 | // $image_model->thumbnail = $filename_thumb;
45 | // $image_model->size = number_format(
46 | // $image_file->getSize() / 1000000,
47 | // 2
48 | // ).'MB';
49 | // $image_model->save();
50 |
51 | return [
52 | //'image_id' => $image_model->id,
53 | 'featured_image' => $filename,
54 | ];
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Resources/Category.php:
--------------------------------------------------------------------------------
1 | sortable(),
51 | Text::make('Name')
52 | ->rules(['required', 'string'])
53 | ->sortable(),
54 | Markdown::make('Description'),
55 | Multilingual::make('lang'),
56 | HasMany::make('Posts'),
57 | ];
58 | }
59 |
60 | /**
61 | * Get the cards available for the request.
62 | * @param \Illuminate\Http\Request $request
63 | * @return array
64 | */
65 | public function cards(Request $request)
66 | {
67 | return [];
68 | }
69 |
70 | /**
71 | * Get the filters available for the resource.
72 | * @param \Illuminate\Http\Request $request
73 | * @return array
74 | */
75 | public function filters(Request $request)
76 | {
77 | return [];
78 | }
79 |
80 | /**
81 | * Get the lenses available for the resource.
82 | * @param \Illuminate\Http\Request $request
83 | * @return array
84 | */
85 | public function lenses(Request $request)
86 | {
87 | return [];
88 | }
89 |
90 | /**
91 | * Get the actions available for the resource.
92 | * @param \Illuminate\Http\Request $request
93 | * @return array
94 | */
95 | public function actions(Request $request)
96 | {
97 | return [];
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Resources/Comment.php:
--------------------------------------------------------------------------------
1 | sortable(),
50 |
51 | BelongsTo::make('Post', 'post', Post::class)
52 | ->sortable()
53 | ->rules('required'),
54 |
55 | BelongsTo::make('Author', 'author', User::class)
56 | ->sortable()
57 | ->rules('required'),
58 |
59 | Markdown::make('Body')
60 | ->rules(['required', 'string']),
61 | ];
62 | }
63 |
64 | /**
65 | * Get the cards available for the request.
66 | * @param \Illuminate\Http\Request $request
67 | * @return array
68 | */
69 | public function cards(Request $request)
70 | {
71 | return [];
72 | }
73 |
74 | /**
75 | * Get the filters available for the resource.
76 | * @param \Illuminate\Http\Request $request
77 | * @return array
78 | */
79 | public function filters(Request $request)
80 | {
81 | return [];
82 | }
83 |
84 | /**
85 | * Get the lenses available for the resource.
86 | * @param \Illuminate\Http\Request $request
87 | * @return array
88 | */
89 | public function lenses(Request $request)
90 | {
91 | return [];
92 | }
93 |
94 | /**
95 | * Get the actions available for the resource.
96 | * @param \Illuminate\Http\Request $request
97 | * @return array
98 | */
99 | public function actions(Request $request)
100 | {
101 | return [];
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Resources/Post.php:
--------------------------------------------------------------------------------
1 | options([
76 | 'draft' => 'Draft',
77 | 'published' => 'Published'
78 | ])->withMeta(['type' => 'hidden'])->onlyOnForms(),
79 |
80 | //Boolean::make('published')->onlyOnForms()
81 | ],
82 | 'main', ''
83 | ),
84 |
85 | $this->mainFieldSet(),
86 |
87 | new FieldSet( 'Tags',
88 | [
89 | Tags::make('Tags')->withMeta(['label' => false]),
90 | ],
91 | 'side', 'Tags'
92 | ),
93 | new FieldSet( 'Category',
94 | [
95 | Checkboxes::make('Category', 'category')->options(\DigitalCloud\NovaBlogTool\Models\Category::pluck('name', 'id')->toArray())->withMeta(['label' => false]),
96 | ],
97 | 'side', 'Category'
98 | ),
99 | new FieldSet( 'Featured Image',
100 | [
101 | FilemanagerField::make('featured_image')->displayAsImage()->withMeta(['label' => false])
102 | //ImageUpload::make('Image', 'featured_image', 'local')->store(new StoreImage),
103 | ],
104 | 'side', 'Featured Image'
105 | ),
106 |
107 | HasMany::make('Comments', 'comments', Comment::class)
108 | ->sortable()
109 | ->rules(['required']),
110 | ], $this->conditionalFields($request));
111 | }
112 |
113 | /**
114 | * Get the cards available for the request.
115 | * @param \Illuminate\Http\Request $request
116 | * @return array
117 | */
118 | public function cards(Request $request)
119 | {
120 | return [
121 | (new NewPosts)->width('1/2'),
122 | (new PostsTrend)->width('1/2'),
123 | ];
124 | }
125 |
126 | /**
127 | * Get the filters available for the resource.
128 | * @param \Illuminate\Http\Request $request
129 | * @return array
130 | */
131 | public function filters(Request $request)
132 | {
133 | return [];
134 | }
135 |
136 | /**
137 | * Get the lenses available for the resource.
138 | * @param \Illuminate\Http\Request $request
139 | * @return array
140 | */
141 | public function lenses(Request $request)
142 | {
143 | return [];
144 | }
145 |
146 | /**
147 | * Get the actions available for the resource.
148 | * @param \Illuminate\Http\Request $request
149 | * @return array
150 | */
151 | public function actions(Request $request)
152 | {
153 | return [
154 | new PublishPost(),
155 | new SaveDraftPost()
156 | ];
157 | }
158 |
159 | public static function form ($request) {
160 | return new PostForm();
161 | }
162 |
163 | private function mainFieldSet() {
164 | return new FieldSet( 'Main Info',
165 | [
166 | ID::make()->sortable(),
167 | Text::make('title')->rules('required')
168 | ->withMeta(['extraAttributes' => ['class' => 'attr1val', 'fullWidthContent' => true]])
169 | ->withMeta(['showLabel' => true]),
170 | Select::make('status')->options([
171 | 'draft' => 'Draft',
172 | 'published' => 'Published'
173 | ])->withMeta(['type' => 'hidden'])->exceptOnForms(),
174 | Boolean::make('Published')->withMeta(['type' => 'hidden']),
175 |
176 | Trix::make('content')->withFiles('public')->rules('required')->withMeta(['label' => false]),
177 | Multilingual::make('lang'),
178 | ],
179 | 'main', 'Main Info'
180 | );
181 | }
182 |
183 | private function conditionalFields(Request $request) {
184 | return ToolServiceProvider::availableAdditionalFields($request);
185 | // $fields = [];
186 | // if(in_array('DigitalCloud\ServiceTool\Resources\Service', Nova::availableResources($request))) {
187 | // $fields[] = BelongsToMany::make('Services', 'service', \DigitalCloud\ServiceTool\Resources\Service::class);
188 | // }
189 | // return $fields;
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/Resources/Resource.php:
--------------------------------------------------------------------------------
1 | isMethod('get')) {
71 | return $creationFields;
72 | }
73 | $indexedCreationFields = $creationFields->mapWithKeys(function ($item) {
74 | return [$item->attribute => $item];
75 | });
76 |
77 | return $this->availableFieldSet($request, $indexedCreationFields);
78 | }
79 |
80 | public function updateFields(NovaRequest $request) {
81 | $updateFields = parent::updateFields($request);
82 | if(!$request->isMethod('get')) {
83 | return $updateFields;
84 | }
85 | $indexedUpdateFields = $updateFields->mapWithKeys(function ($item) {
86 | return [$item->attribute => $item];
87 | });
88 | return $this->availableFieldSet($request, $indexedUpdateFields);
89 | }
90 |
91 | public function detailFields(NovaRequest $request)
92 | {
93 | $detailFields = parent::detailFields($request);
94 | if(!$request->isMethod('get')) {
95 | return $detailFields;
96 | }
97 | //$detailFields = $this->availableFieldSet($request, $detailFields);
98 | return $detailFields;
99 |
100 | }
101 |
102 | public function availableFieldSet(NovaRequest $request, $fields)
103 | {
104 | $fieldSets = collect(array_values($this->fields($request)))
105 | ->whereInstanceOf(FieldSet::class)->mapWithKeys(function ($fieldSet) {
106 | return [camel_case($fieldSet->name) => $fieldSet];
107 | });
108 |
109 | if(count($fieldSets) < 1) {
110 | return $fields;
111 | }
112 | $this->assignFieldsToFieldSet($request, $fields);
113 |
114 | foreach ($fieldSets as $fieldSet) {
115 | $setfields = $fields->filter(function ($field) use ($fieldSet){
116 | return $field->meta['fieldSet'] == $fieldSet->name;
117 | });
118 | $fieldSet->data = $setfields;
119 | }
120 | return $fieldSets;
121 | }
122 |
123 | protected function assignFieldsToFieldSet(NovaRequest $request, $fields)
124 | {
125 | foreach ($fields as $field) {
126 | $name = $field->meta['fieldSet'] ?? Panel::defaultNameFor($request->newResource());
127 | $field->meta['fieldSet'] = $name;
128 | }
129 | return $fields;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/Resources/Tag.php:
--------------------------------------------------------------------------------
1 | getLocale();
52 | return [
53 | ID::make()->sortable(),
54 |
55 | Text::make('name')
56 | ->sortable()
57 | ->rules(['required', 'string', 'max:255'])
58 | ->creationRules(['unique:tags,name->' . $locale])
59 | ->updateRules(['unique:tags,name->' . $locale . ',{{resourceId}}']),
60 | Multilingual::make('lang'),
61 | MorphToMany::make('Posts', 'posts', Post::class),
62 | ];
63 | }
64 |
65 | /**
66 | * Get the cards available for the request.
67 | * @param \Illuminate\Http\Request $request
68 | * @return array
69 | */
70 | public function cards(Request $request)
71 | {
72 | return [];
73 | }
74 |
75 | /**
76 | * Get the filters available for the resource.
77 | * @param \Illuminate\Http\Request $request
78 | * @return array
79 | */
80 | public function filters(Request $request)
81 | {
82 | return [];
83 | }
84 |
85 | /**
86 | * Get the lenses available for the resource.
87 | * @param \Illuminate\Http\Request $request
88 | * @return array
89 | */
90 | public function lenses(Request $request)
91 | {
92 | return [];
93 | }
94 |
95 | /**
96 | * Get the actions available for the resource.
97 | * @param \Illuminate\Http\Request $request
98 | * @return array
99 | */
100 | public function actions(Request $request)
101 | {
102 | return [];
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/Scopes/PublishedScope.php:
--------------------------------------------------------------------------------
1 | where('scheduled_for', '<=', date('Y-m-d H:i:s'));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/ToolServiceProvider.php:
--------------------------------------------------------------------------------
1 | loadViewsFrom(__DIR__.'/../resources/views', 'nova-blog-tool');
28 |
29 | $this->app->booted(function () {
30 | $this->routes();
31 |
32 | Blog::injectToolResources();
33 | });
34 |
35 | $this->publishes([
36 | $this->configPath() => config_path('nova-blog.php'),
37 | ], 'nova-blog-config');
38 | }
39 |
40 | /**
41 | * Register the tool's routes.
42 | *
43 | * @return void
44 | */
45 | protected function routes()
46 | {
47 | if ($this->app->routesAreCached()) {
48 | return;
49 | }
50 |
51 | Route::middleware(['nova', Authorize::class])
52 | ->prefix('nova-vendor/nova-blog-tool')
53 | ->group(__DIR__.'/../routes/api.php');
54 | }
55 |
56 | /**
57 | * Register any application services.
58 | *
59 | * @return void
60 | */
61 | public function register()
62 | {
63 | $this->mergeConfigFrom($this->configPath(), 'nova-blog');
64 | }
65 |
66 | /**
67 | * @return string
68 | */
69 | protected function configPath()
70 | {
71 | return __DIR__.'/../config/nova-blog.php';
72 | }
73 |
74 | /**
75 | * Register the given additional fields.
76 | *
77 | * @param array $additionalFields
78 | * @return void
79 | */
80 | public static function additionalFields(array $additionalFields)
81 | {
82 | static::$additionalFields = array_merge(static::$additionalFields, $additionalFields);
83 | }
84 |
85 | /**
86 | * Get registered additional fields .
87 | *
88 | * @param \Illuminate\Http\Request $request
89 | * @return mixed
90 | */
91 | public static function availableAdditionalFields(Request $request)
92 | {
93 | return collect(static::$additionalFields)
94 | ->filter
95 | ->authorize($request)
96 | ->all();
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/src/Traits/Sluggable.php:
--------------------------------------------------------------------------------
1 | slug = static::generateUniqueSlug(
27 | $model->{$column},
28 | $event === 'updating'
29 | ? $model->id
30 | : null
31 | );
32 | });
33 | }
34 | }
35 |
36 | /**
37 | * Fetch sluggable field.
38 | * @param Model $model
39 | * @return string|null
40 | */
41 | protected static function getSluggableField(Model $model): ?string
42 | {
43 | $table = $model->getTable();
44 |
45 | switch ($table) {
46 | case 'posts':
47 | return 'title';
48 | case 'categories':
49 | return 'name';
50 | default:
51 | return null;
52 | }
53 | }
54 |
55 | /**
56 | * Generate a unique slug.
57 | * @param string $fieldValue
58 | * @param int|null $oldId
59 | * @return string
60 | */
61 | protected static function generateUniqueSlug(string $fieldValue, int $oldId = null): string
62 | {
63 | $slug = str_slug($fieldValue);
64 |
65 | $additionalQuery = static::getAdditionalQueryString($oldId);
66 |
67 | $latestSlug = static::whereRaw("(slug = '$slug' or slug LIKE '$slug-%'){$additionalQuery}")
68 | ->latest('id')
69 | ->value('slug');
70 |
71 | if ($latestSlug) {
72 | $pieces = explode('-', $latestSlug);
73 |
74 | $slug .= '-'.(intval(end($pieces)) + 1);
75 | }
76 |
77 | return $slug;
78 | }
79 |
80 | /**
81 | * Additional check for "updating" event.
82 | * Solves a problem when updating without changing sluggable field.
83 | * @param int|null $oldId
84 | * @return string
85 | */
86 | protected static function getAdditionalQueryString($oldId): string
87 | {
88 | if (is_null($oldId)) {
89 | return '';
90 | }
91 |
92 | return " AND id != '$oldId'";
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/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 |
7 | mix.js('resources/js/post-form.js', 'js')
8 | .sass('resources/sass/post-form.scss', 'css')
9 | .webpackConfig({
10 | resolve: {
11 | alias: {
12 | '@nova': path.resolve(__dirname, '../../vendor/laravel/nova/resources/js/')
13 | },
14 | symlinks: false
15 | }
16 | })
17 |
--------------------------------------------------------------------------------