diff --git a/administrator/components/com_media/package.json b/administrator/components/com_media/package.json index b3a45aa128e9b..07bdfa451e0f9 100644 --- a/administrator/components/com_media/package.json +++ b/administrator/components/com_media/package.json @@ -5,10 +5,11 @@ "author": "", "license": "GPL-3.0", "scripts": { - "build": "browserify ./resources/main.js | uglifyjs -c warnings=false -m > ../../../media/media/js/app.js" + "build": "browserify ./resources/main.js | uglifyjs -c warnings=false -m > ../../../media/com_media/js/mediamanager.js" }, "dependencies": { - "vue": "^2.0.1" + "vue": "^2.0.1", + "vuex": "^2.1.2" }, "devDependencies": { "babel-core": "^6.0.0", diff --git a/administrator/components/com_media/resources/app/Api.js b/administrator/components/com_media/resources/app/Api.js new file mode 100644 index 0000000000000..77f9fddb293dc --- /dev/null +++ b/administrator/components/com_media/resources/app/Api.js @@ -0,0 +1,84 @@ +const path = require('path'); + +/** + * Api class for communication with the server + */ +class Api { + + /** + * Store constructor + */ + constructor() { + this._baseUrl = '/administrator/index.php?option=com_media&format=json'; + } + + /** + * Get the contents of a directory from the server + * @param dir + * @returns {Promise} + */ + getContents(dir) { + // Wrap the jquery call into a real promise + return new Promise((resolve, reject) => { + const url = this._baseUrl + '&task=api.files&path=' + dir; + jQuery.getJSON(url) + .success((json) => resolve(this._normalizeArray(json.data))) + .fail((xhr, status, error) => { + reject(xhr) + }) + }).catch(this._handleError); + } + + /** + * Normalize array data + * @param data + * @returns {{directories, files}} + * @private + */ + _normalizeArray(data) { + + // Directories + const directories = data.filter(item => (item.type === 'dir')) + .map(directory => { + directory.directory = path.dirname(directory.path); + directory.directories = []; + directory.files = []; + return directory; + }); + + // Files + const files = data.filter(item => (item.type === 'file')) + .map(file => { + file.directory = path.dirname(file.path); + return file; + }); + + return { + directories: directories, + files: files, + } + } + + /** + * Handle errors + * @param error + * @private + */ + _handleError(error) { + alert(error.status + ' ' + error.statusText); + switch (error.status) { + case 404: + break; + case 401: + case 403: + case 500: + window.location.href = '/administrator'; + default: + window.location.href = '/administrator'; + } + + throw error; + } +} + +export let api = new Api(); \ No newline at end of file diff --git a/administrator/components/com_media/resources/app/Event.js b/administrator/components/com_media/resources/app/Event.js deleted file mode 100644 index c1f21e501b03f..0000000000000 --- a/administrator/components/com_media/resources/app/Event.js +++ /dev/null @@ -1,17 +0,0 @@ -import Vue from "vue"; - -class Event { - constructor() { - this.vue = new Vue(); - } - - fire(event, data = null) { - this.vue.$emit(event, data); - } - - listen(event, callback) { - this.vue.$on(event, callback); - } -} - -export default Event; \ No newline at end of file diff --git a/administrator/components/com_media/resources/components/app.vue b/administrator/components/com_media/resources/components/app.vue index 6d3a859b39f2c..d1ab533d7ba23 100644 --- a/administrator/components/com_media/resources/components/app.vue +++ b/administrator/components/com_media/resources/components/app.vue @@ -1,12 +1,11 @@ @@ -14,63 +13,32 @@ \ No newline at end of file diff --git a/administrator/components/com_media/resources/components/breadcrumb/breadcrumb.vue b/administrator/components/com_media/resources/components/breadcrumb/breadcrumb.vue new file mode 100644 index 0000000000000..97fb620592a26 --- /dev/null +++ b/administrator/components/com_media/resources/components/breadcrumb/breadcrumb.vue @@ -0,0 +1,43 @@ + + + \ No newline at end of file diff --git a/administrator/components/com_media/resources/components/browser/browser.vue b/administrator/components/com_media/resources/components/browser/browser.vue index f592fe32ed448..aaaa850f36b35 100644 --- a/administrator/components/com_media/resources/components/browser/browser.vue +++ b/administrator/components/com_media/resources/components/browser/browser.vue @@ -1,45 +1,19 @@ + - \ No newline at end of file + \ No newline at end of file diff --git a/administrator/components/com_media/resources/components/browser/item.vue b/administrator/components/com_media/resources/components/browser/item.vue deleted file mode 100644 index 61adea40186a7..0000000000000 --- a/administrator/components/com_media/resources/components/browser/item.vue +++ /dev/null @@ -1,12 +0,0 @@ - - - \ No newline at end of file diff --git a/administrator/components/com_media/resources/components/browser/items/directory.vue b/administrator/components/com_media/resources/components/browser/items/directory.vue new file mode 100644 index 0000000000000..2fa6add5ccda0 --- /dev/null +++ b/administrator/components/com_media/resources/components/browser/items/directory.vue @@ -0,0 +1,28 @@ + + \ No newline at end of file diff --git a/administrator/components/com_media/resources/components/browser/items/file.vue b/administrator/components/com_media/resources/components/browser/items/file.vue new file mode 100644 index 0000000000000..7bb3ddc390021 --- /dev/null +++ b/administrator/components/com_media/resources/components/browser/items/file.vue @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/administrator/components/com_media/resources/components/browser/items/image.vue b/administrator/components/com_media/resources/components/browser/items/image.vue new file mode 100644 index 0000000000000..392dd1f89639a --- /dev/null +++ b/administrator/components/com_media/resources/components/browser/items/image.vue @@ -0,0 +1,20 @@ + + + \ No newline at end of file diff --git a/administrator/components/com_media/resources/components/browser/items/item.js b/administrator/components/com_media/resources/components/browser/items/item.js new file mode 100644 index 0000000000000..da9fcdd90aa9b --- /dev/null +++ b/administrator/components/com_media/resources/components/browser/items/item.js @@ -0,0 +1,36 @@ +import Directory from "./directory.vue"; +import File from "./file.vue"; +import Image from "./image.vue"; + +export default { + functional: true, + props: ['item'], + render: function (createElement, context) { + + // Return the correct item type component + function itemType() { + let item = context.props.item; + let imageExtensions = ['jpg', 'png', 'gif']; + + // Render directory items + if (item.type === 'dir') return Directory; + + // Render image items + if (item.extension && imageExtensions.indexOf(item.extension.toLowerCase()) !== -1) { + return Image; + } + + // Default to file type + return File; + } + + return createElement('div', { + 'class': 'media-browser-item' + }, [ + createElement(itemType(), { + props: context.props + }) + ] + ); + } +} diff --git a/administrator/components/com_media/resources/components/toolbar/toolbar.vue b/administrator/components/com_media/resources/components/toolbar/toolbar.vue new file mode 100644 index 0000000000000..ff60e6adb62c1 --- /dev/null +++ b/administrator/components/com_media/resources/components/toolbar/toolbar.vue @@ -0,0 +1,31 @@ + + + \ No newline at end of file diff --git a/administrator/components/com_media/resources/components/tree/item.vue b/administrator/components/com_media/resources/components/tree/item.vue index a5aeb054c2387..7f149f7bd02ba 100644 --- a/administrator/components/com_media/resources/components/tree/item.vue +++ b/administrator/components/com_media/resources/components/tree/item.vue @@ -1,32 +1,42 @@ - - \ No newline at end of file diff --git a/administrator/components/com_media/resources/components/tree/tree.vue b/administrator/components/com_media/resources/components/tree/tree.vue index 34362580bae19..651046b946bdd 100644 --- a/administrator/components/com_media/resources/components/tree/tree.vue +++ b/administrator/components/com_media/resources/components/tree/tree.vue @@ -1,29 +1,19 @@ \ No newline at end of file diff --git a/administrator/components/com_media/resources/main.js b/administrator/components/com_media/resources/main.js index 2d55adaa27eed..afb5620c1c109 100644 --- a/administrator/components/com_media/resources/main.js +++ b/administrator/components/com_media/resources/main.js @@ -2,19 +2,17 @@ import Vue from "vue"; import App from "./components/app.vue"; import Tree from "./components/tree/tree.vue"; import TreeItem from "./components/tree/item.vue"; +import Toolbar from "./components/toolbar/toolbar.vue"; +import Breadcrumb from "./components/breadcrumb/breadcrumb.vue"; import Browser from "./components/browser/browser.vue"; -import BrowserItem from "./components/browser/item.vue"; -import Event from "./app/Event"; - -// Media Manager namespace -window.Media = window.Media || {}; - -// Register the Event Bus -window.Media.Event = new Event(); +import BrowserItem from "./components/browser/items/item"; +import store from './store/store' // Register the vue components Vue.component('media-tree', Tree); Vue.component('media-tree-item', TreeItem); +Vue.component('media-toolbar', Toolbar); +Vue.component('media-breadcrumb', Breadcrumb); Vue.component('media-browser', Browser); Vue.component('media-browser-item', BrowserItem); @@ -22,6 +20,7 @@ Vue.component('media-browser-item', BrowserItem); document.addEventListener("DOMContentLoaded", (e) => new Vue({ el: '#com-media', + store, render: h => h(App) }) ) diff --git a/administrator/components/com_media/resources/mixins/browser-item.js b/administrator/components/com_media/resources/mixins/browser-item.js new file mode 100644 index 0000000000000..1102dec5e3094 --- /dev/null +++ b/administrator/components/com_media/resources/mixins/browser-item.js @@ -0,0 +1,14 @@ +/** + * Browser item mixin + */ +export default { + methods: { + /** + * Select a browser item + * @param item + */ + selectItem(item) { + window.Media.Store.selectItem(item); + }, + } +}; diff --git a/administrator/components/com_media/resources/store/actions.js b/administrator/components/com_media/resources/store/actions.js new file mode 100644 index 0000000000000..24feffa13dc03 --- /dev/null +++ b/administrator/components/com_media/resources/store/actions.js @@ -0,0 +1,24 @@ +import {api} from "../app/Api"; +import * as types from "./mutation-types"; + +// Actions are similar to mutations, the difference being that: +// - Instead of mutating the state, actions commit mutations. +// - Actions can contain arbitrary asynchronous operations. + +/** + * Get contents of a directory from the api + * @param commit + * @param dir + */ +export const getContents = ({commit}, dir) => { + api.getContents(dir) + .then((contents) => { + commit(types.LOAD_CONTENTS_SUCCESS, contents); + commit(types.SELECT_DIRECTORY, dir); + }) + .catch(error => { + // TODO error handling + console.log("error", error); + }); +} + diff --git a/administrator/components/com_media/resources/store/getters.js b/administrator/components/com_media/resources/store/getters.js new file mode 100644 index 0000000000000..9c047bbd68651 --- /dev/null +++ b/administrator/components/com_media/resources/store/getters.js @@ -0,0 +1,42 @@ +// Sometimes we may need to compute derived state based on store state, +// for example filtering through a list of items and counting them. + +/** + * Get the currently selected directory + * @param state + * @returns {*} + */ +export const getSelectedDirectory = (state) => { + return state.directories.find(directory => (directory.path === state.selectedDirectory)); +} + +/** + * Get the sudirectories of the currently selected directory + * @param state + * @param getters + * @returns {Array|directories|{/}|computed.directories|*|Object} + */ +export const getSelectedDirectoryDirectories = (state, getters) => { + return getters.getSelectedDirectory.directories + .map(directoryPath => state.directories.find(directory => (directory.path === directoryPath))); +} + +/** + * Get the files of the currently selected directory + * @param state + * @param getters + * @returns {Array|files|{}|FileList|*} + */ +export const getSelectedDirectoryFiles= (state, getters) => { + return getters.getSelectedDirectory.files + .map(filePath => state.files.find(file => (file.path === filePath))); +} + +/** + * Get the combined contents (files and directories) of the currently selected directory + * @param state + * @returns {[*,*]} + */ +export const getSelectedDirectoryContents = (state, getters) => { + return [...getters.getSelectedDirectoryDirectories, ...getters.getSelectedDirectoryFiles]; +} \ No newline at end of file diff --git a/administrator/components/com_media/resources/store/mutation-types.js b/administrator/components/com_media/resources/store/mutation-types.js new file mode 100644 index 0000000000000..9d290ff71c2f6 --- /dev/null +++ b/administrator/components/com_media/resources/store/mutation-types.js @@ -0,0 +1,2 @@ +export const SELECT_DIRECTORY = 'SELECT_DIRECTORY'; +export const LOAD_CONTENTS_SUCCESS = 'LOAD_CONTENTS_SUCCESS'; \ No newline at end of file diff --git a/administrator/components/com_media/resources/store/mutations.js b/administrator/components/com_media/resources/store/mutations.js new file mode 100644 index 0000000000000..bb1dc74d47268 --- /dev/null +++ b/administrator/components/com_media/resources/store/mutations.js @@ -0,0 +1,59 @@ +import * as types from "./mutation-types"; + +// The only way to actually change state in a store is by committing a mutation. +// Mutations are very similar to events: each mutation has a string type and a handler. +// The handler function is where we perform actual state modifications, and it will receive the state as the first argument. + +export default { + + /** + * The load content success mutation + * @param state + * @param payload + */ + [types.SELECT_DIRECTORY]: (state, payload) => { + state.selectedDirectory = payload; + }, + + /** + * The load content success mutation + * @param state + * @param payload + */ + [types.LOAD_CONTENTS_SUCCESS]: (state, payload) => { + const newDirectories = payload.directories + .filter(directory => (!state.directories.some(existing => (existing.path === directory.path)))); + const newFiles = payload.files + .filter(file => (!state.files.some(existing => (existing.path === file.path)))); + + // Merge the directories + if (newDirectories.length > 0) { + const newDirectoryIds = newDirectories.map(directory => directory.path); + const parentDirectory = state.directories.find((directory) => (directory.path === newDirectories[0].directory)); + const parentDirectoryIndex = state.directories.indexOf(parentDirectory); + + // Add the new directories to the directories + state.directories.push(...newDirectories); + + // Update the relation to the parent directory + state.directories.splice(parentDirectoryIndex, 1, Object.assign({}, parentDirectory, { + directories: [...parentDirectory.directories, ...newDirectoryIds] + })); + } + + // Merge the files + if (newFiles.length > 0) { + const newFileIds = newFiles.map(file => file.path); + const parentDirectory = state.directories.find((directory) => (directory.path === newFiles[0].directory)); + const parentDirectoryIndex = state.directories.indexOf(parentDirectory); + + // Add the new files to the files + state.files.push(...newFiles); + + // Update the relation to the parent directory + state.directories.splice(parentDirectoryIndex, 1, Object.assign({}, parentDirectory, { + files: [...parentDirectory.files, ...newFileIds] + })); + } + } +} diff --git a/administrator/components/com_media/resources/store/state.js b/administrator/components/com_media/resources/store/state.js new file mode 100644 index 0000000000000..08037c9bd549e --- /dev/null +++ b/administrator/components/com_media/resources/store/state.js @@ -0,0 +1,8 @@ +// The initial state +export default { + selectedDirectory: '/', + directories: [ + {path: '/', directories: [], files: [], directory: null} + ], + files: [], +} \ No newline at end of file diff --git a/administrator/components/com_media/resources/store/store.js b/administrator/components/com_media/resources/store/store.js new file mode 100644 index 0000000000000..f8727e57b75b5 --- /dev/null +++ b/administrator/components/com_media/resources/store/store.js @@ -0,0 +1,18 @@ +import Vue from 'vue' +import Vuex from 'vuex' +import state from './state'; +import * as getters from './getters'; +import * as actions from './actions'; +import mutations from './mutations'; + +Vue.use(Vuex) + +// A Vuex instance is created by combining the state, mutations, actions, +// and getters. +export default new Vuex.Store({ + state, + getters, + actions, + mutations, + strict: true +}) \ No newline at end of file diff --git a/administrator/components/com_media/views/media/view.html.php b/administrator/components/com_media/views/media/view.html.php index 0f1b8b6d03b12..ac1f7af7076a2 100644 --- a/administrator/components/com_media/views/media/view.html.php +++ b/administrator/components/com_media/views/media/view.html.php @@ -47,9 +47,16 @@ protected function prepareDocument() $doc = JFactory::getDocument(); // Add javascripts - $doc->addScript(JUri::root() . 'media/media/js/app.js'); + $doc->addScript(JUri::root() . 'media/com_media/js/mediamanager.js'); - // TODO Add stylesheets + // Add stylesheets + $doc->addStyleSheet(JUri::root() . 'media/com_media/css/mediamanager.css'); + + // TODO include the font in the component media (self hosted) + $doc->addStyleSheet('https://fonts.googleapis.com/icon?family=Material+Icons'); + + // Populate the language + // TODO use JText for all language strings used by the js application } /** @@ -63,7 +70,5 @@ protected function prepareToolbar() { // Set the title JToolbarHelper::title(JText::_('COM_MEDIA'), 'images mediamanager'); - - // TODO add the toolbar buttons } } diff --git a/media/com_media/css/mediamanager.css b/media/com_media/css/mediamanager.css new file mode 100644 index 0000000000000..8a1b7b845e777 --- /dev/null +++ b/media/com_media/css/mediamanager.css @@ -0,0 +1,277 @@ +/* General layout */ +.media-container { + position: absolute; + left: 0; + width: 100%; + margin-top: -70px; + display: flex; + flex-direction: column; +} + +.media-main { + background: #eee; + flex-grow: 1; + display: flex; +} + +.media-sidebar { + width: 16.5%; + background: #fafafa; + border-right: 1px solid #e1e1e1; + padding-bottom: 50px; +} + +/* Media toolbar */ +.media-toolbar { + background-color: #fafafa; + border-bottom: 1px solid transparent; + box-shadow: 0 2px 4px rgba(0, 0, 0, .2); + height: 31px; + position: relative; + z-index: 5; + display: flex; + padding: 11px 0 9px; + border-top: 1px solid #10223e; +} + +.create-wrapper { + width: 16.5%; +} + +.btn-group { + margin: 0 15px; +} + +.btn-group .btn-success:first-child { + padding-left: 35px; + padding-right: 35px; +} + +.btn-group .dropdown-toggle { + padding-bottom: 3px; +} + +.btn-group .dropdown-toggle .caret { + border-top-color: #fff; + border-bottom-color: #fff; +} + +/* Media breadcrumb */ +.media-breadcrumb { + margin: 0; + padding: 0 15px; + list-style: none; + height: 31px; + line-height: 31px; + flex-grow: 1; + +} + +.media-breadcrumb > li { + display: inline-block; +} + +.media-breadcrumb > li > a { + cursor: pointer; + color: #555; + text-decoration: none; + font-size: 16px; +} + +.media-breadcrumb > li > .divider { + color: #555; + vertical-align: middle; + height: 31px; + line-height: 31px; +} + +.media-breadcrumb > li:last-child a { + font-weight: bold; +} + +/* Media tools */ +.media-tools { + display: flex; + width: calc(16.5% - 15); + text-align: right; + justify-content: flex-end; + margin-right: 15px; +} + +.media-tools a { + display: inline-block; + padding: 0 10px; + color: #7d7d7d; + text-decoration: none; + line-height: 32px; +} + +.media-tools a:hover { + color: #333; +} + +.media-tools-divider { + border-right: 1px solid #e5e5e5; + display: inline-block; + height: 53px; + margin: -10.5px 8px; + vertical-align: middle; +} + +/* Media Tree */ +ul.media-tree { + list-style: none; + padding: 15px 0 0; + margin: 0; +} + +ul.media-tree ul { + padding-top: 0; +} + +.media-tree-item { + position: relative; + display: block; +} + +.media-tree-item a { + display: block; + position: relative; + padding: 5px 10px; + margin-bottom: 2px; + cursor: pointer; + color: #333; + border-left: 4px solid transparent; + text-decoration: none; + height: 26px; + line-height: 26px; +} + +.media-tree-item a:hover { + color: #333; + background-color: #e1e1e1; + border-color: #646464; + text-decoration: none; +} + +.media-tree-item.active > a { + background-color: transparent; + border-color: #2384d3; +} + +.item-icon { + display: inline-block; + line-height: normal; + padding-right: 6px; + vertical-align: middle; + color: #8f8f8f; +} + +.media-tree-item.active > a .item-icon { + color: #2384d3; +} + +.item-name { + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + vertical-align: middle; + white-space: nowrap; +} + +.media-tree-item.active > a .item-name { + font-weight: bold; +} + +/* Media browser */ +.media-browser { + width: 83.5%; +} + +.media-browser-items { + padding: 15px; + display: flex; + flex-wrap: wrap; +} + +.media-browser-item { + position: relative; + margin-top: 15px; + margin-right: 15px; + width: calc(25% - 15px); + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; +} + +.media-browser-item-preview { + position: relative; + border-radius: 1px; + width: 100%; + height: 100px; + background: #fff; + box-shadow: 0 1px 1px 0 rgba(0, 0, 0, .2); + display: flex; + align-items: center; + justify-content: center; +} + +.media-browser-item-preview .icon { + font-size: 80px; + color: #2384d3; +} + +.media-browser-item-info { + padding: 8px 5px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + text-align: center; +} + +@media only screen and (min-width: 979px) { + .media-browser-item { + width: calc(20% - 15px); + } + + .media-browser-item-preview { + height: 150px; + } +} + +@media only screen and (min-width: 1200px) { + .media-browser-item { + width: calc(14.285714285714286% - 15px); + } +} + +.image-brackground { + width: 100%; + height: 100%; + background-position: 0px 0px, 10px 10px; + background-size: 20px 20px; + background-color: white; + background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee 100%), linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee 100%); +} + +.image-cropped { + width: 100%; + height: 100%; + margin: 0 auto; + background-position: center; + background-size: cover; +} + +/* Animations */ +.slide-fade-enter-active { + transition: all .3s cubic-bezier(0.4, 0.0, 0.2, 1); +} + +.slide-fade-leave-active { + transition: all .2s cubic-bezier(0.4, 0.0, 0.2, 1); +} + +.slide-fade-enter, .slide-fade-leave-to { + transform: translateY(-10px); + opacity: 0; +} diff --git a/media/com_media/js/mediamanager.js b/media/com_media/js/mediamanager.js new file mode 100644 index 0000000000000..0a2cce0e24d7b --- /dev/null +++ b/media/com_media/js/mediamanager.js @@ -0,0 +1,3 @@ +!function e(t,n,r){function o(a,s){if(!n[a]){if(!t[a]){var c="function"==typeof require&&require;if(!s&&c)return c(a,!0);if(i)return i(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var l=n[a]={exports:{}};t[a][0].call(l.exports,function(e){var n=t[a][1][e];return o(n?n:e)},l,l.exports,e,t,n,r)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;a=0;r--){var o=e[r];"."===o?e.splice(r,1):".."===o?(e.splice(r,1),n++):n&&(e.splice(r,1),n--)}if(t)for(;n--;n)e.unshift("..");return e}function r(e,t){if(e.filter)return e.filter(t);for(var n=[],r=0;r=-1&&!o;i--){var a=i>=0?arguments[i]:e.cwd();if("string"!=typeof a)throw new TypeError("Arguments to path.resolve must be strings");a&&(n=a+"/"+n,o="/"===a.charAt(0))}return n=t(r(n.split("/"),function(e){return!!e}),!o).join("/"),(o?"/":"")+n||"."},n.normalize=function(e){var o=n.isAbsolute(e),i="/"===a(e,-1);return e=t(r(e.split("/"),function(e){return!!e}),!o).join("/"),e||o||(e="."),e&&i&&(e+="/"),(o?"/":"")+e},n.isAbsolute=function(e){return"/"===e.charAt(0)},n.join=function(){var e=Array.prototype.slice.call(arguments,0);return n.normalize(r(e,function(e,t){if("string"!=typeof e)throw new TypeError("Arguments to path.join must be strings");return e}).join("/"))},n.relative=function(e,t){function r(e){for(var t=0;t=0&&""===e[n];n--);return t>n?[]:e.slice(t,n-t+1)}e=n.resolve(e).substr(1),t=n.resolve(t).substr(1);for(var o=r(e.split("/")),i=r(t.split("/")),a=Math.min(o.length,i.length),s=a,c=0;c1)for(var n=1;n-1&&(l="init"),n.compatible=Number(a.version.split(".")[0])>=2,n.compatible?void 0:void console.warn("[HMR] You are using a version of vue-hot-reload-api that is only compatible with Vue.js core ^2.0.0.")},n.createRecord=function(e,t){var n=null;"function"==typeof t&&(n=t,t=n.options),r(e,t),s[e]={Ctor:a.extend(t),instances:[]}},n.rerender=i(function(e,t){var n=s[e];n.Ctor.options.render=t.render,n.Ctor.options.staticRenderFns=t.staticRenderFns,n.instances.slice().forEach(function(e){e.$options.render=t.render,e.$options.staticRenderFns=t.staticRenderFns,e._staticTrees=[],e.$forceUpdate()})}),n.reload=i(function(e,t){r(e,t);var n=s[e];n.Ctor.extendOptions=t;var o=a.extend(t);n.Ctor.options=o.options,n.Ctor.cid=o.cid,o.release&&o.release(),n.instances.slice().forEach(function(e){e.$vnode&&e.$vnode.context?e.$vnode.context.$forceUpdate():console.warn("Root or manually mounted instance modified. Full reload required.")})})},{}],4:[function(e,t,n){(function(e,n){"use strict";function r(e){return null==e?"":"object"==typeof e?JSON.stringify(e,null,2):String(e)}function o(e){var t=parseFloat(e,10);return t||0===t?t:e}function i(e,t){for(var n=Object.create(null),r=e.split(","),o=0;o-1)return e.splice(n,1)}}function s(e,t){return On.call(e,t)}function c(e){return"string"==typeof e||"number"==typeof e}function u(e){var t=Object.create(null);return function(n){var r=t[n];return r||(t[n]=e(n))}}function l(e,t){function n(n){var r=arguments.length;return r?r>1?e.apply(t,arguments):e.call(t,n):e.call(t)}return n._length=e.length,n}function d(e,t){t=t||0;for(var n=e.length-t,r=new Array(n);n--;)r[n]=e[n+t];return r}function f(e,t){for(var n in t)e[n]=t[n];return e}function p(e){return null!==e&&"object"==typeof e}function v(e){return jn.call(e)===Sn}function h(e){for(var t={},n=0;nVn._maxUpdateCount)){Jn("You may have an infinite update loop "+(t.user?'in watcher with expression "'+t.expression+'"':"in a component render function."),t.vm);break}}Gn&&Vn.devtools&&Gn.emit("flush"),Y()}function J(e){var t=e.id;if(null==yr[t]){if(yr[t]=!0,br){for(var n=mr.length-1;n>=0&&mr[n].id>e.id;)n--;mr.splice(Math.max(n,wr)+1,0,e)}else mr.push(e);gr||(gr=!0,Yn(K))}}function W(e){Or.clear(),Z(e,Or)}function Z(e,t){var n,r,o=Array.isArray(e);if((o||p(e))&&Object.isExtensible(e)){if(e.__ob__){var i=e.__ob__.dep.id;if(t.has(i))return;t.add(i)}if(o)for(n=e.length;n--;)Z(e[n],t);else for(r=Object.keys(e),n=r.length;n--;)Z(e[r[n]],t)}}function Q(e){e._watchers=[];var t=e.$options;t.props&&X(e,t.props),t.methods&&re(e,t.methods),t.data?ee(e):k(e._data={},!0),t.computed&&te(e,t.computed),t.watch&&oe(e,t.watch)}function X(t,n){var r=t.$options.propsData||{},o=t.$options._propKeys=Object.keys(n),i=!t.$parent;or.shouldConvert=i;for(var a=function(i){var a=o[i];"production"!==e.env.NODE_ENV?($r[a]&&Jn('"'+a+'" is a reserved attribute and cannot be used as component prop.',t),N(t,a,F(a,n,r,t),function(){t.$parent&&!or.isSettingProps&&Jn("Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: \""+a+'"',t)})):N(t,a,F(a,n,r,t))},s=0;s1?d(n):n;for(var r=d(arguments,1),o=0,i=n.length;o-1:e.test(t)}function et(t){var n={};n.get=function(){return Vn},"production"!==e.env.NODE_ENV&&(n.set=function(){Jn("Do not replace the Vue.config object, set individual fields instead.")}),Object.defineProperty(t,"config",n),t.util=ur,t.set=D,t.delete=j,t.nextTick=Yn,t.options=Object.create(null),Vn._assetTypes.forEach(function(e){t.options[e+"s"]=Object.create(null)}),t.options._base=t,f(t.options.components,Rr),Je(t),We(t),Ze(t),Qe(t)}function tt(e){for(var t=e.data,n=e,r=e;r.child;)r=r.child._vnode,r.data&&(t=nt(r.data,t));for(;n=n.parent;)n.data&&(t=nt(t,n.data));return rt(t)}function nt(e,t){return{staticClass:ot(e.staticClass,t.staticClass),class:e.class?[e.class,t.class]:t.class}}function rt(e){var t=e.class,n=e.staticClass;return n||t?ot(n,it(t)):""}function ot(e,t){return e?t?e+" "+t:e:t||""}function it(e){var t="";if(!e)return t;if("string"==typeof e)return e;if(Array.isArray(e)){for(var n,r=0,o=e.length;r-1?Xr[e]=t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:Xr[e]=/HTMLUnknownElement/.test(t.toString())}function ct(t){if("string"==typeof t){var n=t;if(t=document.querySelector(t),!t)return"production"!==e.env.NODE_ENV&&Jn("Cannot find element: "+n),document.createElement("div")}return t}function ut(e,t){var n=document.createElement(e);return"select"!==e?n:(t.data&&t.data.attrs&&"multiple"in t.data.attrs&&n.setAttribute("multiple","multiple"),n)}function lt(e,t){return document.createElementNS(Jr[e],t)}function dt(e){return document.createTextNode(e)}function ft(e){return document.createComment(e)}function pt(e,t,n){ +e.insertBefore(t,n)}function vt(e,t){e.removeChild(t)}function ht(e,t){e.appendChild(t)}function mt(e){return e.parentNode}function yt(e){return e.nextSibling}function _t(e){return e.tagName}function gt(e,t){e.textContent=t}function bt(e,t,n){e.setAttribute(t,n)}function wt(e,t){var n=e.data.ref;if(n){var r=e.context,o=e.child||e.elm,i=r.$refs;t?Array.isArray(i[n])?a(i[n],o):i[n]===o&&(i[n]=void 0):e.data.refInFor?Array.isArray(i[n])&&i[n].indexOf(o)<0?i[n].push(o):i[n]=[o]:i[n]=o}}function Ct(e){return null==e}function xt(e){return null!=e}function Et(e,t){return e.key===t.key&&e.tag===t.tag&&e.isComment===t.isComment&&!e.data==!t.data}function Ot(e,t,n){var r,o,i={};for(r=t;r<=n;++r)o=e[r].key,xt(o)&&(i[o]=r);return i}function $t(t){function n(e){return new kr(N.tagName(e).toLowerCase(),{},[],void 0,e)}function r(e,t){function n(){0===--n.listeners&&o(e)}return n.listeners=t,n}function o(e){var t=N.parentNode(e);t&&N.removeChild(t,e)}function a(t,n,r,o,i){if(t.isRootInsert=!i,!s(t,n,r,o)){var a=t.data,c=t.children,u=t.tag;xt(u)?("production"!==e.env.NODE_ENV&&(a&&a.pre&&D++,D||t.ns||Vn.ignoredElements.length&&Vn.ignoredElements.indexOf(u)>-1||!Vn.isUnknownElement(u)||Jn("Unknown custom element: <"+u+'> - did you register the component correctly? For recursive components, make sure to provide the "name" option.',t.context)),t.elm=t.ns?N.createElementNS(t.ns,u):N.createElement(u,t),h(t),d(t,c,n),xt(a)&&p(t,n),l(r,t.elm,o),"production"!==e.env.NODE_ENV&&a&&a.pre&&D--):t.isComment?(t.elm=N.createComment(t.text),l(r,t.elm,o)):(t.elm=N.createTextNode(t.text),l(r,t.elm,o))}}function s(e,t,n,r){var o=e.data;if(xt(o)){var i=xt(e.child)&&o.keepAlive;if(xt(o=o.hook)&&xt(o=o.init)&&o(e,!1,n,r),xt(e.child))return v(e,t),i&&u(e,t,n,r),!0}}function u(e,t,n,r){for(var o,i=e;i.child;)if(i=i.child._vnode,xt(o=i.data)&&xt(o=o.transition)){for(o=0;op?(l=Ct(r[y+1])?null:r[y+1].elm,m(t,l,r,f,y,o)):f>y&&_(t,n,d,p)}function w(e,t,n,r){if(e!==t){if(t.isStatic&&e.isStatic&&t.key===e.key&&(t.isCloned||t.isOnce))return t.elm=e.elm,void(t.child=e.child);var o,i=t.data,a=xt(i);a&&xt(o=i.hook)&&xt(o=o.prepatch)&&o(e,t);var s=t.elm=e.elm,c=e.children,u=t.children;if(a&&f(t)){for(o=0;o, or missing . Bailing hydration and performing full client-side render.")}t=n(t)}if(u=t.elm,l=N.parentNode(u),a(r,p,l,N.nextSibling(u)),r.parent){for(var h=r.parent;h;)h.elm=r.elm,h=h.parent;if(f(r))for(var m=0;m-1?t.split(/\s+/).forEach(function(t){return e.classList.add(t)}):e.classList.add(t);else{var n=" "+e.getAttribute("class")+" ";n.indexOf(" "+t+" ")<0&&e.setAttribute("class",(n+t).trim())}}function Yt(e,t){if(t&&t.trim())if(e.classList)t.indexOf(" ")>-1?t.split(/\s+/).forEach(function(t){return e.classList.remove(t)}):e.classList.remove(t);else{for(var n=" "+e.getAttribute("class")+" ",r=" "+t+" ";n.indexOf(r)>=0;)n=n.replace(r," ");e.setAttribute("class",n.trim())}}function Kt(e){$o(function(){$o(e)})}function Jt(e,t){(e._transitionClasses||(e._transitionClasses=[])).push(t),Gt(e,t)}function Wt(e,t){e._transitionClasses&&a(e._transitionClasses,t),Yt(e,t)}function Zt(e,t,n){var r=Qt(e,t),o=r.type,i=r.timeout,a=r.propCount;if(!o)return n();var s=o===bo?xo:Oo,c=0,u=function(){e.removeEventListener(s,l),n()},l=function(t){t.target===e&&++c>=a&&u()};setTimeout(function(){c0&&(n=bo,l=a,d=i.length):t===wo?u>0&&(n=wo,l=u,d=c.length):(l=Math.max(a,u),n=l>0?a>u?bo:wo:null,d=n?n===bo?i.length:c.length:0);var f=n===bo&&Ao.test(r[Co+"Property"]);return{type:n,timeout:l,propCount:d,hasTransform:f}}function Xt(e,t){for(;e.length1,S=n._enterCb=on(function(){D&&(Wt(n,O),Wt(n,E)),S.cancelled?(D&&Wt(n,x),N&&N(n)):k&&k(n),n._enterCb=null});e.data.show||de(e.data.hook||(e.data.hook={}),"insert",function(){var t=n.parentNode,r=t&&t._pending&&t._pending[e.key];r&&r.context===e.context&&r.tag===e.tag&&r.elm._leaveCb&&r.elm._leaveCb(),A&&A(n,S)},"transition-insert"),$&&$(n),D&&(Jt(n,x),Jt(n,E),Kt(function(){Jt(n,O),Wt(n,x),S.cancelled||j||Zt(n,i,S)})),e.data.show&&(t&&t(),A&&A(n,S)),D||j||S()}}}function nn(e,t){function n(){y.cancelled||(e.data.show||((r.parentNode._pending||(r.parentNode._pending={}))[e.key]=e),l&&l(r),h&&(Jt(r,s),Jt(r,u),Kt(function(){Jt(r,c),Wt(r,s),y.cancelled||m||Zt(r,a,y)})),d&&d(r,y),h||m||y())}var r=e.elm;r._enterCb&&(r._enterCb.cancelled=!0,r._enterCb());var o=rn(e.data.transition);if(!o)return t();if(!r._leaveCb&&1===r.nodeType){var i=o.css,a=o.type,s=o.leaveClass,c=o.leaveToClass,u=o.leaveActiveClass,l=o.beforeLeave,d=o.leave,f=o.afterLeave,p=o.leaveCancelled,v=o.delayLeave,h=i!==!1&&!Fn,m=d&&(d._length||d.length)>1,y=r._leaveCb=on(function(){r.parentNode&&r.parentNode._pending&&(r.parentNode._pending[e.key]=null),h&&(Wt(r,c),Wt(r,u)),y.cancelled?(h&&Wt(r,s),p&&p(r)):(t(),f&&f(r)),r._leaveCb=null});v?v(n):n()}}function rn(e){if(e){if("object"==typeof e){var t={};return e.css!==!1&&f(t,ko(e.name||"v")),f(t,e),t}return"string"==typeof e?ko(e):void 0}}function on(e){var t=!1;return function(){t||(t=!0,e())}}function an(e,t){t.data.show||tn(t)}function sn(t,n,r){var o=n.value,i=t.multiple;if(i&&!Array.isArray(o))return void("production"!==e.env.NODE_ENV&&Jn(' expects an Array value for its binding, but got '+Object.prototype.toString.call(o).slice(8,-1),r));for(var a,s,c=0,u=t.options.length;c-1,s.selected!==a&&(s.selected=a);else if(_(un(s),o))return void(t.selectedIndex!==c&&(t.selectedIndex=c));i||(t.selectedIndex=-1)}function cn(e,t){for(var n=0,r=t.length;n0,Fn=In&&In.indexOf("edge/")>0,Bn=In&&In.indexOf("android")>0,Hn=In&&/iphone|ipad|ipod|ios/.test(In),qn=function(){return void 0===wn&&(wn=!Ln&&"undefined"!=typeof n&&"server"===n.process.env.VUE_ENV),wn},zn=Ln&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__,Jn=function(){function e(){r=!1;var e=n.slice(0);n.length=0;for(var t=0;t":"anonymous component")+(e._isVue&&e.$options.__file?" at "+e.$options.__file:"")};var Zn=function(e){return"anonymous component"===e&&(e+=' - use the "name" option for better debugging messages.'),"\n(found in "+e+")"}}var Gn=0,Qn=function(){this.id=Gn++,this.subs=[]};Qn.prototype.addSub=function(e){this.subs.push(e)},Qn.prototype.removeSub=function(e){a(this.subs,e)},Qn.prototype.depend=function(){Qn.target&&Qn.target.addDep(this)},Qn.prototype.notify=function(){for(var e=this.subs.slice(),t=0,n=e.length;t1&&(t[n[0].trim()]=n[1].trim())}}),t}),fo=/^--/,po=/\s*!important$/,vo=function(e,t,n){fo.test(t)?e.style.setProperty(t,n):po.test(n)?e.style.setProperty(t,n.replace(po,""),"important"):e.style[mo(t)]=n},ho=["Webkit","Moz","ms"],mo=u(function(e){if(Rr=Rr||document.createElement("div"),e=Nn(e),"filter"!==e&&e in Rr.style)return e;for(var t=e.charAt(0).toUpperCase()+e.slice(1),n=0;n. If you are working with contenteditable, it's recommended to wrap a library dedicated for that purpose inside a custom component.",r.context)),"select"===r.tag){var o=function(){sn(t,n,r.context)};o(),(Rn||Fn)&&setTimeout(o,0)}else"textarea"!==r.tag&&"text"!==t.type||(t._vModifiers=n.modifiers,n.modifiers.lazy||(Bn||(t.addEventListener("compositionstart",ln),t.addEventListener("compositionend",dn)),Un&&(t.vmodel=!0)))},componentUpdated:function(e,t,n){if("select"===n.tag){sn(e,t,n.context);var r=e.multiple?t.value.some(function(t){return cn(t,e.options)}):t.value!==t.oldValue&&cn(t.value,e.options);r&&fn(e,"change")}}},Vo={bind:function(e,t,n){var r=t.value;n=pn(n);var o=n.data&&n.data.transition,i=e.__vOriginalDisplay="none"===e.style.display?"":e.style.display;r&&o&&!Un?(n.data.show=!0,tn(n,function(){e.style.display=i})):e.style.display=r?i:"none"},update:function(e,t,n){var r=t.value,o=t.oldValue;if(r!==o){n=pn(n);var i=n.data&&n.data.transition;i&&!Un?(n.data.show=!0,r?tn(n,function(){e.style.display=e.__vOriginalDisplay}):nn(n,function(){e.style.display="none"})):e.style.display=r?e.__vOriginalDisplay:"none"}},unbind:function(e,t,n,r,o){o||(e.style.display=e.__vOriginalDisplay)}},Mo={model:To,show:Vo},Po={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String},Lo={name:"transition",props:Po,abstract:!0,render:function(t){var n=this,r=this.$slots.default;if(r&&(r=r.filter(function(e){return e.tag}),r.length)){"production"!==e.env.NODE_ENV&&r.length>1&&Wn(" can only be used on a single element. Use for lists.",this.$parent);var o=this.mode;"production"!==e.env.NODE_ENV&&o&&"in-out"!==o&&"out-in"!==o&&Wn("invalid mode: "+o,this.$parent);var i=r[0];if(yn(this.$vnode))return i;var a=vn(i);if(!a)return i;if(this._leaving)return mn(t,i);var s=a.key=null==a.key||a.isStatic?"__v"+(a.tag+this._uid)+"__":a.key,c=(a.data||(a.data={})).transition=hn(this),u=this._vnode,l=vn(u);if(a.data.directives&&a.data.directives.some(function(e){return"show"===e.name})&&(a.data.show=!0),l&&l.data&&l.key!==s){var d=l.data.transition=f({},c);if("out-in"===o)return this._leaving=!0,de(d,"afterLeave",function(){n._leaving=!1,n.$forceUpdate()},s),mn(t,i);if("in-out"===o){var p,v=function(){p()};de(c,"afterEnter",v,s),de(c,"enterCancelled",v,s),de(d,"delayLeave",function(e){p=e},s)}}return i}}},Io=f({tag:String,moveClass:String},Po);delete Io.mode;var Ro={props:Io,render:function(t){for(var n=this.tag||this.$vnode.data.tag||"span",r=Object.create(null),o=this.prevChildren=this.children,i=this.$slots.default||[],a=this.children=[],s=hn(this),c=0;c children must be keyed: <"+d+">")}}if(o){for(var f=[],p=[],v=0;v1&&void 0!==arguments[1]?arguments[1]:null;this.vue.$emit(e,t)}},{key:"listen",value:function(e,t){this.vue.$on(e,t)}}]),e}();n.default=c},{vue:3}],6:[function(e,t,n){!function(){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.default={name:"media-app",data:function(){return{dir:"/",content:[],tree:{path:"/",children:[]},baseUrl:"https://github.com/gitapi/repos/joomla/joomla-cms/contents"}},methods:{getContent:function(){var e=this,t=this.baseUrl+this.dir;jQuery.getJSON(t,function(t){e.content=t,e._updateLeafByPath(e.tree,e.dir,t)}).error(function(){alert("Error loading directory content.")})},_updateLeafByPath:function(e,t,n){if(e.path&&e.path===t)return this.$set(e,"children",n),!0;if(e.children&&e.children.length)for(var r=0;r a {\n font-weight: bold;\n}\n.media-tree-item a {\n cursor: pointer;\n}");!function(){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.default={name:"media-tree-item",props:["item","dir"],computed:{isActive:function(){return this.item.path===this.dir}},methods:{toggleItem:function(e){Media.Event.fire("dirChanged",e.path)}}}}(),t.exports.__esModule&&(t.exports=t.exports.default);var o="function"==typeof t.exports?t.exports.options:t.exports;o.functional&&console.error("[vueify] functional components are not supported and should be defined in plain js files using render functions."),o.render=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("li",{staticClass:"media-tree-item",class:{active:e.isActive}},[n("a",{on:{click:function(t){t.stopPropagation(),t.preventDefault(),e.toggleItem(e.item)}}},[e._v(e._s(e.item.name))]),e._v(" "),e.item.children&&e.item.children.length?n("media-tree",{attrs:{tree:e.item,dir:e.dir}}):e._e()],1)},o.staticRenderFns=[],t.hot&&!function(){var n=e("vue-hot-reload-api");n.install(e("vue"),!0),n.compatible&&(t.hot.accept(),t.hot.dispose(r),t.hot.data?n.reload("data-v-550db2f4",o):n.createRecord("data-v-550db2f4",o))}()},{vue:3,"vue-hot-reload-api":2,"vueify/lib/insert-css":4}],10:[function(e,t,n){!function(){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.default={name:"media-tree",props:["tree","dir"],computed:{directories:function(){return this.tree.children.filter(function(e){return 0!==e.name.indexOf(".")}).filter(function(e){return"dir"===e.type}).sort(function(e,t){return e.name.toUpperCase()