Frontent dev env (#247)

* Added frontend development files/environment

* More items-categories related removals

* Improvements in pages templates (inc. static pages)

* Improvements in video player

* Added empty home page message + cta

* Updates in media, playlist and management pages

* Improvements in material icons font loading

* Replaced media & playlists links in frontend dev-env

* frontend package version update

* chnaged frontend dev url port

* static files update

* Changed default position of theme switcher

* enabled frontend docker container
This commit is contained in:
Yiannis Stergiou 2021-07-11 18:01:34 +03:00 committed by GitHub
parent 060bb45725
commit aa6520daac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
555 changed files with 201927 additions and 66002 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
node_modules
npm-debug.log

View File

@ -1,18 +1,18 @@
version: "3"
services:
# frontend:
# image: node:14
# volumes:
# - ${PWD}/frontend:/home/mediacms.io/mediacms/frontend/
# working_dir: /home/mediacms.io/mediacms/frontend/
# command: bash -c "npm install && npm run start"
# env_file:
# - ${PWD}/frontend/.env
# ports:
# - "8097:8097"
# depends_on:
# - web
frontend:
image: node:14
volumes:
- ${PWD}/frontend:/home/mediacms.io/mediacms/frontend/
working_dir: /home/mediacms.io/mediacms/frontend/
command: bash -c "npm install && npm run start"
env_file:
- ${PWD}/frontend/.env
ports:
- "8088:8088"
depends_on:
- web
web:
build:
context: .
@ -60,7 +60,7 @@ services:
image: "redis:alpine"
restart: always
healthcheck:
test: ["CMD", "redis-cli","ping"]
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
retries: 3

12
frontend/.babelrc Executable file
View File

@ -0,0 +1,12 @@
{
"presets": [
"@babel/react", ["@babel/env", {
"modules": false,
"useBuiltIns": "usage",
"corejs": 3,
"targets": {
"browsers": ["defaults"]
}
}]
]
}

12
frontend/.env Normal file
View File

@ -0,0 +1,12 @@
MEDIACMS_ID=mediacms-frontend
MEDIACMS_TITLE=MediaCMS Frontend
MEDIACMS_URL=http://localhost
MEDIACMS_API=http://localhost/api/v1
MEDIACMS_USER_IS_ADMIN=true
MEDIACMS_USER_IS_ANONYMOUS=false
MEDIACMS_USER_USERNAME=admin
MEDIACMS_USER_NAME=Admin
MEDIACMS_USER_THUMB=http://localhost/media/userlogos/user.jpg

131
frontend/.gitignore vendored Executable file
View File

@ -0,0 +1,131 @@
.DS_Store
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
!modules/**/*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env.development
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
# dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# System files
.fuse_hidden*
# Exports
/build
/dist
# Packages dev files
# /packages/**/package-lock.json
# Other
*playground*

1
frontend/.prettierignore Normal file
View File

@ -0,0 +1 @@
config/templates/*.ejs

5
frontend/.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 120
}

3
frontend/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"editor.formatOnSave": true
}

29
frontend/README.md Normal file
View File

@ -0,0 +1,29 @@
# MediaCMS Web Client (demo)
### **Requirements**
- nodejs: version >= 14.17.0
---
### **Installation**
npm install
---
### **Development**
npm run start
Open in browser: [http://localhost:8088](http://localhost:8088)
---
### **Build**
npm run dist
Generates the folder "**_frontend/dist_**".
Copy folders and files from "**_frontend/dist/static_**" into "**_static_**".

View File

@ -0,0 +1,44 @@
module.exports = {
head: {
meta: [
{ charset: 'utf-8' },
{ content: 'ie=edge', 'http-equiv': 'x-ua-compatible' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ name: 'theme-color', content: '#fafafa' },
{ name: 'msapplication-TileColor', content: '#fafafa' },
{ name: 'msapplication-config', content: 'favicons/browserconfig.xml' },
],
links: [
/**
* Manifest file link.
*/
{ rel: 'manifest', href: 'static/favicons/site.webmanifest' },
/**
* Favicon links.
*/
{ rel: 'apple-touch-icon', sizes: '180x180', href: 'static/favicons/apple-touch-icon.png' },
{ rel: 'icon', type: 'image/png', sizes: '32x32', href: 'static/favicons/favicon-32x32.png' },
{ rel: 'icon', type: 'image/png', sizes: '16x16', href: 'static/favicons/favicon-16x16.png' },
{ rel: 'mask-icon', href: 'static/favicons/safari-pinned-tab.svg', color: '#fafafa' },
{ rel: 'shortcut icon', href: 'static/favicons/favicon.ico' },
/**
* Stylesheet links
*/
{ rel: 'preload', href: 'static/css/_extra.css', as: 'style' },
{ rel: 'stylesheet', href: 'static/css/_extra.css' },
// 'https://fonts.googleapis.com/icon?family=Material+Icons',
{ rel: 'preload', href: 'static/lib/material-icons/material-icons.css', as: 'style' },
{ rel: 'stylesheet', href: 'static/lib/material-icons/material-icons.css' },
// 'https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,500,500i,700,700i&display=swap
{ rel: 'preload', href: 'static/lib/gfonts/gfonts.css', as: 'style' },
{ rel: 'stylesheet', href: 'static/lib/gfonts/gfonts.css' },
],
scripts: [],
},
body: {
scripts: [],
snippet: '',
},
};

View File

@ -0,0 +1,31 @@
const path = require('path');
const rootDir = '..';
const srcDir = rootDir + '/src';
const configDir = srcDir + '/templates/config';
const coreConfigDir = configDir + '/core';
const installConfigDir = configDir + '/installation';
module.exports = {
src: path.resolve(__dirname, srcDir),
build: path.resolve(__dirname, rootDir),
html: require('./mediacms.config.html.js'),
pages: require('./mediacms.config.pages.js'),
window: {
MediaCMS: {
api: require(coreConfigDir + '/api.config.js'),
url: require(coreConfigDir + '/url.config.js'),
user: require(coreConfigDir + '/user.config.js'),
site: require(installConfigDir + '/site.config.js'),
pages: require(installConfigDir + '/pages.config.js'),
contents: require(installConfigDir + '/contents.config.js'),
features: require(installConfigDir + '/features.config.js'),
/*notifications: [
'Message one text',
'Message two text',
'Message three text'
],*/
},
},
postcssConfigFile: path.resolve(__dirname, './postcss.config.js'),
};

View File

@ -0,0 +1,268 @@
const templates = require('./mediacms.config.templates');
const DEV_SAMPLE_DATA = {
profileId: process.env.MEDIACMS_USER_USERNAME,
media: {
videoId: process.env.MEDIACMS_VIDEO_ID,
audioId: process.env.MEDIACMS_AUDIO_ID,
imageId: process.env.MEDIACMS_IMAGE_ID,
pdfId: process.env.MEDIACMS_PDF_ID,
},
playlistId: process.env.MEDIACMS_PLAYLIST_ID,
};
const formatPage = (page) => {
const pageContentId = 'page-' + page.id;
const filename = page.filename ? page.filename : ('home' === page.id ? 'index' : page.id) + '.html';
const render = page.renderer
? page.renderer
: page.component
? templates.renderPageContent({ page: { id: pageContentId, component: page.component } })
: undefined;
const headLinks = [
{ rel: 'preload', href: './static/lib/video-js/7.7.5/video.min.js', as: 'script' },
...(page.headLinks ? page.headLinks : []),
];
const bodyScripts = [
{ src: './static/lib/video-js/7.7.5/video.min.js' },
...(page.bodyScripts ? page.bodyScripts : []),
];
const ret = {
buildExclude: !!page.buildExclude,
title: page.title,
filename,
render,
html: {
head: {
links: headLinks,
},
body: {
scripts: bodyScripts,
snippet: page.snippet || templates.htmlBodySnippet({ id: pageContentId }),
},
},
window: { MediaCMS: page.global ? { ...page.global } : {} },
};
return ret;
};
const formatPageData = (page) => {
return formatPage({
...page,
});
};
const formatStaticPageData = (page) => {
const pageContentId = 'page-' + page.id;
return formatPage({
...page,
renderer: page.renderer
? page.renderer
: page.component
? templates.renderPageStaticContent({ page: { id: pageContentId, component: page.component } })
: undefined,
});
};
const PAGES = {
base: {
id: 'base',
title: 'Layout base',
renderer: templates.renderBase(),
},
index: { id: 'home', title: 'Home', component: 'HomePage' },
search: { id: 'search', title: 'Search results', component: 'SearchPage' },
latest: { id: 'latest', title: 'Recent uploads', component: 'LatestMediaPage' },
featured: { id: 'featured', title: 'Featured', component: 'FeaturedMediaPage' },
recommended: { id: 'recommended', title: 'Recommended', component: 'RecommendedMediaPage' },
members: { id: 'members', title: 'Members', component: 'MembersPage' },
history: { id: 'history', title: 'History', component: 'HistoryPage' },
liked: { id: 'liked', title: 'Liked media', component: 'LikedMediaPage' },
tags: { id: 'tags', title: 'Tags', component: 'TagsPage' },
categories: { id: 'categories', title: 'Categories', component: 'CategoriesPage' },
'manage-media': { id: 'manage-media', title: 'Manage media', component: 'ManageMediaPage' },
'manage-users': { id: 'manage-users', title: 'Manage users', component: 'ManageUsersPage' },
'manage-comments': { id: 'manage-comments', title: 'Manage comments', component: 'ManageCommentsPage' },
'add-media': {
id: 'add-media',
title: 'Add media',
renderer: templates.renderAddMediaPageContent(),
snippet: templates.htmlBodySnippetAddMediaPage(),
headLinks: [{ rel: 'preload', href: './static/lib/file-uploader/5.13.0/fine-uploader.min.js', as: 'script' }],
bodyScripts: [{ src: './static/lib/file-uploader/5.13.0/fine-uploader.min.js' }],
},
embed: {
id: 'embed',
title: 'Embedded player',
renderer: templates.renderEmbedPageContent({ page: { id: 'page-embed', component: 'EmbedPage' } }),
snippet: templates.htmlBodySnippetEmbedPage({ id: 'page-embed' }),
global: { mediaId: DEV_SAMPLE_DATA.media.videoId },
},
media: {
id: 'media',
title: 'Media',
component: 'MediaPage',
global: { mediaId: DEV_SAMPLE_DATA.media.videoId },
},
'media-video': {
buildExclude: true,
id: 'media-video',
title: 'Media - Video',
component: 'MediaVideoPage',
global: { mediaId: DEV_SAMPLE_DATA.media.videoId },
},
'media-audio': {
buildExclude: true,
id: 'media-audio',
title: 'Media - Audio',
component: 'MediaAudioPage',
global: { mediaId: DEV_SAMPLE_DATA.media.audioId },
},
'media-image': {
buildExclude: true,
id: 'media-image',
title: 'Media - Image',
component: 'MediaImagePage',
global: { mediaId: DEV_SAMPLE_DATA.media.imageId },
},
'media-pdf': {
buildExclude: true,
id: 'media-pdf',
title: 'Media - Pdf',
component: 'MediaPdfPage',
global: { mediaId: DEV_SAMPLE_DATA.media.pdfId },
},
playlist: {
id: 'playlist',
title: 'Playlist',
component: 'PlaylistPage',
global: { playlistId: DEV_SAMPLE_DATA.playlistId },
},
'profile-media': {
id: 'profile-media',
title: 'Profile - Media',
component: 'ProfileMediaPage',
global: { profileId: DEV_SAMPLE_DATA.profileId },
},
'profile-about': {
id: 'profile-about',
title: 'Profile - About',
component: 'ProfileAboutPage',
global: { profileId: DEV_SAMPLE_DATA.profileId },
},
'profile-playlists': {
id: 'profile-playlists',
title: 'Profile - Playlist',
component: 'ProfilePlaylistsPage',
global: { profileId: DEV_SAMPLE_DATA.profileId },
},
};
const STATIC_PAGES = {
error: {
buildExclude: true,
id: 'error',
title: 'Error',
renderer: templates.renderBase(),
snippet: templates.static.errorPage(),
},
about: {
id: 'about',
title: 'About',
renderer: templates.renderBase(),
snippet: templates.static.aboutPage(),
},
terms: {
buildExclude: true,
id: 'terms',
title: 'Terms',
renderer: templates.renderBase(),
snippet: templates.static.termsPage(),
},
};
const DEV_ONLY_STATIC_PAGES = {
'add-media-template': {
buildExclude: true,
id: 'add-media-template',
title: 'Add media - Template',
renderer: templates.renderAddMediaPageContent(),
snippet: templates.static.addMediaPageTemplate(),
headLinks: [{ rel: 'preload', href: './static/lib/file-uploader/5.13.0/fine-uploader.min.js', as: 'script' }],
bodyScripts: [{ src: './static/lib/file-uploader/5.13.0/fine-uploader.min.js' }],
},
'edit-media': {
buildExclude: true,
id: 'edit-media',
title: 'Edit media',
renderer: templates.renderBase(),
snippet: templates.static.editMediaPage(),
},
'edit-channel': {
buildExclude: true,
id: 'edit-channel',
title: 'Edit channel',
renderer: templates.renderBase(),
snippet: templates.static.editChannelPage(),
},
'edit-profile': {
buildExclude: true,
id: 'edit-profile',
title: 'Edit profile',
renderer: templates.renderBase(),
snippet: templates.static.editProfilePage(),
},
signin: {
buildExclude: true,
id: 'signin',
title: 'Sign in',
renderer: templates.renderBase(),
snippet: templates.static.signinPage(),
},
signout: {
buildExclude: true,
id: 'signout',
title: 'Sign out',
renderer: templates.renderBase(),
snippet: templates.static.signoutPage(),
},
register: {
buildExclude: true,
id: 'register',
title: 'Register',
renderer: templates.renderBase(),
snippet: templates.static.registerPage(),
},
'reset-password': {
buildExclude: true,
id: 'reset-password',
title: 'Reset password',
renderer: templates.renderBase(),
snippet: templates.static.resetPasswordPage(),
},
contact: {
buildExclude: true,
id: 'contact',
title: 'Contact us',
renderer: templates.renderBase(),
snippet: templates.static.contactPage(),
},
};
const pages = {};
for (let k in PAGES) {
pages[k] = formatPageData(PAGES[k]);
}
for (let k in STATIC_PAGES) {
pages[k] = formatStaticPageData(STATIC_PAGES[k]);
}
for (let k in DEV_ONLY_STATIC_PAGES) {
pages[k] = formatStaticPageData(DEV_ONLY_STATIC_PAGES[k]);
}
module.exports = pages;

View File

@ -0,0 +1,45 @@
const fs = require('fs');
const path = require('path');
const ejs = require('ejs');
const templatesPath = path.join(__dirname, './templates');
const staticTemplatesPath = path.join(__dirname, './templates/static');
const compileTmpl = (filename) =>
ejs.compile(fs.readFileSync(path.join(templatesPath, filename), 'utf8'), {
root: [templatesPath],
filename: path.join(templatesPath, filename),
outputFunctionName: 'echo',
});
const compileStaticTmpl = (filename) =>
ejs.compile(fs.readFileSync(path.join(staticTemplatesPath, filename), 'utf8'), {
root: [staticTemplatesPath],
filename: path.join(staticTemplatesPath, filename),
outputFunctionName: 'echo',
});
module.exports = {
htmlBodySnippet: compileTmpl('htmlBodySnippet.ejs'),
htmlBodySnippetEmbedPage: compileTmpl('htmlBodySnippetEmbedPage.ejs'),
htmlBodySnippetAddMediaPage: compileTmpl('htmlBodySnippetAddMediaPage.ejs'),
renderBase: compileTmpl('renderBase.ejs'),
renderPageContent: compileTmpl('renderPageContent.ejs'),
renderPageStaticContent: compileTmpl('renderPageStaticContent.ejs'),
renderEmbedPageContent: compileTmpl('renderEmbedPageContent.ejs'),
renderAddMediaPageContent: compileTmpl('renderAddMediaPageContent.ejs'),
static: {
errorPage: compileStaticTmpl('errorPage.html'),
aboutPage: compileStaticTmpl('aboutPage.html'),
termsPage: compileStaticTmpl('termsPage.html'),
contactPage: compileStaticTmpl('contactPage.html'),
signinPage: compileStaticTmpl('signinPage.html'),
signoutPage: compileStaticTmpl('signoutPage.html'),
registerPage: compileStaticTmpl('registerPage.html'),
resetPasswordPage: compileStaticTmpl('resetPasswordPage.html'),
editMediaPage: compileStaticTmpl('editMediaPage.html'),
editChannelPage: compileStaticTmpl('editChannelPage.html'),
editProfilePage: compileStaticTmpl('editProfilePage.html'),
addMediaPageTemplate: compileStaticTmpl('addMediaPageTemplate.html'),
},
};

View File

