Video shortcodes, Webpack Image compression, other improvements

This commit is contained in:
Tony Air 2020-01-22 16:46:53 +07:00
parent 75ef9f569f
commit fdcc1be513
35 changed files with 781 additions and 388 deletions

View File

@ -3,7 +3,10 @@
use SilverStripe\Forms\HTMLEditor\HtmlEditorConfig;
use SilverStripe\Core\Manifest\ModuleResourceLoader;
use SilverStripe\ORM\Search\FulltextSearchable;
use SilverStripe\View\Parsers\ShortcodeParser;
use Site\Extensions\EmbedShortcodeProvider;
// setup TinyMCE editor
$config = HtmlEditorConfig::get('cms');
$config->enablePlugins([
'template',
@ -29,3 +32,9 @@ $config->setOption(
);
FulltextSearchable::enable();
// replace embed parser
$parser = ShortcodeParser::get('default');
$parser->unregister('embed');
$parser->register('embed', [EmbedShortcodeProvider::class, 'handle_shortcode']);

View File

@ -7,6 +7,8 @@ Site\Templates\DeferredRequirements:
version: false
static_domain: false
deferred: true
jquery_version: '3.4.1'
fontawesome_version: '5.10.2'
SilverStripe\View\Requirements:
disable_flush_combined: true

View File

@ -1,6 +1,5 @@
"use strict";
import '../scss/app.scss';
import $ from 'jquery';
import './_consts';
// import Bootstrap
import 'popper.js';
@ -9,21 +8,7 @@ import 'bootstrap/js/dist/alert';
import 'bootstrap/js/dist/button';
import 'bootstrap/js/dist/carousel';
import 'bootstrap/js/dist/collapse';
import 'hammerjs/hammer';
import 'jquery-hammerjs/jquery.hammer';
// Routie
//import 'pouchdb/dist/pouchdb';
//import '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/routes/index';
// conflicts with _components/_ui.hover.js (shows dropdown on hover)
//import 'bootstrap/js/dist/dropdown';
import '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.hover';
import '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.carousel';
import '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.menu';
import 'bootstrap/js/dist/dropdown';
import 'bootstrap/js/dist/modal';
import 'bootstrap/js/dist/tooltip';
import 'bootstrap/js/dist/popover';
@ -31,57 +16,37 @@ import 'bootstrap/js/dist/scrollspy';
import 'bootstrap/js/dist/tab';
//
import Spinner from '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.spinner';
//import Multislider from '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.multislider';
// Flyout UI
//import Flyout from '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.flyout';
// Offcanvas menu
//import 'offcanvas-bootstrap/dist/js/bootstrap.offcanvas';
import '../scss/app.scss';
import '@a2nt/meta-lightbox/src/js/meta-lightbox';
// youtube video preview image
import '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.video.preview';
// MainUI
import '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_main';
// Uncomment it to enable meta-lightbox zooming on hover
//import 'jquery-zoom/jquery.zoom';
// Toggle bootstrap form fields
//import FormToggleUI from '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.form.fields.toggle';
// Bootstrap Date & Time fields
//import FormDatetime from '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.form.datetime';
// Stepped forms functionality
//import FormStepped from '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.form.stepped';
// Forms validation functionality
//import FormValidate from '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.form.validate';
// Store forms data into localStorage
//import FormStorage from '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.form.storage';
// client-side images cropping
//import FormCroppie from '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.form.croppie';
// Google NoCaptcha fields
//import NoCaptcha from '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.nocaptcha';
// youtube video preview image
import '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.video.preview';
// Meta Lightbox
import '@a2nt/meta-lightbox/src/js/index';
//import Confirmation from 'bootstrap-confirmation2/dist/bootstrap-confirmation';
//import Table from 'bootstrap-table/dist/bootstrap-table';
// AJAX UI
//import AjaxUI from '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.ajax';
import '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_main';
import './_layout';
// Forms UI
import '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.form.basics';
import '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.form.validate';
import '@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/js/_components/_ui.form.stepped';
// Import fonts and images
function importAll(r) {
return r.keys().map(r);
return r.keys().map(r);
}
const images = importAll(require.context('../img/', false, /\.(png|jpe?g|svg)$/));
const fontAwesome = importAll(require.context('font-awesome', false, /\.(otf|eot|svg|ttf|woff|woff2)$/));
const images = importAll(
require.context('../img/', false, /\.(png|jpe?g|svg)$/),
);
const fontAwesome = importAll(
require.context('font-awesome', false, /\.(otf|eot|svg|ttf|woff|woff2)$/),
);
// Your custom JS
import './_layout';

View File

@ -10,27 +10,29 @@
.bootstrap-select .dropdown-toggle .filter-option .option {
background: #dedede;
padding: .2rem .5rem;
margin: .2rem;
padding: 0.2rem 0.5rem;
margin: 0.2rem;
color: #212529;
}
// shrink elements on scroll
body.shrink {}
body.shrink {
}
// sticky footer
@media (min-width: map-get($grid-breakpoints, "sm")) {
html, body {
@media (min-width: map-get($grid-breakpoints, 'sm')) {
html,
body {
height: 100%;
min-height: 100%;
}
.wrapper {
min-height: 100%;
padding-bottom: $footer-size + $footer-bar-size + $grid-gutter-height / 2;
padding-bottom: $footer-size + $footer-bar-size + $grid-gutter-height /
2;
//padding-top: $grid-gutter-height;
}
.footer {
@ -97,12 +99,6 @@ body.shrink {}
background-color: $dark;
color: darken($white, 5%);
.container,
.container-fluid {
padding-top: $grid-gutter-height / 2;
padding-bottom: $grid-gutter-height / 2;
}
a {
color: $white;
}
@ -114,22 +110,16 @@ body.shrink {}
.footer {
background-color: darken($dark, 5%);
.container,
.container-fluid {
padding-top: 0;
padding-bottom: 0;
}
.copyright {
padding-right: .5rem;
padding-right: 0.5rem;
}
li {
padding: 0 .5rem;
padding: 0 0.5rem;
}
}
@media (min-width: map-get($grid-breakpoints, "sm")) {
@media (min-width: map-get($grid-breakpoints, 'sm')) {
.wrapper {
padding-bottom: $footer-bar-size;
}

View File

@ -2,19 +2,45 @@
* Your custom variables
*/
$grid-breakpoints: (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1390px, xxxl: 1590px);
$container-max-widths: (sm: 540px, md: 720px, lg: 960px, xl: 1140px, xxl: 1330px, xxxl: 1560px);
$grid-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px,
xxl: 1390px,
xxxl: 1590px,
);
$container-max-widths: (
sm: 540px,
md: 720px,
lg: 960px,
xl: 1140px,
xxl: 1330px,
xxxl: 1560px,
);
$font-family-base: "Lato", sans-serif;
$font-family-base: 'Lato', sans-serif;
$grid-gutter-width: 2rem;
$grid-gutter-height: 2rem;
$grid-gutter-element-height: $grid-gutter-height * 2;
@import "~@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/scss/_variables";
@import '~@a2nt/ss-bootstrap-ui-webpack-boilerplate/src/scss/_variables';
// Add your site-wide + content editor typography styling
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
color: $dark;
h1,
h2,
h3,
h4,
h5,
h6,
.h1,
.h2,
.h3,
.h4,
.h5,
.h6 {
color: $headings-color;
}

View File

@ -19,6 +19,7 @@ use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
use SilverStripe\Forms\NumericField;
use Site\Controllers\MapElementController;
use Site\Extensions\MapExtension;
use SilverStripe\Forms\GridField\GridFieldDataColumns;
class MapElement extends ElementContent
{
@ -60,10 +61,16 @@ class MapElement extends ElementContent
'Locations',
'Locations',
$this->owner->Locations(),
GridFieldConfig_RelationEditor::create(100)
$cfg = GridFieldConfig_RelationEditor::create(100)
)
]);
$cfg->getComponentByType(GridFieldDataColumns::class)->setFieldFormatting([
'ShowAtMap' => static function ($v, $obj) {
return $v ? 'YES' : 'NO';
}
]);
return $fields;
}

View File

@ -14,6 +14,7 @@ use SilverStripe\Forms\CheckboxField;
use SilverStripe\Forms\DropdownField;
use SilverStripe\ORM\DataExtension;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\LiteralField;
class ElementRows extends DataExtension
{
@ -95,6 +96,15 @@ class ElementRows extends DataExtension
} else {
$fields->removeByName('Size');
}
$tab = $fields->findOrMakeTab('Root.Settings')
->push(LiteralField::create(
'ClassName',
'<div class="form-group field text">'
.'<div class="form__field-label">Class</div>'
.'<div class="form__field-holder">'.$this->owner->getField('ClassName').'</div>'
.'</div>'
));
}
public function getWidthPercetage()

View File

@ -0,0 +1,52 @@
<?php
namespace Site\Extensions;
use SilverStripe\Core\Convert;
use SilverStripe\View\HTML;
class EmbedShortcodeProvider extends \SilverStripe\View\Shortcodes\EmbedShortcodeProvider
{
/**
* Build video embed tag
*
* @param array $arguments
* @param string $content Raw HTML content
* @return string
*/
protected static function videoEmbed($arguments, $content)
{
// Ensure outer div has given width (but leave height auto)
if (!empty($arguments['width'])) {
$arguments['style'] = 'width:' . (int) $arguments['width'] . 'px';
//.';height:' . (int) $arguments['height'] . 'px';
}
// Convert caption to <p>
if (!empty($arguments['caption'])) {
$xmlCaption = Convert::raw2xml($arguments['caption']);
$content .= "\n<p class=\"caption\">{$xmlCaption}</p>";
}
// Convert arguments to data-*argument_name*
foreach ($arguments as $k => $v) {
if($k === 'class' || $k === 'style') {
continue;
}
unset($arguments[$k]);
$arguments['data-'.$k] = $v;
}
$arguments['class'] .= ' embed-youtube embed-responsive embed-responsive-16by9';
$iframe = strpos($content, 'iframe');
if($iframe >= 0) {
$content = substr($content, 0, $iframe+6).' class="embed-responsive-item" '.substr($content, $iframe +7);
}
return HTML::createTag('div', $arguments, $content);
}
}

View File

@ -3,7 +3,6 @@
namespace Site\Extensions;
use SilverStripe\ORM\DataExtension;
class EmbeddedObjectExtension extends DataExtension
@ -25,7 +24,15 @@ class EmbeddedObjectExtension extends DataExtension
public function setEmbedParams($params = [])
{
// YouTube params
if(stripos($this->owner->EmbedHTML, 'https://www.youtube.com/embed/') > 0) {
if (stripos($this->owner->EmbedHTML, 'https://www.youtube.com/embed/') > 0) {
$url = $this->owner->getField('SourceURL');
preg_match(
'/^(?:http(?:s)?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com\/(?:(?:watch)?\?(?:.*&)?v(?:i)?=|(?:embed|v|vi|user)\/))([^\?&"\'>]+)/',
$url,
$matches
);
$videoID = $matches[1];
$params = array_merge([
'feature=oembed',
'wmode=transparent',
@ -48,7 +55,7 @@ class EmbeddedObjectExtension extends DataExtension
$this->owner->EmbedHTML = preg_replace(
'/src="([A-z0-9:\/\.]+)\??(.*?)"/',
'src="${1}?' . implode('&', $params) . '"',
'src="https://www.youtube.com/embed/'.$videoID.'?' . implode('&', $params) . '"',
$this->owner->EmbedHTML
);
}
@ -59,4 +66,4 @@ class EmbeddedObjectExtension extends DataExtension
parent::onBeforeWrite();
$this->setEmbedParams();
}
}
}

View File

@ -13,6 +13,7 @@ use SilverStripe\Forms\CompositeField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
use SilverStripe\Forms\GridField\GridFieldDataColumns;
use SilverStripe\Forms\NumericField;
use SilverStripe\ORM\DataExtension;
use Site\Models\MapPin;
@ -42,19 +43,28 @@ class MapExtension extends DataExtension
'Locations',
'Locations',
$this->owner->Locations(),
GridFieldConfig_RelationEditor::create(100)
$cfg = GridFieldConfig_RelationEditor::create(100)
)
]);
$cfg->getComponentByType(GridFieldDataColumns::class)->setFieldFormatting([
'ShowAtMap' => static function ($v, $obj) {
return $v ? 'YES' : 'NO';
}
]);
$fields->findOrMakeTab('Root.MapPins')->setTitle('Locations');
}
public function getGeoJSON(): string
{
$locs = $this->owner->Locations()->filter('ShowAtMap', true);
$pins = [];
foreach ($this->owner->Locations() as $off) {
foreach ($locs as $off) {
$pins[] = $off->getGeo();
}
return json_encode([
'type' => 'MarkerCollection',
'features' => $pins

View File

@ -49,7 +49,9 @@ class SiteConfigExtension extends DataExtension
'Navigation',
'Navigation',
SiteTree::class
),
)->setDisableFunction(static function ($el) {
return $el->getField('ParentID') !== 0;
}),
TextareaField::create('Description', 'Website Description'),
TextareaField::create('ExtraCode', 'Extra site-wide HTML code'),
DropdownField::create(

View File

@ -24,6 +24,7 @@ class MapPin extends DataObject
private static $db = [
'Title' => 'Varchar(255)',
'ShowAtMap' => 'Boolean(1)',
];
private static $has_one = [
@ -43,6 +44,12 @@ class MapPin extends DataObject
private static $default_sort = 'Title ASC, ID DESC';
private static $summary_fields = [
'Title',
'Address',
'ShowAtMap',
];
public function getCMSFields()
{
$fields = parent::getCMSFields();
@ -63,6 +70,7 @@ class MapPin extends DataObject
$fields->removeByName(['Map', 'LatLngOverride', 'Lng','Lat']);
$fields->addFieldsToTab('Root.Main', [
CheckboxField::create('ShowAtMap', 'Show at the map?'),
CheckboxField::create('LatLngOverride', 'Override Latitude and Longitude?')
->setDescription('Check this box and save to be able to edit the latitude and longitude manually.'),
MapboxField::create('Map', 'Choose a location', 'Lat', 'Lng'),

View File

@ -50,7 +50,7 @@ class PageController extends ContentController
public function ElementalArea()
{
if($this->CurrentElement() || $this->getAction() !== 'index') {
if ($this->CurrentElement() || $this->getAction() !== 'index') {
return false;
}
@ -61,7 +61,7 @@ class PageController extends ContentController
{
$controller_curr = Controller::curr();
if(is_a($controller_curr, ElementFormController::class)) {
if (is_a($controller_curr, ElementFormController::class)) {
return $controller_curr;
}
@ -104,7 +104,7 @@ class PageController extends ContentController
public function SearchResults()
{
$term = $this->search_term;
if(!$term) {
if (!$term) {
return false;
}
@ -126,7 +126,7 @@ class PageController extends ContentController
foreach ($elements as $element) {
$page = Page::get()->filter('ElementalAreaID', $element->getField('ParentID'))->first();
if(!$page) {
if (!$page) {
continue;
}
@ -177,4 +177,9 @@ class PageController extends ContentController
{
return DBDatetime::now();
}
public function isDev()
{
return Director::isDev();
}
}

View File

@ -19,7 +19,9 @@ class DeferredRequirements implements TemplateGlobalProvider
private static $static_domain;
private static $version;
private static $nojquery = false;
private static $jquery_version = '3.4.1';
private static $nofontawesome = false;
private static $fontawesome_version = '5.10.2';
private static $custom_requirements = [];
/**
@ -60,12 +62,19 @@ class DeferredRequirements implements TemplateGlobalProvider
// Main libs
if (!$config['nojquery']) {
self::loadJS('//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js');
self::loadJS(
'//ajax.googleapis.com/ajax/libs/jquery/'
.$config['jquery_version'].'/jquery.min.js'
);
}
// App libs
if (!$config['nofontawesome']) {
self::loadCSS('//use.fontawesome.com/releases/v5.4.0/css/all.css');
self::loadCSS(
'//use.fontawesome.com/releases/v'
.$config['fontawesome_version'].'/css/all.css'
);
}
self::loadCSS($mainTheme.'.css');
self::loadJS($mainTheme.'.js');
@ -176,17 +185,16 @@ class DeferredRequirements implements TemplateGlobalProvider
return $url;
}
$version = $config['version'];
$version = $version
? strpos($url, '?') // inner URL
? '&'.$version // add param
: '?'.$version // new param
: ''; // no version defined
$path = WebpackTemplateProvider::toPublicPath($url);
$absolutePath = Director::getAbsFile($path);
$hash = sha1_file($absolutePath);
$version = $config['version'] ? '&v='.$config['version'] : '';
//$static_domain = $config['static_domain'];
//$static_domain = $static_domain ?: '';
return WebpackTemplateProvider::toPublicPath($url.$version);
return Controller::join_links(WebpackTemplateProvider::toPublicPath($url), '?m='.$hash.$version);
}
public static function config(): array

View File

@ -1,36 +1,28 @@
<?php
/** @noinspection PhpUnusedPrivateFieldInspection */
/**
* Directs assets requests to Webpack server or to static files
*/
/** @noinspection PhpUnusedPrivateFieldInspection */
namespace Site\Templates;
use SilverStripe\Core\Manifest\ModuleManifest;
use SilverStripe\View\SSViewer;
use SilverStripe\Control\Controller;
use SilverStripe\View\TemplateGlobalProvider;
use SilverStripe\View\Requirements;
use SilverStripe\Control\Director;
use SilverStripe\Control\Controller;
use SilverStripe\Core\Config\Config;
use SilverStripe\Control\Director;
use SilverStripe\Core\Path;
class WebpackTemplateProvider implements TemplateGlobalProvider
class DeferredRequirements implements TemplateGlobalProvider
{
/**
* @var int port number
*/
private static $port = 3000;
/**
* @var string host name
*/
private static $hostname = 'localhost';
/**
* @var string assets static files directory
*/
private static $dist = 'client/dist';
private static $css = [];
private static $js = [];
private static $deferred = false;
private static $static_domain;
private static $version;
private static $nojquery = false;
private static $jquery_version = '3.4.1';
private static $nofontawesome = false;
private static $fontawesome_version = '5.11.0';
private static $custom_requirements = [];
/**
* @return array
@ -38,97 +30,172 @@ class WebpackTemplateProvider implements TemplateGlobalProvider
public static function get_template_global_variables(): array
{
return [
'WebpackDevServer' => 'isActive',
'WebpackCSS' => 'loadCSS',
'WebpackJS' => 'loadJS',
'ResourcesURL' => 'resourcesURL',
'ProjectName' => 'themeName',
'AutoRequirements' => 'Auto',
'DeferedCSS' => 'loadCSS',
'DeferedJS' => 'loadJS',
'WebpackActive' => '_webpackActive',
];
}
/**
* Load CSS file
* @param $path
*/
public static function loadCSS($path): void
public static function Auto($class = false): string
{
if (self::isActive()) {
return;
$config = Config::inst()->get(self::class);
$projectName = WebpackTemplateProvider::projectName();
$mainTheme = WebpackTemplateProvider::mainTheme();
$mainTheme = $mainTheme ?: $projectName;
$dir = Path::join(
Director::publicFolder(),
RESOURCES_DIR,
$projectName,
'client',
'dist'
);
$cssPath = Path::join($dir, 'css');
$jsPath = Path::join($dir, 'js');
// Initialization
Requirements::block(THIRDPARTY_DIR.'/jquery/jquery.js');
/*if (defined('FONT_AWESOME_DIR')) {
Requirements::block(FONT_AWESOME_DIR.'/css/lib/font-awesome.min.css');
}*/
Requirements::set_force_js_to_bottom(true);
// Main libs
if (!$config['nojquery']) {
self::loadJS(
'//ajax.googleapis.com/ajax/libs/jquery/'
.$config['jquery_version'].'/jquery.min.js'
);
}
// App libs
if (!$config['nofontawesome']) {
self::loadCSS(
'//use.fontawesome.com/releases/v'
.$config['fontawesome_version'].'/css/all.css'
);
}
Requirements::css(self::_getPath($path));
self::loadCSS($mainTheme.'.css');
self::loadJS($mainTheme.'.js');
// Custom controller requirements
$curr_class = $class ?: get_class(Controller::curr());
if (isset($config['custom_requirements'][$curr_class])) {
foreach ($config['custom_requirements'][$curr_class] as $file) {
if (strpos($file, '.css')) {
self::loadCSS($file);
}
if (strpos($file, '.js')) {
self::loadJS($file);
}
}
}
$curr_class = str_replace('\\', '.', $curr_class);
// Controller requirements
$themePath = Path::join($cssPath, $mainTheme.'_'.$curr_class . '.css');
$projectPath = Path::join($cssPath, $projectName.'_'.$curr_class . '.css');
if ($mainTheme && file_exists($themePath)) {
self::loadCSS($mainTheme.'_'.$curr_class . '.css');
} elseif (file_exists($projectPath)) {
self::loadCSS($projectName.'_'.$curr_class . '.css');
}
$themePath = Path::join($jsPath, $mainTheme.'_'.$curr_class . '.js');
$projectPath = Path::join($jsPath, $projectName.'_'.$curr_class . '.js');
if ($mainTheme && file_exists($themePath)) {
self::loadJS($mainTheme.'_'.$curr_class . '.js');
} elseif (file_exists($projectPath)) {
self::loadJS($projectName.'_'.$curr_class . '.js');
}
return self::forTemplate();
}
/**
* Load JS file
* @param $path
*/
public static function loadJS($path): void
public static function loadCSS($css): void
{
Requirements::javascript(self::_getPath($path), ['type' => '']);
$external = (mb_strpos($css, '//') === 0 || mb_strpos($css, 'http') === 0);
if ($external || (self::getDeferred() && !self::_webpackActive())) {
self::$css[] = $css;
} else {
WebpackTemplateProvider::loadCSS($css);
}
}
public static function projectName(): string
public static function loadJS($js): void
{
return Config::inst()->get(ModuleManifest::class, 'project');
/*$external = (mb_substr($js, 0, 2) === '//' || mb_substr($js, 0, 4) === 'http');
if ($external || (self::getDeferred() && !self::_webpackActive())) {*/
// webpack supposed to load external JS
if (self::getDeferred() && !self::_webpackActive()) {
self::$js[] = $js;
} else {
WebpackTemplateProvider::loadJS($js);
}
}
public static function mainTheme()
protected static function _webpackActive(): bool
{
$themes = Config::inst()->get(SSViewer::class, 'themes');
return is_array($themes) && $themes[0] !== '$public' && $themes[0] !== '$default' ? $themes[0] : false;
return WebpackTemplateProvider::isActive();
}
public static function resourcesURL($link = null): string
public static function setDeferred($bool): void
{
return Controller::join_links(Director::baseURL(), '/resources/'.self::projectName().'/client/dist/img/', $link);
Config::inst()->set(__CLASS__, 'deferred', $bool);
}
/**
* Checks if dev mode is enabled and if webpack server is online
* @return bool
*/
public static function isActive(): bool
public static function getDeferred(): bool
{
$cfg = self::config();
return Director::isDev() && @fsockopen(
$cfg['HOSTNAME'],
$cfg['PORT']
);
return self::config()['deferred'];
}
protected static function _getPath($path): string
public static function forTemplate(): string
{
return self::isActive() && strpos($path, '//') === false ?
self::_toDevServerPath($path) :
self::toPublicPath($path);
$result = '';
self::$css = array_unique(self::$css);
foreach (self::$css as $css) {
$result .= '<i class="defer-cs" data-load="' . self::get_url($css) . '"></i>';
}
self::$js = array_unique(self::$js);
foreach (self::$js as $js) {
$result .= '<i class="defer-sc" data-load="' . self::get_url($js) . '"></i>';
}
$result .=
'<script>function lsc(a,b){var c=document.createElement("script");c.readyState'
.'?c.onreadystatechange=function(){"loaded"!=c.readyState&&"complete"!=c.readyState||(c.onreadystatechange=null,b())}'
.':c.onload=function(){b()},c.src=a,document.getElementsByTagName("body")[0].appendChild(c)}'
.'function lscd(a){a<s.length-1&&(a++,lsc(s.item(a).getAttribute("data-load"),function(){lscd(a)}))}'
.'for(var s=document.getElementsByClassName("defer-cs"),i=0;i<s.length;i++){var b=document.createElement("link");b.rel="stylesheet",'
.'b.type="text/css",b.href=s.item(i).getAttribute("data-load"),b.media="all";var c=document.getElementsByTagName("body")[0];'
.'c.appendChild(b)}var s=document.getElementsByClassName("defer-sc"),i=0;if(s.item(i)!==null)lsc(s.item(i).getAttribute("data-load"),function(){lscd(i)});'
.'</script>';
return $result;
}
protected static function _toDevServerPath($path): string
private static function get_url($url): string
{
$cfg = self::config();
return sprintf(
'%s%s:%s/%s',
Director::protocol(),
$cfg['HOSTNAME'],
$cfg['PORT'],
basename($path)
);
}
$config = self::config();
public static function toPublicPath($path): string
{
$cfg = self::config();
return strpos($path, '//') === false ?
Controller::join_links(
RESOURCES_DIR,
self::projectName(),
$cfg['DIST'],
(strpos($path, '.css') ? 'css' : 'js'),
$path
)
: $path;
// external URL
if (strpos($url, '//') !== false) {
return $url;
}
$path = WebpackTemplateProvider::toPublicPath($url);
$absolutePath = Director::getAbsFile($path);
$hash = sha1_file($absolutePath);
$version = $config['version'] ? '&v='.$config['version'] : '';
//$static_domain = $config['static_domain'];
//$static_domain = $static_domain ?: '';
return Controller::join_links(WebpackTemplateProvider::toPublicPath($url), '?m='.$hash.$version);
}
public static function config(): array

View File

@ -0,0 +1,37 @@
<?php
namespace Site\Widgets;
use Sheadawson\Linkable\Forms\LinkField;
use Sheadawson\Linkable\Models\Link;
use SilverStripe\AssetAdmin\Forms\UploadField;
use SilverStripe\Assets\Image;
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
use SilverStripe\Widgets\Model\Widget;
if (!class_exists(Widget::class)) {
return;
}
class ContentWidget extends Widget
{
private static $title = 'Content';
private static $cmsTitle = 'Content';
private static $description = 'Shows text content.';
private static $icon = '<i class="icon font-icon-block-content"></i>';
private static $table_name = 'ContentWidget';
private static $db = [
'Text' => 'HTMLText',
];
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->push(HTMLEditorField::create('Text'));
return $fields;
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace Site\Widgets;
use DNADesign\Elemental\Models\BaseElement;
use DNADesign\ElementalList\Model\ElementList;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\TreeDropdownField;
use SilverStripe\Widgets\Model\Widget;
if (!class_exists(Widget::class)) {
return;
}
class ElementWidget extends Widget
{
private static $title = 'Virtual Element';
private static $cmsTitle = 'Virtual Element';
private static $description = 'Adds existing element to side bar';
private static $icon = '<i class="icon font-icon-block-banner"></i>';
private static $table_name = 'ElementWidget';
private static $has_one = [
'Element' => BaseElement::class,
];
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->push(
DropdownField::create(
'ElementID',
'Displayed Element',
BaseElement::get()
->filter(['AvailableGlobally' => true])
->exclude(['ClassName' => ElementList::class])
)
/*TreeDropdownField::create(
'ElementID',
'Displayed Element',
SiteTree::class
)->setFilterFunction(static function($el){
return (bool) $el->getField('ElementalArea')->Elements()->count();
})*/
);
return $fields;
}
public function SimpleClassName()
{
$el = $this->getField('Element');
var_dump($el);
die();
return $el->getSimpleClassName();
}
}

View File

@ -3,10 +3,6 @@
namespace Site\Widgets;
use Sheadawson\Linkable\Forms\LinkField;
use Sheadawson\Linkable\Models\Link;
use SilverStripe\AssetAdmin\Forms\UploadField;
use SilverStripe\Assets\Image;
use SilverStripe\Widgets\Model\Widget;
if (!class_exists(Widget::class)) {

View File

@ -3,7 +3,6 @@
namespace Site\Widgets;
use DNADesign\Elemental\Controllers\ElementalAreaController;
use DNADesign\Elemental\Forms\ElementalAreaConfig;
use DNADesign\Elemental\Forms\ElementalAreaField;
@ -67,10 +66,10 @@ class WidgetAreaField extends GridField
'Title' => 'Title',
'Enabled' => 'Enabled',
])->setFieldFormatting([
'Icon' => static function($v, Widget $item) {
'Icon' => static function ($v, Widget $item) {
return '<span style="font-size:2rem">'.$item::config()->get('icon').'</span>';
},
'Enabled' => static function($v, Widget $item) {
'Enabled' => static function ($v, Widget $item) {
return $item->getField('Enabled') ? 'Yes' : 'No';
},
]);
@ -141,7 +140,7 @@ class WidgetAreaField extends GridField
// Extract the ID
$elementId = (int) substr($form, $idPrefixLength);
/** @var BaseElement $element */
// @var BaseElement $element
$element = $this->getArea()->Widgets()->byID($elementId);
if (!$element) {

View File

@ -3,11 +3,11 @@
namespace Site\Widgets;
use DNADesign\Elemental\Forms\ElementalAreaField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Widgets\Forms\WidgetAreaEditor;
use SilverStripe\Widgets\Model\Widget;
use SilverStripe\Widgets\Model\WidgetArea;
class WidgetPageExtension extends \SilverStripe\Widgets\Extensions\WidgetPageExtension
{
@ -34,4 +34,16 @@ class WidgetPageExtension extends \SilverStripe\Widgets\Extensions\WidgetPageExt
$available
));
}
public function onBeforeWrite()
{
parent::onBeforeWrite();
if (!$this->owner->getField('SideBarID')) {
$area = WidgetArea::create();
$area->write();
$this->owner->setField('SideBarID', $area->ID);
}
}
}

View File

@ -8,19 +8,17 @@
<% if $ShowTitle || $Content || $ImageLink %>
<div class="image-element__caption img-content">
<div class="container">
<% if $ShowTitle %><h3 class="image-element__title title">$Title</h3><% end_if %>
<% if $ShowTitle %><h3 class="image-element__title title">$Title</h3><% end_if %>
<% if $Content %>
<div class="image-element__content typography">$Content</div>
<% end_if %>
<% if $Content %>
<div class="image-element__content typography">$Content</div>
<% end_if %>
<% if $ImageLink %>
<a href="$ImageLink.URL" class="image-element__btn btn btn-default">
$ImageLink.Title
<i class="fas fa-caret-right"></i>
</a>
<% end_if %>
</div>
<% if $ImageLink %>
<a href="$ImageLink.URL" class="image-element__btn btn btn-default">
$ImageLink.Title
<i class="fas fa-caret-right"></i>
</a>
<% end_if %>
</div>
<% end_if %>

View File

@ -1,23 +1,49 @@
<div class="page-content">
<h1 class="element page-header $DefaultContainer<% if $ElementalArea.Elements.Count < 1 %> no-elements<% end_if %>">
$Title
</h1>
<div class="page-content page-content-main">
<div class="
element
page-header-element
<% if $ElementalArea.Elements.Count < 1 %>
d-block no-elements
<% else_if not $ElementalArea.Elements.First.ShowTitle && $ElementalArea.Elements.First.ClassName != Site\Elements\SliderElement %>
d-block
<% end_if %>
">
<div class="$DefaultContainer">
<h1 class="page-header">
$Title
</h1>
</div>
</div>
<% if $CurrentElement %>
$CurrentElement
<div class="current-element">
$CurrentElement
</div>
<% else %>
$ElementalArea
<div class="elemental-area">
$ElementalArea
</div>
<% end_if %>
<% if $Form %>
<div class="element $DefaultContainer">
$Form
<div class="page-form-element element">
<div class="element_container">
<div class="$DefaultContainer">
$Form
</div>
</div>
</div>
<% end_if %>
<% if $ExtraCode %>
<div class="element $DefaultContainer extra-code extra-code-page">
$ExtraCode
<div class="page-extra-code">
<div class="element">
<div class="element_container">
<div class="$DefaultContainer">
$ExtraCode
</div>
</div>
</div>
</div>
<% end_if %>
</div>

View File

@ -13,5 +13,10 @@
</div>
</div>
<% if $isDev || $WebpackActive %>
<div id="DevOriginal"></div>
<% end_if %>
<%-- Site Wide Alert Message --%>
<% include SiteWideMessage %>

View File

@ -1,34 +1,36 @@
<% with $SiteConfig %>
<div class="wrapper">
<div class="container">
<div class="row">
<div class="col-sm-6">
<a href="/" class="logo2">
<img src="$ResourcesURL('logo2.png')" alt="{$Title}" />
</a>
<div class="element">
<div class="footer__container $Top.DefaultContainer">
<div class="row">
<div class="col-sm-6">
<a href="/" class="logo2">
<img src="$ResourcesURL('logo2.png')" alt="{$Title}" />
</a>
<div class="field">
<div class="fn">$Title</div>
<address>
$Address<br/>
$Suburb, $State $ZipCode
</address>
</div>
<div class="field">
T: $PhoneNumber
</div>
<% if $PublicEmail %>
<div class="field">
E: $PublicEmail
<div class="fn">$Title</div>
<address>
$Address<br/>
$Suburb, $State $ZipCode
</address>
</div>
<% end_if %>
<% include Objects\SocialLinks %>
</div>
<div class="col-sm-6 text-right">
<div class="field">
T: $PhoneNumber
</div>
<% if $PublicEmail %>
<div class="field">
E: $PublicEmail
</div>
<% end_if %>
<% include Objects\SocialLinks %>
</div>
<div class="col-sm-6 text-right">
</div>
</div>
</div>
</div>

View File

@ -1,35 +1,29 @@
<div class="element">
<div class="row">
<div class="col-sm-4">
<a id="Logo" href="/"><img src="{$ResourcesURL('logo.png')}" alt="{$SiteConfig.Title}" /></a>
</div>
<div class="col-sm-8 text-right">
<div class="btn-group btn-group-lg" role="group">
<a href="#" class="btn btn-success">Contact</a>
<a href="#" class="btn btn-warning">Donate</a>
<a href="#" class="btn btn-secondary">
<i class="fas fa-search"></i>
<span class="sr-only">Search</span>
</a>
<div class="element__container $DefaultContainer">
<div class="row">
<div class="col-sm-4">
<a id="Logo" href="/"><img src="{$ResourcesURL('logo.png')}" alt="{$SiteConfig.Title}" /></a>
</div>
<% with $SiteConfig %>
<% if $PhoneNumber %>
<span class="phone-number">
$PhoneNumber
</span>
<div class="col-sm-8 text-right">
<% with $SiteConfig %>
<% if $PhoneNumber %>
<span class="phone-number">
$PhoneNumber
</span>
<% end_if %>
<% if $PublicEmail %>
<span class="public-email">
$PublicEmail
</span>
<% end_if %>
<% end_with %>
<%-- if $SearchForm %>
<div id="SearchFormContainer">$SearchForm</div>
<% end_if --%>
<% if $SiteConfig.Navigation %>
<% include Navigation Navigation=$SiteConfig.Navigation, NavID="Navigation" %>
<% end_if %>
<% if $PublicEmail %>
<span class="public-email">
$PublicEmail
</span>
<% end_if %>
<% end_with %>
<%-- if $SearchForm %>
<div id="SearchFormContainer">$SearchForm</div>
<% end_if --%>
<% if $SiteConfig.Navigation %>
<% include Navigation Navigation=$SiteConfig.Navigation, NavID="Navigation" %>
<% end_if %>
</div>
</div>
</div>
</div>

View File

@ -32,6 +32,8 @@ $MetaTags
<link rel="author" type="text/plain" href="{$AbsoluteBaseURL}humans.txt" />
<link rel="sitemap" type="application/xml" title="Sitemap" href="{$AbsoluteBaseURL}sitemap.xml" />
<link rel="preconnect" href="https://use.fontawesome.com/" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preconnect" href="https://maps.google.com" />
<link rel="preconnect" href="https://ajax.googleapis.com" />

View File

@ -4,7 +4,8 @@
class="nav-link dropdown-toggle"
id="NavItem{$ID}"
role="button"
data-toggle="dropdown"
data-toggle="hover"
data-allow-click="true"
aria-haspopup="true"
aria-expanded="false"
href="{$Link}"

View File

@ -1,30 +1,30 @@
<div class="sc-links">
<% if $Facebook %>
<a href="$Facebook.URL" title="Facebook" target="_blank">
<a href="$Facebook.LinkURL" title="Facebook" target="_blank">
<i class="fab fa-facebook"></i>
<i class="sr-only">Facebook</i>
</a>
<% end_if %>
<% if $LinkedIn %>
<a href="$LinkedIn.URL" title="LinkedIn" target="_blank">
<a href="$LinkedIn.LinkURL" title="LinkedIn" target="_blank">
<i class="fab fa-linkedin"></i>
<i class="sr-only">LinkedIn</i>
</a>
<% end_if %>
<% if $GooglePlus %>
<a href="$GooglePlus.URL" title="Google+" target="_blank">
<a href="$GooglePlus.LinkURL" title="Google+" target="_blank">
<i class="fab fa-google-plus-g"></i>
<i class="sr-only">Google+</i>
</a>
<% end_if %>
<% if $Instagram %>
<a href="$Instagram.URL" title="Instagram" target="_blank">
<a href="$Instagram.LinkURL" title="Instagram" target="_blank">
<i class="fab fa-instagram"></i>
<i class="sr-only">Instagram</i>
</a>
<% end_if %>
<% if $Twitter %>
<a href="$Twitter.URL" title="Twitter" target="_blank">
<a href="$Twitter.LinkURL" title="Twitter" target="_blank">
<i class="fab fa-twitter"></i>
<i class="sr-only">Twitter</i>
</a>

View File

@ -10,31 +10,49 @@
<div class="wrapper">
<header id="Header">
<div class="$DefaultContainer">
<% include Header %>
</div>
<% include Header %>
</header>
<main id="MainContent" data-ajax-region="LayoutAjax">
<% if $ParentID %>
$Breadcrumbs
<div id="PageBreadcumbs">
$Breadcrumbs
</div>
<% else_if $ClassName != 'Site\Pages\HomePage' %>
<div id="PageBreadcumbs">
<nav class="breadcrumbs $DefaultContainer" aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="/">Home</a>
</li>
<li class="breadcrumb-item current active" aria-current="page">
<a href="$Link" class="breadcrumb-2">
$MenuTitle
</a>
</li>
</ol>
</nav>
</div>
<% end_if %>
<% if $SideBarView && $SideBarView.Widgets.Count %>
<div class="$DefaultContainer">
<div class="row">
<div class="col-md-8">
$Layout
</div>
<div class="col-md-4">
<div class="sidebar page-content">
$SideBarView
<div class="content-holder">
<div class="row">
<div class="col-md-9">
$Layout
</div>
<div class="col-md-3">
<div class="page-content-sidebar page-content">
$SideBarView
</div>
</div>
</div>
</div>
</div>
<% else %>
<div class="content-holder">
$Layout
</div>
<% end_if %>
</main>
</div>

View File

@ -4,13 +4,13 @@
<% if $Address2 %>
<div class="addr2">{$Address2}</div>
<% end_if %>
<% if $City || $PostalCode %>
<% if $City || $Suburb || $PostalCode || $Postcode %>
<div class="city">
{$City}, {$State} {$PostalCode}
{$City}{$Suburb}, {$State} {$PostalCode}{$Postcode}
</div>
<% end_if %>
<% if $Country %>
<div class="d-none">{$Country}</div>
<div class="country d-none">{$Country}</div>
<% end_if %>
<% if $PhoneNumber %>
<% with $PhoneNumber %>

View File

@ -0,0 +1,3 @@
<% if $Text %>
<div class="typography">$Text</div>
<% end_if %>

View File

@ -0,0 +1,9 @@
<% if $Element %>
<% with $Element %>
<div class="element $SimpleClassName.LowerCase<% if $StyleVariant %> $StyleVariant<% end_if %><% if $ExtraClass %> $ExtraClass<% end_if %>" id="$Anchor">
<div class="element-container<% if $ContainerClass %> $ContainerClass<% end_if %>">
$forTemplate
</div>
</div>
<% end_with %>
<% end_if %>

View File

@ -1,6 +1,15 @@
<% if $Submenu %>
<nav>
<ul class="nav flex-column">
<% with $Page.Level(1) %>
<li class="nav-item-level1 nav-item {$CSSClass} $ExtraClass <% if $isCurrent || $isSection %> active<% end_if %>">
<b class="nav-link">
$MenuTitle.XML
<% if $isCurrent || $isSection %><i class="sr-only">(current)</i><% end_if %>
</b>
</li>
<% end_with %>
<% loop $Submenu %>
<% include NavItem %>
<% end_loop %>

View File

@ -69,6 +69,11 @@
"foundation-emails": "^2.2.1",
"gijgo": "^1.9.13",
"html-webpack-plugin": "^4.0.0-beta.11",
"imagemin-gifsicle": "^7.0.0",
"imagemin-jpegtran": "^6.0.0",
"imagemin-optipng": "^7.1.0",
"imagemin-svgo": "^7.0.0",
"imagemin-webpack": "^5.1.1",
"jquery": "^3.4.1",
"jquery-hammerjs": "^2.0.0",
"jquery-hoverintent": "*",

View File

@ -17,20 +17,21 @@ const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const ImageminPlugin = require('imagemin-webpack');
let plugins = [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
NODE_ENV: JSON.stringify('production'),
},
}),
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
debug: false,
}),
new ExtractTextPlugin({
filename: 'css/[name].css',
allChunks: true
allChunks: true,
}),
new FaviconsWebpackPlugin({
@ -51,8 +52,8 @@ let plugins = [
opengraph: true,
twitter: true,
yandex: true,
windows: true
}
windows: true,
},
}),
new OptimizeCssAssetsPlugin({
//assetNameRegExp: /\.optimize\.css$/g,
@ -71,38 +72,73 @@ let plugins = [
discardOverridden: true,
discardDuplicates: true,
discardComments: {
removeAll: true
removeAll: true,
},
},
canPrint: true
canPrint: true,
}),
new ImageminPlugin({
bail: false, // Ignore errors on corrupted images
cache: true,
filter: (source, sourcePath) => {
if (source.byteLength < 512000) {
return false;
}
return true;
},
imageminOptions: {
plugins: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['optipng', { optimizationLevel: 5 }],
[
'svgo',
{
plugins: [
{
removeViewBox: false,
},
],
},
],
],
},
}),
];
// add themes favicons
commonVariables.themes.forEach((theme) => {
commonVariables.themes.forEach(theme => {
const faviconPath = path.join(__dirname, theme, conf.SRC, 'favicon.png');
if (filesystem.existsSync(faviconPath)) {
plugins.push(new FaviconsWebpackPlugin({
title: 'Webpack App',
logo: faviconPath,
prefix: '/' + theme + '-icons/',
emitStats: false,
persistentCache: true,
inject: false,
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
}
}));
plugins.push(
new FaviconsWebpackPlugin({
title: 'Webpack App',
logo: faviconPath,
prefix: '/' + theme + '-icons/',
emitStats: false,
persistentCache: true,
inject: false,
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,
},
}),
);
}
});
@ -110,9 +146,10 @@ module.exports = merge(common, {
mode: 'production',
optimization: {
namedModules: true, // NamedModulesPlugin()
splitChunks: { // CommonsChunkPlugin()
splitChunks: {
// CommonsChunkPlugin()
name: 'vendor',
minChunks: 2
minChunks: 2,
},
noEmitOnErrors: true, // NoEmitOnErrorsPlugin
concatenateModules: true, //ModuleConcatenationPlugin
@ -129,7 +166,7 @@ module.exports = merge(common, {
},
},
}),
]
],
},
devtool: '',
@ -141,66 +178,78 @@ module.exports = merge(common, {
},
module: {
rules: [{
test: /\.s?css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [{
loader: 'css-loader',
options: {
sourceMap: false
}
}, {
loader: 'postcss-loader',
options: {
sourceMap: false,
plugins: [
autoprefixer()
]
}
}, {
loader: 'resolve-url-loader'
}, {
loader: 'sass-loader',
options: {
sourceMap: false
}
}, ]
})
}, {
test: /fontawesome([^.]+).(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
use: [{
rules: [
{
test: /\.s?css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
sourceMap: false,
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: false,
plugins: [autoprefixer()],
},
},
{
loader: 'resolve-url-loader',
},
{
loader: 'sass-loader',
options: {
sourceMap: false,
},
},
],
}),
},
{
test: /fontawesome([^.]+).(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/',
publicPath: '../fonts/',
},
},
],
},
{
test: /\.(ttf|otf|eot|svg|woff(2)?)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/',
publicPath: '../fonts/',
},
},
],
},
{
test: /\.(png|jpg|jpeg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/',
publicPath: '../fonts/'
}
}]
}, {
test: /\.(ttf|otf|eot|svg|woff(2)?)$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/',
publicPath: '../fonts/'
}
}]
}, {
test: /\.(png|jpg|jpeg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'img/',
publicPath: '../img/'
outputPath: 'img/',
publicPath: '../img/',
/*,
name(file) {
//return 'public/[path][name].[ext]';
return '[hash].[ext]';
},*/
},
},
}, ]
],
},
plugins: plugins,