Browse Source

initial build

Created Webpack, Nodemon, and BrowserSync harness with UIkit,
FontAwesome, Pretty Checkbox configured and tested.
develop
Rob Colbert 1 year ago
parent
commit
8bf428b961
  1. 4
      .gitignore
  2. 25
      .jshintrc
  3. 2
      LICENSE
  4. 37
      app/controllers/home.js
  5. 20
      app/views/components/pwa-support.pug
  6. 78
      app/views/components/social-card/facebook.pug
  7. 75
      app/views/components/social-card/twitter.pug
  8. 13
      app/views/home.pug
  9. 84
      app/views/layout/main.pug
  10. 5
      client/css/main.less
  11. 11
      client/js/chat-client.js
  12. 13
      config/site.js
  13. 132
      dtp-chat.js
  14. 51
      package.json
  15. 103
      webpack.config.js
  16. 5392
      yarn.lock

4
.gitignore

@ -0,0 +1,4 @@
.env
node_modules
dist

25
.jshintrc

@ -0,0 +1,25 @@
{
"bitwise": true,
"browser": true,
"curly": true,
"eqeqeq": true,
"latedef": true,
"noarg": true,
"node": true,
"strict": "global",
"undef": true,
"unused": true,
"futurehostile": true,
"esversion": 11,
"mocha": true,
"globals": {
"markdown": true,
"moment": true,
"numeral": true,
"io": true,
"Chart": true,
"CodeMirror": true,
"UIkit": true,
"twttr": true
}
}

2
LICENSE

@ -0,0 +1,2 @@
DTP Chat Copyright (C) 2024 DTP Technologies, LLC
All Rights Reserved

37
app/controllers/home.js

@ -0,0 +1,37 @@
// home.js
// Copyright (C) 2024 DTP Technologies, LLC
// All Rights Reserved
'use strict';
import express from 'express';
export default class HomeController {
static get isHome ( ) { return true; }
static get slug ( ) { return 'home'; }
static get className ( ) { return 'HomeController'; }
constructor (dtp) {
this.dtp = dtp;
}
static create (dtp) {
const instance = new HomeController(dtp);
return instance;
}
async start ( ) {
const router = express.Router();
this.dtp.app.use('/', router);
router.get('/', this.getHome.bind(this));
return router;
}
async getHome (req, res) {
res.locals.pageDescription = 'DTP Chat Home';
res.render('home');
}
}

20
app/views/components/pwa-support.pug

@ -0,0 +1,20 @@
link(rel="apple-touch-icon" sizes="57x57" href=`/img/icon/${site.domainKey}/icon-57x57.png?v=${pkg.version}`)
link(rel="apple-touch-icon" sizes="60x60" href=`/img/icon/${site.domainKey}/icon-60x60.png?v=${pkg.version}`)
link(rel="apple-touch-icon" sizes="72x72" href=`/img/icon/${site.domainKey}/icon-72x72.png?v=${pkg.version}`)
link(rel="apple-touch-icon" sizes="76x76" href=`/img/icon/${site.domainKey}/icon-76x76.png?v=${pkg.version}`)
link(rel="apple-touch-icon" sizes="114x114" href=`/img/icon/${site.domainKey}/icon-114x114.png?v=${pkg.version}`)
link(rel="apple-touch-icon" sizes="120x120" href=`/img/icon/${site.domainKey}/icon-120x120.png?v=${pkg.version}`)
link(rel="apple-touch-icon" sizes="144x144" href=`/img/icon/${site.domainKey}/icon-144x144.png?v=${pkg.version}`)
link(rel="apple-touch-icon" sizes="152x152" href=`/img/icon/${site.domainKey}/icon-152x152.png?v=${pkg.version}`)
link(rel="apple-touch-icon" sizes="180x180" href=`/img/icon/${site.domainKey}/icon-180x180.png?v=${pkg.version}`)
link(rel="icon" type="image/png" sizes="32x32" href=`/img/icon/${site.domainKey}/icon-32x32.png?v=${pkg.version}`)
link(rel="icon" type="image/png" sizes="96x96" href=`/img/icon/${site.domainKey}/icon-96x96.png?v=${pkg.version}`)
link(rel="icon" type="image/png" sizes="16x16" href=`/img/icon/${site.domainKey}/icon-16x16.png?v=${pkg.version}`)
link(rel="icon" type="image/png" sizes="512x512" href=`/img/icon/${site.domainKey}/icon-512x512.png?v=${pkg.version}`)
link(rel="icon" type="image/png" sizes="384x384" href=`/img/icon/${site.domainKey}/icon-384x384.png?v=${pkg.version}`)
link(rel="icon" type="image/png" sizes="256x256" href=`/img/icon/${site.domainKey}/icon-512x512.png?v=${pkg.version}`)
link(rel="icon" type="image/png" sizes="192x192" href=`/img/icon/${site.domainKey}/icon-192x192.png?v=${pkg.version}`)
link(rel="manifest" href=`/manifest.json?v=${pkg.version}`)
meta(name="msapplication-TileColor" content="#f1c52f")
meta(name="msapplication-TileImage" content=`/img/icon/ms-icon-144x144.png?v=${pkg.version}`)
meta(name="theme-color" content="#f1c52f")