@ -0,0 +1,10 @@
module.exports = (ctx) => {
const ret = {
map: ctx.env === 'development' ? ctx.map : false,
plugins: {
autoprefixer: {},
},
};
return ret;
};

View File

@ -0,0 +1,6 @@
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<% if (id) { %><div id="<%= id %>"></div><% } %>
</div>
<div id="app-footer"></div>

View File

@ -0,0 +1,65 @@
<div id="add-media-page">
<div class="page-container">
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<main class="page-main">
<div class="page-main-inner">
<div class="media-uploader-wrap">
<div class="media-uploader-top-wrap">
<div class="media-uploader-top-left-wrap">
<h1>Upload media files</h1>
</div>
<div class="media-uploader-top-right-wrap"></div>
</div>
<script type="text/template" id="qq-template"> <div class="media-uploader-bottom-wrap qq-uploader-selector"> <div class="media-uploader-bottom-left-wrap"> <div class="media-drag-drop-wrap"> <div class="media-drag-drop-inner" qq-drop-area-text="Drop files here"> <div class="media-drag-drop-content"> <div class="media-drag-drop-content-inner"> <span><i class="material-icons">cloud_upload</i></span> <span>Drag and drop files</span> <span>or</span> <span class="browse-files-btn-wrap"><span class="qq-upload-button-selector">Browse your files</span></span><div class="qq-upload-drop-area-selector media-dropzone" qq-hide-dropzone><span class="qq-upload-drop-area-text-selector"></span></div> </div></div></div></div></div><div class="media-uploader-bottom-right-wrap"> <ul class="media-upload-items-list qq-upload-list-selector"> <li> <div class="media-upload-item-main"> <div class="media-upload-item-thumb"> <img class="qq-thumbnail-selector" qq-max-size="120" qq-server-scale alt=""/> <span class="media-upload-item-spinner qq-upload-spinner-selector"><i class="material-icons">autorenew</i></span> <button type="button" class="qq-upload-retry-selector retry-media-upload-item" aria-label="Retry"><i class="material-icons">refresh</i> Retry</button> </div><div class="media-upload-item-details"> <div class="media-upload-item-name"> <span class="media-upload-item-filename qq-upload-file-selector"></span> <input class="media-upload-item-filename-input qq-edit-filename-selector" tab-index="0" type="text"/> </div><div class="media-upload-item-details-bottom"> <div class="media-upload-item-progress-bar-container qq-progress-bar-container-selector"> <div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="media-upload-item-progress-bar qq-progress-bar-selector"></div></div><span class="media-upload-item-upload-size qq-upload-size-selector"></span> <span role="status" class="media-upload-item-status-text qq-upload-status-text-selector"></span> </div><div class="media-upload-item-top-actions"> <span class="filename-edit qq-edit-filename-icon-selector" aria-label="Edit filename">Edit filename <i class="material-icons">create</i></span> <button type="button" class="delete-media-upload-item qq-upload-delete-selector" aria-label="Delete">Delete <i class="material-icons">delete</i></button> <button type="button" class="cancel-media-upload-item qq-upload-cancel-selector" aria-label="Cancel">Cancel <i class="material-icons">cancel</i></button> </div><div class="media-upload-item-bottom-actions"> <button type="button" class="continue-media-upload-item qq-upload-continue-selector" aria-label="Continue"><i class="material-icons">play_circle_outline</i> Continue</button> <button type="button" class="pause-media-upload-item qq-upload-pause-selector" aria-label="Pause"><i class="material-icons">pause_circle_outline</i> Pause</button> </div></div></div></li></ul> <dialog class="qq-alert-dialog-selector"> <div class="qq-dialog-message-selector"></div><div class="qq-dialog-buttons"> <button type="button" class="qq-cancel-button-selector">Close</button> </div></dialog> <dialog class="qq-confirm-dialog-selector"> <div class="qq-dialog-message-selector"></div><div class="qq-dialog-buttons"> <button type="button" class="qq-cancel-button-selector">No</button> <button type="button" class="qq-ok-button-selector">Yes</button> </div></dialog> <dialog class="qq-prompt-dialog-selector"> <div class="qq-dialog-message-selector"></div><input type="text"> <div class="qq-dialog-buttons"> <button type="button" class="qq-cancel-button-selector">Cancel</button> <button type="button" class="qq-ok-button-selector">Ok</button> </div></dialog> </div></div></script>
<div class="media-uploader"></div>
</div>
</div>
</main>
</div>
</div>
</div>
<script type='text/javascript'>
function csrfToken(){var a,b=null;if(document.cookie&&""!==document.cookie){var c=document.cookie.split(";");for(a=0;a<c.length;){var d=c[a].trim();if("csrftoken="===d.substring(0,10)){b=decodeURIComponent(d.substring(10));break}a+=1}}return b};
document.addEventListener("DOMContentLoaded", function(event) {
var default_concurrent_chunked_uploader = new qq.FineUploader({
debug: true,
element: document.querySelector('.media-uploader'),
request: {
endpoint: '/fu/upload/',
customHeaders: {
'X-CSRFToken': csrfToken('csrftoken'),
},
},
retry: {
enableAuto: true,
},
chunking: {
enabled: true,
concurrent: {
enabled: true,
},
success: {
endpoint: '/fu/upload/?done',
},
},
});
});
</script>

View File

@ -0,0 +1,3 @@
<div class="page-main-wrap">
<% if (id) { %><div id="<%= id %>"></div><% } %>
</div>

View File

@ -0,0 +1,5 @@
import { renderPage } from './static/js/utils/renderer';
import './static/css/AddMediaPage.scss';
renderPage();

View File

@ -0,0 +1,3 @@
import { renderPage } from './static/js/utils/renderer';
renderPage();

View File

@ -0,0 +1,15 @@
<% if (page) { %>
import { renderEmbedPage } from './static/js/utils/renderer';
<% if (page.component) { %>
import { <%= page.component %> } from './static/js/pages/<%= page.component %>';
<% if (page.id) { %>
renderEmbedPage( '<%= page.id %>', <%= page.component %> );
<% } %>
<% } %>
<% } %>

View File

@ -0,0 +1,15 @@
<% if (page) { %>
import { renderPage } from './static/js/utils/renderer';
<% if (page.component) { %>
import { <%= page.component %> } from './static/js/pages/<%= page.component %>';
<% if (page.id) { %>
renderPage( '<%= page.id %>', <%= page.component %> );
<% } %>
<% } %>
<% } %>

View File

@ -0,0 +1,15 @@
<% if (page) { %>
import { renderPage } from './static/js/utils/renderer';
<% if (page.component) { %>
import { <%= page.component %> } from './static/js/static-pages/<%= page.component %>';
<% if (page.id) { %>
renderPage( '<%= page.id %>', <%= page.component %> );
<% } %>
<% } %>
<% } %>

View File

@ -0,0 +1,18 @@
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<div class="page-main">
<div class="page-main-inner">
<div class="custom-page-wrapper">
<h2>About</h2>
<hr />
<p>
<a href="https://mediacms.io">MediaCMS</a> is a modern, fully featured open source video and media CMS. It is
developed to meet the needs of modern web platforms for viewing and sharing media.
</p>
</div>
</div>
<div class="page-sidebar-content-overlay"></div>
</div>
</div>
<div id="app-footer"></div>

View File

@ -0,0 +1,144 @@
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<div class="page-main">
<div class="page-main-inner">
<div class="media-uploader-wrap">
<div class="media-uploader-top-wrap">
<div class="media-uploader-top-left-wrap">
<h1>Upload media files</h1>
</div>
<div class="media-uploader-top-right-wrap"></div>
</div>
<div class="media-uploader">
<div class="media-uploader-bottom-wrap">
<div class="media-uploader-bottom-left-wrap">
<div class="media-drag-drop-wrap">
<div class="media-drag-drop-inner">
<div class="media-drag-drop-content">
<div class="media-drag-drop-content-inner">
<span><i class="material-icons">cloud_upload</i></span>
<span>Drag and drop files</span>
<span>or</span>
<span class="browse-files-btn-wrap">
<span
class="qq-upload-button-selector"
style="position: relative; overflow: hidden; direction: ltr"
>Browse your files<input
qq-button-id="9523a4a3-6688-40a5-b01d-7c72a79c7bcb"
title="file input"
multiple=""
type="file"
name="qqfile"
style="
position: absolute;
right: 0px;
top: 0px;
font-family: Arial;
font-size: 118px;
margin: 0px;
padding: 0px;
cursor: pointer;
opacity: 0;
height: 100%;
"
/></span>
</span>
<div class="qq-upload-drop-area-selector media-dropzone" qq-hide-dropzone>
<span class="qq-upload-drop-area-text-selector"></span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="media-uploader-bottom-right-wrap">
<ul class="media-upload-items-list qq-upload-list-selector">
<li>
<div class="media-upload-item-main">
<div class="media-upload-item-thumb">
<img class="qq-thumbnail-selector" alt="" />
<span class="media-upload-item-spinner qq-upload-spinner-selector"
><i class="material-icons">autorenew</i></span
>
<button type="button" class="qq-upload-retry-selector retry-media-upload-item" aria-label="Retry">
<i class="material-icons">refresh</i> Retry
</button>
</div>
<div class="media-upload-item-details">
<div class="media-upload-item-name">
<span class="media-upload-item-filename qq-upload-file-selector" title="">File #1</span>
<input
class="
media-upload-item-filename-input
qq-edit-filename-selector qq-edit-filename-icon-selector
"
tabindex="0"
type="text"
value="File #1"
/>
</div>
<div class="media-upload-item-details-bottom">
<div class="media-upload-item-progress-bar-container qq-progress-bar-container-selector">
<div
role="progressbar"
aria-valuenow="80"
aria-valuemin="0"
aria-valuemax="100"
class="media-upload-item-progress-bar qq-progress-bar-selector"
style="width: 80%"
></div>
</div>
<span class="media-upload-item-upload-size qq-upload-size-selector">80% of 7.5MB</span>
<span role="status" class="media-upload-item-status-text qq-upload-status-text-selector"
>Retrying 3/3...</span
>
</div>
<div class="media-upload-item-top-actions">
<span class="filename-edit qq-edit-filename-icon-selector" aria-label="Edit filename"
>Edit filename <i class="material-icons">create</i></span
>
<button
type="button"
class="delete-media-upload-item qq-upload-delete-selector"
aria-label="Delete"
>
Delete <i class="material-icons">delete</i>
</button>
<button
type="button"
class="cancel-media-upload-item qq-upload-cancel-selector"
aria-label="Cancel"
>
Cancel <i class="material-icons">cancel</i>
</button>
</div>
<div class="media-upload-item-bottom-actions">
<button
type="button"
class="continue-media-upload-item qq-upload-continue-selector"
aria-label="Continue"
>
<i class="material-icons">play_circle_outline</i> Continue
</button>
<button
type="button"
class="pause-media-upload-item qq-upload-pause-selector"
aria-label="Pause"
>
<i class="material-icons">pause_circle_outline</i> Pause
</button>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="page-sidebar-content-overlay"></div>
</div>
</div>
<div id="app-footer"></div>

View File

@ -0,0 +1,27 @@
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<div class="page-main">
<div class="page-main-inner">
<div class="user-action-form-wrap">
<div class="user-action-form-inner">
<h1>Contact us</h1>
<form enctype="multipart/form-data" action="" method="post" class="post-form">
<p>
<label for="id_from_email">Your email:</label>
<input type="email" name="from_email" required="" id="id_from_email" />
</p>
<p><label for="id_name">Your name:</label> <input type="text" name="name" id="id_name" /></p>
<p>
<label for="id_message">Please add your message here and submit:</label>
<textarea name="message" cols="40" rows="10" required="" id="id_message"></textarea>
</p>
<button class="primaryAction" type="submit">Submit</button>
</form>
</div>
</div>
</div>
<div class="page-sidebar-content-overlay"></div>
</div>
</div>
<div id="app-footer"></div>

View File

@ -0,0 +1,30 @@
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<div class="page-main">
<div class="page-main-inner">
<div class="user-action-form-wrap">
<div class="user-action-form-inner">
<h1>Edit Channel</h1>
<form enctype="multipart/form-data" action="" method="post" class="post-form">
<div id="div_id_banner_logo" class="control-group">
<label for="id_banner_logo" class="control-label"> Banner logo </label>
<div class="controls">
Currently: <a href="#">*****</a>
<input type="checkbox" name="banner_logo-clear" id="banner_logo-clear_id" />
<label for="banner_logo-clear_id">Clear</label><br />
Change:
<input type="file" name="banner_logo" accept="image/*" class="clearablefileinput" id="id_banner_logo" />
</div>
</div>
<button class="primaryAction" type="submit">Update Channel</button>
</form>
</div>
</div>
</div>
<div class="page-sidebar-content-overlay"></div>
</div>
</div>
<div id="app-footer"></div>

View File

@ -0,0 +1,137 @@
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<div class="page-main">
<div class="page-main-inner">
<div class="user-action-form-wrap">
<div class="user-action-form-inner">
<h1>Edit Media</h1>
<form enctype="multipart/form-data" action="" method="post" class="post-form">
<div id="div_id_title" class="control-group">
<label for="id_title" class="control-label"> Title </label>
<div class="controls">
<input
type="text"
name="title"
value="Media title..."
maxlength="100"
class="textinput textInput"
id="id_title"
/>
<p id="hint_id_title" class="help-block">media title</p>
</div>
</div>
<div id="div_id_category" class="control-group">
<label for="id_category" class="control-label"> Category </label>
<div class="controls">
<select name="category" class="selectmultiple" id="id_category" multiple="">
<option value="1">Art</option>
<option value="2">Documentary</option>
<option value="3">Experimental</option>
<option value="4">Film</option>
<option value="5">Music</option>
<option value="6">TV</option>
</select>
<p id="hint_id_category" class="help-block">Media can be part of one or more categories</p>
</div>
</div>
<div id="div_id_new_tags" class="control-group">
<label for="id_new_tags" class="control-label"> Tags </label>
<div class="controls">
<input type="text" name="new_tags" class="textinput textInput" id="id_new_tags" />
<p id="hint_id_new_tags" class="help-block">a comma separated list of new tags.</p>
</div>
</div>
<div id="div_id_add_date" class="control-group">
<label for="id_add_date" class="control-label"> Date produced </label>
<div class="controls">
<input type="text" name="add_date" value="2021-01-01 00:00:01" class="datetimeinput" id="id_add_date" />
</div>
</div>
<div id="div_id_uploaded_poster" class="control-group">
<label for="id_uploaded_poster" class="control-label"> Upload image </label>
<div class="controls">
<input
type="file"
name="uploaded_poster"
accept="image/*"
class="clearablefileinput"
id="id_uploaded_poster"
/>
<p id="hint_id_uploaded_poster" class="help-block">This image will characterize the media</p>
</div>
</div>
<div id="div_id_description" class="control-group">
<label for="id_description" class="control-label"> Description </label>
<div class="controls">
<textarea name="description" cols="40" rows="10" class="textarea" id="id_description"></textarea>
</div>
</div>
<div id="div_id_state" class="control-group">
<label for="id_state" class="control-label requiredField">
State<span class="asteriskField">*</span>
</label>
<div class="controls">
<select name="state" class="select" id="id_state">
<option value="private">Private</option>
<option value="public" selected="">Public</option>
<option value="unlisted">Unlisted</option>
</select>
<p id="hint_id_state" class="help-block">state of Media</p>
</div>
</div>
<div id="div_id_enable_comments" class="control-group">
<div class="controls">
<label for="id_enable_comments" class="checkbox">
<input
type="checkbox"
name="enable_comments"
class="checkboxinput"
id="id_enable_comments"
checked=""
/>
Enable comments
</label>
<p id="hint_id_enable_comments" class="help-block">Whether comments will be allowed for this media</p>
</div>
</div>
<div id="div_id_thumbnail_time" class="control-group">
<label for="id_thumbnail_time" class="control-label"> Thumbnail time </label>
<div class="controls">
<input
type="number"
name="thumbnail_time"
value="132.5"
step="any"
class="numberinput"
id="id_thumbnail_time"
/>
<p id="hint_id_thumbnail_time" class="help-block">Time on video that a thumbnail will be taken</p>
</div>
</div>
<div id="div_id_allow_download" class="control-group">
<div class="controls">
<label for="id_allow_download" class="checkbox">
<input
type="checkbox"
name="allow_download"
class="checkboxinput"
id="id_allow_download"
checked=""
/>
Allow download
</label>
<p id="hint_id_allow_download" class="help-block">Whether option to download media is shown</p>
</div>
</div>
<button class="primaryAction" type="submit">Update Media</button>
</form>
</div>
</div>
</div>
<div class="page-sidebar-content-overlay"></div>
</div>
</div>
<div id="app-footer"></div>

View File

