JS improvements

This commit is contained in:
Tony Air 2019-05-23 18:02:32 +07:00
parent 4231243674
commit 18af5420d5
20 changed files with 473 additions and 113 deletions

View File

@ -26,6 +26,7 @@ SilverStripe\CMS\Model\SiteTree:
- Dynamic\Elements\Elements\ElementTestimonials
- Site\Elements\TeamMembersElement
- Site\Elements\SliderElement
- DNADesign\ElementalVirtual\Model\ElementVirtual
DNADesign\ElementalList\Model\ElementList:
default_global_elements: false

View File

@ -5,7 +5,7 @@ SilverStripe\SiteConfig\SiteConfig:
SilverStripe\SiteTree\SiteTree:
extensions:
- SiteTreeExtension
- Site\Extensions\SiteTreeExtension
SilverStripe\Blog\Model\BlogPost:
extensions:

View File

@ -1,3 +1,4 @@
BetterBrief\GoogleMapField:
default_options:
api_key: 'YOUR_API_KEY'
show_search_box: true

View File

@ -22,30 +22,77 @@ const FormBasics = (($) => {
const $fields = $element.find('input,textarea,select');
const $selectFields = $element.find('select:not([readonly])');
const $radioOptions = $element.find('input[type="radio"]');
const separator = ', ';
$selectFields.each((i, el) => {
const $el = $(el);
const maxOptions = $el.data('max-options') || false;
$el.selectpicker({
$el.selectpicker($.extend({
iconBase: 'fas',
tickIcon: 'fa-check',
liveSearch: $el.data('live-search'),
noneSelectedText: $el.data('none-selected-text') || 'Nothing selected',
maxOptions: $el.data('max-options') || false,
virtualScroll: false,
dropupAuto: false,
size: 10,
maxOptions: maxOptions,
}, $el.data(), {
multipleSeparator: separator,
}));
// wrap options
$el.on('rendered.bs.select', () => {
if (!$el.val().length) {
return true;
}
const $container = $el.parent().find('.filter-option-inner-inner');
const val = $container.text();
const vals = val.split(separator);
let html = '';
vals.forEach((opt) => {
const $opt = $el.find('option').filter((i, e) => {
return $(e).text() === opt;
});
html += '<span class="option" data-val=' + $opt.attr('value') + '>' + opt +
' <i class="fas fa-times btn-remove"></i></span>';
});
$container.html(html);
// remove value
$container.find('.option').on('click', (e) => {
e.preventDefault();
const $opt = $(e.currentTarget);
const val = $opt.data('val').toString();
//$opt.remove();
let vals = $el.selectpicker('val');
const i = vals.indexOf(val);
if (i > -1) {
vals.splice(i, 1);
$el.selectpicker('val', vals);
}
});
});
// FIX: hidden picker
$el.selectpicker('render');
$el.selectpicker('refresh');
$el.selectpicker('toggle');
document.activeElement.blur();
//$el.selectpicker('show');
//$el.selectpicker('hide');
/*$el.parents('.field.dropdown').find('.dropdown-toggle').click();
$el.parents('.field.dropdown').find('.dropdown-toggle').click();
$el.parents('.field.dropdown').find('.dropdown-toggle').click();
$el.parents('.field.dropdown').find('.dropdown-toggle').blur();
$el.parents('.field.dropdown').find('.dropdown-toggle').blur();*/
});
// FIX: hidden picker click
if ($selectFields.length) {
setTimeout(() => {
$(window).scrollTop(0, 0);
}, 600);
}
$fields.each((e, el) => {
const $el = $(el);

View File

@ -2,6 +2,7 @@
import $ from 'jquery';
import MainUI from "../_main";
import Events from '../_events';
import SpinnerUI from './_ui.spinner';
@ -33,7 +34,7 @@ const CroppieUI = (($) => {
const ui = this;
const $el = $(element);
ui._element = element;
ui.$el = $el;
$el.data(DATA_KEY, this);
ui.input = $el.find('input[type="file"]');
@ -42,10 +43,14 @@ const CroppieUI = (($) => {
ui.width = ui.input.data('width');
ui.height = ui.input.data('height');
$el.append('<div class="cropper-wrap"><div class="cropper-container"></div></div>');
$el.append(
'<div class="cropper-wrap"><div class="cropper-container"></div>' +
'<a href="#" class="btn-remove"><i class="fas fa-times"></i> Remove</a></div>'
);
//$el.append(ui.inputData);
ui.uploadCrop = $el.find('.cropper-container');
ui.uploadCropWrap = $el.find('.cropper-wrap');
ui.uploadCrop = ui.uploadCropWrap.find('.cropper-container');
const ratio = ui.width / (ui.uploadCrop.width() - 32);
ui.uploadCrop.croppie({
@ -62,11 +67,23 @@ const CroppieUI = (($) => {
ui.input.on('change', (e) => {
this.readFile(e.currentTarget);
});
$el.find('.btn-remove').on('click', (e) => {
e.preventDefault();
ui.uploadCrop.removeClass('ready');
$el.find('.croppie-image').remove();
ui.$el.find('input[type="file"]').val('');
ui.$el.find('input[type="file"]').change();
ui.uploadCropWrap.hide();
});
}
readFile(input) {
const ui = this;
const $el = $(ui._element);
const $el = ui.$el;
const $form = $el.closest('form');
if (input.files && input.files[0]) {
@ -79,6 +96,7 @@ const CroppieUI = (($) => {
});
ui.uploadCrop.show();
ui.uploadCropWrap.show();
}
reader.readAsDataURL(input.files[0]);
@ -87,6 +105,10 @@ const CroppieUI = (($) => {
//$(input).val('');
SpinnerUI.show();
if (!ui.uploadCrop.hasClass('ready')) {
return true;
}
ui.uploadCrop.croppie('result', {
type: 'blob',
size: {
@ -129,12 +151,24 @@ const CroppieUI = (($) => {
$form.find('select[name="' + k + '"],input[name="' + k + '"],textarea[name="' + k + '"]').setError(true, json[k]);
}*/
if (typeof json['status'] !== 'undefined' && json['status'] === 'success') {
if (typeof json['status'] !== 'undefined') {
if (json['status'] === 'success') {
MainUI.alert(json['message'], json['status']);
if (typeof json['link'] !== 'undefined') {
setTimeout(() => {
G.location = json['link'];
}, 2000);
} else {
//G.location.reload(false);
}
} else if (json['status'] === 'error') {
MainUI.alert(json['message'], json['status']);
}
}
if (typeof json['form'] !== 'undefined') {
$(form).replaceWith(json['form']);
}
} else {
$(form).replaceWith(data);

View File

@ -0,0 +1,72 @@
import $ from 'jquery';
import Events from '../_events';
import Spinner from './_ui.spinner';
const NoCaptcha = (($) => {
// Constants
const W = window;
const D = document;
const $Body = $('body');
const NAME = 'NoCaptcha';
class NoCaptcha {
static init() {
const ui = this;
ui.dispose();
console.log(`Initializing: ${NAME}`);
this.renderCaptcha();
}
static dispose() {
console.log(`Destroying: ${NAME}`);
}
static renderCaptcha() {
console.log(`Rendering Captcha: ${NAME}`);
if (typeof grecaptcha === 'undefined') {
console.log('Captcha API isn\'t available yet');
}
const $_noCaptchaFields = $('.g-recaptcha');
const submitListener = (e) => {
e.preventDefault();
grecaptcha.execute();
};
$_noCaptchaFields.each((i, field) => {
const $field = $(field);
if ($field.data('widgetid')) {
return;
}
const $form = $field.data('form') ? $('#' + $field.data('form')) : $field.parents('form');
//For the invisible captcha we need to setup some callback listeners
if ($field.data('size') === 'invisible' && !$field.data('callback')) {
$form.on('submit', submitListener);
}
const widget_id = grecaptcha.render(field, $field.data());
$field.data('widgetid', widget_id);
});
}
}
$(W).on(`${Events.AJAX}`, () => {
NoCaptcha.init();
});
W.NoCaptcha = NoCaptcha;
W.noCaptchaFieldRender = NoCaptcha.renderCaptcha;
return NoCaptcha;
})($);
export default NoCaptcha;

View File

@ -0,0 +1,105 @@
"use strict";
import $ from 'jquery';
import MainUI from "../_main";
import Events from '../_events';
import SpinnerUI from './_ui.spinner';
const VideoPreviewUI = (($) => {
const NAME = 'jsVideoPreviewUI';
const DATA_KEY = NAME;
const G = window;
const D = document;
class VideoPreviewUI {
constructor(el) {
console.log(`Initializing: ${NAME}`);
const ui = this;
ui.$_el = $(el);
ui.innerHTML = ui.$_el[0].innerHTML;
ui.$_el.data(DATA_KEY, this);
const href = ui.$_el.attr('href') || ui.$_el.data('href');
let video;
if (video = href.match(/(youtube|youtube-nocookie|youtu|vimeo)\.(com|be)\/(watch\?v=([\w-]+)|([\w-]+))/)) {
let video_id;
if (video[1] === 'youtube' || video[1] === 'youtube-nocookie') {
video_id = video[4];
}
if (video[1] === 'youtu') {
video_id = video[3];
}
if (video[1] == 'vimeo') {
video_id = video[3];
ui.$_el.addClass('loading');
$.ajax({
type: 'GET',
url: 'https://vimeo.com/api/v2/video/' + video_id + '.json',
jsonp: 'callback',
dataType: 'jsonp',
success: function(data) {
const thumbnail_src = data[0].thumbnail_large;
ui.show(thumbnail_src);
ui.$_el.removeClass('loading');
}
});
return;
}
if (video_id) {
ui.show(`//i3.ytimg.com/vi/${video_id}/0.jpg`);
}
}
}
show(src) {
const ui = this;
ui.$_el[0].innerHTML = '';
ui.$_el.append(`<img src="${src}" alt="Video" />`);
}
static dispose() {
console.log(`Destroying: ${NAME}`);
ui.$_el[0].innerHTML = ui.innerHTML;
}
static _jQueryInterface() {
return this.each((i, el) => {
// attach functionality to element
const $el = $(el);
let data = $el.data(DATA_KEY);
if (!data) {
data = new VideoPreviewUI(el);
$el.data(DATA_KEY, data);
}
});
}
}
// jQuery interface
$.fn[NAME] = VideoPreviewUI._jQueryInterface;
$.fn[NAME].Constructor = VideoPreviewUI;
$.fn[NAME].noConflict = () => {
$.fn[NAME] = JQUERY_NO_CONFLICT;
return VideoPreviewUI._jQueryInterface;
};
// auto-apply
$(window).on(`${Events.AJAX} ${Events.LOADED}`, () => {
$('[data-video-preview="true"]').jsVideoPreviewUI();
});
return VideoPreviewUI;
})($);
export default VideoPreviewUI;

View File

@ -62,3 +62,10 @@ body.shrink {}
}
}
}
.bootstrap-select .dropdown-toggle .filter-option .option {
background: #dedede;
padding: .2rem .5rem;
margin: .2rem;
color: #212529;
}

View File

@ -80,15 +80,7 @@ class ElementImageWidget extends DataExtension
public function getWidth()
{
$columnSize = $this->owner->getColumnSizeRecursive();
if (!$columnSize) {
return false;
}
$max = Config::inst()->get(ElementRows::class, 'container_max_width');
$size = 12 / $columnSize;
return $max / $size;
return $this->owner->getColumnWidthRecursive();
}
public function getHeight()

View File

@ -98,7 +98,7 @@ class ElementRows extends DataExtension
public function getWidthPercetage()
{
return $this->isColumn() ? $this->owner->getField('Size') / 12 * 100 : false;
return $this->isColumn() ? $this->owner->getField('Size') / self::colsNumber() * 100 : false;
}
public function isList()
@ -153,15 +153,47 @@ class ElementRows extends DataExtension
if ($object->isColumn() && $object->getField('Size')) {
return (int) $object->getField('Size');
} else {
}
$parent = $object->Parent()->getOwnerPage();
if (is_a($parent, 'Page')) {
return ($this->owner->getField('ContainerType') === 'container-fluid') ? false : 12;
return ($this->owner->getField('ContainerType') === 'container-fluid') ? false : self::colsNumber();
}
return $object->getColumnSizeRecursive($parent);
}
public function getColumnWidthRecursive($object = null, $max = null)
{
$max = $max ? $max : self::maxWidth();
$object = $object ? $object : $this->owner;
if(!$object->isRoot()){
$size = $object->getField('Size');
$max = $size ? $max / (self::colsNumber() / $size) : $max;
$parent = $object->Parent()->getOwnerPage();
return $this->getColumnWidthRecursive($parent, $max);
}
return $max;
}
public static function colsNumber()
{
$db = Config::inst()->get(self::class, 'db');
$sizes = $db['Size'];
$sizes = preg_replace('!Enum\("([0-9,]+)","([0-9]+)"\)!i','$1', $sizes);
$sizes = explode(',',$sizes);
return max($sizes);
}
public static function maxWidth()
{
return Config::inst()->get(self::class, 'container_max_width');
}
public function ExtraClass()

View File

@ -7,7 +7,7 @@ use SilverStripe\Forms\CompositeField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
class UserFormExtension extends Extension
class PlaceholderFormExtension extends Extension
{
public function updateFormFields(FieldList $fields)
{

View File

@ -5,6 +5,7 @@ namespace Site\Extensions;
use Innoweb\Sitemap\Pages\SitemapPage;
use SilverStripe\AssetAdmin\Forms\UploadField;
use SilverStripe\Assets\Image;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\Forms\TextareaField;
use SilverStripe\Forms\TextField;
use SilverStripe\ORM\DataExtension;
@ -21,7 +22,8 @@ class SiteConfigExtension extends DataExtension
'Longitude' => 'Varchar(255)',
'Latitude' => 'Varchar(255)',
'MapZoom' => 'Int',
'MapAPIKey' => 'Varchar(255)'
'MapAPIKey' => 'Varchar(255)',
'Description' => 'Varchar(255)',
];
private static $has_one = [
@ -57,12 +59,17 @@ class SiteConfigExtension extends DataExtension
SitemapPage::get()->map()->toArray()
));
$tab->push(TextareaField::create('Description', 'Website Description'));
$mapTab = $fields->findOrMakeTab('Root.GoogleMaps');
$mapTab->push(TextField::create('MapAPIKey'));
$mapTab->push(TextField::create('MapZoom'));
$mapTab->push(GoogleMapField::create(
$this->owner,
'Location'
'Location',
[
'show_search_box' => true,
]
));
}
@ -80,4 +87,9 @@ class SiteConfigExtension extends DataExtension
.$this->owner->getField('Longitude').'" class="btn btn-primary btn-directions" target="_blank">'
.'<i class="fas fa-road"></i> Get Directions</a>';
}
public function getLatestBlogPosts()
{
return BlogPost::get()->sort('PublishDate DESC');
}
}

View File

@ -0,0 +1 @@
Remove it in case u need silvershop module

View File

@ -9,6 +9,7 @@ use SilverStripe\Core\Config\Config;
use SilverStripe\Control\Director;
use SilverStripe\Core\Path;
use SilverStripe\Core\Manifest\ManifestFileFinder;
use SilverStripe\CMS\Controllers\CMSMain;
class DeferedRequirements implements TemplateGlobalProvider
{
@ -35,9 +36,14 @@ class DeferedRequirements implements TemplateGlobalProvider
public static function Auto($class = false)
{
if (is_a(Controller::curr(), CMSMain::class)) {
return;
}
$config = Config::inst()->get(self::class);
$projectName = WebpackTemplateProvider::projectName();
$mainTheme = WebpackTemplateProvider::mainTheme();
$mainTheme = $mainTheme ? $mainTheme : $projectName;
// Initialization
Requirements::block(THIRDPARTY_DIR.'/jquery/jquery.js');
@ -54,6 +60,7 @@ class DeferedRequirements implements TemplateGlobalProvider
if (!$config['nofontawesome']) {
DeferedRequirements::loadCSS('//use.fontawesome.com/releases/v5.4.0/css/all.css');
}
DeferedRequirements::loadCSS($mainTheme.'.css');
DeferedRequirements::loadJS($mainTheme.'.js');

View File

@ -3,12 +3,22 @@
*/
const webpack = require('webpack');
const conf = require('./webpack.configuration');
const commonVariables = require('./webpack.configuration');
const conf = commonVariables.configuration;
const path = require('path');
const filesystem = require('fs');
const includes = {};
const modules = [
path.resolve(__dirname, 'public'),
path.resolve(__dirname, conf.APPDIR, 'client', 'src'),
path.resolve(__dirname, conf.APPDIR, 'client', 'src', 'js'),
path.resolve(__dirname, conf.APPDIR, 'client', 'src', 'scss'),
path.resolve(__dirname, conf.APPDIR, 'client', 'src', 'img'),
path.resolve(__dirname, conf.APPDIR, 'client', 'src', 'thirdparty'),
path.resolve(__dirname, 'node_modules')
];
const _addAppFiles = (theme) => {
@ -21,6 +31,11 @@ const _addAppFiles = (theme) => {
includes[`${themeName}`] = path.join(dirPath, conf.SRC, 'scss', 'app.scss');
}
modules.push(path.join(dirPath, 'client', 'src', 'js'));
modules.push(path.join(dirPath, 'client', 'src', 'scss'));
modules.push(path.join(dirPath, 'client', 'src', 'img'));
modules.push(path.join(dirPath, 'client', 'src', 'thirdparty'));
const _getAllFilesFromFolder = function(dir, includeSubFolders = true) {
const dirPath = path.resolve(__dirname, dir);
let results = [];
@ -30,7 +45,7 @@ const _addAppFiles = (theme) => {
return;
}
const filePath = `${dirPath}/${file}`;
const filePath = path.join(dirPath, file);
const stat = filesystem.statSync(filePath);
if (stat && stat.isDirectory() && includeSubFolders) {
@ -65,20 +80,9 @@ const _addAppFiles = (theme) => {
_addAppFiles(conf.APPDIR);
// add themes
if (conf.THEMESDIR) {
const dir = path.resolve(__dirname, conf.THEMESDIR);
if (filesystem.existsSync(dir)) {
filesystem.readdirSync(dir).forEach((file) => {
filePath = `${dir}/${file}`;
const stat = filesystem.statSync(filePath);
if (stat && stat.isDirectory()) {
_addAppFiles(path.join(conf.THEMESDIR, file));
}
commonVariables.themes.forEach((theme) => {
_addAppFiles(theme);
});
}
}
module.exports = {
entry: includes,
@ -120,15 +124,7 @@ module.exports = {
}],
},
resolve: {
modules: [
path.resolve(__dirname, 'public'),
path.resolve(__dirname, conf.APPDIR, 'client', 'src'),
path.resolve(__dirname, conf.APPDIR, 'client', 'src', 'js'),
path.resolve(__dirname, conf.APPDIR, 'client', 'src', 'scss'),
path.resolve(__dirname, conf.APPDIR, 'client', 'src', 'img'),
path.resolve(__dirname, conf.APPDIR, 'client', 'src', 'thirdparty'),
path.resolve(__dirname, 'node_modules')
],
modules: modules,
alias: {
'jquery': require.resolve('jquery'),
'jQuery': require.resolve('jquery'),

View File

@ -7,7 +7,8 @@ const autoprefixer = require('autoprefixer');
const webpack = require('webpack');
const merge = require('webpack-merge');
const common = require('./webpack.config.common.js');
const conf = require('./webpack.configuration');
const commonVariables = require('./webpack.configuration');
const conf = commonVariables.configuration;
const IP = process.env.IP || conf.HOSTNAME;
const PORT = process.env.PORT || conf.PORT;
@ -101,6 +102,7 @@ const config = merge.strategy({
historyApiFallback: true,
hot: false,
clientLogLevel: 'info',
disableHostCheck: true,
contentBase: [
path.resolve(__dirname, 'public'),
path.resolve(__dirname, 'public', 'resources'),

View File

@ -3,16 +3,81 @@
*/
const webpack = require('webpack');
const conf = require('./webpack.configuration');
const commonVariables = require('./webpack.configuration');
const conf = commonVariables.configuration;
const merge = require('webpack-merge');
const common = require('./webpack.config.common.js');
const filesystem = require('fs');
const path = require('path');
const autoprefixer = require('autoprefixer');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
let plugins = [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
}),
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.optimize.UglifyJsPlugin({
sourceMap: false,
comments: false
}),
new ExtractTextPlugin({
filename: 'css/[name].css',
allChunks: true
}),
new FaviconsWebpackPlugin({
logo: path.join(__dirname, conf.APPDIR, conf.SRC, 'favicon.png'),
prefix: '/icons/',
statsFilename: path.join(conf.APPDIR, conf.DIST, 'icons', 'iconstats.json'),
icons: {
android: true,
appleIcon: true,
appleStartup: true,
coast: true,
favicons: true,
firefox: true,
opengraph: true,
twitter: true,
yandex: true,
windows: true
}
}),
];
// add themes favicons
commonVariables.themes.forEach((theme) => {
const faviconPath = path.join(__dirname, theme, conf.SRC, 'favicon.png');
if (filesystem.existsSync(faviconPath)) {
plugins.push(new FaviconsWebpackPlugin({
logo: faviconPath,
prefix: '/' + theme + '-icons/',
statsFilename: path.join(conf.APPDIR, conf.DIST, theme + '-icons', 'iconstats.json'),
icons: {
android: true,
appleIcon: true,
appleStartup: true,
coast: true,
favicons: true,
firefox: true,
opengraph: true,
twitter: true,
yandex: true,
windows: true
}
}));
}
});
module.exports = merge(common, {
devtool: '',
@ -98,42 +163,5 @@ module.exports = merge(common, {
}, ]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
}),
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.optimize.UglifyJsPlugin({
sourceMap: false,
comments: false
}),
new ExtractTextPlugin({
filename: 'css/[name].css',
allChunks: true
}),
new FaviconsWebpackPlugin({
logo: path.join(__dirname, conf.APPDIR, conf.SRC, 'favicon.png'),
prefix: '/icons/',
statsFilename: path.join(conf.APPDIR, conf.DIST, 'icons', 'iconstats.json'),
icons: {
android: true,
appleIcon: true,
appleStartup: true,
coast: true,
favicons: true,
firefox: true,
opengraph: true,
twitter: true,
yandex: true,
windows: true
}
}),
],
plugins: plugins,
});

View File

@ -3,8 +3,31 @@
*/
const path = require('path');
const filesystem = require('fs');
const fs = require('fs');
const yaml = require('js-yaml');
const conf = yaml.safeLoad(fs.readFileSync(path.join(__dirname, 'app/_config/webpack.yml'), 'utf8'));
module.exports = conf['Site\\Templates\\WebpackTemplateProvider'];
let themes = [];
// add themes
if (conf['Site\\Templates\\WebpackTemplateProvider'].THEMESDIR) {
const themeDir = conf['Site\\Templates\\WebpackTemplateProvider'].THEMESDIR;
const dir = path.resolve(__dirname, themeDir);
if (filesystem.existsSync(dir)) {
filesystem.readdirSync(dir).forEach((file) => {
filePath = path.join(themeDir, file);
const stat = filesystem.statSync(filePath);
if (stat && stat.isDirectory()) {
themes.push(filePath);
}
});
}
}
module.exports = {
configuration: conf['Site\\Templates\\WebpackTemplateProvider'],
themes: themes,
};