78
app/views/components/social-card/facebook.pug

@ -0,0 +1,78 @@
block facebook-card
-
var fbCardImg = `https://${site.domain}/img/social-cards/${site.domain}.png?v=${pkg.version}`;
var fbContentType = 'website';
var fbTitle = pageTitle || site.name;
var fbDescription = pageDescription || site.description;
var fbVideo, fbVideoWidth, fbVideoHeight;
if ((currentView === 'dvr') && (viewName === 'home-player')) {
// use default social card assigned above
fbTitle = pageTitle;
fbDescription = pageDescription;
} else if (episode && episode.images && episode.images.thumbnail) {
if (clip && clip.images && clip.images.thumbnail) {
fbCardImg = `https://${site.domain}/image/${clip.images.thumbnail._id || clip.images.thumbnail}`;
fbTitle = clip.title;
fbDescription = clip.description;
fbContentType = 'video';
fbVideoEmbedUrl = `https://${site.domain}/dvr/${clip.episode._id}/clip/${clip._id}/embed`;
fbVideoType = 'video/mp4';
fbVideoWidth = 960;
fbVideoHeight = 540;
} else {
fbCardImg = `https://${site.domain}/image/${episode.images.thumbnail._id || episode.images.thumbnail}`;
fbTitle = episode.title;
fbDescription = episode.description;
}
} else if (clip && clip.images && clip.images.thumbnail) {
fbCardImg = `https://${site.domain}/image/${clip.images.thumbnail._id || clip.images.thumbnail}`;
fbTitle = clip.title;
fbDescription = clip.description;
fbContentType = 'video';
fbVideoEmbedUrl = `https://${site.domain}/dvr/${clip.episode._id}/clip/${clip._id}/embed`;
fbVideoType = 'video/mp4';
fbVideoWidth = 960;
fbVideoHeight = 540;
} else if (video && video.images && video.images.thumbnail) {
fbCardImg = `https://${site.domain}/image/${video.images.thumbnail._id || video.images.thumbnail}`;
fbTitle = video.title;
fbDescription = video.description;
} else if (userProfile) {
fbCardImg = getUserPictureUrl(userProfile, 'large');
fbTitle = pageTitle;
fbDescription = pageDescription;
} else if (channel && channel.images && channel.images.thumbnail) {
fbCardImg = `https://${site.domain}/image/${channel.images.thumbnail._id || channel.images.thumbnail}`;
fbTitle = channel.name;
fbDescription = channel.description;
} else if (advertisement && advertisement.content) {
fbTitle = advertisement.content.title;
if (advertisement.content.body) {
fbDescription = marked.parse(advertisement.content.body, { renderer: fullMarkdownRenderer }).trim();
}
switch (advertisement.content.mediaType) {
case 'Image':
fbCardImg = `https://${site.domain}/image/${advertisement.content.media._id}`;
break;
case 'Video':
if (advertisement.content && advertisement.content.media && advertisement.content.media.images && advertisement.content.media.images.thumbnail) {
fbCardImg = `https://${site.domain}/image/${advertisement.content.media.images.thumbnail._id || advertisement.content.media.images.thumbnail}`;
}
break;
}
}
meta(property='og:site_name', content= site.name)
meta(property='og:type', content= fbContentType)
if fbContentType === 'video'
meta(property='og:video', content= fbVideoEmbedUrl)
meta(property='og:video:type', content= fbVideoType)
meta(property='og:video:width', content= fbVideoWidth)
meta(property='og:video:height', content= fbVideoHeight)
meta(property='og:image', content= fbCardImg)
meta(property='og:image:alt', content= `${fbTitle} | ${fbDescription}`)
meta(property='og:url', content= `https://${site.domain}${dtp.request.originalUrl}`)
meta(property='og:title', content= fbTitle)
if fbDescription && (fbDescription.length > 0)
meta(property='og:description', content= fbDescription)