@ -0,0 +1,43 @@
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<div class="page-main">
<div class="page-main-inner">
<div class="user-action-form-wrap">
<div class="user-action-form-inner">
<h1>Edit Profile</h1>
<form enctype="multipart/form-data" action="" method="post" class="post-form">
<p>
<label for="id_name">Full name:</label>
<input type="text" name="name" value="" maxlength="250" required="" id="id_name" />
</p>
<p>
<label for="id_description">About me:</label>
<textarea name="description" cols="40" rows="10" id="id_description"></textarea>
</p>
<p>
<label for="id_email">Email address:</label>
<input type="email" name="email" value="" maxlength="254" id="id_email" />
</p>
<p>
<label for="id_logo">Logo:</label> Currently: <a href="#">*****</a>
<input type="checkbox" name="logo-clear" id="logo-clear_id" />
<label for="logo-clear_id">Clear</label><br />
Change:
<input type="file" name="logo" accept="image/*" id="id_logo" />
</p>
<p>
<label for="id_notification_on_comments"
>Whether you will receive email notifications for comments added to your content:</label
>
<input type="checkbox" name="notification_on_comments" id="id_notification_on_comments" checked="" />
</p>
<button class="primaryAction" type="submit">Update Profile</button>
</form>
</div>
</div>
</div>
<div class="page-sidebar-content-overlay"></div>
</div>
</div>
<div id="app-footer"></div>

View File

@ -0,0 +1,11 @@
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<div class="page-main">
<div class="page-main-inner">
<div class="error-page-wrap">Error</div>
</div>
<div class="page-sidebar-content-overlay"></div>
</div>
</div>
<div id="app-footer"></div>

View File

@ -0,0 +1,45 @@
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<div class="page-main">
<div class="page-main-inner">
<div class="user-action-form-wrap">
<div class="user-action-form-inner">
<h1>Sign Up</h1>
<p>Already have an account? Then please <a href="./signin.html">sign in</a>.</p>
<form class="signup" id="signup_form" method="post" action="#">
<input type="hidden" name="csrfmiddlewaretoken" value="" />
<p>
<label for="id_email">E-mail:</label>
<input type="email" name="email" id="id_email" required="" placeholder="E-mail address" />
</p>
<p>
<label for="id_username">Username:</label>
<input
type="text"
name="username"
id="id_username"
autofocus="autofocus"
placeholder="Username"
minlength="4"
maxlength="150"
required=""
/>
</p>
<p>
<label for="id_name">Name:</label>
<input type="text" name="name" id="id_name" maxlength="100" required="" />
</p>
<p>
<label for="id_password1">Password:</label>
<input type="password" name="password1" id="id_password1" required="" placeholder="Password" />
</p>
<button type="submit">Sign Up »</button>
</form>
</div>
</div>
</div>
<div class="page-sidebar-content-overlay"></div>
</div>
</div>
<div id="app-footer"></div>

View File

@ -0,0 +1,28 @@
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<div class="page-main">
<div class="page-main-inner">
<div class="user-action-form-wrap">
<div class="user-action-form-inner">
<h1>Password Reset</h1>
<p>
Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset
it.
</p>
<form method="POST" action="#" class="password_reset">
<input type="hidden" name="csrfmiddlewaretoken" value="" />
<p>
<label for="id_email">E-mail:</label>
<input type="email" name="email" size="30" id="id_email" required="" placeholder="E-mail address" />
</p>
<input type="submit" value="Reset My Password" />
</form>
<p>Please contact us if you have any trouble resetting your password.</p>
</div>
</div>
</div>
<div class="page-sidebar-content-overlay"></div>
</div>
</div>
<div id="app-footer"></div>

View File

@ -0,0 +1,36 @@
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<div class="page-main">
<div class="page-main-inner">
<div class="user-action-form-wrap">
<div class="user-action-form-inner">
<h1>Sign In</h1>
<p>If you have not created an account yet, then please <a href="./register.html">sign up</a> first.</p>
<form class="login" method="POST" action="#">
<input type="hidden" name="csrfmiddlewaretoken" value="" />
<p>
<label for="id_login">Login:</label>
<input
type="text"
name="login"
id="id_login"
required=""
autofocus="autofocus"
placeholder="Username or e-mail"
/>
</p>
<p>
<label for="id_password">Password:</label>
<input type="password" name="password" id="id_password" required="" placeholder="Password" />
</p>
<a class="button secondaryAction" href="./reset-password.html">Forgot Password?</a>
<button class="primaryAction" type="submit">Sign In</button>
</form>
</div>
</div>
</div>
<div class="page-sidebar-content-overlay"></div>
</div>
</div>
<div id="app-footer"></div>

View File

@ -0,0 +1,20 @@
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<div class="page-main">
<div class="page-main-inner">
<div class="user-action-form-wrap">
<div class="user-action-form-inner">
<h1>Sign Out</h1>
<p>Are you sure you want to sign out?</p>
<form method="post" action="#">
<input type="hidden" name="csrfmiddlewaretoken" value="" />
<button type="submit">Sign Out</button>
</form>
</div>
</div>
</div>
<div class="page-sidebar-content-overlay"></div>
</div>
</div>
<div id="app-footer"></div>

View File

@ -0,0 +1,15 @@
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<div class="page-main">
<div class="page-main-inner">
<div class="custom-page-wrapper">
<h2>Terms</h2>
<hr />
<p>Terms of service</p>
</div>
</div>
<div class="page-sidebar-content-overlay"></div>
</div>
</div>
<div id="app-footer"></div>

39075
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

53
frontend/package.json Executable file
View File

@ -0,0 +1,53 @@
{
"name": "mediacms-frontend",
"version": "0.9.1",
"description": "",
"author": "",
"license": "",
"keywords": [],
"main": "index.js",
"scripts": {
"start": "mediacms-scripts development --config=./config/mediacms.config.js --host=0.0.0.0 --port=8088",
"dist": "mediacms-scripts rimraf ./dist && mediacms-scripts build --config=./config/mediacms.config.js --env=dist"
},
"browserslist": [
"cover 99.5%"
],
"devDependencies": {
"@babel/core": "^7.14.5",
"@babel/preset-env": "^7.14.5",
"@babel/preset-react": "^7.14.5",
"@types/react": "^17.0.11",
"@types/react-dom": "^17.0.7",
"autoprefixer": "^10.2.6",
"babel-loader": "^8.2.2",
"compass-mixins": "^0.12.10",
"copy-webpack-plugin": "^9.0.0",
"core-js": "^3.14.0",
"css-loader": "^5.2.6",
"dotenv": "^10.0.0",
"ejs": "^3.1.6",
"ejs-compiled-loader": "^3.1.0",
"mediacms-scripts": "file:packages/scripts",
"postcss-loader": "^6.1.0",
"prettier": "^2.3.1",
"prop-types": "^15.7.2",
"sass": "^1.34.1",
"sass-loader": "^12.1.0",
"ts-loader": "^9.2.3",
"typescript": "^4.3.2",
"url-loader": "^4.1.1",
"webpack": "^5.38.1"
},
"dependencies": {
"axios": "^0.21.1",
"flux": "^4.0.1",
"mediacms-player": "file:packages/player",
"normalize.css": "^8.0.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"sortablejs": "^1.13.0",
"timeago.js": "^4.0.2",
"url-parse": "^1.5.1"
}
}

View File

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}

View File

@ -0,0 +1,18 @@
# editorconfig.org
root = true
[*]
charset = utf-8
indent_style = tab
indent_size = 1
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[package.json]
indent_style = space
indent_size = 2
[*.md]
insert_final_newline = true
trim_trailing_whitespace = false

View File

@ -0,0 +1,8 @@
{
"minify": true,
"options": [],
"feature-detects": [
"css/transforms",
"test/storage/localstorage"
]
}

View File

@ -0,0 +1 @@
# mediacms-player

View File

@ -0,0 +1,113 @@
import gzip from 'rollup-plugin-gzip';
import postcss from 'rollup-plugin-postcss';
import babel from 'rollup-plugin-babel';
import cleanup from 'rollup-plugin-cleanup';
// import { uglify } from "rollup-plugin-uglify";
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import visualizer from 'rollup-plugin-visualizer';
import json from '@rollup/plugin-json';
export default function rollup_builds(input_file, output_folder, pkg) {
const package_name = pkg.name;
const dependencies = pkg.dependencies;
const dependencies_names = !!dependencies ? Object.keys(pkg.dependencies) : [];
const esm_format = 'es';
const browser_format = 'umd';
const commonjs_format = 'cjs';
const postcss_config = {
extract: true,
modules: false, // Avoid adding prefixes to classnames (etc).
extensions: ['.css', '.sss', '.pcss', '.scss'],
};
const postcss_plugin = postcss(postcss_config);
const postcss_plugin_minimized = postcss({ ...postcss_config, minimize: true });
const commonjs_resolve_config = {
// pass custom options to the resolve plugin
customResolveOptions: { moduleDirectory: 'node_modules' },
};
function beautify_plugin() {
return cleanup(/*{
maxEmptyLines: 1,
sourcemap: false,
}*/);
}
function visualizer_plugin(name) {
return visualizer({
title: name,
filename: output_folder + '/visualizer/' + name + '.html',
});
}
function es_build(filename, visualize, bundle) {
const plugins = [postcss_plugin, json(), beautify_plugin()];
if (!!visualize) {
plugins.push(visualizer_plugin(filename));
}
return {
input: input_file,
external: !!bundle ? {} : dependencies_names,
output: [{ format: esm_format, file: filename }],
plugins: plugins,
};
}
function commonjs_build(filename, visualize, bundle) {
const plugins = [postcss_plugin, json(), resolve(commonjs_resolve_config), beautify_plugin()];
if (!!visualize) {
plugins.push(visualizer_plugin(filename));
}
return {
input: input_file,
external: !!bundle ? {} : dependencies_names,
output: [{ format: commonjs_format, file: filename }],
plugins: plugins,
};
}
function browser_build(filename, visualize, minimize, compact) {
const plugins = [
!!minimize ? postcss_plugin_minimized : postcss_plugin,
json(),
babel(),
resolve(),
commonjs(),
beautify_plugin(),
];
if (!!minimize) {
// plugins.push( uglify() );
if (!!compact) {
plugins.push(gzip());
}
}
if (!!visualize) {
plugins.push(visualizer_plugin(filename));
}
return {
input: input_file,
output: { name: package_name, format: browser_format, file: filename },
plugins: plugins,
};
}
return Object.freeze({
es: es_build,
browser: browser_build,
commonjs: commonjs_build,
});
}

View File

@ -0,0 +1,11 @@
import rollup_builds from './includes/rollup_builds';
import pckg from '../package.json';
const dists = rollup_builds('./src/index.js', './out', pckg);
export default [
dists.browser('./dist/mediacms-player.js'),
// dists.browser("./dist/mediacms-player.js", true),
// dists.browser("./dist/mediacms-player.min.js", true, true),
// dists.browser("./dist/mediacms-player.min.js", true, true, true)
];

View File

@ -0,0 +1,6 @@
import rollup_builds from './includes/rollup_builds';
import pckg from '../package.json';
const dists = rollup_builds('./src/index.js', './out', pckg);
export default [dists.browser('./dist/mediacms-player.js')];

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

16686
frontend/packages/player/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,62 @@
{
"name": "mediacms-player",
"version": "0.9.0",
"description": "",
"author": "",
"license": "",
"engines": {
"node": ">=14.17.0"
},
"keywords": [
"mediacms",
"media player",
"videojs"
],
"main": "./dist/mediacms-player.js",
"module": "./src/",
"browser": "./dist/mediacms-player.js",
"files": [
"dist"
],
"browserslist": [
"defaults"
],
"scripts": {
"start": "npx rollup -w -c config/rollup.config.js",
"build": "npx rollup -c config/rollup.config.build.js",
"clean:build": "node ./scripts/rmrf.js ./dist"
},
"peerDependencies": {
"video.js": "^7.12.3"
},
"dependencies": {
"mediacms-vjs-plugin": "file:../vjs-plugin"
},
"devDependencies": {
"@babel/core": "^7.14.5",
"@babel/preset-env": "^7.14.5",
"@rollup/plugin-commonjs": "^19.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.0.0",
"ajv": "^8.6.0",
"babel-core": "^6.26.3",
"babel-loader": "^8.2.2",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"core-js": "^3.14.0",
"css-loader": "^5.2.6",
"global": "^4.4.0",
"json-loader": "^0.5.7",
"node-sass": "^6.0.0",
"postcss": "^8.3.2",
"rollup": "^2.51.2",
"rollup-plugin-babel": "^4.3.3",
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-gzip": "^2.5.0",
"rollup-plugin-postcss": "^4.0.0",
"rollup-plugin-visualizer": "^5.5.0",
"sass-loader": "^12.1.0",
"style-loader": "^2.0.0",
"trim-newlines": "^4.0.2"
}
}

View File

@ -0,0 +1,21 @@
var fs = require('fs');
var path = require('path');
var rimraf = require('rimraf');
var cliArgs = process.argv.slice(2);
function rmdir_callback(err) {
if (err) {
throw err;
}
}
var i, dir;
for (i = 0; i < cliArgs.length; i++) {
dir = path.resolve(cliArgs[i]);
if (fs.existsSync(dir)) {
rimraf.sync(dir, {}, rmdir_callback);
}
}

View File

