diff --git a/README.md b/README.md index 6c6af50..ec27d9d 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This [Laravel Nova](https://nova.laravel.com) package adds a notes field to Nova - Notes field on Detail view - Differentiation between user-added and system-added notes - Ability to add notes through the UI or programmatically +- Ability to edit user-made notes - Ability to delete user-made notes (w/ confirmation modal) - Customizable placeholder support - Set ability to hide or show the 'Add Note' button @@ -84,6 +85,34 @@ To add notes programmatically, use the method provided by the `HasNotes` trait: public function addNote($note, $user = true, $system = true) ``` +## Editing notes programmatically + +To edit notes programmatically, use the `editNote` method provided by the `HasNotes` trait: + +```php +/** + * Edit a note with the given ID and text. + * + * @param int|string $noteId The ID of the note to edit. + * @param string $text The note text which can contain raw HTML. + * @return \Outl1ne\NovaNotesField\Models\Note + **/ +public function editNote($noteId, $text) +``` + +_Alternatively, you can simply update an existing Note record that's already in memory via standard Eloquent methods:_ +```php +$note = $notable->notes()->where('id', '=', $noteId)->first(); + +$note->update([ + 'text' => $noteText, +]); + +// Or... +$note->text = $noteText; +$note->save(); +``` + ## Configuration ### Publish configuration @@ -105,11 +134,11 @@ The available configuration option(s): | full_width_inputs | boolean | Optionally force all notes fields to display in full width. | | display_order | string | Optionally set the sort order for notes. Default is `DESC`. | -## Custom delete authorization +## Custom edit & delete authorization -By default, only the user that wrote the note can delete it and no one can delete system notes. +By default, only the user that wrote the note can edit/delete it and no one can edit/delete system notes. -You can define which user(s) can delete which notes by defining a new Laravel authorization Gate called `delete-nova-note`. +You can define which user(s) can edit/delete which notes by defining a new Laravel authorization Gate called `edit-nova-note` and `delete-note-note` respectively. In your `AuthServiceProvider.php` add a Gate definition like so: @@ -121,8 +150,12 @@ use Outl1ne\NovaNotesField\Models\Note; public function boot() { + Gate::define('edit-nova-note', function ($user, Note $note) { + // Do whatever here to add custom edit authorization logic, ie: + return $note->created_by === $user->id || $user->isAdmin; + }); Gate::define('delete-nova-note', function ($user, Note $note) { - // Do whatever here, ie: + // Do whatever here to add custom delete authorization logic, ie: return $note->created_by === $user->id || $user->isAdmin; }); } diff --git a/dist/js/entry.js b/dist/js/entry.js index 7067f0e..ac8caf9 100644 --- a/dist/js/entry.js +++ b/dist/js/entry.js @@ -1,2 +1,2 @@ /*! For license information please see entry.js.LICENSE.txt */ -(()=>{var t,e={9680:(t,e,r)=>{function n(t){return t&&"object"==typeof t&&"default"in t?t.default:t}var o=n(r(3945)),i=r(129),a=n(r(9996));function s(){return(s=Object.assign?Object.assign.bind():function(t){for(var e=1;e0&&"back_forward"===window.performance.getEntriesByType("navigation")[0].type},r.handleBackForwardVisit=function(t){var e=this;window.history.state.version=t.version,this.setPage(window.history.state,{preserveScroll:!0,preserveState:!0}).then((function(){e.restoreScrollPositions(),b(t)}))},r.locationVisit=function(t,e){try{window.sessionStorage.setItem("inertiaLocationVisit",JSON.stringify({preserveScroll:e})),window.location.href=t.href,v(window.location).href===v(t).href&&window.location.reload()}catch(t){return!1}},r.isLocationVisit=function(){try{return null!==window.sessionStorage.getItem("inertiaLocationVisit")}catch(t){return!1}},r.handleLocationVisit=function(t){var e,r,n,o,i=this,a=JSON.parse(window.sessionStorage.getItem("inertiaLocationVisit")||"");window.sessionStorage.removeItem("inertiaLocationVisit"),t.url+=window.location.hash,t.rememberedState=null!=(e=null==(r=window.history.state)?void 0:r.rememberedState)?e:{},t.scrollRegions=null!=(n=null==(o=window.history.state)?void 0:o.scrollRegions)?n:[],this.setPage(t,{preserveScroll:a.preserveScroll,preserveState:!0}).then((function(){a.preserveScroll&&i.restoreScrollPositions(),b(t)}))},r.isLocationVisitResponse=function(t){return t&&409===t.status&&t.headers["x-inertia-location"]},r.isInertiaResponse=function(t){return null==t?void 0:t.headers["x-inertia"]},r.createVisitId=function(){return this.visitId={},this.visitId},r.cancelVisit=function(t,e){var r=e.cancelled,n=void 0!==r&&r,o=e.interrupted,i=void 0!==o&&o;!t||t.completed||t.cancelled||t.interrupted||(t.cancelToken.cancel(),t.onCancel(),t.completed=!1,t.cancelled=n,t.interrupted=i,g(t),t.onFinish(t))},r.finishVisit=function(t){t.cancelled||t.interrupted||(t.completed=!0,t.cancelled=!1,t.interrupted=!1,g(t),t.onFinish(t))},r.resolvePreserveOption=function(t,e){return"function"==typeof t?t(e):"errors"===t?Object.keys(e.props.errors||{}).length>0:t},r.visit=function(t,r){var n=this,i=void 0===r?{}:r,a=i.method,u=void 0===a?e.n$.GET:a,l=i.data,p=void 0===l?{}:l,d=i.replace,g=void 0!==d&&d,b=i.preserveScroll,w=void 0!==b&&b,x=i.preserveState,S=void 0!==x&&x,O=i.only,E=void 0===O?[]:O,_=i.headers,j=void 0===_?{}:_,P=i.errorBag,A=void 0===P?"":P,T=i.forceFormData,k=void 0!==T&&T,C=i.onCancelToken,N=void 0===C?function(){}:C,R=i.onBefore,D=void 0===R?function(){}:R,M=i.onStart,U=void 0===M?function(){}:M,B=i.onProgress,I=void 0===B?function(){}:B,L=i.onFinish,F=void 0===L?function(){}:L,V=i.onCancel,W=void 0===V?function(){}:V,q=i.onSuccess,Y=void 0===q?function(){}:q,z=i.onError,H=void 0===z?function(){}:z,$=i.queryStringArrayFormat,G=void 0===$?"brackets":$,J="string"==typeof t?h(t):t;if(!function t(e){return e instanceof File||e instanceof Blob||e instanceof FileList&&e.length>0||e instanceof FormData&&Array.from(e.values()).some((function(e){return t(e)}))||"object"==typeof e&&null!==e&&Object.values(e).some((function(e){return t(e)}))}(p)&&!k||p instanceof FormData||(p=f(p)),!(p instanceof FormData)){var Q=y(u,J,p,G),X=Q[1];J=h(Q[0]),p=X}var K={url:J,method:u,data:p,replace:g,preserveScroll:w,preserveState:S,only:E,headers:j,errorBag:A,forceFormData:k,queryStringArrayFormat:G,cancelled:!1,completed:!1,interrupted:!1};if(!1!==D(K)&&function(t){return m("before",{cancelable:!0,detail:{visit:t}})}(K)){this.activeVisit&&this.cancelVisit(this.activeVisit,{interrupted:!0}),this.saveScrollPositions();var Z=this.createVisitId();this.activeVisit=s({},K,{onCancelToken:N,onBefore:D,onStart:U,onProgress:I,onFinish:F,onCancel:W,onSuccess:Y,onError:H,queryStringArrayFormat:G,cancelToken:o.CancelToken.source()}),N({cancel:function(){n.activeVisit&&n.cancelVisit(n.activeVisit,{cancelled:!0})}}),function(t){m("start",{detail:{visit:t}})}(K),U(K),o({method:u,url:v(J).href,data:u===e.n$.GET?{}:p,params:u===e.n$.GET?p:{},cancelToken:this.activeVisit.cancelToken.token,headers:s({},j,{Accept:"text/html, application/xhtml+xml","X-Requested-With":"XMLHttpRequest","X-Inertia":!0},E.length?{"X-Inertia-Partial-Component":this.page.component,"X-Inertia-Partial-Data":E.join(",")}:{},A&&A.length?{"X-Inertia-Error-Bag":A}:{},this.page.version?{"X-Inertia-Version":this.page.version}:{}),onUploadProgress:function(t){p instanceof FormData&&(t.percentage=Math.round(t.loaded/t.total*100),function(t){m("progress",{detail:{progress:t}})}(t),I(t))}}).then((function(t){var e;if(!n.isInertiaResponse(t))return Promise.reject({response:t});var r=t.data;E.length&&r.component===n.page.component&&(r.props=s({},n.page.props,r.props)),w=n.resolvePreserveOption(w,r),(S=n.resolvePreserveOption(S,r))&&null!=(e=window.history.state)&&e.rememberedState&&r.component===n.page.component&&(r.rememberedState=window.history.state.rememberedState);var o=J,i=h(r.url);return o.hash&&!i.hash&&v(o).href===i.href&&(i.hash=o.hash,r.url=i.href),n.setPage(r,{visitId:Z,replace:g,preserveScroll:w,preserveState:S})})).then((function(){var t=n.page.props.errors||{};if(Object.keys(t).length>0){var e=A?t[A]?t[A]:{}:t;return function(t){m("error",{detail:{errors:t}})}(e),H(e)}return m("success",{detail:{page:n.page}}),Y(n.page)})).catch((function(t){if(n.isInertiaResponse(t.response))return n.setPage(t.response.data,{visitId:Z});if(n.isLocationVisitResponse(t.response)){var e=h(t.response.headers["x-inertia-location"]),r=J;r.hash&&!e.hash&&v(r).href===e.href&&(e.hash=r.hash),n.locationVisit(e,!0===w)}else{if(!t.response)return Promise.reject(t);m("invalid",{cancelable:!0,detail:{response:t.response}})&&c.show(t.response.data)}})).then((function(){n.activeVisit&&n.finishVisit(n.activeVisit)})).catch((function(t){if(!o.isCancel(t)){var e=m("exception",{cancelable:!0,detail:{exception:t}});if(n.activeVisit&&n.finishVisit(n.activeVisit),e)return Promise.reject(t)}}))}},r.setPage=function(t,e){var r=this,n=void 0===e?{}:e,o=n.visitId,i=void 0===o?this.createVisitId():o,a=n.replace,s=void 0!==a&&a,u=n.preserveScroll,c=void 0!==u&&u,l=n.preserveState,f=void 0!==l&&l;return Promise.resolve(this.resolveComponent(t.component)).then((function(e){i===r.visitId&&(t.scrollRegions=t.scrollRegions||[],t.rememberedState=t.rememberedState||{},(s=s||h(t.url).href===window.location.href)?r.replaceState(t):r.pushState(t),r.swapComponent({component:e,page:t,preserveState:f}).then((function(){c||r.resetScrollPositions(),s||b(t)})))}))},r.pushState=function(t){this.page=t,window.history.pushState(t,"",t.url)},r.replaceState=function(t){this.page=t,window.history.replaceState(t,"",t.url)},r.handlePopstateEvent=function(t){var e=this;if(null!==t.state){var r=t.state,n=this.createVisitId();Promise.resolve(this.resolveComponent(r.component)).then((function(t){n===e.visitId&&(e.page=r,e.swapComponent({component:t,page:r,preserveState:!1}).then((function(){e.restoreScrollPositions(),b(r)})))}))}else{var o=h(this.page.url);o.hash=window.location.hash,this.replaceState(s({},this.page,{url:o.href})),this.resetScrollPositions()}},r.get=function(t,r,n){return void 0===r&&(r={}),void 0===n&&(n={}),this.visit(t,s({},n,{method:e.n$.GET,data:r}))},r.reload=function(t){return void 0===t&&(t={}),this.visit(window.location.href,s({},t,{preserveScroll:!0,preserveState:!0}))},r.replace=function(t,e){var r;return void 0===e&&(e={}),console.warn("Inertia.replace() has been deprecated and will be removed in a future release. Please use Inertia."+(null!=(r=e.method)?r:"get")+"() instead."),this.visit(t,s({preserveState:!0},e,{replace:!0}))},r.post=function(t,r,n){return void 0===r&&(r={}),void 0===n&&(n={}),this.visit(t,s({preserveState:!0},n,{method:e.n$.POST,data:r}))},r.put=function(t,r,n){return void 0===r&&(r={}),void 0===n&&(n={}),this.visit(t,s({preserveState:!0},n,{method:e.n$.PUT,data:r}))},r.patch=function(t,r,n){return void 0===r&&(r={}),void 0===n&&(n={}),this.visit(t,s({preserveState:!0},n,{method:e.n$.PATCH,data:r}))},r.delete=function(t,r){return void 0===r&&(r={}),this.visit(t,s({preserveState:!0},r,{method:e.n$.DELETE}))},r.remember=function(t,e){var r,n;void 0===e&&(e="default"),w||this.replaceState(s({},this.page,{rememberedState:s({},null==(r=this.page)?void 0:r.rememberedState,(n={},n[e]=t,n))}))},r.restore=function(t){var e,r;if(void 0===t&&(t="default"),!w)return null==(e=window.history.state)||null==(r=e.rememberedState)?void 0:r[t]},r.on=function(t,e){var r=function(t){var r=e(t);t.cancelable&&!t.defaultPrevented&&!1===r&&t.preventDefault()};return document.addEventListener("inertia:"+t,r),function(){return document.removeEventListener("inertia:"+t,r)}},t}(),S={buildDOMElement:function(t){var e=document.createElement("template");e.innerHTML=t;var r=e.content.firstChild;if(!t.startsWith(" diff --git a/resources/js/components/NoteInput.vue b/resources/js/components/NoteInput.vue index d6becae..076a04e 100644 --- a/resources/js/components/NoteInput.vue +++ b/resources/js/components/NoteInput.vue @@ -28,7 +28,7 @@ type="button" :disabled="loading || !modelValue" > - {{ __('novaNotesField.addNote') }} + {{ editing ? __('novaNotesField.updateNote') : __('novaNotesField.addNote') }} @@ -36,7 +36,7 @@