75
app/views/components/social-card/twitter.pug

@ -0,0 +1,75 @@
block twitter-card
-
var twCardType = 'summary_large_image';
var twCardImg = `https://${site.domain}/img/social-cards/${site.domain}.png?v=${pkg.version}`;
var twTitle = pageTitle || `${site.name} | ${site.description}`;
var twDescription = pageDescription || site.description;
var twPlayerEmbedUrl, twPlayerStreamUrl, twPlayerWidth, twPlayerHeight;
if ((currentView === 'dvr') && (viewName === 'home-player')) {
// use default social card assigned above
twTitle = pageTitle;
twDescription = pageDescription;
} else if (episode && episode.images && episode.images.thumbnail) {
if (clip && clip.images && clip.images.thumbnail) {
twCardImg = `https://${site.domain}/image/${clip.images.thumbnail._id || clip.images.thumbnail}`;
twTitle = clip.title;
twDescription = clip.description;
twCardType = 'player';
twPlayerEmbedUrl = `https://${site.domain}/dvr/${clip.episode._id}/clip/${clip._id}/embed`;
twPlayerStreamUrl = `https://${site.domain}/dvr/${clip.episode._id}/clip/${clip._id}/media`;
twPlayerWidth = 960;
twPlayerHeight = 540;
} else {
twCardImg = `https://${site.domain}/image/${episode.images.thumbnail._id || episode.images.thumbnail}`;
twTitle = episode.title;
twDescription = episode.description;
}
} else if (clip && clip.images && clip.images.thumbnail) {
twCardImg = `https://${site.domain}/image/${clip.images.thumbnail._id || clip.images.thumbnail}`;
twTitle = clip.title;
twDescription = clip.description;
twCardType = 'player';
twPlayerEmbedUrl = `https://${site.domain}/dvr/${clip.episode._id}/clip/${clip._id}/embed`;
twPlayerStreamUrl = `https://${site.domain}/dvr/${clip.episode._id}/clip/${clip._id}/media`;
twPlayerWidth = 960;
twPlayerHeight = 540;
} else if (video && video.images && video.images.thumbnail) {
twCardImg = `https://${site.domain}/image/${video.images.thumbnail._id || video.images.thumbnail}`;
twTitle = video.title;
twDescription = video.description;
} else if (userProfile) {
twCardImg = getUserPictureUrl(userProfile, 'large');
twTitle = pageTitle;
twDescription = pageDescription;
} else if (channel && channel.images && channel.images.thumbnail) {
twCardImg = `https://${site.domain}/image/${channel.images.thumbnail._id || channel.images.thumbnail}`;
twTitle = channel.name;
twDescription = channel.description;
} else if (advertisement && advertisement.content) {
twTitle = advertisement.content.title;
if (advertisement.content.body) {
twDescription = marked.parse(advertisement.content.body, { renderer: fullMarkdownRenderer }).trim();
}
switch (advertisement.content.mediaType) {
case 'Image':
twCardImg = `https://${site.domain}/image/${advertisement.content.media._id}`;
break;
case 'Video':
if (advertisement.content && advertisement.content.media && advertisement.content.media.images && advertisement.content.media.images.thumbnail) {
twCardImg = `https://${site.domain}/image/${advertisement.content.media.images.thumbnail._id || advertisement.content.media.images.thumbnail}`;
}
break;
}
}
meta(name='twitter:card', content= twCardType)
meta(name='twitter:image' content= twCardImg)
meta(name='twitter:title', content= twTitle)
if twDescription && (twDescription.length > 0)
meta(name='twitter:description', content= twDescription)
if twCardType === 'player'
meta(name='twitter:player', content= twPlayerEmbedUrl)
meta(name='twitter:player:stream', content= twPlayerStreamUrl)
meta(name='twitter:player:width', content= twPlayerWidth)
meta(name='twitter:player:height', content= twPlayerHeight)