@ -0,0 +1,453 @@
import 'mediacms-vjs-plugin/dist/mediacms-vjs-plugin.js';
import 'mediacms-vjs-plugin/dist/mediacms-vjs-plugin.css';
function isString(v) {
return 'string' === typeof v || v instanceof String;
}
function isArray(v) {
return !Array.isArray ? '[object Array]' === Object.prototype.toString.call(v) : Array.isArray(v);
}
function isBoolean(v) {
return 'boolean' === typeof v || v instanceof Boolean;
}
function ifBooleanElse(bol, els) {
return isBoolean(bol) ? bol : els;
}
const defaults = {
options: {
sources: [],
keyboardControls: !0,
enabledTouchControls: !0,
nativeDimensions: !1,
suppressNotSupportedError: !0,
poster: '',
loop: !1,
controls: !0,
preload: 'auto',
autoplay: !1,
bigPlayButton: !0,
liveui: !1,
controlBar: {
bottomBackground: !0,
progress: !0,
play: !0,
next: !1,
previous: !1,
volume: !0,
pictureInPicture: !0, // @link: https://docs.videojs.com/control-bar_picture-in-picture-toggle.js.html
fullscreen: !0,
theaterMode: !0,
time: !0,
},
cornerLayers: {
topLeft: null,
topRight: null,
bottomLeft: null,
bottomRight: null,
},
videoPreviewThumb: {},
subtitles: {
on: false,
default: null,
languages: [],
},
},
};
/**
* Filter plugin options values.
* @param {Object} opt Options object.
* @return {Object} Filtered/Validated options object.
*/
function filterPlayerOptions(domPlayer, opt) {
let k, x, j, i;
opt.sources = isArray(opt.sources) && opt.sources.length ? opt.sources : [];
opt.loop = ifBooleanElse(opt.loop, defaults.options.loop);
opt.controls = ifBooleanElse(opt.controls, defaults.options.controls);
if (opt.subtitles && opt.subtitles instanceof Object) {
opt.subtitles.default = void 0 !== opt.subtitles.default ? opt.subtitles.default : defaults.options.subtitles.default;
opt.subtitles.languages = isArray(opt.subtitles.languages)
? opt.subtitles.languages
: defaults.options.subtitles.languages;
opt.subtitles.on = ifBooleanElse(opt.subtitles.on, defaults.options.subtitles.on);
} else {
opt.subtitles.default = defaults.options.subtitles;
}
opt.autoplay =
'any' === opt.autoplay || 'play' === opt.autoplay || 'muted' === opt.autoplay
? opt.autoplay
: ifBooleanElse(opt.autoplay, defaults.options.autoplay);
// console.log(opt.autoplay);
opt.bigPlayButton = ifBooleanElse(opt.bigPlayButton, defaults.options.bigPlayButton);
opt.poster = isString(opt.poster) && '' !== opt.poster.trim() ? opt.poster : defaults.options.poster;
opt.preload =
isString(opt.preload) && -1 < ['auto', 'metadata', 'none'].indexOf(opt.preload.trim())
? opt.preload
: defaults.options.preload;
// Control bar options.
if (opt.controlBar && opt.controlBar instanceof Object && Object.keys(opt.controlBar).length) {
for (k in opt.controlBar) {
if (opt.controlBar.hasOwnProperty(k)) {
opt.controlBar[k] = ifBooleanElse(opt.controlBar[k], defaults.options.controlBar[k]);
}
}
}
// Corner layers.
if (opt.cornerLayers && opt.cornerLayers instanceof Object && Object.keys(opt.cornerLayers).length) {
for (k in opt.cornerLayers) {
if (opt.cornerLayers.hasOwnProperty(k)) {
if ('string' === typeof opt.cornerLayers[k]) {
opt.cornerLayers[k] = '' !== opt.cornerLayers[k] ? opt.cornerLayers[k] : defaults.options.cornerLayers[k];
} else if (Node.prototype.isPrototypeOf(opt.cornerLayers[k]) || !isNaN(opt.cornerLayers[k])) {
opt.cornerLayers[k] = opt.cornerLayers[k];
} else {
opt.cornerLayers[k] = opt.cornerLayers[k] || defaults.options.cornerLayers[k];
}
} else {
opt.cornerLayers[k] = defaults.options.cornerLayers[k];
}
}
}
opt.previewSprite = 'object' === typeof opt.previewSprite ? opt.previewSprite : {};
// Include HTML sources.
let obj;
let sources_el = domPlayer.querySelectorAll('source');
i = 0;
while (i < sources_el.length) {
if (void 0 !== sources_el[i].attributes.src) {
obj = {
src: sources_el[i].src,
};
if (void 0 !== sources_el[i].attributes.type) {
obj.type = sources_el[i].type;
}
x = 0;
while (x < opt.sources.length && obj.src !== opt.sources[x].src) {
x += 1;
}
if (x >= opt.sources.length) {
opt.sources.push(obj);
}
}
i += 1;
}
// Include HTML subtitle tracks.
let subs_el = domPlayer.querySelectorAll('track[kind="subtitles"]');
const subtitles_options = {
on: opt.subtitles.on,
default: null,
languages: [],
};
const languages = {};
function addSubtitle(track) {
track.src = void 0 !== track.src && null !== track.src ? track.src.toString().trim() : '';
track.srclang = void 0 !== track.srclang && null !== track.srclang ? track.srclang.toString().trim() : '';
if (track.src.length && track.srclang.length) {
track.label = void 0 !== track.label && null !== track.label ? track.label.toString().trim() : track.srclang;
if (void 0 !== languages[track.srclang]) {
languages[track.srclang].src = track.src;
languages[track.srclang].label = track.label;
} else {
subtitles_options.languages.push({
label: track.label,
src: track.src,
srclang: track.srclang,
});
languages[track.srclang] = subtitles_options.languages[subtitles_options.languages.length - 1];
}
if (void 0 !== track.default && null !== track.default) {
track.default = track.default.toString().trim();
if (!track.default.length || '1' === track.default || 'true' === track.default) {
subtitles_options.default = track.srclang;
}
}
}
}
i = 0;
while (i < subs_el.length) {
addSubtitle({
src: subs_el[i].getAttribute('src'),
srclang: subs_el[i].getAttribute('srclang'),
default: subs_el[i].getAttribute('default'),
label: subs_el[i].getAttribute('label'),
});
i += 1;
}
if (opt.subtitles.languages.length) {
i = 0;
while (i < opt.subtitles.languages.length) {
addSubtitle({
src: opt.subtitles.languages[i].src,
srclang: opt.subtitles.languages[i].srclang,
default: opt.subtitles.languages[i].default,
label: opt.subtitles.languages[i].label,
});
i += 1;
}
}
if (null !== opt.subtitles.default && void 0 !== languages[opt.subtitles.default]) {
subtitles_options.default = opt.subtitles.default;
}
if (null === subtitles_options.default && opt.subtitles.languages.length) {
subtitles_options.default = opt.subtitles.languages[0].srclang;
}
opt.subtitles = subtitles_options;
return opt;
}
/**
* Construct VideoJs options by player options.
* @param {Object} opt Plugin options.
* @param {Object} vjopt Initial VideoJs object.
* @return {Object} Final VideoJs object.
*/
function constructVideojsOptions(opt, vjopt) {
// {
// /*autoplay: false,
// controls: true,
// preload: "auto", // preload: "metadata",
// loop: false,
// bigPlayButton: true,*/
// // poster: "",
// // width: "",
// // height: "",
// // children: {}
// controlBar: {
// children: [],
// // children: {
// // bottomGradientComponent: true,
// // progressControl: true, // (hidden during live playback)
// // leftControls: true,
// // // playbackRateMenuButton: true, // (hidden, unless playback tech supports rate changes)
// // // chaptersButton: true, // (hidden, unless there are relevant tracks)
// // // descriptionsButton: true, // (hidden, unless there are relevant tracks)
// // // subtitlesButton: true, // (hidden, unless there are relevant tracks)
// // // captionsButton: true, // (hidden, unless there are relevant tracks)
// // // audioTrackButton: true, // (hidden, unless there are relevant tracks)
// // }
// // seekBar: false,
// // loadProgressBar: false,
// // mouseTimeDisplay: false,
// // playProgressBar: false,
// // liveDisplay: false, // (hidden during VOD playback)
// // remainingTimeDisplay: false,
// // customControlSpacer: false, // (has no UI)
// // playbackRateMenuButton: true, // (hidden, unless playback tech supports rate changes)
// // chaptersButton: true, // (hidden, unless there are relevant tracks)
// // descriptionsButton: true, // (hidden, unless there are relevant tracks)
// // subtitlesButton: true, // (hidden, unless there are relevant tracks)
// // captionsButton: true, // (hidden, unless there are relevant tracks)
// // audioTrackButton: true, // (hidden, unless there are relevant tracks)
// }
// }
vjopt.sources = opt.sources;
vjopt.loop = opt.loop;
vjopt.controls = opt.controls;
vjopt.autoplay = opt.autoplay;
vjopt.bigPlayButton = opt.bigPlayButton;
vjopt.poster = opt.poster;
vjopt.preload = opt.preload;
vjopt.suppressNotSupportedError = opt.suppressNotSupportedError;
// console.log( vjopt );
// console.log( opt );
return vjopt;
}
/**
* A wrapper/container class of MediaCMS VideoJs player.
* @param {DOM Node} domPlayer The video element in html.
* @param {Object} pluginOptions Plugin (genral player's) options.
* @param {Object} pluginState Plugin initial state values.
* @param {Function} pluginStateUpdateCallback The function will be called on plugin's state values update.
*/
export function MediaPlayer(
domPlayer,
pluginOptions,
pluginState,
videoResolutions,
videoPlaybackSpeeds,
pluginStateUpdateCallback,
onNextButtonClick,
onPrevButtonClick
) {
if (!Node.prototype.isPrototypeOf(domPlayer)) {
console.error('Invalid player DOM element', domPlayer); // TODO: Validate that element is <video> or <audio>.
return null;
}
function sourcesSrcs(urls) {
const ret = [];
let i = 0;
while (i < urls.length) {
if (!!urls[i]) {
ret.push(urls[i]); // @todo: Validate url file extension.
}
i += 1;
}
return ret;
}
function sourcesFormats(formats) {
const ret = [];
let i = 0;
while (i < formats.length) {
if (!!formats[i]) {
ret.push(formats[i]); // @todo: Validate format.
}
i += 1;
}
return ret;
}
let k,
i,
pluginVideoResolutions = {},
pluginVideoPlaybackSpeeds = {};
if (!!videoResolutions) {
for (k in videoResolutions) {
if (videoResolutions.hasOwnProperty(k)) {
if (
isArray(videoResolutions[k].url) &&
videoResolutions[k].url.length &&
isArray(videoResolutions[k].format) &&
videoResolutions[k].format.length
) {
pluginVideoResolutions[k] = {
title: k,
src: sourcesSrcs(videoResolutions[k].url),
format: sourcesFormats(videoResolutions[k].format),
};
}
}
}
}
if (!!videoPlaybackSpeeds) {
k = 0;
while (k < videoPlaybackSpeeds.length) {
pluginVideoPlaybackSpeeds[k] = {
title: 1 === videoPlaybackSpeeds[k] ? 'Normal' : videoPlaybackSpeeds[k],
speed: videoPlaybackSpeeds[k].toString(),
};
k += 1;
}
}
/*
* Filter options value.
*/
// console.log( '####################' );
// console.log( domPlayer );
// console.log( defaults.options );
// console.log( Object.keys(pluginOptions) );
pluginOptions = filterPlayerOptions(
domPlayer,
videojs.mergeOptions(
defaults.options,
pluginOptions && pluginOptions instanceof Object && Object.keys(pluginOptions).length ? pluginOptions : {}
)
);
// console.log( pluginOptions );
// console.log( '####################' );
/*
* Filter state value.
*/
// console.log( '####################' );
// console.log( pluginState );
// console.warn( pluginOptions.subtitles );
// console.log( pluginState );
if (null !== pluginOptions.subtitles.default && pluginOptions.subtitles.on) {
pluginState.theSelectedSubtitleOption = pluginOptions.subtitles.default;
}
// console.log( pluginState );
// console.log( pluginState );
// console.log( '####################' );
/*
* Initialize videojs player.
*/
const passOptions = constructVideojsOptions(pluginOptions, {
controlBar: {
children: [],
},
});
this.player = videojs(domPlayer, passOptions);
/*
* Call plugin.
*/
this.player.mediaCmsVjsPlugin(
domPlayer,
pluginOptions,
pluginState,
pluginVideoResolutions,
pluginVideoPlaybackSpeeds,
pluginStateUpdateCallback,
onNextButtonClick,
onPrevButtonClick
);
/*
* Public methods.
*/
this.isEnded = this.player.mediaCmsVjsPlugin().isEnded;
this.isFullscreen = this.player.mediaCmsVjsPlugin().isFullscreen;
this.isTheaterMode = this.player.mediaCmsVjsPlugin().isTheaterMode;
if (void 0 !== typeof window) {
window.HELP_IMPROVE_VIDEOJS = false;
}
}

View File

@ -0,0 +1,3 @@
import { MediaPlayer } from './MediaPlayer';
export default MediaPlayer;

View File

@ -0,0 +1,36 @@
module.exports = {
"extends": ["eslint:recommended"],
"env": { "es6": true, "browser": true, "node": true },
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2018
},
"rules": {
"indent": ["error", "tab"],
"linebreak-style": ["error", "unix"],
"quotes": ["error", "single"],
"semi": ["error", "always"],
'no-empty': ["error", { "allowEmptyCatch": true }],
'no-constant-condition': ["error", { "checkLoops": false }],
},
"overrides": [{
"plugins": [ "@typescript-eslint" ],
"parser": "@typescript-eslint/parser",
"files": ["**/*.ts", "**/*.tsx"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"ecmaFeatures": { "jsx": true },
"ecmaVersion": 2018,
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
"@typescript-eslint/no-var-requires": "off"
},
}]
}

View File

@ -0,0 +1 @@
# mediacms-scripts

View File

@ -0,0 +1,47 @@
#!/usr/bin/env node
const spawn = require('cross-spawn');
('use strict');
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', (err) => {
throw err;
});
const args = process.argv.slice(2);
const scriptIndex = args.findIndex(
(x) => x === 'build' || x === 'development' || x === 'analyzer' || x === 'rimraf' // TODO
);
const script = scriptIndex === -1 ? args[0] : args[scriptIndex];
const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : [];
if (['build', 'development', 'analyzer', 'rimraf'].includes(script)) {
const result = spawn.sync(
process.execPath,
nodeArgs.concat(require.resolve('./scripts/' + script)).concat(args.slice(scriptIndex + 1)),
{ stdio: 'inherit' }
);
if (result.signal) {
if (result.signal === 'SIGKILL') {
console.log(
'The build failed because the process exited too early. ' +
'This probably means the system ran out of memory or someone called ' +
'`kill -9` on the process.'
);
} else if (result.signal === 'SIGTERM') {
console.log(
'The build failed because the process exited too early. ' +
'Someone might have called `kill` or `killall`, or the system could ' +
'be shutting down.'
);
}
process.exit(1);
}
process.exit(result.status);
} else {
console.log('Unknown script "' + script + '".');
}

View File

@ -0,0 +1,3 @@
import { buildCommonjs } from './helpers/buildCommonjs.js';
export default buildCommonjs('./src/index.ts', '.')('./dist/webpack-dev-env.js');

View File

@ -0,0 +1,24 @@
import typescript from 'rollup-plugin-typescript2';
import resolve from '@rollup/plugin-node-resolve';
import visualizer from 'rollup-plugin-visualizer';
import cleanup from 'rollup-plugin-cleanup';
export function buildCommonjs(input_file, output_folder) {
return function (filename, visualize) {
const plugins = [
resolve({ customResolveOptions: { moduleDirectories: 'node_modules' } }),
typescript(),
cleanup({ comments: 'none' }),
];
if (visualize) {
plugins.push(visualizer({ title: filename, filename: output_folder + filename + '.html' }));
}
return {
input: input_file,
output: [{ format: 'cjs', file: filename }],
plugins: plugins,
};
};
}

View File

@ -0,0 +1,3 @@
import { buildCommonjs } from './helpers/buildCommonjs.js';
export default buildCommonjs('./src/index.ts', './visualizer/')('./dist/webpack-dev-env.js', true);

View File

@ -0,0 +1,3 @@
import { buildCommonjs } from './helpers/buildCommonjs.js';
export default buildCommonjs('./src/index.ts', '.')('./dist/webpack-dev-env.js');

View File

