parent
8382f503c0
commit
b4c9714202
@ -22,5 +22,8 @@
|
|||||||
"sass-loader": "7.*",
|
"sass-loader": "7.*",
|
||||||
"vue": "^2.5.17",
|
"vue": "^2.5.17",
|
||||||
"vue-template-compiler": "^2.6.10"
|
"vue-template-compiler": "^2.6.10"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"textarea-caret": "^3.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
resources/js/app.js
vendored
9
resources/js/app.js
vendored
@ -19,7 +19,7 @@ window.Vue = require('vue');
|
|||||||
// const files = require.context('./', true, /\.vue$/i)
|
// const files = require.context('./', true, /\.vue$/i)
|
||||||
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))
|
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))
|
||||||
|
|
||||||
Vue.component('example-component', require('./components/ExampleComponent.vue').default);
|
// Vue.component('example-component', require('./components/ExampleComponent.vue').default);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Next, we will create a fresh Vue application instance and attach it to
|
* Next, we will create a fresh Vue application instance and attach it to
|
||||||
@ -27,6 +27,9 @@ Vue.component('example-component', require('./components/ExampleComponent.vue').
|
|||||||
* or customize the JavaScript scaffolding to fit your unique needs.
|
* or customize the JavaScript scaffolding to fit your unique needs.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import App from './components/App.vue';
|
||||||
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
el: '#app',
|
render: h => h(App),
|
||||||
});
|
}).$mount('#app');
|
||||||
|
14
resources/js/components/App.vue
Normal file
14
resources/js/components/App.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Autocomplete hasLabel="false" id="message" name="message" rows="5" placeholder="Que s'est-il passé aujourd'hui ?" textarea="true" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Autocomplete from './Autocomplete.vue';
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Autocomplete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
195
resources/js/components/Autocomplete.vue
Normal file
195
resources/js/components/Autocomplete.vue
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
<template>
|
||||||
|
<div class="autocomplete">
|
||||||
|
<label :for="id" v-if="hasLabel">{{label}}</label>
|
||||||
|
<textarea v-if="textarea" :id="id" :name="name" :rows="rows" :cols="cols" class="autocomplete-input" :placeholder="placeholder"
|
||||||
|
@focusout="focusout" @focus="focus" @keydown.13="chooseItem" @keydown.tab="chooseItem"
|
||||||
|
@keydown.40="moveDown" @keydown.38="moveUp" v-model="inputValue" type="text"></textarea>
|
||||||
|
<input v-else :id="id" :name="name" class="autocomplete-input" :placeholder="placeholder" @focusout="focusout" @focus="focus"
|
||||||
|
@keydown.13="chooseItem" @keydown.tab="chooseItem" @keydown.40="moveDown" @keydown.38="moveUp"
|
||||||
|
v-model="inputValue" type="text">
|
||||||
|
<ul :class="{'autocomplete-list': true, [id+'-list']: true}" v-if="searchMatch.length > 0">
|
||||||
|
<li :class="{active: selectedIndex === index}" v-for="(result, index) in searchMatch"
|
||||||
|
@click="selectItem(index), chooseItem()" v-html="highlightWord(result)">
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// https://codepen.io/ph1p/pen/GEJYBZ
|
||||||
|
const getCaretCoordinates = require('textarea-caret');
|
||||||
|
const standardItems = [
|
||||||
|
"#Fitness",
|
||||||
|
"#Humeur"
|
||||||
|
];
|
||||||
|
export default {
|
||||||
|
name: "Autocomplete",
|
||||||
|
props: ["items", "placeholder", "label", "textarea", "rows", "cols", "hasLabel", "name"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: 'input-' + parseInt(Math.random() * 1000, 10),
|
||||||
|
inputValue: "",
|
||||||
|
searchMatch: [],
|
||||||
|
selectedIndex: 0,
|
||||||
|
clickedChooseItem: false,
|
||||||
|
wordIndex: 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const _self = this;
|
||||||
|
document.querySelector('#' + this.id)
|
||||||
|
.addEventListener('input', function() {
|
||||||
|
const caret = getCaretCoordinates(this, this.selectionEnd);
|
||||||
|
|
||||||
|
if (_self.searchMatch.length > 0) {
|
||||||
|
const element = document.querySelectorAll('.' + _self.id + '-list');
|
||||||
|
|
||||||
|
if (element[0]) {
|
||||||
|
element[0].style.top = caret.top + 40 + 'px';
|
||||||
|
element[0].style.left = caret.left + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
listToSearch() {
|
||||||
|
if (typeof this.items !== "undefined" && this.items.length > 0) {
|
||||||
|
return this.items;
|
||||||
|
} else {
|
||||||
|
return standardItems;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
currentWord() {
|
||||||
|
return this.inputValue.replace(/(\r\n|\n|\r)/gm, ' ').split(' ')[this.wordIndex];
|
||||||
|
},
|
||||||
|
inputSplitted() {
|
||||||
|
return this.inputValue.replace(/(\r\n|\n|\r)/gm, ' ').split(" ");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
inputValue() {
|
||||||
|
this.focus();
|
||||||
|
console.log(this.inputSplitted)
|
||||||
|
this.selectedIndex = 0;
|
||||||
|
this.wordIndex = this.inputSplitted.length - 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
highlightWord(word) {
|
||||||
|
const regex = new RegExp("(" + this.currentWord + ")", "g");
|
||||||
|
return word.replace(regex, '<mark>$1</mark>');
|
||||||
|
},
|
||||||
|
setWord(word) {
|
||||||
|
let currentWords = this.inputValue.replace(/(\r\n|\n|\r)/gm, '__br__ ').split(' ');
|
||||||
|
currentWords[this.wordIndex] = currentWords[this.wordIndex].replace(this.currentWord, word + ' ');
|
||||||
|
this.wordIndex += 1;
|
||||||
|
this.inputValue = currentWords.join(' ').replace(/__br__\s/g, '\n');
|
||||||
|
},
|
||||||
|
moveDown() {
|
||||||
|
if (this.selectedIndex < this.searchMatch.length - 1) {
|
||||||
|
this.selectedIndex++;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
moveUp() {
|
||||||
|
if (this.selectedIndex !== -1) {
|
||||||
|
this.selectedIndex--;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectItem(index) {
|
||||||
|
this.selectedIndex = index;
|
||||||
|
this.chooseItem();
|
||||||
|
},
|
||||||
|
chooseItem(e) {
|
||||||
|
this.clickedChooseItem = true;
|
||||||
|
|
||||||
|
if (this.selectedIndex !== -1 && this.searchMatch.length > 0) {
|
||||||
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
this.setWord(this.searchMatch[this.selectedIndex]);
|
||||||
|
this.selectedIndex = -1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
focusout(e) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.clickedChooseItem) {
|
||||||
|
this.searchMatch = [];
|
||||||
|
this.selectedIndex = -1;
|
||||||
|
}
|
||||||
|
this.clickedChooseItem = false;
|
||||||
|
}, 100);
|
||||||
|
},
|
||||||
|
focus() {
|
||||||
|
this.searchMatch = [];
|
||||||
|
if (this.currentWord !== "") {
|
||||||
|
this.searchMatch = this.listToSearch.filter(
|
||||||
|
el => el.indexOf(this.currentWord) === 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this.searchMatch.length === 1 &&
|
||||||
|
this.currentWord === this.searchMatch[0]
|
||||||
|
) {
|
||||||
|
this.searchMatch = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.autocomplete {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.autocomplete label {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
.autocomplete-input {
|
||||||
|
padding: 7px 10px;
|
||||||
|
width: 93%;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.autocomplete-input:focus {
|
||||||
|
border-color: #000;
|
||||||
|
}
|
||||||
|
.autocomplete-list {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
min-width: 250px;
|
||||||
|
max-height: 150px;
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
border: 1px solid #eee;
|
||||||
|
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 5px 25px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px 15px;
|
||||||
|
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
}
|
||||||
|
li:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
li:hover, li.active {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,23 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="container">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-8">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">Example Component</div>
|
|
||||||
|
|
||||||
<div class="card-body">
|
|
||||||
I'm an example component.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
mounted() {
|
|
||||||
console.log('Component mounted.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,4 +1,4 @@
|
|||||||
@extends('layouts.app')
|
@extends('layouts.connected')
|
||||||
|
|
||||||
@inject('tag_detector', 'App\Services\TagDetectorService)
|
@inject('tag_detector', 'App\Services\TagDetectorService)
|
||||||
|
|
||||||
@ -26,7 +26,8 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-sm-5" for="message">Quoi de neuf aujourd'hui ?</label>
|
<label class="control-label col-sm-5" for="message">Quoi de neuf aujourd'hui ?</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<textarea class="form-control" rows="5" name="message" id="message"></textarea>
|
<div id="app">
|
||||||
|
</div>
|
||||||
<span class="text-danger">{{ $errors->first('message') }}</span>
|
<span class="text-danger">{{ $errors->first('message') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
80
resources/views/layouts/connected.blade.php
Normal file
80
resources/views/layouts/connected.blade.php
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<!-- CSRF Token -->
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
|
||||||
|
<title>{{ config('app.name', 'Laravel') }}</title>
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script src="{{ asset('js/app.js') }}" defer></script>
|
||||||
|
|
||||||
|
<!-- Fonts -->
|
||||||
|
<link rel="dns-prefetch" href="//fonts.gstatic.com">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Styles -->
|
||||||
|
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="{{ url('/') }}">
|
||||||
|
{{ config('app.name', 'Laravel') }}
|
||||||
|
</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<!-- Left Side Of Navbar -->
|
||||||
|
<ul class="navbar-nav mr-auto">
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Right Side Of Navbar -->
|
||||||
|
<ul class="navbar-nav ml-auto">
|
||||||
|
<!-- Authentication Links -->
|
||||||
|
@guest
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
|
||||||
|
</li>
|
||||||
|
@if (Route::has('register'))
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
@else
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
|
||||||
|
{{ Auth::user()->name }} <span class="caret"></span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
|
||||||
|
<a class="dropdown-item" href="{{ route('logout') }}"
|
||||||
|
onclick="event.preventDefault();
|
||||||
|
document.getElementById('logout-form').submit();">
|
||||||
|
{{ __('Logout') }}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
|
||||||
|
@csrf
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@endguest
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="py-4">
|
||||||
|
@yield('content')
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user