13
app/views/home.pug

@ -0,0 +1,13 @@
include layout/main
block view-content
section.uk-section.uk-section-default
.uk-container
h1= site.name
div= site.description
p The app is doing something else.
p
span
i.fas.fa-home

84
app/views/layout/main.pug

@ -0,0 +1,84 @@
doctype html
html(lang='en', data-obs-widget= obsWidget)
head
meta(charset='UTF-8')
meta(name='viewport', content='width=device-width, initial-scale=1.0')
meta(name='description', content= pageDescription || site.description)
title= pageTitle ? `${pageTitle} | ${site.name}` : site.name
meta(name="robots", content= "index,follow")
meta(name="googlebot", content= "index,follow")
meta(name="theme-color", content="#ff0013")
meta(name="apple-mobile-web-app-status-bar-style", content="black-translucent")
block css
link(rel='stylesheet', href=`/fontawesome/css/all.min.css?v=${pkg.version}`)
block vendorcss
link(rel='stylesheet', href=`/dist/chat-client.css?v=${pkg.version}`)
link(rel='stylesheet', href=`/pretty-checkbox/pretty-checkbox.min.css?v=${pkg.version}`)
block viewcss
block js
script(async, src=`/fontawesome/js/fontawesome.min.js?v=${pkg.version}`)
script(src=`/uikit/dist/js/uikit.min.js?v=${pkg.version}`)
script(src=`/uikit/dist/js/uikit-icons.min.js?v=${pkg.version}`)
block pwa-support
include ../components/pwa-support
block social-card
include ../components/social-card/twitter
include ../components/social-card/facebook
block view-header
script.
function onImageLoadError (event) {
const imageType = event.currentTarget.getAttribute('data-image-type') || 'thumb';
console.error('image error', imageType, event);
switch (imageType) {
case 'profile':
event.currentTarget.setAttribute('src', '/img/default-member.png');
break;
case 'thumb':
event.currentTarget.setAttribute(
'src',
`/img/default-poster.${process.env.DTP_SITE_DOMAIN_KEY}.jpg`,
);
break;
}
}
body.dtp(
class= 'dtp-dark',
data-dtp-env= process.env.NODE_ENV,
data-dtp-domain= site.domainKey,
data-current-view= currentView,
data-is-popout= isPopOutView,
data-obs-widget= obsWidget,
data-embed-widget= embedWidget,
)
block view-navbar
nav(uk-navbar).uk-navbar-container
.uk-navbar-left
ul.uk-navbar-nav
li.uk-active
a(href="/")
span
i.fas.fa-home
span HOME
.uk-navbar-right
ul.uk-navbar-nav
li
a(href="/welcome/sign-up") SIGN UP
li
a(href="/welcome/login") LOGIN
block view-content