@ -0,0 +1,782 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __spreadArray(to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || from);
}
function bodySnippet(id) {
return '<div id="' + id + '"></div>';
}
var homePage = {
staticPage: true,
buildExclude: false,
title: 'Home',
filename: 'index.html',
html: {
head: {},
body: {
scripts: [],
snippet: bodySnippet('page-home'),
}
},
window: {},
render: 'import { renderPage } from \'./js/helpers\'; import { HomePage } from \'./js/pages/HomePage\'; renderPage( \'page-home\', HomePage );',
};
var errorPage = {
staticPage: true,
buildExclude: false,
title: 'Error',
filename: 'error.html',
html: {
head: {},
body: {
scripts: [],
snippet: bodySnippet('page-error'),
}
},
window: {},
render: 'import { renderPage } from \'./js/helpers\'; import { ErrorPage } from \'./js/pages/ErrorPage\'; renderPage( \'page-error\', ErrorPage );',
};
var pages = {
home: homePage,
error: errorPage,
};
var htmlHead = {
meta: [
{ charset: 'utf-8' },
{ content: 'ie=edge', 'http-equiv': 'x-ua-compatible' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
links: [],
scripts: [],
};
var htmlBody = {
scripts: [],
snippet: '',
};
var html = {
head: htmlHead,
body: htmlBody,
};
var config$3 = {
src: '',
build: '',
pages: pages,
html: html,
window: {},
postcssConfigFile: '',
};
/*const chunksCacheGroups_0 = {
commons: {
test: /[\\/]src[\\/]/,
name: "_commons",
chunks: "all",
enforce: true,
reuseExistingChunk: true,
},
};*/
/*const chunksCacheGroups_1 = {
commons: {
test: /[\\/]src[\\/]/,
name: "_commons",
// priority: -10,
chunks: "all",
enforce: true,
reuseExistingChunk: true,
},
vendors: {
test: /[\\/]node_modules[\\/]/,
// test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
// test: /[\\/]node_modules[\\/](!MediaCmsPlayer)[\\/]/,
name: "_vendors",
// priority: -20,
chunks: "all",
enforce: true,
// reuseExistingChunk: true,
},
};*/
/*const chunksCacheGroups_2 = {
commons: {
minChunks: 2,
// maxInitialRequests: 8, // @note: Tested values from 0 to 10, and changes applied with values 0, 4, 5, 6, 7, 8.
// minSize: 0,
name: "_commons",
chunks: "all",
enforce: true,
reuseExistingChunk: true,
},
};*/
/*const chunksCacheGroups_3 = {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: "_commons",
priority: 1,
chunks: "initial",
},
};*/
var config$2 = {
mode: 'production',
devtool: 'source-map',
optimization: {
runtimeChunk: false,
/*splitChunks: {
// minSize: 1000000,
chunks: 'all',
automaticNameDelimiter: '-',
},*/
/*splitChunks: {
// minSize: 1000000,
chunks: 'all',
automaticNameDelimiter: '-',
cacheGroups: chunksCacheGroups_0,
},*/
/*splitChunks: {
chunks: 'all',
automaticNameDelimiter: '-',
cacheGroups: chunksCacheGroups_1,
},*/
/*splitChunks: {
chunks: 'all',
automaticNameDelimiter: '-',
cacheGroups: chunksCacheGroups_2,
},*/
/*splitChunks: {
chunks: 'all',
automaticNameDelimiter: '-',
cacheGroups: chunksCacheGroups_3,
},*/
splitChunks: {
chunks: 'all',
automaticNameDelimiter: '-',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: "_commons",
priority: 1,
chunks: "initial",
},
},
},
}
};
/*const chunksCacheGroups_0 = {
commons: {
test: /[\\/]src[\\/]/,
name: "_commons",
chunks: "all",
enforce: true,
reuseExistingChunk: true,
},
};
const chunksCacheGroups_1 = {
commons: {
test: /[\\/]src[\\/]/,
name: "_commons",
// priority: -10,
chunks: "all",
enforce: true,
reuseExistingChunk: true,
},
vendors: {
test: /[\\/]node_modules[\\/]/,
// test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
// test: /[\\/]node_modules[\\/](!MediaCmsPlayer)[\\/]/,
name: "_vendors",
// priority: -20,
chunks: "all",
enforce: true,
// reuseExistingChunk: true,
},
};
const chunksCacheGroups_2 = {
commons: {
minChunks: 2,
// maxInitialRequests: 8, // @note: Tested values from 0 to 10, and changes applied with values 0, 4, 5, 6, 7, 8.
// minSize: 0,
name: "_commons",
chunks: "all",
enforce: true,
reuseExistingChunk: true,
},
};
const chunksCacheGroups_3 = {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: "_commons",
priority: 1,
chunks: "initial",
},
};*/
var config$1 = {
mode: 'production',
optimization: {
minimize: true,
runtimeChunk: false,
splitChunks: {
chunks: 'all',
automaticNameDelimiter: '-',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: "_commons",
priority: 1,
chunks: "initial",
},
},
},
}
};
/**
* @see {link: https://github.com/seeyoulater/html-beautify-webpack-plugin/blob/master/index.js}
*/
var prettify = require('html-prettify');
var HtmlWebpackPlugin$1 = require('html-webpack-plugin');
require('webpack/lib/WebpackError.js');
function htmlPluginDataFunction(pluginData, options, callback) {
pluginData.html = prettify(options.replace.reduce(function (res, item) { return res.replace(item instanceof RegExp ? new RegExp(item, 'gi') : item, ''); }, pluginData.html) /*,
options.config*/);
callback(null, pluginData);
}
var MyHtmlBeautifyWebpackPlugin = /** @class */ (function () {
function MyHtmlBeautifyWebpackPlugin() {
}
MyHtmlBeautifyWebpackPlugin.prototype.apply = function (compiler) {
var options = {
config: {
indent_size: 4,
indent_with_tabs: false,
html: {
end_with_newline: true,
indent_inner_html: true,
preserve_newlines: true,
max_preserve_newlines: 0,
}
},
replace: []
};
function tapAsyncCallback(pluginData, callback) {
return htmlPluginDataFunction(pluginData, options, callback);
}
function tapHookCallback(compilation) {
return HtmlWebpackPlugin$1.getHooks(compilation).beforeEmit.tapAsync('MyHtmlBeautifyWebpackPlugin', tapAsyncCallback);
}
compiler.hooks.compilation.tap('MyHtmlBeautifyWebpackPlugin', tapHookCallback);
};
return MyHtmlBeautifyWebpackPlugin;
}());
var fs = require('fs');
var path$1 = require('path');
var ejs = require('ejs');
var templatePath = path$1.join(__dirname, '../templates');
var sitemapTemplatePath = path$1.join(templatePath, 'sitemap.ejs');
var sitemapTemplate = ejs.compile(fs.readFileSync(sitemapTemplatePath, 'utf8'), { root: [templatePath], filename: sitemapTemplatePath, outputFunctionName: 'echo' });
function pagesConfig(pagesKeys) {
var pages = {};
if (-1 === pagesKeys.indexOf('sitemap')) {
pages.sitemap = {
staticPage: true,
buildExclude: true,
title: 'Sitemap',
filename: 'sitemap.html',
html: {
head: {},
body: {
scripts: [],
snippet: sitemapTemplate({ pages: __spreadArray(__spreadArray([], pagesKeys), Object.keys(pages)) }),
},
},
window: {},
render: ''
};
}
return pages;
}
var merge = require('lodash.merge');
function validateBoolean(value, defaultValue) {
if (defaultValue === void 0) { defaultValue = false; }
if (true === value || false === value) {
return value;
}
if (0 === value || 1 === value) {
return !!value;
}
return defaultValue;
}
function validateString(value, defaultValue) {
if (defaultValue === void 0) { defaultValue = ''; }
return value ? value : defaultValue;
}
function getArrayType(sourcesArr, pageArr) {
if (pageArr === void 0) { pageArr = []; }
if ((!sourcesArr || !sourcesArr.length) && (!pageArr || !pageArr.length)) {
return [];
}
if (sourcesArr && sourcesArr.length && pageArr && pageArr.length) {
return sourcesArr.concat(pageArr);
}
if (sourcesArr && sourcesArr.length) {
return sourcesArr;
}
return pageArr;
}
function formatPagesConfig(sources, pages) {
var ret = {};
for (var pk in pages) {
ret[pk] = {
staticPage: validateBoolean(pages[pk].staticPage, false),
buildExclude: validateBoolean(pages[pk].buildExclude, false),
title: validateString(pages[pk].title, sources.title),
filename: validateString(pages[pk].filename, sources.filename),
html: {
head: {
meta: getArrayType(sources.html.head.meta, pages[pk].html.head.meta),
links: getArrayType(sources.html.head.links, pages[pk].html.head.links),
scripts: getArrayType(sources.html.head.scripts, pages[pk].html.head.scripts),
},
body: {
scripts: getArrayType(sources.html.body.scripts, pages[pk].html.body.scripts),
snippet: validateString(pages[pk].html.body.snippet, sources.html.body.snippet),
},
},
window: merge({}, sources.window, pages[pk].window),
render: validateString(sources.render, pages[pk].render),
};
}
return ret;
}
var path = require('path');
var NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
// Webpack plugins.
var DefinePlugin = require('webpack').DefinePlugin;
var LimitChunkCountPlugin = require('webpack').optimize.LimitChunkCountPlugin;
var HtmlWebpackPlugin = require('html-webpack-plugin');
var VirtualModulesPlugin = require('webpack-virtual-modules');
var MiniCssExtractPlugin = require('mini-css-extract-plugin');
var ProgressBarPlugin = require('progress-bar-webpack-plugin');
var CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
var CopyPlugin = require("copy-webpack-plugin");
var dotenv = require('dotenv').config({ path: path.resolve(__dirname + '../../../../.env') });
function webpackEntry(env, srcDir, pages) {
var ret = {};
for (var p in pages) {
if ('development' === env || !pages[p].buildExclude) {
ret[p] = path.resolve(srcDir + '/' + p + '.js');
}
}
return ret;
}
function webpackOutput(env, destinationDir, buildDir, chunkhash, hash) {
var ret = {
path: destinationDir,
filename: '',
};
var prefix = 'development' === env ? '' : buildDir;
var tmp;
if (undefined !== chunkhash) {
tmp = chunkhash.trim();
if ('' === tmp) {
throw Error('Invalid chunkhash argument value: ' + chunkhash);
}
ret.filename = (prefix || '') + '[name]-[chunkhash].js';
}
else if (undefined !== hash) {
tmp = hash.trim();
if ('' === tmp) {
throw Error('Invalid hash argument value: ' + hash);
}
ret.filename = (prefix || '') + '[name]-[hash].js';
}
else {
ret.filename = (prefix || '') + '[name].js';
}
return ret;
}
function webpackAlias() {
return {
// modernizr$: path.resolve(__dirname, "../../.modernizrrc"), // TODO: Enable this?
};
}
function webpackRules(env, srcDir, postcssConfigFile) {
return [{
test: /\.(jsx|js)?$/,
use: 'babel-loader'
},
{
test: /\.(tsx|ts)?$/,
use: 'ts-loader',
// exclude: /node_modules/,
// options: {
// compilerOptions: {
// "sourceMap": !isProduction,
// },
// },
},
{
test: /\.ejs$/,
use: {
loader: 'ejs-compiled-loader',
options: {
// beautify: true,
htmlmin: true,
// htmlminOptions: {
// removeComments: true,
// collapseWhitespace: true,
// preserveLineBreaks: true
// }
}
}
},
{
test: /\.(sa|sc|c)ss$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
// { loader: 'development' === env ? MiniCssExtractPlugin.loader : 'style-loader' }, // Use inline <style> tag.
{ loader: 'css-loader', options: { importLoaders: 1 } },
{ loader: 'postcss-loader', options: { postcssOptions: { config: postcssConfigFile } } },
{ loader: 'sass-loader' },
],
},
{
test: /\.module\.(sa|sc|c)ss$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
// { loader: 'development' === env ? MiniCssExtractPlugin.loader : 'style-loader' }, // Use inline <style> tag.
{ loader: 'css-loader', options: { importLoaders: 1, modules: true, onlyLocals: false } },
{ loader: 'postcss-loader', options: { postcssOptions: { config: postcssConfigFile } } },
{ loader: 'sass-loader' },
]
},
{
test: /\.(png|jpe?g|gif)(\?\S*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 1024,
fallback: 'file-loader',
name: function (file) {
return '.' + path.join(file.replace(srcDir, ''), '..').replace(/\\/g, '/') + '/[name].[ext]';
},
},
},
},
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
/*issuer: {
test: /\.jsx?$/
},*/
use: ['babel-loader', '@svgr/webpack', 'url-loader']
},
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url-loader'
},
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'file-loader',
options: {
name: function (file) {
return '.' + path.join(file.replace(srcDir, ''), '..').replace(/\\/g, '/') + '/[name].[ext]';
},
}
}]
},
{
test: /\.modernizrrc.js$/,
use: 'modernizr-loader',
},
{
test: /\.modernizrrc(\.json)?$/,
use: ['modernizr-loader', 'json-loader'],
}];
}
function webpackPlugins(env, srcDir, pages, cssSrc) {
var ret = [
new DefinePlugin({ "process.env": JSON.stringify(dotenv.parsed) }),
new NodePolyfillPlugin(),
new MyHtmlBeautifyWebpackPlugin(),
];
if ('development' !== env) {
ret.push(new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, '../../../src/static/lib'),
to: path.resolve(__dirname, '../../../' + env + '/static/lib'),
},
{
from: path.resolve(__dirname, '../../../src/static/images'),
to: path.resolve(__dirname, '../../../' + env + '/static/images'),
},
{
from: path.resolve(__dirname, '../../../src/static/favicons'),
to: path.resolve(__dirname, '../../../' + env + '/static/favicons'),
},
{
from: path.resolve(__dirname, '../../../src/static/css/_extra.css'),
to: path.resolve(__dirname, '../../../' + env + '/static/css/_extra.css'),
},
],
}));
}
var virtualPages = {};
var file;
for (var k in pages) {
if ('production' !== env || !pages[k].buildExclude) {
file = path.resolve(srcDir + '/' + k + '.js');
if ((void 0 !== pages[k].staticPage && pages[k].staticPage) || void 0 === pages[k].render) {
virtualPages[file] = '';
}
else {
virtualPages[file] = pages[k].render;
}
}
if ('development' === env) {
// Export pages HTML files.
ret.push(new HtmlWebpackPlugin(__assign({ template: path.resolve(__dirname, '../templates/index.ejs'), hash: false, chunks: [k] }, pages[k])));
}
}
ret.push(new VirtualModulesPlugin(virtualPages));
ret.push(new MiniCssExtractPlugin({
ignoreOrder: true,
// filename: ! is_build ? '[name].css' : '[name].[hash].css',
// chunkFilename: ! is_build ? '[id].css' : '[id].[hash].css',
filename: cssSrc + '[name].css',
// chunkFilename: "../css/[id].css",
}));
if ('development' !== env) {
ret.push(new LimitChunkCountPlugin({ maxChunks: 1 }));
ret.push(new ProgressBarPlugin({
clear: false,
}));
}
if ('production' === env) {
ret.push(new CssMinimizerPlugin({
cache: true,
minimizerOptions: {
preset: [
'default',
{
discardComments: { removeAll: true },
},
],
},
}));
}
return ret;
}
function generateConfig(env, config) {
var srcDir = config.src;
var buildDir = config.build + '/' + env + ('development' === env ? '' : '/static');
var cssbuild = './css/';
var jsbuild = './js/';
var configPages = config.pages;
var configPagesKeys = config.pages ? Object.keys(configPages) : [];
var defPages = pagesConfig(configPagesKeys);
var pages = formatPagesConfig({ title: '', filename: '', render: '', html: config.html, window: config.window }, __assign(__assign({}, configPages), defPages));
var ret = {
entry: webpackEntry(env, srcDir, pages),
output: 'development' === env ? webpackOutput(env, srcDir, void 0, void 0, void 0) : webpackOutput(env, buildDir, jsbuild, void 0, void 0),
plugins: webpackPlugins(env, srcDir, pages, cssbuild),
module: {
rules: webpackRules(env, srcDir, config.postcssConfigFile),
},
resolve: {
alias: webpackAlias(),
extensions: ['.tsx', '.ts', '.jsx', '.js'],
},
};
return ret;
}
var isAbsolutePath$2 = require('path').isAbsolute;
var webpack$2 = require('webpack');
var webpackFormatMessages$1 = require('webpack-format-messages');
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
var defaultOptions$2 = {
env: 'production',
host: '127.0.0.1',
port: 8888,
mode: 'static',
config: config$3,
};
function analyzer(analyzerOptions) {
if (analyzerOptions === void 0) { analyzerOptions = defaultOptions$2; }
var options = __assign(__assign({}, defaultOptions$2), analyzerOptions);
options.config = __assign(__assign({}, defaultOptions$2.config), analyzerOptions.config);
var config = generateConfig(options.env, options.config);
if (!isAbsolutePath$2(options.config.src)) {
throw Error('"src" is not an absolute path');
}
if (!isAbsolutePath$2(options.config.build)) {
throw Error('"build" is not an absolute path');
}
if (!isAbsolutePath$2(options.config.postcssConfigFile)) {
throw Error('"postcssConfigFile" is not an absolute path');
}
var analyzerConfig = {
analyzerMode: options.mode,
analyzerHost: options.host,
analyzerPort: options.port,
generateStatsFile: 'server' !== options.mode,
startAnalyzer: 'server' === options.mode,
statsFilename: 'analyzer-stats.json',
reportFilename: 'analyzer-report.html',
};
var compiler = 'dist' === options.env ? webpack$2(__assign(__assign({}, config$1), config)) : webpack$2(__assign(__assign({}, config$2), config));
var analyzer = new BundleAnalyzerPlugin(analyzerConfig);
analyzer.apply(compiler);
compiler.run(function (err, stats) {
if (err)
throw err;
var messages = webpackFormatMessages$1(stats);
if (!messages.errors.length && !messages.warnings.length) {
console.log('Compiled successfully!', '\n');
}
if (messages.errors.length) {
console.log('Failed to compile.', '\n');
for (var _i = 0, _a = messages.errors; _i < _a.length; _i++) {
var m = _a[_i];
console.log(m);
}
}
else if (messages.warnings.length) {
console.log('Compiled with warnings.', '\n');
for (var _b = 0, _c = messages.warnings; _b < _c.length; _b++) {
var m = _c[_b];
console.log(m);
}
}
});
}
var isAbsolutePath$1 = require('path').isAbsolute;
var webpack$1 = require('webpack');
var webpackFormatMessages = require('webpack-format-messages');
var defaultOptions$1 = {
env: 'production',
config: config$3,
};
function build(buildOptions) {
if (buildOptions === void 0) { buildOptions = defaultOptions$1; }
var options = __assign(__assign({}, defaultOptions$1), buildOptions);
options.config = __assign(__assign({}, defaultOptions$1.config), buildOptions.config);
if (!isAbsolutePath$1(options.config.src)) {
throw Error('"src" is not an absolute path');
}
if (!isAbsolutePath$1(options.config.build)) {
throw Error('"build" is not an absolute path');
}
if (!isAbsolutePath$1(options.config.postcssConfigFile)) {
throw Error('"postcssConfigFile" is not an absolute path');
}
var config = generateConfig(options.env, options.config);
var compiler = 'dist' === options.env ? webpack$1(__assign(__assign({}, config$1), config)) : webpack$1(__assign(__assign({}, config$2), config));
compiler.run(function (err, stats) {
if (err)
throw err;
var messages = webpackFormatMessages(stats);
if (!messages.errors.length && !messages.warnings.length) {
console.log('Compiled successfully!', '\n');
}
if (messages.errors.length) {
console.log('Failed to compile.', '\n');
for (var _i = 0, _a = messages.errors; _i < _a.length; _i++) {
var m = _a[_i];
console.log(m);
}
}
else if (messages.warnings.length) {
console.log('Compiled with warnings.', '\n');
for (var _b = 0, _c = messages.warnings; _b < _c.length; _b++) {
var m = _c[_b];
console.log(m);
}
}
});
}
var config = {
mode: 'development',
// devtool: 'eval',
// devtool: 'source-map',
// devtool: 'eval-cheap-source-map',
optimization: {
minimize: false
}
};
function configFunc(contentBase) {
return {
watchOptions: {
poll: true,
},
contentBase: contentBase,
compress: true,
hot: true
};
}
var isAbsolutePath = require('path').isAbsolute;
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var defaultOptions = {
env: 'development',
host: '0.0.0.0',
port: 8080,
config: config$3,
};
function dev(devOptions) {
if (devOptions === void 0) { devOptions = defaultOptions; }
var options = __assign(__assign({}, defaultOptions), devOptions);
options.config = __assign(__assign({}, defaultOptions.config), devOptions.config);
var config$1 = generateConfig(options.env, options.config);
if (!isAbsolutePath(options.config.src)) {
throw Error('"src" is not an absolute path');
}
if (!isAbsolutePath(options.config.build)) {
throw Error('"build" is not an absolute path');
}
if (!isAbsolutePath(options.config.postcssConfigFile)) {
throw Error('"postcssConfigFile" is not an absolute path');
}
var compilerConfig = __assign(__assign({}, config), config$1);
var serverOptions = configFunc(options.config.src);
WebpackDevServer.addDevServerEntrypoints(compilerConfig, serverOptions);
var compiler = webpack(compilerConfig);
var server = new WebpackDevServer(compiler, serverOptions);
server.listen(options.port, options.host, function (err) {
if (err)
throw err;
});
}
exports.analyzer = analyzer;
exports.build = build;
exports.dev = dev;

