From a4103e7084a816af77c90add1fef39ba375d40e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sun, 5 Aug 2018 17:07:34 +0200 Subject: [PATCH] Row edit and delete card working --- app/Http/Controllers/Controller.php | 6 + app/Http/Controllers/TableEditController.php | 59 ++++++++- app/Tables/Changeset.php | 99 ++++++++++++-- app/Tables/Column.php | 29 ++-- porklib/helpers.php | 124 +++++++++++------- resources/assets/js/app.js | 73 +---------- resources/assets/js/base-setup.js | 52 ++++++++ resources/assets/js/bootstrap.js | 55 -------- resources/assets/js/components/RowEditor.vue | 38 +++++- resources/assets/js/{ => lib}/url-slug.js | 0 resources/assets/js/modules/block-collapse.js | 21 +++ resources/assets/js/modules/flash-messages.js | 10 ++ resources/assets/js/modules/form-autoalias.js | 19 +++ resources/assets/js/vue-init.js | 9 ++ .../views/table/propose/layout.blade.php | 10 +- 15 files changed, 394 insertions(+), 210 deletions(-) create mode 100644 resources/assets/js/base-setup.js delete mode 100644 resources/assets/js/bootstrap.js rename resources/assets/js/{ => lib}/url-slug.js (100%) create mode 100644 resources/assets/js/modules/block-collapse.js create mode 100644 resources/assets/js/modules/flash-messages.js create mode 100644 resources/assets/js/modules/form-autoalias.js create mode 100644 resources/assets/js/vue-init.js diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index e05d058..a19a7b0 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use Illuminate\Foundation\Bus\DispatchesJobs; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Routing\Controller as BaseController; use Illuminate\Foundation\Validation\ValidatesRequests; @@ -55,6 +56,11 @@ class Controller extends BaseController 'mastodon', // mastodon fetching previews ]; + protected function jsonResponse($data = [], $code=200) + { + return new JsonResponse($data, $code); + } + // Hacks to allow recursive nesting of validations in string and array format public function makeValidator($data, $rules, $messages = array(), $customAttributes = array()) diff --git a/app/Http/Controllers/TableEditController.php b/app/Http/Controllers/TableEditController.php index 9d4d484..0cb8d37 100644 --- a/app/Http/Controllers/TableEditController.php +++ b/app/Http/Controllers/TableEditController.php @@ -3,10 +3,12 @@ namespace App\Http\Controllers; +use App\Models\Row; use App\Models\Table; use App\Models\User; use App\Tables\Changeset; use App\Tables\Column; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Input; @@ -35,6 +37,14 @@ class TableEditController extends Controller }); } + private function storeChangeset(Changeset $chs) + { + $session_key = "proposal_{$chs->table->id}"; + + session()->put($session_key, $chs); + session()->save(); + } + public function draft(User $user, string $table, $tab = null) { /** @var Table $tableModel */ @@ -47,13 +57,18 @@ class TableEditController extends Controller $changeset = $this->getChangeset($tableModel); + if (Input::has('dump')) { + dd($changeset); + } + return $this->{camel_case($tab)}($changeset); } + /** @noinspection PhpUnusedPrivateMethodInspection */ private function editRows(Changeset $changeset) { $revision = $changeset->revision; - $columns = $changeset->transformColumns(); + $columns = $changeset->fetchAndTransformColumns(); $rows = $revision->rowsData($columns, true, false)->paginate(25, []); return view('table.propose.edit-rows', [ @@ -63,4 +78,46 @@ class TableEditController extends Controller 'rows' => $rows, ]); } + + // TODO other tab handlers + + public function draftUpdate(User $user, string $table, Request $request) + { + /** @var Table $tableModel */ + $tableModel = $user->tables()->where('name', $table)->first(); + if ($tableModel === null) abort(404, "No such table."); + + $changeset = $this->getChangeset($tableModel); + + $input = objBag($request->all(), false); + + $code = 200; + switch ($input->action) { + case 'row.update': + $data = (object)$input->data; + $changeset->rowUpdate($data); + + $resp = $changeset->fetchAndTransformRow($data->_id); + break; + + case 'row.remove': + $changeset->rowRemove($input->id); + $resp = $changeset->fetchAndTransformRow($input->id); + break; + + case 'row.restore': + $changeset->rowRestore($input->id); + $resp = $changeset->fetchAndTransformRow($input->id); + break; + + default: + $resp = "Bad Action"; + $code = 400; + break; + } + + $this->storeChangeset($changeset); + + return $this->jsonResponse($resp, $code); + } } diff --git a/app/Tables/Changeset.php b/app/Tables/Changeset.php index cc61ce0..e049af1 100644 --- a/app/Tables/Changeset.php +++ b/app/Tables/Changeset.php @@ -4,8 +4,11 @@ namespace App\Tables; use App\Models\Revision; +use App\Models\Row; use App\Models\Table; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Collection; +use MightyPork\Exceptions\NotExistException; use ReflectionClass; /** @@ -34,7 +37,7 @@ class Changeset /** * @var string - user's note attached to this changeset (future proposal) */ - public $note; + public $note = ''; /** * Rows whose content changed, identified by _id. @@ -100,9 +103,6 @@ class Changeset */ public $removedColumns = []; - /** @var Column[] - loaded and transformed columns, cached from previous call to transformColumns() */ - private $cachedColumns; - private function walkProps() { $properties = (new ReflectionClass($this))->getProperties(); @@ -147,7 +147,7 @@ class Changeset $object = new \stdClass(); foreach ($this->walkProps() as $prop) { - $object->$prop = array_values($this->$prop); + $object->$prop = $this->$prop; } return $object; @@ -178,6 +178,8 @@ class Changeset */ public function transformRow($row, $decorate) { + if ($row instanceof Row) $row = (object)$row->getAttributes(); + if ($decorate) { $row->_remove = false; $row->_changed = []; @@ -222,16 +224,16 @@ class Changeset * * @return Column[] */ - public function transformColumns() + public function fetchAndTransformColumns() { - if ($this->cachedColumns) return $this->cachedColumns; + /** @var Column[] - loaded and transformed columns, cached from previous call to transformColumns() */ + static $cachedColumns = []; + + if ($cachedColumns) return $cachedColumns; $columns = Column::columnsFromJson($this->revision->columns); // Modify columns - $byId = []; foreach ($columns as $column) { - $byId[$column->id] = $column; - if (isset($this->columnUpdates[$column->id])) { $column->modifyByChangeset($this->columnUpdates[$column->id]); } @@ -248,16 +250,85 @@ class Changeset } // Reorder + $colsById = collect($columns)->keyBy('id')->all(); $newOrder = []; foreach ($this->columnOrder as $id) { - $newOrder[] = $byId[$id]; + $newOrder[] = $colsById[$id]; } - $leftover_keys = array_diff(array_keys($byId), $this->columnOrder); + $leftover_keys = array_diff(array_keys($colsById), $this->columnOrder); foreach ($leftover_keys as $id) { - $newOrder[] = $byId[$id]; + $newOrder[] = $colsById[$id]; + } + + return $cachedColumns = $newOrder; + } + + public function rowRemove(int $id) + { + $this->removedRows[] = $id; + } + + public function rowRestore(int $id) + { + $this->removedRows = array_diff($this->removedRows, [$id]); + } + + public function fetchAndTransformRow(int $id) + { + $r = $this->fetchRow($id); + $transformed = $this->transformRow($r, true); + return $transformed; + } + + public function fetchColumns() + { + return Column::columnsFromJson($this->revision->columns); + } + + public function fetchRow($id) + { + $r = $this->revision->rowsData($this->fetchColumns(), true, false) + ->whereRaw("data->>'_id' = ?", $id)->first(); + + if (! $r) throw new NotExistException("No such row _id = $id in this revision."); + + // remove junk + unset($r->pivot_revision_id); + unset($r->pivot_row_id); + + return (object)$r->getAttributes(); + } + + public function rowUpdate($newVals) + { + $_id = $newVals->_id; + $origRow = $this->fetchRow($_id); + + /** @var Column[]|Collection $cols */ + $cols = collect($this->fetchAndTransformColumns())->keyBy('id'); + + $updateObj = []; + \Debugbar::addMessage(json_encode($cols)); + \Debugbar::addMessage(var_export($newVals, true)); + + foreach ($newVals as $colId => $value) { + if (starts_with($colId, '_')) continue; // internals + + if (!isset($origRow->$colId) || $value != $origRow->$colId) { + $updateObj[$colId] = $cols[$colId]->cast($value); + } } - return $this->cachedColumns = $newOrder; + \Debugbar::addMessage("New: ".json_encode($newVals)); + \Debugbar::addMessage("Orig: ".json_encode($origRow)); + \Debugbar::addMessage("UpdateObj: ".json_encode($updateObj)); + + if (!empty($updateObj)) { + $this->rowUpdates[$_id] = $updateObj; + } else { + // remove possible old update record for this row, if nothing changes now + unset($this->rowUpdates[$_id]); + } } } diff --git a/app/Tables/Column.php b/app/Tables/Column.php index 1434153..3741e1a 100644 --- a/app/Tables/Column.php +++ b/app/Tables/Column.php @@ -10,13 +10,6 @@ use MightyPork\Utils\Utils; /** * Helper class representing one column in a data table. * - * @property-read string $id - * @property-read string $type - * @property-read string $name - * @property-read string $title - * @property-read bool $isNew - * @property-read bool $toRemove - * * @property-read bool $id_modified * @property-read bool $type_modified * @property-read bool $name_modified @@ -33,16 +26,18 @@ class Column implements JsonSerializable, Arrayable 'int', 'bool', 'float', 'string' ]; - private $id; - private $type; - private $name; - private $title; + // marked public to make keyBy() work + + public $id; + public $type; + public $name; + public $title; /** @var bool - column is marked to be deleted by a changeset */ - private $toRemove = false; + public $toRemove = false; /** @var bool - column is new in this changeset */ - private $isNew = false; + public $isNew = false; /** @var array - original attrib values if edited in a changeset */ private $orig_attribs = []; @@ -83,10 +78,6 @@ class Column implements JsonSerializable, Arrayable public function __get($name) { - if (property_exists($this, $name)) { - return $this->$name; - } - if (ends_with($name, '_modified')) { $basename = str_replace('_modified', '', $name); if (property_exists($this, $basename)) { @@ -177,20 +168,24 @@ class Column implements JsonSerializable, Arrayable { switch ($this->type) { case 'int': + if (is_null($value)) return 0; if (is_int($value)) return $value; if (is_float($value)) return round($value); if (is_numeric($value)) return intval($value); throw new NotApplicableException("Could not convert value \"$value\" to int!"); case 'float': + if (is_null($value)) return 0; if (is_int($value) || is_float($value)) return (float)$value; if (is_numeric($value)) return floatval($value); throw new NotApplicableException("Could not convert value \"$value\" to float!"); case 'bool': + if (is_null($value)) return false; return Utils::parseBool($value); case 'string': + if (is_null($value)) return ""; return "$value"; default: diff --git a/porklib/helpers.php b/porklib/helpers.php index 8134d80..77368bb 100644 --- a/porklib/helpers.php +++ b/porklib/helpers.php @@ -65,29 +65,45 @@ function unless($cond, $then, $else = '') * - Undefined keys are returned as null. * - array and object values are wrapped in objBag when returned. */ -class objBag implements JsonSerializable, ArrayAccess { +class objBag implements JsonSerializable, ArrayAccess, \Illuminate\Contracts\Support\Arrayable +{ /** @var object */ - private $wrapped; + private $wrapped; + private $recursive; + + /** + * objBag constructor. + * @param mixed $wrapped - wrapped object/array/class + * @param bool $recursive - return array/object values as objBags too + */ + public function __construct($wrapped, $recursive = true) + { + $this->wrapped = (object)$wrapped; + $this->recursive = $recursive; + } - public function __construct($wrapped) - { - $this->wrapped = (object)$wrapped; - } + /** + * @param $name + * @return null|objBag|mixed + */ + public function __get($name) + { + if ($this->wrapped) { + if (isset($this->wrapped->$name)) { + $x = $this->wrapped->$name; - public function __get($name) - { - if ($this->wrapped) { - if (isset($this->wrapped->$name)) { - $x = $this->wrapped->$name; - if (is_array($x) || is_object($x)) return objBag($x); - return $x; - } - } - - return null; - } + if ($this->recursive && (is_array($x) || is_object($x))) { + return objBag($x); + } - public function __set($name, $value) + return $x; + } + } + + return null; + } + + public function __set($name, $value) { if ($this->wrapped) { $this->wrapped->$name = $value; @@ -102,48 +118,53 @@ class objBag implements JsonSerializable, ArrayAccess { } public function __isset($name) - { - return isset($this->wrapped->$name); - } + { + return isset($this->wrapped->$name); + } - public function get($name, $def = null) - { - if (!isset($this->$name)) return $def; - return $this->$name; - } + /** + * @param $name + * @param null $def + * @return null|array|mixed + */ + public function get($name, $def = null) + { + if (!isset($this->$name)) return $def; + return $this->$name; + } - public function has($name) - { - return isset($this->$name) && $this->$name !== null; - } + public function has($name) + { + return isset($this->$name) && $this->$name !== null; + } - public function unpack() - { - return $this->wrapped; - } + public function unpack() + { + return $this->wrapped; + } public function toArray() { - return(array)$this->wrapped; - } + return (array)$this->wrapped; + } public function all() { return $this->toArray(); } - /** - * Specify data which should be serialized to JSON - * - * @link http://php.net/manual/en/jsonserializable.jsonserialize.php - * @return mixed data which can be serialized by json_encode, - * which is a value of any type other than a resource. - * @since 5.4.0 - */ - public function jsonSerialize() - { - return $this->wrapped; - } + /** + * Specify data which should be serialized to JSON + * + * @link http://php.net/manual/en/jsonserializable.jsonserialize.php + * @return mixed data which can be serialized by json_encode, + * which is a value of any type other than a resource. + * @since 5.4.0 + */ + public function jsonSerialize() + { + return $this->wrapped; + } public function offsetExists($offset) { @@ -168,10 +189,11 @@ class objBag implements JsonSerializable, ArrayAccess { /** * @param $obj + * @param bool $recursive - whether the bag should be recursive * @return objBag */ -function objBag($obj) { - return new \objBag($obj); +function objBag($obj, $recursive=true) { + return new \objBag($obj, $recursive); } /** diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js index 22eb7a3..628b4a9 100644 --- a/resources/assets/js/app.js +++ b/resources/assets/js/app.js @@ -1,76 +1,17 @@ -/** - * First we will load all of this project's JavaScript dependencies which - * includes Vue and other libraries. It is a great starting point when - * building robust, powerful web applications using Vue and Laravel. - */ +/* Project entrypoint */ -require('./bootstrap') - -let url_slug = require('./url-slug') +require('./base-setup') +require('./modules/block-collapse') +require('./modules/flash-messages') +require('./modules/form-autoalias') +require('./vue-init') $(function () { // Remove all noscript from forms etc $('noscript').remove(); + // Bootstrap tooltips $('[data-toggle="tooltip"]').tooltip({ container: 'body' }) - - // auto hide flash alerts - let $notifs = $('div.alert').not('.alert-important').addClass('fadeout') - setTimeout(() => { - $notifs.addClass('fade') - setTimeout(() => { - $notifs.addClass('hidden') - }, 500) - }, 2500) - - // toggle collapse when clicked outside link, without drag - $('.block-collapse') - .on('mousedown', (e) => { - let $bc = $(e.target).closest('.block-collapse') - $bc.data('mx', e.screenX); - $bc.data('my', e.screenY); - }) - .on('mouseup', (e) => { - if (e.target.nodeName === 'A') return - let $bc = $(e.target).closest('.block-collapse') - if (typeof $bc.data('mx') !== 'undefined') { - let x0 = +$bc.data('mx'); - let y0 = +$bc.data('my'); - if (Math.abs(x0 - e.screenX) > 5 || Math.abs(y0 - e.screenY) > 5) { - // drag - } else { - $(e.target).closest('.block-collapse').toggleClass('reveal') - } - } - }) }) - -// auto-alias -$(document).on('input keypress paste keyup', 'input[data-autoalias]', function () { - const $this = $(this) - const target_name = $this.data('autoalias') - const delimiter = $this.data('aa-delimiter') || '_' - - const new_alias = url_slug($this.val(), {'delimiter': delimiter}) - - const $target = $(`input[name="${target_name}"]`) - const lastset = $target.data('aa-last-set-val') - - // 1. pick up, or 2. continue - if (new_alias === $target.val() || lastset === $target.val()) { - $target.val(new_alias) - $target.data('aa-last-set-val', new_alias) - } -}) - -window.Vue = require('vue'); - -Vue.component('column-editor', require('./components/ColumnEditor.vue')); -Vue.component('row-editor', require('./components/RowEditor.vue')); -Vue.component('v-icon', require('./components/Icon.vue')); - -const app = new Vue({ - el: '#app' -}); diff --git a/resources/assets/js/base-setup.js b/resources/assets/js/base-setup.js new file mode 100644 index 0000000..3bd82f6 --- /dev/null +++ b/resources/assets/js/base-setup.js @@ -0,0 +1,52 @@ +// window._ = require('lodash'); +window.Popper = require('popper.js').default + +/** + * We'll load jQuery and the Bootstrap jQuery plugin which provides support + * for JavaScript based Bootstrap features such as modals and tabs. This + * code may be modified to fit the specific needs of your application. + */ + +window.$ = window.jQuery = require('jquery') +require('bootstrap') + +/** + * We'll load the axios HTTP library which allows us to easily issue requests + * to our Laravel back-end. This library automatically handles sending the + * CSRF token as a header based on the value of the "XSRF" token cookie. + */ + +window.axios = require('axios') + +window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest' + +/** + * Next we will register the CSRF Token as a common header with Axios so that + * all outgoing HTTP requests automatically have it attached. This is just + * a simple convenience so we don't have to attach every token manually. + */ + +let token = document.head.querySelector('meta[name="csrf-token"]') + +if (token) { + window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content +} else { + console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token') +} + +/** + * Echo exposes an expressive API for subscribing to channels and listening + * for events that are broadcast by Laravel. Echo and event broadcasting + * allows your team to easily build robust real-time web applications. + */ + +// import Echo from 'laravel-echo' + +// window.Pusher = require('pusher-js'); + +// window.Echo = new Echo({ +// broadcaster: 'pusher', +// key: process.env.MIX_PUSHER_APP_KEY, +// cluster: process.env.MIX_PUSHER_APP_CLUSTER, +// encrypted: true +// }); diff --git a/resources/assets/js/bootstrap.js b/resources/assets/js/bootstrap.js deleted file mode 100644 index c71b30a..0000000 --- a/resources/assets/js/bootstrap.js +++ /dev/null @@ -1,55 +0,0 @@ - -// window._ = require('lodash'); -window.Popper = require('popper.js').default; - -/** - * We'll load jQuery and the Bootstrap jQuery plugin which provides support - * for JavaScript based Bootstrap features such as modals and tabs. This - * code may be modified to fit the specific needs of your application. - */ - -try { - window.$ = window.jQuery = require('jquery'); - require('bootstrap'); -} catch (e) {} - -// /** -// * We'll load the axios HTTP library which allows us to easily issue requests -// * to our Laravel back-end. This library automatically handles sending the -// * CSRF token as a header based on the value of the "XSRF" token cookie. -// */ -// -// window.axios = require('axios'); -// -// window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; -// -// /** -// * Next we will register the CSRF Token as a common header with Axios so that -// * all outgoing HTTP requests automatically have it attached. This is just -// * a simple convenience so we don't have to attach every token manually. -// */ -// -// let token = document.head.querySelector('meta[name="csrf-token"]'); -// -// if (token) { -// window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content; -// } else { -// console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'); -// } - -/** - * Echo exposes an expressive API for subscribing to channels and listening - * for events that are broadcast by Laravel. Echo and event broadcasting - * allows your team to easily build robust real-time web applications. - */ - -// import Echo from 'laravel-echo' - -// window.Pusher = require('pusher-js'); - -// window.Echo = new Echo({ -// broadcaster: 'pusher', -// key: process.env.MIX_PUSHER_APP_KEY, -// cluster: process.env.MIX_PUSHER_APP_CLUSTER, -// encrypted: true -// }); diff --git a/resources/assets/js/components/RowEditor.vue b/resources/assets/js/components/RowEditor.vue index 880851e..0142e72 100644 --- a/resources/assets/js/components/RowEditor.vue +++ b/resources/assets/js/components/RowEditor.vue @@ -17,7 +17,7 @@ - @@ -59,11 +59,43 @@ export default { } }, methods: { + busy (yes) { + $('#draft-busy').css('opacity', yes ? 1 : 0) + }, + query (data, sucfn) { + this.busy(true) + if (!sucfn) sucfn = ()=>{} + window.axios.post(this.route, data).then(sucfn).catch((e) => { + console.error(e) + }).then(() => { + this.busy(false) + }) + }, toggleRowDelete(_id) { - this.$set(this.rows[_id], '_remove', !this.rows[_id]._remove); + let remove = !this.rows[_id]._remove; + + this.query({ + action: remove ? 'row.remove' : 'row.restore', + id: _id + }, (resp) => { + this.$set(this.rows, _id, resp.data); + }) }, toggleRowEditing(_id) { - this.$set(this.rows[_id], '_editing', !this.rows[_id]._editing); + if (this.rows[_id]._remove) return false; // can't edit row marked for removal + + let editing = !this.rows[_id]._editing; + + if (!editing) { + this.query({ + action: 'row.update', + data: this.rows[_id] + }, (resp) => { + this.$set(this.rows, _id, resp.data); + }) + } else { + this.$set(this.rows[_id], '_editing', true); + } }, colClasses(col) { return [ diff --git a/resources/assets/js/url-slug.js b/resources/assets/js/lib/url-slug.js similarity index 100% rename from resources/assets/js/url-slug.js rename to resources/assets/js/lib/url-slug.js diff --git a/resources/assets/js/modules/block-collapse.js b/resources/assets/js/modules/block-collapse.js new file mode 100644 index 0000000..27ff883 --- /dev/null +++ b/resources/assets/js/modules/block-collapse.js @@ -0,0 +1,21 @@ +// toggle collapse when clicked outside link, without drag +$(document) + .on('mousedown', '.block-collapse', function(e) { + let $bc = $(e.target).closest('.block-collapse') + $bc.data('mx', e.screenX) + $bc.data('my', e.screenY) + }) + .on('mouseup', '.block-collapse', function(e) { + if (e.target.nodeName === 'A') return + let $bc = $(e.target).closest('.block-collapse') + + if (typeof $bc.data('mx') !== 'undefined') { + let x0 = +$bc.data('mx') + let y0 = +$bc.data('my') + if (Math.abs(x0 - e.screenX) > 5 || Math.abs(y0 - e.screenY) > 5) { + // drag + } else { + $(e.target).closest('.block-collapse').toggleClass('reveal') + } + } + }) diff --git a/resources/assets/js/modules/flash-messages.js b/resources/assets/js/modules/flash-messages.js new file mode 100644 index 0000000..6ab01e0 --- /dev/null +++ b/resources/assets/js/modules/flash-messages.js @@ -0,0 +1,10 @@ +$(function() { + // auto hide flash alerts + let $notifs = $('div.alert').not('.alert-important').addClass('fadeout') + setTimeout(() => { + $notifs.addClass('fade') + setTimeout(() => { + $notifs.addClass('hidden') + }, 500) + }, 2500) +}) diff --git a/resources/assets/js/modules/form-autoalias.js b/resources/assets/js/modules/form-autoalias.js new file mode 100644 index 0000000..fcb4bf9 --- /dev/null +++ b/resources/assets/js/modules/form-autoalias.js @@ -0,0 +1,19 @@ +let url_slug = require('../lib/url-slug') + +// auto-alias +$(document).on('input keypress paste keyup', 'input[data-autoalias]', function () { + const $this = $(this) + const target_name = $this.data('autoalias') + const delimiter = $this.data('aa-delimiter') || '_' + + const new_alias = url_slug($this.val(), {'delimiter': delimiter}) + + const $target = $(`input[name="${target_name}"]`) + const lastset = $target.data('aa-last-set-val') + + // 1. pick up, or 2. continue + if (new_alias === $target.val() || lastset === $target.val()) { + $target.val(new_alias) + $target.data('aa-last-set-val', new_alias) + } +}) diff --git a/resources/assets/js/vue-init.js b/resources/assets/js/vue-init.js new file mode 100644 index 0000000..5b51bf6 --- /dev/null +++ b/resources/assets/js/vue-init.js @@ -0,0 +1,9 @@ +window.Vue = require('vue'); + +Vue.component('column-editor', require('./components/ColumnEditor.vue')); +Vue.component('row-editor', require('./components/RowEditor.vue')); +Vue.component('v-icon', require('./components/Icon.vue')); + +const app = new Vue({ + el: '#app' +}); diff --git a/resources/views/table/propose/layout.blade.php b/resources/views/table/propose/layout.blade.php index ee8c2af..c2cd99e 100644 --- a/resources/views/table/propose/layout.blade.php +++ b/resources/views/table/propose/layout.blade.php @@ -14,12 +14,16 @@ if (!isset($tab) || $tab == '') $tab = 'edit-rows'; {{ $table->owner->handle }}{{-- --}}/{{-- - --}}{{ $table->name }} + --}} + {{ $table->name }} + -

{{ $table->title }}

+

+ {{ $table->title }} +

- @icon(fa-close, sr:Discard) + @icon(fa-trash-o, sr:Discard) @if(user()->ownsTable($table))