5
client/css/main.less

@ -0,0 +1,5 @@
@import "uikit/src/less/uikit.less";
html, body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

11
client/js/chat-client.js

@ -0,0 +1,11 @@
// chat-client.js
// Copyright (C) 2024 DTP Technologies, LLC
// All Rights Reserved
'use strict';
export class ChatApp {
constructor ( ) {
console.log('DTP app client online');
}
}

13
config/site.js

@ -0,0 +1,13 @@
// config.js
// Copyright (C) 2024 DTP Technologies, LLC
// All Rights Reserved
'use strict';
export default {
name: process.env.DTP_SITE_NAME,
description: process.env.DTP_SITE_DESCRIPTION,
domain: process.env.DTP_SITE_DOMAIN,
domainKey: process.env.DTP_SITE_DOMAIN_KEY,
company: process.env.DTP_SITE_COMPANY || 'DTP Technologies, LLC',
};

132
dtp-chat.js

@ -0,0 +1,132 @@
// dtp-chat.js
// Copyright (C) 2024 DTP Technologies, LLC
// All Rights Reserved
'use strict';
import 'dotenv/config';
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url)); // jshint ignore:line
import { createRequire } from 'module';
const require = createRequire(import.meta.url); // jshint ignore:line
import * as glob from 'glob';
import webpack from 'webpack';
import webpackDevMiddleware from 'webpack-dev-middleware';
import WEBPACK_CONFIG from './webpack.config.js';
import express from 'express';
const APP_CONFIG = {
pkg: require('./package.json'),
};
class Harness {
constructor ( ) {
this.config = {
root: __dirname,
};
}
async start ( ) {
this.app = express();
this.app.locals.config = APP_CONFIG;
this.app.locals.pkg = APP_CONFIG.pkg; // convenience
this.app.locals.site = (await import(path.join(this.config.root, 'config', 'site.js'))).default;
this.app.set('view engine', 'pug');
this.app.set('views', path.join(__dirname, 'app', 'views'));
this.app.use('/static', express.static(path.join(__dirname, 'client', 'static')));
this.app.use('/fontawesome', express.static(path.join(__dirname, 'node_modules', '@fortawesome', 'fontawesome-free')));
this.app.use('/uikit', express.static(path.join(__dirname, 'node_modules', 'uikit')));
this.app.use('/pretty-checkbox', express.static(path.join(__dirname, 'node_modules', 'pretty-checkbox', 'dist')));
this.app.use('/dist', express.static(path.join(__dirname, 'dist')));
/*
* Webpack integration
*/
if (process.env.NODE_ENV !== 'production') {
this.compiler = webpack(WEBPACK_CONFIG);
this.webpackDevMiddleware = webpackDevMiddleware(this.compiler, {
publicPath: WEBPACK_CONFIG.output.publicPath,
writeToDisk: true,
});
this.app.use(this.webpackDevMiddleware);
}
this.app.use((req, res, next) => {
res.locals.dtp = {
request: req,
};
return next();
});
await this.loadControllers();
/*
* Start the ExpressJS server
*/
const host = process.env.DTP_HTTP_HOST || '127.0.0.1';
const port = parseInt(process.env.DTP_HTTP_PORT || '3000', 10);
console.log('Starting application server', { host, port });
this.app.listen(port, host, ( ) => {
console.log(`${APP_CONFIG.pkg.name} online.`);
});
}
async loadControllers ( ) {
const scripts = glob.sync(path.join(this.config.root, 'app', 'controllers', '*.js'));
const inits = [ ];
this.controllers = { };
for await (const script of scripts) {
try {
const file = path.parse(script);
console.log('loading controller', { name: file.base });
let controller = await import(script);
controller = controller.default;
controller.instance = controller.create(this);
this.controllers[controller.slug] = controller;
inits.push(controller);
} catch (error) {
console.error('failed to load controller', { error });
throw new Error('failed to load controller', { cause: error });
}
}
for await (const controller of inits) {
if (controller.isHome) {
continue;
}
await controller.instance.start();
}
/*
* Start the Home controller
*/
await this.controllers.home.instance.start();
}
}
(async ( ) => {
try {
const harness = new Harness();
await harness.start();
} catch (error) {
console.log('failed to start application harness', error);
}
})();