View File

@ -0,0 +1,99 @@
import { Configuration } from 'webpack';
/*const chunksCacheGroups_0 = {
commons: {
test: /[\\/]src[\\/]/,
name: "_commons",
chunks: "all",
enforce: true,
reuseExistingChunk: true,
},
};*/
/*const chunksCacheGroups_1 = {
commons: {
test: /[\\/]src[\\/]/,
name: "_commons",
// priority: -10,
chunks: "all",
enforce: true,
reuseExistingChunk: true,
},
vendors: {
test: /[\\/]node_modules[\\/]/,
// test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
// test: /[\\/]node_modules[\\/](!MediaCmsPlayer)[\\/]/,
name: "_vendors",
// priority: -20,
chunks: "all",
enforce: true,
// reuseExistingChunk: true,
},
};*/
/*const chunksCacheGroups_2 = {
commons: {
minChunks: 2,
// maxInitialRequests: 8, // @note: Tested values from 0 to 10, and changes applied with values 0, 4, 5, 6, 7, 8.
// minSize: 0,
name: "_commons",
chunks: "all",
enforce: true,
reuseExistingChunk: true,
},
};*/
/*const chunksCacheGroups_3 = {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: "_commons",
priority: 1,
chunks: "initial",
},
};*/
export const config: Configuration = {
mode: 'production',
devtool: 'source-map',
optimization: {
runtimeChunk: false,
/*splitChunks: {
// minSize: 1000000,
chunks: 'all',
automaticNameDelimiter: '-',
},*/
/*splitChunks: {
// minSize: 1000000,
chunks: 'all',
automaticNameDelimiter: '-',
cacheGroups: chunksCacheGroups_0,
},*/
/*splitChunks: {
chunks: 'all',
automaticNameDelimiter: '-',
cacheGroups: chunksCacheGroups_1,
},*/
/*splitChunks: {
chunks: 'all',
automaticNameDelimiter: '-',
cacheGroups: chunksCacheGroups_2,
},*/
/*splitChunks: {
chunks: 'all',
automaticNameDelimiter: '-',
cacheGroups: chunksCacheGroups_3,
},*/
splitChunks: {
chunks: 'all',
automaticNameDelimiter: '-',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: '_commons',
priority: 1,
chunks: 'initial',
},
},
},
},
};

View File

@ -0,0 +1,12 @@
import { Configuration } from 'webpack-dev-server';
export function configFunc(contentBase: string): Configuration {
return {
watchOptions: {
poll: true,
},
contentBase: contentBase,
compress: true,
hot: true,
};
}

View File

@ -0,0 +1,11 @@
import { Configuration } from 'webpack';
export const config: Configuration = {
mode: 'development',
// devtool: 'eval',
// devtool: 'source-map',
// devtool: 'eval-cheap-source-map',
optimization: {
minimize: false,
},
};

View File

@ -0,0 +1,73 @@
import { Configuration } from 'webpack';
/*const chunksCacheGroups_0 = {
commons: {
test: /[\\/]src[\\/]/,
name: "_commons",
chunks: "all",
enforce: true,
reuseExistingChunk: true,
},
};
const chunksCacheGroups_1 = {
commons: {
test: /[\\/]src[\\/]/,
name: "_commons",
// priority: -10,
chunks: "all",
enforce: true,
reuseExistingChunk: true,
},
vendors: {
test: /[\\/]node_modules[\\/]/,
// test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
// test: /[\\/]node_modules[\\/](!MediaCmsPlayer)[\\/]/,
name: "_vendors",
// priority: -20,
chunks: "all",
enforce: true,
// reuseExistingChunk: true,
},
};
const chunksCacheGroups_2 = {
commons: {
minChunks: 2,
// maxInitialRequests: 8, // @note: Tested values from 0 to 10, and changes applied with values 0, 4, 5, 6, 7, 8.
// minSize: 0,
name: "_commons",
chunks: "all",
enforce: true,
reuseExistingChunk: true,
},
};
const chunksCacheGroups_3 = {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: "_commons",
priority: 1,
chunks: "initial",
},
};*/
export const config: Configuration = {
mode: 'production',
optimization: {
minimize: true,
runtimeChunk: false,
splitChunks: {
chunks: 'all',
automaticNameDelimiter: '-',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: '_commons',
priority: 1,
chunks: 'initial',
},
},
},
},
};

View File

@ -0,0 +1,114 @@
function bodySnippet(id:string) {
return '<div id="' + id + '"></div>';
}
interface ConfigHtmlHead{
meta?: { [key: string]: string }[],
links?: { [key: string]: string }[],
scripts?: { [key: string]: string }[],
}
interface ConfigHtmlBody{
scripts: { [key: string]: string }[],
snippet: string,
}
export interface ConfigHtml{
head: ConfigHtmlHead,
body: ConfigHtmlBody,
}
export interface ConfigPages{
[key: string]: ConfigPage
}
export interface ConfigWindow{
[key: string ]: unknown
}
export interface ConfigType {
src: string,
build: string,
html: ConfigHtml,
pages: ConfigPages,
window: ConfigWindow,
postcssConfigFile: string,
}
export interface ConfigPage{
staticPage: boolean,
buildExclude: boolean,
title: string,
filename: string,
html: ConfigHtml,
window: ConfigWindow,
render: string,
}
const homePage: ConfigPage = {
staticPage: true,
buildExclude: false,
title: 'Home',
filename: 'index.html',
html: {
head: {},
body: {
scripts: [],
snippet: bodySnippet('page-home'),
}
},
window: {},
render: 'import { renderPage } from \'./js/helpers\'; import { HomePage } from \'./js/pages/HomePage\'; renderPage( \'page-home\', HomePage );',
};
const errorPage: ConfigPage = {
staticPage: true,
buildExclude: false,
title: 'Error',
filename: 'error.html',
html: {
head: {},
body: {
scripts: [],
snippet: bodySnippet('page-error'),
}
},
window: {},
render: 'import { renderPage } from \'./js/helpers\'; import { ErrorPage } from \'./js/pages/ErrorPage\'; renderPage( \'page-error\', ErrorPage );',
};
const pages: { [key: string]: ConfigPage } = {
home: homePage,
error: errorPage,
};
const htmlHead: ConfigHtmlHead = {
meta: [
{ charset: 'utf-8' },
{ content: 'ie=edge', 'http-equiv': 'x-ua-compatible' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
links: [],
scripts: [],
};
const htmlBody: ConfigHtmlBody = {
scripts: [],
snippet: '',
};
const html: ConfigHtml = {
head: htmlHead,
body: htmlBody,
};
export const config : ConfigType = {
src: '',
build: '',
pages,
html,
window: {},
postcssConfigFile: '',
};
export default config;

View File

@ -0,0 +1,21 @@
import { ConfigType } from '../config';
export interface DevOptionsType {
env: string,
host: string,
port: number,
config: ConfigType,
}
export interface BuildOptionsType {
env: string,
config: ConfigType,
}
export interface AnalyzerOptionsType {
env: string,
host: string,
port: number,
mode: string,
config: ConfigType,
}

View File

@ -0,0 +1,69 @@
const merge = require('lodash.merge');
import { ConfigHtml, ConfigPages, ConfigWindow } from '../config';
function validateBoolean(value?: boolean | 0 | 1, defaultValue = false): boolean {
if (true === value || false === value) {
return value;
}
if (0 === value || 1 === value) {
return !!value;
}
return defaultValue;
}
function validateString(value?: string, defaultValue = ''): string {
return value ? value : defaultValue;
}
function getArrayType(sourcesArr?: Array<{ [key: string]: string }>, pageArr: Array<{ [key: string]: string }> = []): Array<{ [key: string]: string }> {
if ((!sourcesArr || !sourcesArr.length) && (!pageArr || !pageArr.length)) {
return [];
}
if (sourcesArr && sourcesArr.length && pageArr && pageArr.length) {
return sourcesArr.concat(pageArr);
}
if (sourcesArr && sourcesArr.length) {
return sourcesArr;
}
return pageArr;
}
function formatPagesConfig(sources: { title: string, filename: string, render: string, html: ConfigHtml, window: ConfigWindow }, pages: ConfigPages): ConfigPages {
const ret: ConfigPages = {};
for (const pk in pages) {
ret[pk] = {
staticPage: validateBoolean(pages[pk].staticPage, false),
buildExclude: validateBoolean(pages[pk].buildExclude, false),
title: validateString(pages[pk].title, sources.title),
filename: validateString(pages[pk].filename, sources.filename),
html: {
head: {
meta: getArrayType(sources.html.head.meta, pages[pk].html.head.meta),
links: getArrayType(sources.html.head.links, pages[pk].html.head.links),
scripts: getArrayType(sources.html.head.scripts, pages[pk].html.head.scripts),
},
body: {
scripts: getArrayType(sources.html.body.scripts, pages[pk].html.body.scripts),
snippet: validateString(pages[pk].html.body.snippet, sources.html.body.snippet),
},
},
window: merge({}, sources.window, pages[pk].window),
render: validateString(sources.render, pages[pk].render),
};
}
return ret;
}
export default formatPagesConfig;

View File

@ -0,0 +1,308 @@
const path = require('path');
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
// Webpack plugins.
const { DefinePlugin } = require('webpack');
const { LimitChunkCountPlugin } = require('webpack').optimize;
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VirtualModulesPlugin = require('webpack-virtual-modules');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const CopyPlugin = require("copy-webpack-plugin");
var dotenv = require('dotenv').config({ path: path.resolve(__dirname + '../../../../.env') });
import MyHtmlBeautifyWebpackPlugin from '../webpack-plugins/MyHtmlBeautifyWebpackPlugin';
import { ConfigType, ConfigPages } from '../config';
import defaultPages from './pagesConfig';
import formatPagesConfig from './formatPagesConfig';
function webpackEntry(env: string, srcDir: string, pages: ConfigPages) {
const ret: { [key: string]: string } = {};
for (const p in pages) {
if ('development' === env || !pages[p].buildExclude) {
ret[p] = path.resolve(srcDir + '/' + p + '.js');
}
}
return ret;
}
function webpackOutput(env: string, destinationDir: string, buildDir?: string, chunkhash?: string, hash?: string) {
const ret = {
path: destinationDir,
filename: '',
};
const prefix = 'development' === env ? '' : buildDir;
let tmp;
if (undefined !== chunkhash) {
tmp = chunkhash.trim();
if ('' === tmp) {
throw Error('Invalid chunkhash argument value: ' + chunkhash);
}
ret.filename = (prefix || '') + '[name]-[chunkhash].js';
}
else if (undefined !== hash) {
tmp = hash.trim();
if ('' === tmp) {
throw Error('Invalid hash argument value: ' + hash);
}
ret.filename = (prefix || '') + '[name]-[hash].js';
}
else {
ret.filename = (prefix || '') + '[name].js';
}
return ret;
}
function webpackAlias() {
return {
// modernizr$: path.resolve(__dirname, "../../.modernizrrc"), // TODO: Enable this?
};
}
function webpackRules(env: string, srcDir: string, postcssConfigFile: string): any[] {
return [{
test: /\.(jsx|js)?$/,
use: 'babel-loader'
},
{
test: /\.(tsx|ts)?$/,
use: 'ts-loader',
// exclude: /node_modules/,
// options: {
// compilerOptions: {
// "sourceMap": !isProduction,
// },
// },
},
{
test: /\.ejs$/,
use: {
loader: 'ejs-compiled-loader',
options: {
// beautify: true,
htmlmin: true,
// htmlminOptions: {
// removeComments: true,
// collapseWhitespace: true,
// preserveLineBreaks: true
// }
}
}
},
{
test: /\.(sa|sc|c)ss$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
// { loader: 'development' === env ? MiniCssExtractPlugin.loader : 'style-loader' }, // Use inline <style> tag.
{ loader: 'css-loader', options: { importLoaders: 1 } },
{ loader: 'postcss-loader', options: { postcssOptions: { config: postcssConfigFile } } },
{ loader: 'sass-loader' },
],
},
{
test: /\.module\.(sa|sc|c)ss$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
// { loader: 'development' === env ? MiniCssExtractPlugin.loader : 'style-loader' }, // Use inline <style> tag.
{ loader: 'css-loader', options: { importLoaders: 1, modules: true, onlyLocals: false } },
{ loader: 'postcss-loader', options: { postcssOptions: { config: postcssConfigFile } } },
{ loader: 'sass-loader' },
]
},
{
test: /\.(png|jpe?g|gif)(\?\S*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 1024,
fallback: 'file-loader',
name: (file: string) => {
return '.' + path.join(file.replace(srcDir, ''), '..').replace(/\\/g, '/') + '/[name].[ext]';
},
},
},
},
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
/*issuer: {
test: /\.jsx?$/
},*/
use: ['babel-loader', '@svgr/webpack', 'url-loader']
},
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url-loader'
},
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'file-loader',
options: {
name: (file: string) => {
return '.' + path.join(file.replace(srcDir, ''), '..').replace(/\\/g, '/') + '/[name].[ext]';
},
}
}]
},
{
test: /\.modernizrrc.js$/,
use: 'modernizr-loader',
},
{
test: /\.modernizrrc(\.json)?$/,
use: ['modernizr-loader', 'json-loader'],
}
];
}
function webpackPlugins(env: string, srcDir: string, pages: ConfigPages, cssSrc: string) {
const ret = [
new DefinePlugin({ "process.env": JSON.stringify(dotenv.parsed) }),
new NodePolyfillPlugin(),
new MyHtmlBeautifyWebpackPlugin(),
];
if ('development' !== env) {
ret.push(
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, '../../../src/static/lib'),
to: path.resolve(__dirname, '../../../' + env + '/static/lib'),
},
{
from: path.resolve(__dirname, '../../../src/static/images'),
to: path.resolve(__dirname, '../../../' + env + '/static/images'),
},
{
from: path.resolve(__dirname, '../../../src/static/favicons'),
to: path.resolve(__dirname, '../../../' + env + '/static/favicons'),
},
{
from: path.resolve(__dirname, '../../../src/static/css/_extra.css'),
to: path.resolve(__dirname, '../../../' + env + '/static/css/_extra.css'),
},
],
})
);
}
const virtualPages: { [key: string]: string } = {};
let file: string;
for (const k in pages) {
if ('production' !== env || !pages[k].buildExclude) {
file = path.resolve(srcDir + '/' + k + '.js');
if ((void 0 !== pages[k].staticPage && pages[k].staticPage) || void 0 === pages[k].render) {
virtualPages[file] = '';
} else {
virtualPages[file] = pages[k].render;
}
}
if ('development' === env) {
// Export pages HTML files.
ret.push(new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../templates/index.ejs'),
hash: false,
chunks: [k],
...pages[k],
}));
}
}
ret.push(new VirtualModulesPlugin(virtualPages));
ret.push(new MiniCssExtractPlugin({
ignoreOrder: true, // TODO: Remove it...
// filename: ! is_build ? '[name].css' : '[name].[hash].css',
// chunkFilename: ! is_build ? '[id].css' : '[id].[hash].css',
filename: cssSrc + '[name].css',
// chunkFilename: "../css/[id].css",
}));
if ('development' !== env) {
ret.push(new LimitChunkCountPlugin({ maxChunks: 1 }));
ret.push(
new ProgressBarPlugin({
clear: false,
})
);
}
if ('production' === env) {
ret.push(new CssMinimizerPlugin({
cache: true, // TODO: Ignore in Webpack 5. Use https://webpack.js.org/configuration/other-options/#cache.
minimizerOptions: {
preset: [
'default',
{
discardComments: { removeAll: true },
},
],
},
}));
}
return ret;
}
export default function generateConfig(env: string, config: ConfigType) {
const srcDir = config.src;
const buildDir = config.build + '/' + env + ('development' === env ? '' : '/static');
const cssbuild = './css/';
const jsbuild = './js/';
const configPages = config.pages;
const configPagesKeys = config.pages ? Object.keys(configPages) : [];
const defPages = defaultPages(configPagesKeys);
const pages = formatPagesConfig(
{ title: '', filename: '', render: '', html: config.html, window: config.window },
{ ...configPages, ...defPages }
);
const ret = {
entry: webpackEntry(env, srcDir, pages),
output: 'development' === env ? webpackOutput(env, srcDir, void 0, void 0, void 0) : webpackOutput(env, buildDir, jsbuild, void 0, void 0),
plugins: webpackPlugins(env, srcDir, pages, cssbuild),
module: {
rules: webpackRules(env, srcDir, config.postcssConfigFile),
},
resolve: {
alias: webpackAlias(),
extensions: ['.tsx', '.ts', '.jsx', '.js'],
},
};
return ret;
}