51
package.json

@ -0,0 +1,51 @@
{
"name": "dtp-chat",
"type": "module",
"version": "0.1.0",
"description": "Next-generation realtime communications tool for the web.",
"dtpApp": {
"title": "CyberEgg 2077: Interstellar Food Fight",
"description": "In a world where memes come to life, a pineapple takes on evil and wins!"
},
"main": "dtp-chat.js",
"scripts": {
"develop": "nodemon dtp-chat.js",
"build": "NODE_ENV=production yarn webpack --config webpack.config.js"
},
"repository": "[email protected]:digital-telepresence/dtp-chat.git",
"author": "Rob Colbert",
"license": "LicenseRef-LICENSE",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-free": "^6.5.1",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"glob": "^10.3.10",
"marked": "^12.0.1",
"mongoose": "^8.2.3",
"multer": "^1.4.5-lts.1",
"numeral": "^2.0.6",
"pretty-checkbox": "^3.0.3",
"pug": "^3.0.2",
"socket.io": "^4.7.5",
"striptags": "^3.2.0"
},
"devDependencies": {
"browser-sync": "^3.0.2",
"browser-sync-webpack-plugin": "^2.3.0",
"css-loader": "^6.10.0",
"jshint": "^2.13.6",
"less": "^4.2.0",
"less-loader": "^12.2.0",
"mini-css-extract-plugin": "^2.8.1",
"nodemon-webpack-plugin": "^4.8.2",
"style-loader": "^3.3.4",
"terser-webpack-plugin": "^5.3.10",
"uikit": "^3.19.2",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"webpack-dev-middleware": "^7.1.1",
"webpack-stream": "^7.0.0",
"workbox-webpack-plugin": "^7.0.0"
}
}

103
webpack.config.js

@ -0,0 +1,103 @@
// webpack.config.js
// Copyright (C) 2022 Rob Colbert @[email protected]
// All Rights Reserved
'use strict';
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url)); // jshint ignore:line
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import BrowserSyncPlugin from 'browser-sync-webpack-plugin';
const webpackMode = (process.env.NODE_ENV === 'production') ? 'production' : 'development';
console.log('Webpack mode:', webpackMode);
const plugins = [ ];
plugins.push(new MiniCssExtractPlugin());
if (webpackMode === 'development') {
plugins.push(
new BrowserSyncPlugin({
proxy: {
target: 'http://localhost:3000',
ws: true,
},
host: 'localhost',
open: 'local',
port: 3333,
cors: true,
ui: {
port: 3400,
},
notify: false,
ghostMode: {
clicks: false,
forms: false,
scroll: true,
},
logLevel: 'info',
files: [
'./dist/*.js',
'./dist/*.css',
],
}),
);
}
export default {
entry: {
'chat-client': [
'./client/js/chat-client.js',
'./client/css/main.less',
],
},
mode: webpackMode,
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
// clean: true,
// publicPath: '/dist',
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
plugins,
module: {
rules: [
{
test: /\.less$/i,
use: [
{
loader: "style-loader",
},
{
loader: MiniCssExtractPlugin.loader,
options: {
esModule: false,
},
},
{
loader: "css-loader",
options: {
sourceMap: true,
},
},
{
loader: "less-loader",
options: {
sourceMap: true,
lessOptions: {
strictMath: false,
},
},
},
],
},
],
},
};

5392
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save