View File

@ -0,0 +1,37 @@
const fs = require('fs');
const path = require('path');
const ejs = require('ejs');
const templatePath = path.join(__dirname, '../templates');
const sitemapTemplatePath = path.join(templatePath, 'sitemap.ejs');
const sitemapTemplate = ejs.compile(fs.readFileSync(sitemapTemplatePath, 'utf8'), { root: [templatePath], filename: sitemapTemplatePath, outputFunctionName: 'echo' });
import { ConfigPages } from '../config';
export default function pagesConfig(pagesKeys: string[]): ConfigPages {
const pages: ConfigPages = {};
if (-1 === pagesKeys.indexOf('sitemap')) {
pages.sitemap = {
staticPage: true,
buildExclude: true,
title: 'Sitemap',
filename: 'sitemap.html',
html: {
head: {},
body: {
scripts: [],
snippet: sitemapTemplate({ pages: [...pagesKeys, ...Object.keys(pages)] }),
},
},
window: {},
render: ''
};
}
return pages;
}

View File

@ -0,0 +1,74 @@
/**
* @see {link: https://github.com/seeyoulater/html-beautify-webpack-plugin/blob/master/index.js}
*/
const prettify = require('html-prettify');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WebpackError = require( 'webpack/lib/WebpackError.js' );
import { Compiler, Compilation } from 'webpack';
interface OptionsConfigHtml {
end_with_newline: boolean,
indent_inner_html: boolean,
preserve_newlines: boolean,
max_preserve_newlines: number,
}
interface OptionsConfig {
indent_size: number,
indent_with_tabs: boolean,
html: OptionsConfigHtml
}
interface Options{
config: OptionsConfig,
replace: Array<string|RegExp>
}
interface HtmlWebpackPluginArgs{
html: string,
plugin: typeof HtmlWebpackPlugin,
outputName: string
}
function htmlPluginDataFunction (pluginData: HtmlWebpackPluginArgs, options: Options, callback: (err:typeof WebpackError, arg1: HtmlWebpackPluginArgs) => void) {
pluginData.html = prettify(
options.replace.reduce( (res:string, item: string | RegExp) => res.replace( item instanceof RegExp ? new RegExp(item, 'gi') : item, '' ), pluginData.html )/*,
options.config*/
);
callback(null, pluginData);
}
export default class MyHtmlBeautifyWebpackPlugin {
apply(compiler: Compiler): void {
const options: Options = {
config: { // TODO: Remove it.
indent_size: 4,
indent_with_tabs: false,
html: {
end_with_newline: true,
indent_inner_html: true,
preserve_newlines: true,
max_preserve_newlines: 0,
}
},
replace: []
};
function tapAsyncCallback(pluginData: HtmlWebpackPluginArgs, callback: (err:typeof WebpackError, arg1: HtmlWebpackPluginArgs) => void ){
return htmlPluginDataFunction (pluginData, options, callback);
}
function tapHookCallback(compilation: Compilation){
return HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync( 'MyHtmlBeautifyWebpackPlugin', tapAsyncCallback );
}
compiler.hooks.compilation.tap( 'MyHtmlBeautifyWebpackPlugin', tapHookCallback );
}
}

10148
frontend/packages/scripts/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,86 @@
{
"name": "mediacms-scripts",
"version": "0.9.0",
"description": "",
"author": "",
"license": "",
"engines": {
"node": ">=14.17.0"
},
"files": [
"scripts",
"cli.js"
],
"scripts": {
"start": "npx rollup -w -c ./config/watch.config.js",
"build": "npx rollup -c ./config/build.config.js",
"visual": "npx rollup -c ./config/visual.config.js"
},
"bin": {
"mediacms-scripts": "./cli.js"
},
"types": "",
"devDependencies": {
"@babel/core": "^7.14.5",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-object-rest-spread": "^7.14.5",
"@babel/preset-env": "^7.14.5",
"@babel/preset-typescript": "^7.14.5",
"@rollup/plugin-node-resolve": "^13.0.0",
"@rollup/plugin-typescript": "^8.2.1",
"@types/react": "^17.0.11",
"@types/react-dom": "^17.0.7",
"axios": "^0.21.1",
"babel-core": "^6.26.3",
"copy-webpack-plugin": "^9.0.0",
"cross-spawn": "^7.0.3",
"dotenv": "^10.0.0",
"rimraf": "^3.0.2",
"rollup": "^2.51.2",
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-typescript2": "^0.30.0",
"rollup-plugin-visualizer": "^5.5.0",
"serialize-javascript": "^5.0.1",
"source-map-loader": "^3.0.0",
"ts-loader": "^9.2.3",
"typescript": "^4.3.2"
},
"dependencies": {
"@svgr/webpack": "^5.5.0",
"@types/webpack": "^5.28.0",
"@types/webpack-bundle-analyzer": "^4.4.0",
"@types/webpack-dev-server": "^3.11.4",
"autoprefixer": "^10.2.6",
"babel-loader": "^8.2.2",
"css-loader": "^5.2.6",
"css-minimizer-webpack-plugin": "^3.0.1",
"ejs": "^3.1.6",
"ejs-compiled-loader": "^3.1.0",
"ejs-loader": "^0.5.0",
"file-loader": "^6.2.0",
"html-prettify": "^1.0.3",
"html-webpack-plugin": "^5.3.1",
"json-loader": "^0.5.7",
"lodash.merge": "^4.6.2",
"mini-css-extract-plugin": "^1.6.0",
"node-polyfill-webpack-plugin": "^1.1.2",
"node-sass": "^6.0.0",
"postcss": "^8.3.2",
"postcss-import": "^14.0.2",
"postcss-loader": "^6.1.0",
"postcss-modules": "^4.1.3",
"postcss-nested": "^5.0.5",
"postcss-scss": "^3.0.5",
"progress-bar-webpack-plugin": "^2.1.0",
"sass-loader": "^12.1.0",
"style-loader": "^2.0.0",
"url-loader": "^4.1.1",
"webpack": "^5.38.1",
"webpack-bundle-analyzer": "^4.4.2",
"webpack-dev-server": "^3.11.2",
"webpack-format-messages": "^2.0.6",
"webpack-virtual-modules": "^0.4.3"
},
"peerDependenciesMeta": {},
"browserslist": {}
}

View File

@ -0,0 +1,5 @@
const { analyzer } = require('../dist/webpack-dev-env.js');
const parseCliArgs = require('./utils/parseCliArgs.js');
const { validateAnalyzerOptions } = require('./utils/validateOptions.js');
const options = validateAnalyzerOptions(parseCliArgs(process.argv.slice(2)));
analyzer(options);

View File

@ -0,0 +1,5 @@
const { build } = require('../dist/webpack-dev-env.js');
const parseCliArgs = require('./utils/parseCliArgs.js');
const { validateBuildOptions } = require('./utils/validateOptions.js');
const options = validateBuildOptions(parseCliArgs(process.argv.slice(2)));
build(options);

View File

@ -0,0 +1,5 @@
const { dev } = require('../dist/webpack-dev-env.js');
const parseCliArgs = require('./utils/parseCliArgs.js');
const { validateDevOptions } = require('./utils/validateOptions.js');
const options = validateDevOptions(parseCliArgs(process.argv.slice(2)));
dev(options);

View File

@ -0,0 +1,20 @@
var fs = require('fs');
var path = require('path');
var cliArgs = process.argv.slice(2);
function mkdir_callback(err) {
if (err) {
throw err;
}
}
var i, dir;
for (i = 0; i < cliArgs.length; i++) {
dir = path.resolve(cliArgs[i]);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, mkdir_callback);
}
}

View File

@ -0,0 +1,21 @@
var fs = require('fs');
var path = require('path');
var rimraf = require('rimraf');
var cliArgs = process.argv.slice(2);
function rmdir_callback(err) {
if (err) {
throw err;
}
}
var i, dir;
for (i = 0; i < cliArgs.length; i++) {
dir = path.resolve(cliArgs[i]);
if (fs.existsSync(dir)) {
rimraf.sync(dir, {}, rmdir_callback);
}
}

View File

@ -0,0 +1,21 @@
function parseCliArgsFn(cliArgs) {
const ret = {};
let arr;
for (let arg of cliArgs) {
arr = arg.split('--');
if (2 === arr.length) {
arr = arr[1].split('=');
if (2 === arr.length) {
ret[arr[0]] = arr[1];
}
}
}
return ret;
}
module.exports = parseCliArgsFn;

View File

@ -0,0 +1,94 @@
const fs = require('fs');
const path = require('path');
function validateDevOptions(options) {
if (void 0 !== options.config) {
if ('string' !== typeof options.config) {
throw Error('Invalid configuration file: ' + options.config);
}
options.config = path.resolve(options.config);
if (!fs.existsSync(options.config)) {
throw Error('Invalid configuration file: ' + options.config);
}
options.config = require(path.resolve(options.config));
}
if (void 0 !== options.env) {
// TODO!
}
if (void 0 !== options.host) {
// TODO!
}
if (void 0 !== options.port) {
// TODO!
}
return options;
}
function validateBuildOptions(options) {
if (void 0 !== options.config) {
if ('string' !== typeof options.config) {
throw Error('Invalid configuration file: ' + options.config);
}
options.config = path.resolve(options.config);
if (!fs.existsSync(options.config)) {
throw Error('Invalid configuration file: ' + options.config);
}
options.config = require(path.resolve(options.config));
}
if (void 0 !== options.env) {
// TODO!
}
return options;
}
function validateAnalyzerOptions(options) {
if (void 0 !== options.config) {
if ('string' !== typeof options.config) {
throw Error('Invalid configuration file: ' + options.config);
}
options.config = path.resolve(options.config);
if (!fs.existsSync(options.config)) {
throw Error('Invalid configuration file: ' + options.config);
}
options.config = require(path.resolve(options.config));
}
if (void 0 !== options.env) {
// TODO!
}
if (void 0 !== options.host) {
// TODO!
}
if (void 0 !== options.port) {
// TODO!
}
if (void 0 !== options.mode) {
// TODO!
}
return options;
}
module.exports = {
validateDevOptions,
validateBuildOptions,
validateAnalyzerOptions,
};

View File

@ -0,0 +1,84 @@
const isAbsolutePath = require('path').isAbsolute;
const webpack = require('webpack');
const webpackFormatMessages = require('webpack-format-messages');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
import { config as defaultConfig } from '../lib/config';
import { AnalyzerOptionsType } from '../lib/interfaces/OptionsTypes';
import { config as buildWebpackConfig } from '../lib/.webpack/build.config';
import { config as distWebpackConfig } from '../lib/.webpack/dist.config';
import generateConfig from '../lib/webpack-helpers/generateConfig';
const defaultOptions: AnalyzerOptionsType = {
env: 'production',
host: '127.0.0.1',
port: 8888,
mode: 'static',
config: defaultConfig,
};
export function analyzer(analyzerOptions: AnalyzerOptionsType = defaultOptions): void {
const options: AnalyzerOptionsType = { ...defaultOptions, ...analyzerOptions };
options.config = { ...defaultOptions.config, ...analyzerOptions.config };
const config = generateConfig(options.env, options.config);
if (!isAbsolutePath(options.config.src)) {
throw Error('"src" is not an absolute path');
}
if (!isAbsolutePath(options.config.build)) {
throw Error('"build" is not an absolute path');
}
if (!isAbsolutePath(options.config.postcssConfigFile)) {
throw Error('"postcssConfigFile" is not an absolute path');
}
const analyzerConfig = {
analyzerMode: options.mode,
analyzerHost: options.host,
analyzerPort: options.port,
generateStatsFile: 'server' !== options.mode,
startAnalyzer: 'server' === options.mode,
statsFilename: 'analyzer-stats.json',
reportFilename: 'analyzer-report.html',
};
const compiler =
'dist' === options.env
? webpack({ ...distWebpackConfig, ...config })
: webpack({ ...buildWebpackConfig, ...config });
const analyzer = new BundleAnalyzerPlugin(analyzerConfig);
analyzer.apply(compiler);
compiler.run((err?: Error, stats?: any) => {
if (err) throw err;
const messages = webpackFormatMessages(stats);
if (!messages.errors.length && !messages.warnings.length) {
console.log('Compiled successfully!', '\n');
}
if (messages.errors.length) {
console.log('Failed to compile.', '\n');
for (const m of messages.errors) {
console.log(m);
}
} else if (messages.warnings.length) {
console.log('Compiled with warnings.', '\n');
for (const m of messages.warnings) {
console.log(m);
}
}
});
}

View File

@ -0,0 +1,67 @@
const isAbsolutePath = require('path').isAbsolute;
const webpack = require('webpack');
const webpackFormatMessages = require('webpack-format-messages');
import { config as defaultConfig } from '../lib/config';
import { BuildOptionsType } from '../lib/interfaces/OptionsTypes';
import { config as buildWebpackConfig } from '../lib/.webpack/build.config';
import { config as distWebpackConfig } from '../lib/.webpack/dist.config';
import generateConfig from '../lib/webpack-helpers/generateConfig';
const defaultOptions: BuildOptionsType = {
env: 'production',
config: defaultConfig,
};
export function build(buildOptions: BuildOptionsType = defaultOptions): void {
const options: BuildOptionsType = { ...defaultOptions, ...buildOptions };
options.config = { ...defaultOptions.config, ...buildOptions.config };
if (!isAbsolutePath(options.config.src)) {
throw Error('"src" is not an absolute path');
}
if (!isAbsolutePath(options.config.build)) {
throw Error('"build" is not an absolute path');
}
if (!isAbsolutePath(options.config.postcssConfigFile)) {
throw Error('"postcssConfigFile" is not an absolute path');
}
const config = generateConfig(options.env, options.config);
const compiler =
'dist' === options.env
? webpack({ ...distWebpackConfig, ...config })
: webpack({ ...buildWebpackConfig, ...config });
compiler.run((err?: Error, stats?: any) => {
if (err) throw err;
const messages = webpackFormatMessages(stats);
if (!messages.errors.length && !messages.warnings.length) {
console.log('Compiled successfully!', '\n');
}
if (messages.errors.length) {
console.log('Failed to compile.', '\n');
for (const m of messages.errors) {
console.log(m);
}
} else if (messages.warnings.length) {
console.log('Compiled with warnings.', '\n');
for (const m of messages.warnings) {
console.log(m);
}
}
});
}

View File

@ -0,0 +1,52 @@
const isAbsolutePath = require('path').isAbsolute;
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
import { config as defaultConfig } from '../lib/config';
import { DevOptionsType } from '../lib/interfaces/OptionsTypes';
import { config as webpackDefaultConfig } from '../lib/.webpack/dev.config';
import { configFunc as webpackDefaultServerConfig } from '../lib/.webpack/dev-server.config';
import generateConfig from '../lib/webpack-helpers/generateConfig';
const defaultOptions: DevOptionsType = {
env: 'development',
host: '0.0.0.0',
port: 8080,
config: defaultConfig,
};
export function dev(devOptions: DevOptionsType = defaultOptions): void {
const options: DevOptionsType = { ...defaultOptions, ...devOptions };
options.config = { ...defaultOptions.config, ...devOptions.config };
const config = generateConfig(options.env, options.config);
if (!isAbsolutePath(options.config.src)) {
throw Error('"src" is not an absolute path');
}
if (!isAbsolutePath(options.config.build)) {
throw Error('"build" is not an absolute path');
}
if (!isAbsolutePath(options.config.postcssConfigFile)) {
throw Error('"postcssConfigFile" is not an absolute path');
}
const compilerConfig = { ...webpackDefaultConfig, ...config };
const serverOptions = webpackDefaultServerConfig(options.config.src);
WebpackDevServer.addDevServerEntrypoints(compilerConfig, serverOptions);
const compiler = webpack(compilerConfig);
const server = new WebpackDevServer(compiler, serverOptions);
server.listen(options.port, options.host, (err?: Error) => {
if (err) throw err;
});
}

View File

@ -0,0 +1,3 @@
export * from './analyzer';
export * from './build';
export * from './dev';

View File

@ -0,0 +1,6 @@
<%- include ./partials/variables %>
<!DOCTYPE html>
<html lang="<%= htmlWebpackPlugin.options.lang %>">
<%- include ./partials/head %>
<%- include ./partials/body %>
</html>

View File

@ -0,0 +1,92 @@
<% var item, key %>
<body>
<% if (htmlWebpackPlugin.options.unsupportedBrowser) { %>
<!-- Unsupported browser -->
<style>.unsupported-browser { display: none; }</style>
<div class="unsupported-browser">
Sorry, your browser is not supported. Please upgrade to the latest version or switch your browser to use this
site. See <a href="http://outdatedbrowser.com/">outdatedbrowser.com</a> for options.
</div>
<% } %>
<% if (htmlWebpackPlugin.options.appMountId) { %>
<!-- App mount (single) -->
<div id="<%= htmlWebpackPlugin.options.appMountId %>">
<% if (htmlWebpackPlugin.options.appMountHtmlSnippet) { %>
<%= htmlWebpackPlugin.options.appMountHtmlSnippet %>
<% } %>
</div>
<% } %>
<% if (Array.isArray(htmlWebpackPlugin.options.appMountIds) && htmlWebpackPlugin.options.appMountIds.length ) { %>
<!-- App mount ids (multiple) -->
<% for (item of htmlWebpackPlugin.options.appMountIds) { %>
<div id="<%= item %>"></div>
<% } %>
<% } %>
<% if (htmlWebpackPlugin.options.html.body.snippet && '' !== htmlWebpackPlugin.options.html.body.snippet ) { %>
<!-- Body snippet -->
<%- htmlWebpackPlugin.options.html.body.snippet %>
<% } %>
<% if (htmlWebpackPlugin.options.window && Object.keys( htmlWebpackPlugin.options.window ).length) { %>
<!-- Global object -->
<script type="text/javascript">
<% for (key in htmlWebpackPlugin.options.window) { %>
window['<%= key %>'] = <%- JSON.stringify( htmlWebpackPlugin.options.window[key] ) %>;
<% } %>
</script>
<% } %>
<% if (Array.isArray(htmlWebpackPlugin.options.html.body.scripts) && htmlWebpackPlugin.options.html.body.scripts.length ) { %>
<!-- Scripts -->
<% for (item of htmlWebpackPlugin.options.html.body.scripts) {
%><script<% for (key in item) { %> <%= key %>="<%= item[key] %>"<% } %>></script><%
}
} %>
<% if ( Array.isArray(htmlWebpackPlugin.files.chunks) && htmlWebpackPlugin.files.chunks.length ) { %>
<!-- Chunks -->
<% for (key in htmlWebpackPlugin.files.chunks) {
if (htmlWebpackPlugin.files.jsIntegrity) { %>
<script
src="<%= htmlWebpackPlugin.files.chunks[key].entry %>"
type="text/javascript"
integrity="<%= htmlWebpackPlugin.files.jsIntegrity[htmlWebpackPlugin.files.js.indexOf(htmlWebpackPlugin.files.chunks[key].entry)] %>"
crossorigin="<%= webpackConfig.output.crossOriginLoading %>"></script>
<% } else { %>
<script src="<%= htmlWebpackPlugin.files.chunks[key].entry %>" type="text/javascript"></script>
<% }
}
} %>
<% if (htmlWebpackPlugin.options.googleAnalytics) { %>
<!-- Google analytics script -->
<script type="text/javascript">
window.GoogleAnalyticsObject='ga';window.ga=function(){ga.q.push(arguments)};ga.q=[];ga.l=+new Date;
<% if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
ga('create','<%= htmlWebpackPlugin.options.googleAnalytics.trackingId %>','auto');
<% } else {
throw new Error("html-webpack-template requires googleAnalytics.trackingId config");
} %>
<% if (htmlWebpackPlugin.options.googleAnalytics.pageViewOnLoad) { %>
ga('send','pageview');
<% } %>
</script>
<script async defer src="https://www.google-analytics.com/analytics.js" type="text/javascript"></script>
<% } %>
<!---->
</body>

View File

@ -0,0 +1,32 @@
<% var item, key %>
<head>
<% if (htmlWebpackPlugin.options.title) { %>
<!-- Title -->
<title><%= htmlWebpackPlugin.options.title %></title>
<% } %>
<% if (htmlWebpackPlugin.options.baseUrl) { %>
<!-- Base -->
<base href="<%= htmlWebpackPlugin.options.baseUrl %>">
<% } %>
<% if (Array.isArray(htmlWebpackPlugin.options.html.head.meta) && htmlWebpackPlugin.options.html.head.meta.length ) { %>
<!-- Meta -->
<% for (item of htmlWebpackPlugin.options.html.head.meta) { %><meta<% for (key in item) { %> <%= key %>="<%= item[key] %>"<% } %> /><% }
} %>
<% if (Array.isArray(htmlWebpackPlugin.options.html.head.links) && htmlWebpackPlugin.options.html.head.links.length ) { %>
<!-- Links -->
<% for (item of htmlWebpackPlugin.options.html.head.links) { %><link<% for (key in item) { %> <%= key %>="<%= item[key] %>"<% } %> /><% }
} %>
<% if (Array.isArray(htmlWebpackPlugin.options.html.head.scripts) && htmlWebpackPlugin.options.html.head.scripts.length ) { %>
<!-- Scripts -->
<% for (item of htmlWebpackPlugin.options.html.head.scripts) { %>
<script<% for (key in item) { %> <%= key %>="<%= item[key] %>"<% } %>></script><%
}
} %>
</head>

View File

@ -0,0 +1,25 @@
<% htmlWebpackPlugin.options.appMountId = htmlWebpackPlugin.options.appMountId || null %>
<% htmlWebpackPlugin.options.appMountIds = htmlWebpackPlugin.options.appMountIds || [] %>
<% htmlWebpackPlugin.options.files = htmlWebpackPlugin.options.files || [] %>
<% htmlWebpackPlugin.options.files.chunks = htmlWebpackPlugin.options.files.chunks || [] %>
<% htmlWebpackPlugin.options.files.jsIntegrity = htmlWebpackPlugin.options.files.jsIntegrity || false %>
<% htmlWebpackPlugin.options.unsupportedBrowser = htmlWebpackPlugin.options.unsupportedBrowser || false %>
<% htmlWebpackPlugin.options.lang = htmlWebpackPlugin.options.lang || "en" %>
<% htmlWebpackPlugin.options.html = htmlWebpackPlugin.options.html || {} %>
<% htmlWebpackPlugin.options.html.head = htmlWebpackPlugin.options.html.head || {} %>
<% htmlWebpackPlugin.options.html.head.meta = htmlWebpackPlugin.options.html.head.meta || [] %>
<% htmlWebpackPlugin.options.html.head.links = htmlWebpackPlugin.options.html.head.links || [] %>
<% htmlWebpackPlugin.options.html.head.scripts = htmlWebpackPlugin.options.html.head.scripts || [] %>
<% htmlWebpackPlugin.options.html.body = htmlWebpackPlugin.options.html.body || {} %>
<% htmlWebpackPlugin.options.html.body.scripts = htmlWebpackPlugin.options.html.body.scripts || [] %>
<% htmlWebpackPlugin.options.html.body.snippet = htmlWebpackPlugin.options.html.body.snippet || '' %>
<% htmlWebpackPlugin.options.window = htmlWebpackPlugin.options.window || {} %>
<% htmlWebpackPlugin.options.googleAnalytics = htmlWebpackPlugin.options.googleAnalytics || false %>

View File

@ -0,0 +1,23 @@
<% pages = pages || [] %>
<% if (pages.length) { %>
<div id="app-header"></div>
<div id="app-sidebar"></div>
<div class="page-main-wrap">
<div class="page-main-inner">
<div class="custom-page-wrapper">
<h2>Sitemap</h2>
<hr>
<h3>Pages</h3>
<ol>
<% for (p of pages) { %>
<li><a href="./<%= p %>.html"><%= p %></a></li>
<% } %>
</ol>
</div>
</div>
</div>
<% } %>

View File

@ -0,0 +1,71 @@
{
"include":["lib/**/*", "src/**/*"],
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
"allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
//"declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "declarationDir": "./dist/types",
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./dist", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

View File

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}

View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
*.DS_Store
# Exports
/build

View File

@ -0,0 +1,45 @@
{
"evil" : true,
"validthis": true,
"node" : true,
"debug" : true,
"boss" : true,
"expr" : true,
"eqnull" : true,
"quotmark" : "single",
"sub" : true,
"trailing" : true,
"undef" : true,
"laxbreak" : true,
"esnext" : true,
"eqeqeq" : true,
"predef" : [
"_V_",
"goog",
"console",
"require",
"define",
"module",
"exports",
"process",
"q",
"asyncTest",
"deepEqual",
"equal",
"expect",
"module",
"notDeepEqual",
"notEqual",
"notStrictEqual",
"ok",
"QUnit",
"raises",
"start",
"stop",
"strictEqual",
"test",
"sinon"
]
}

View File

@ -0,0 +1 @@
lts/*

View File

@ -0,0 +1,14 @@
dist: xenial
language: node_js
node_js:
- lts/*
install:
- npm install -g grunt
- npm install
script:
- grunt
cache: npm

View File

@ -0,0 +1,8 @@
require('@babel/register');
module.exports = function(grunt) {
require('time-grunt')(grunt);
require('load-grunt-tasks')(grunt);
require('./lib/grunt.js')(grunt);
};

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,16 @@
# mediacms-vjs-plugin-font-icons
> This project is cloned from _video.js_ [font icons repository](https://github.com/videojs/font) to cover the needs of "**mediacms-vjs-plugin**"
Compared to the original repository:
- Added folder "**scripts**"
- Added folder "**mediacms**"
- Added npm cli command "**build**" which generates "_build_" and "_dist_" folders
- Added npm cli command **"clean:builds"** which removes _"build"_ and _"dist"_ folders
- Added "_rimraf_" package
- Updated dependency packages
The final css file, is generated inside "_dist_" folder
- **dist/mediacms-vjs-icons.css**

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
<title>AD</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="AD" sketch:type="MSArtboardGroup" fill="#000000">
<g id="g24" sketch:type="MSLayerGroup" transform="translate(226.904216, 162.124958)">
<path d="M0.385466989,219.226204 L0.385466989,0.867948105 C50.7660025,-0.149278544 89.4938709,-2.16027378 118.016886,17.9940357 C145.39121,37.3362698 166.750707,74.9591545 162.906445,123.318579 C158.839382,174.474203 121.571663,217.457893 73.1311827,221.793795 C49.0460488,223.949377 1.1583283,221.793795 1.1583283,221.793795 C1.1583283,221.793795 0.318395733,220.441758 0.385466989,219.226204 M49.1404882,164.421786 C80.5703101,165.681697 102.34881,147.788744 105.636072,119.036417 C110.038491,80.5268177 84.4473371,55.4838492 47.5943801,58.2399576 L47.5943801,161.852062 C47.5585317,163.318404 48.1702678,164.071194 49.1404882,164.421786" id="path26" sketch:type="MSShapeGroup"></path>
</g>
<g id="g28" sketch:type="MSLayerGroup" transform="translate(383.779991, 168.926023)">
<path d="M0,212.402042 C13.3360014,216.111401 17.386874,201.342635 23.2151349,190.99422 C35.936702,168.422877 45.5086182,139.400143 45.6604922,106.220214 C45.8813648,58.6259492 27.3172746,23.7033002 10.059532,0.0383859113 L1.54919183,0.0383859113 C0.96289654,3.91152436 3.77564916,7.35260805 5.41542574,10.3142944 C18.5814362,34.0755999 30.7818519,66.8674044 30.9556975,104.507776 C31.1545985,147.683822 16.7932549,183.786198 0,212.402042" id="path30" sketch:type="MSShapeGroup"></path>
</g>
<g id="g32" sketch:type="MSLayerGroup" transform="translate(425.153705, 168.926023)">
<path d="M0,212.402042 C13.3360014,216.111401 17.3841758,201.340502 23.2151349,190.99422 C35.936702,168.422877 45.5066909,139.400143 45.6604922,106.220214 C45.8813648,58.6259492 27.3172746,23.7033002 10.059532,0.0383859113 L1.54919183,0.0383859113 C0.96289654,3.91152436 3.77487823,7.35346107 5.41542574,10.3142944 C18.5814362,34.0755999 30.7822374,66.8674044 30.9556975,104.507776 C31.1545985,147.683822 16.7932549,183.786198 0,212.402042" id="path34" sketch:type="MSShapeGroup"></path>
</g>
<g id="g36" sketch:type="MSLayerGroup" transform="translate(466.260868, 168.926023)">
<path d="M0,212.402042 C13.3360014,216.111401 17.3841758,201.340502 23.2151349,190.99422 C35.936702,168.422877 45.5066909,139.400143 45.6604922,106.220214 C45.8813648,58.6259492 27.3172746,23.7033002 10.059532,0.0383859113 L1.54919183,0.0383859113 C0.96289654,3.91152436 3.77487823,7.35303456 5.41542574,10.3142944 C18.5814362,34.0755999 30.7818519,66.8674044 30.9556975,104.507776 C31.1545985,147.683822 16.7932549,183.786198 0,212.402042" id="path38" sketch:type="MSShapeGroup"></path>
</g>
<path d="M4.4765625,383.005158 L72.5800993,383.005158 L91.1530552,354.521486 L155.321745,354.386058 C155.321745,354.386058 155.386889,373.799083 155.386889,383.005158 L204.142681,383.005158 L204.142681,160.308263 L145.326586,160.308263 C139.673713,169.845383 4.4765625,383.005158 4.4765625,383.005158 L4.4765625,383.005158 Z M157.144233,237.722611 L157.144233,308.881058 L116.6914,308.610203 L157.144233,237.722611 L157.144233,237.722611 Z" id="path22" sketch:type="MSShapeGroup"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1343 12v264h-157q-86 0-116 36t-30 108v189h293l-39 296h-254v759h-306v-759h-255v-296h255v-218q0-186 104-288.5t277-102.5q147 0 228 12z"/></svg>

After

Width:  |  Height:  |  Size: 243 B

View File

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M799 796q0 36 32 70.5t77.5 68 90.5 73.5 77 104 32 142q0 90-48 173-72 122-211 179.5t-298 57.5q-132 0-246.5-41.5t-171.5-137.5q-37-60-37-131 0-81 44.5-150t118.5-115q131-82 404-100-32-42-47.5-74t-15.5-73q0-36 21-85-46 4-68 4-148 0-249.5-96.5t-101.5-244.5q0-82 36-159t99-131q77-66 182.5-98t217.5-32h418l-138 88h-131q74 63 112 133t38 160q0 72-24.5 129.5t-59 93-69.5 65-59.5 61.5-24.5 66zm-146-96q38 0 78-16.5t66-43.5q53-57 53-159 0-58-17-125t-48.5-129.5-84.5-103.5-117-41q-42 0-82.5 19.5t-65.5 52.5q-47 59-47 160 0 46 10 97.5t31.5 103 52 92.5 75 67 96.5 26zm2 873q58 0 111.5-13t99-39 73-73 27.5-109q0-25-7-49t-14.5-42-27-41.5-29.5-35-38.5-34.5-36.5-29-41.5-30-36.5-26q-16-2-48-2-53 0-105 7t-107.5 25-97 46-68.5 74.5-27 105.5q0 70 35 123.5t91.5 83 119 44 127.5 14.5zm810-876h213v108h-213v219h-105v-219h-212v-108h212v-217h105v217z"/></svg>

After

Width:  |  Height:  |  Size: 933 B

View File

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M477 625v991h-330v-991h330zm21-306q1 73-50.5 122t-135.5 49h-2q-82 0-132-49t-50-122q0-74 51.5-122.5t134.5-48.5 133 48.5 51 122.5zm1166 729v568h-329v-530q0-105-40.5-164.5t-126.5-59.5q-63 0-105.5 34.5t-63.5 85.5q-11 30-11 81v553h-329q2-399 2-647t-1-296l-1-48h329v144h-2q20-32 41-56t56.5-52 87-43.5 114.5-15.5q171 0 275 113.5t104 332.5z"/></svg>

After

Width:  |  Height:  |  Size: 443 B

View File

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1664 896q0 209-103 385.5t-279.5 279.5-385.5 103q-111 0-218-32 59-93 78-164 9-34 54-211 20 39 73 67.5t114 28.5q121 0 216-68.5t147-188.5 52-270q0-114-59.5-214t-172.5-163-255-63q-105 0-196 29t-154.5 77-109 110.5-67 129.5-21.5 134q0 104 40 183t117 111q30 12 38-20 2-7 8-31t8-30q6-23-11-43-51-61-51-151 0-151 104.5-259.5t273.5-108.5q151 0 235.5 82t84.5 213q0 170-68.5 289t-175.5 119q-61 0-98-43.5t-23-104.5q8-35 26.5-93.5t30-103 11.5-75.5q0-50-27-83t-77-33q-62 0-105 57t-43 142q0 73 25 122l-99 418q-17 70-13 177-206-91-333-281t-127-423q0-209 103-385.5t279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/></svg>

After

Width:  |  Height:  |  Size: 713 B

Some files were not shown because too many files have changed in this diff Show More