mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
4.0.0-rc8
* Added: Initial support for chat on Clips. * Fixed: Do not attempt to apply the dark theme to the Twitch Prime landing page. * Fixed: Remove debug logging from Chat on Videos.
This commit is contained in:
parent
ae9aa66799
commit
2297edb051
26 changed files with 962 additions and 34 deletions
41
package-lock.json
generated
41
package-lock.json
generated
|
@ -429,8 +429,7 @@
|
|||
"asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
|
||||
"dev": true
|
||||
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
|
||||
},
|
||||
"asn1": {
|
||||
"version": "0.2.3",
|
||||
|
@ -2802,7 +2801,6 @@
|
|||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
|
||||
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"iconv-lite": "0.4.19"
|
||||
}
|
||||
|
@ -3599,7 +3597,6 @@
|
|||
"version": "0.8.16",
|
||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
|
||||
"integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-js": "1.2.7",
|
||||
"isomorphic-fetch": "2.2.1",
|
||||
|
@ -3613,8 +3610,7 @@
|
|||
"core-js": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=",
|
||||
"dev": true
|
||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -4494,8 +4490,7 @@
|
|||
"iconv-lite": {
|
||||
"version": "0.4.19",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
|
||||
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
|
||||
},
|
||||
"icss-replace-symbols": {
|
||||
"version": "1.1.0",
|
||||
|
@ -5030,8 +5025,7 @@
|
|||
"is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
|
||||
"dev": true
|
||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
||||
},
|
||||
"is-svg": {
|
||||
"version": "2.1.0",
|
||||
|
@ -5094,7 +5088,6 @@
|
|||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
|
||||
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"node-fetch": "1.7.3",
|
||||
"whatwg-fetch": "2.0.4"
|
||||
|
@ -5845,7 +5838,6 @@
|
|||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
|
||||
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"encoding": "0.1.12",
|
||||
"is-stream": "1.1.0"
|
||||
|
@ -6046,8 +6038,7 @@
|
|||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
||||
"dev": true
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"object-copy": {
|
||||
"version": "0.1.0",
|
||||
|
@ -7168,7 +7159,6 @@
|
|||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asap": "2.0.6"
|
||||
}
|
||||
|
@ -7183,7 +7173,6 @@
|
|||
"version": "15.6.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz",
|
||||
"integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fbjs": "0.8.16",
|
||||
"loose-envify": "1.3.1",
|
||||
|
@ -7361,6 +7350,17 @@
|
|||
"integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=",
|
||||
"dev": true
|
||||
},
|
||||
"react": {
|
||||
"version": "16.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.4.1.tgz",
|
||||
"integrity": "sha512-3GEs0giKp6E0Oh/Y9ZC60CmYgUPnp7voH9fbjWsvXtYFb4EWtgQub0ADSq0sJR0BbHc4FThLLtzlcFaFXIorwg==",
|
||||
"requires": {
|
||||
"fbjs": "0.8.16",
|
||||
"loose-envify": "1.3.1",
|
||||
"object-assign": "4.1.1",
|
||||
"prop-types": "15.6.1"
|
||||
}
|
||||
},
|
||||
"read-pkg": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
|
||||
|
@ -8001,8 +8001,7 @@
|
|||
"setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
|
||||
"dev": true
|
||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.0",
|
||||
|
@ -9019,8 +9018,7 @@
|
|||
"ua-parser-js": {
|
||||
"version": "0.7.17",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz",
|
||||
"integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g=="
|
||||
},
|
||||
"uc.micro": {
|
||||
"version": "1.0.5",
|
||||
|
@ -10115,8 +10113,7 @@
|
|||
"whatwg-fetch": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
|
||||
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==",
|
||||
"dev": true
|
||||
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
|
||||
},
|
||||
"whet.extend": {
|
||||
"version": "0.9.9",
|
||||
|
|
|
@ -8,8 +8,12 @@
|
|||
"start": "webpack-dev-server --config webpack.web.dev.js",
|
||||
"eslint": "eslint \"src/**/*.{js,jsx,vue}\"",
|
||||
"dev": "webpack-dev-server --config webpack.web.dev.js",
|
||||
"dev:clips": "webpack-dev-server --config webpack.clips.dev.js",
|
||||
"dev:babel": "webpack-dev-server --config webpack.web.dev.babel.js",
|
||||
"build:all": "npm run build && npm run build:babel && npm run build:clips && npm run build:clips:babel",
|
||||
"build": "webpack --config webpack.web.prod.js --define process.env.NODE_ENV='production'",
|
||||
"build:clips": "webpack --config webpack.clips.prod.js --define process.env.NODE_ENV='production'",
|
||||
"build:clips:babel": "webpack --config webpack.clips.babel.js --define process.env.NODE_ENV='production'",
|
||||
"build:stats": "webpack --config webpack.web.prod.js --define process.env.NODE_ENV='production' --json > stats.json",
|
||||
"build:babel": "webpack --config webpack.web.babel.js --define process.env.NODE_ENV='production'",
|
||||
"build:prod": "webpack --config webpack.web.prod.js --define process.env.NODE_ENV='production'",
|
||||
|
@ -59,6 +63,7 @@
|
|||
"path-to-regexp": "^2.2.1",
|
||||
"popper.js": "^1.14.3",
|
||||
"raven-js": "^3.24.2",
|
||||
"react": "^16.4.1",
|
||||
"safe-regex": "^1.1.0",
|
||||
"sortablejs": "^1.7.0",
|
||||
"vue": "^2.5.16",
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
const DEBUG = localStorage.ffzDebugMode == 'true' && document.body.classList.contains('ffz-dev') && ! window.Ember,
|
||||
SERVER = DEBUG ? '//localhost:8000' : '//cdn.frankerfacez.com',
|
||||
BABEL = /Edge/.test(window.navigator.userAgent) ? 'babel/' : '',
|
||||
CLIPS = /clips\.twitch\.tv/.test(location.hostname) ? 'clips/' : '',
|
||||
FLAVOR = window.Ember ? 'umbral' : 'avalon',
|
||||
|
||||
script = document.createElement('script');
|
||||
|
@ -15,6 +16,6 @@
|
|||
script.id = 'ffz-script';
|
||||
script.async = true;
|
||||
script.crossOrigin = 'anonymous';
|
||||
script.src = `${SERVER}/script/${BABEL}${FLAVOR}.js?_=${Date.now()}`;
|
||||
script.src = `${SERVER}/script/${CLIPS}${BABEL}${FLAVOR}.js?_=${Date.now()}`;
|
||||
document.head.appendChild(script);
|
||||
})();
|
|
@ -100,7 +100,7 @@ class FrankerFaceZ extends Module {
|
|||
FrankerFaceZ.Logger = Logger;
|
||||
|
||||
const VER = FrankerFaceZ.version_info = {
|
||||
major: 4, minor: 0, revision: 0, extra: '-rc7.1',
|
||||
major: 4, minor: 0, revision: 0, extra: '-rc8',
|
||||
commit: __git_commit__,
|
||||
build: __webpack_hash__,
|
||||
toString: () =>
|
||||
|
|
|
@ -268,13 +268,14 @@ export default class Badges extends Module {
|
|||
for(const d of data) {
|
||||
const p = d.provider;
|
||||
if ( p === 'twitch' ) {
|
||||
const bd = this.getTwitchBadge(d.badge, d.version, room_id, room_login);
|
||||
const bd = this.getTwitchBadge(d.badge, d.version, room_id, room_login),
|
||||
global_badge = this.getTwitchBadge(d.badge, d.version) || {};
|
||||
if ( ! bd )
|
||||
continue;
|
||||
|
||||
out.push(<div class="ffz-badge-tip">
|
||||
{show_previews && <img class="preview-image ffz-badge" src={bd.image4x} />}
|
||||
{bd.title}
|
||||
{bd.title || global_badge.title}
|
||||
</div>);
|
||||
|
||||
/*out.push(e('div', {className: 'ffz-badge-tip'}, [
|
||||
|
@ -457,6 +458,7 @@ export default class Badges extends Module {
|
|||
props = data.props;
|
||||
|
||||
props.className = 'ffz-tooltip ffz-badge';
|
||||
props.key = `${props['data-provider']}-${props['data-badge']}`;
|
||||
props['data-tooltip-type'] = 'badge';
|
||||
props['data-badge-data'] = JSON.stringify(data.badges);
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@ import {has, deep_copy} from 'utilities/object';
|
|||
|
||||
import {parse_path} from 'src/settings';
|
||||
|
||||
const EXCLUSIVE_SELECTOR = '.twilight-main,.twilight-minimal-root>div,.twilight-root>.tw-full-height',
|
||||
MAXIMIZED_SELECTOR = '.twilight-main,.twilight-minimal-root,.twilight-root .dashboard-side-nav+.tw-full-height',
|
||||
SELECTOR = '.twilight-root>.tw-full-height,.twilight-minimal-root>.tw-full-height';
|
||||
const EXCLUSIVE_SELECTOR = '.twilight-main,.twilight-minimal-root>div,.twilight-root>.tw-full-height,.clips-root',
|
||||
MAXIMIZED_SELECTOR = '.twilight-main,.twilight-minimal-root,.twilight-root .dashboard-side-nav+.tw-full-height,.clips-root>.tw-full-height .scrollable-area',
|
||||
SELECTOR = '.twilight-root>.tw-full-height,.twilight-minimal-root>.tw-full-height,.clips-root>.tw-full-height .scrollable-area';
|
||||
|
||||
function format_term(term) {
|
||||
return term.replace(/<[^>]*>/g, '').toLocaleLowerCase();
|
||||
|
|
|
@ -36,7 +36,7 @@ export default class TooltipProvider extends Module {
|
|||
}
|
||||
|
||||
onEnable() {
|
||||
const container = document.querySelector('.twilight-root,.twilight-minimal-root') || document.body,
|
||||
const container = document.querySelector('.twilight-root,.twilight-minimal-root,.clips-root') || document.body,
|
||||
is_minimal = container && container.classList.contains('twilight-minimal-root');
|
||||
|
||||
this.tips = new Tooltip(is_minimal ? '.twilight-minimal-root,body' : 'body #root,body', 'ffz-tooltip', {
|
||||
|
|
89
src/sites/twitch-clips/index.js
Normal file
89
src/sites/twitch-clips/index.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
'use strict';
|
||||
|
||||
// ============================================================================
|
||||
// Site Support: Twitch Clips
|
||||
// ============================================================================
|
||||
|
||||
import BaseSite from '../base';
|
||||
|
||||
import WebMunch from 'utilities/compat/webmunch';
|
||||
import Fine from 'utilities/compat/fine';
|
||||
import Apollo from 'utilities/compat/apollo';
|
||||
|
||||
import {createElement} from 'utilities/dom';
|
||||
|
||||
import MAIN_URL from 'site/styles/main.scss';
|
||||
|
||||
import Switchboard from './switchboard';
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// The Site
|
||||
// ============================================================================
|
||||
|
||||
export default class Clippy extends BaseSite {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.inject(WebMunch);
|
||||
this.inject(Fine);
|
||||
this.inject(Apollo, false);
|
||||
|
||||
this.inject(Switchboard);
|
||||
}
|
||||
|
||||
onLoad() {
|
||||
this.populateModules();
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
const thing = this.fine.searchTree(null, n => n.props && n.props.store),
|
||||
store = this.store = thing && thing.props && thing.props.store;
|
||||
|
||||
if ( ! store )
|
||||
return new Promise(r => setTimeout(r, 50)).then(() => this.onEnable());
|
||||
|
||||
// Share Context
|
||||
store.subscribe(() => this.updateContext());
|
||||
this.updateContext();
|
||||
|
||||
this.settings.updateContext({
|
||||
clips: true
|
||||
});
|
||||
|
||||
document.head.appendChild(createElement('link', {
|
||||
href: MAIN_URL,
|
||||
rel: 'stylesheet',
|
||||
type: 'text/css',
|
||||
crossOrigin: 'anonymouse'
|
||||
}));
|
||||
}
|
||||
|
||||
updateContext() {
|
||||
try {
|
||||
const state = this.store.getState(),
|
||||
history = this.router && this.router.history;
|
||||
|
||||
this.settings.updateContext({
|
||||
location: history && history.location,
|
||||
ui: state && state.ui,
|
||||
session: state && state.session
|
||||
});
|
||||
} catch(err) {
|
||||
this.log.error('Error updating context.', err);
|
||||
}
|
||||
}
|
||||
|
||||
getSession() {
|
||||
const state = this.store && this.store.getState();
|
||||
return state && state.session;
|
||||
}
|
||||
|
||||
getUser() {
|
||||
if ( this._user )
|
||||
return this._user;
|
||||
|
||||
const session = this.getSession();
|
||||
return this._user = session && session.user;
|
||||
}
|
||||
}
|
13
src/sites/twitch-clips/modules/chat/get_badges.gql
Normal file
13
src/sites/twitch-clips/modules/chat/get_badges.gql
Normal file
|
@ -0,0 +1,13 @@
|
|||
query FFZ_GetBadges {
|
||||
badges {
|
||||
id
|
||||
setID
|
||||
version
|
||||
title
|
||||
clickAction
|
||||
clickURL
|
||||
image1x: imageURL(size: NORMAL)
|
||||
image2x: imageURL(size: DOUBLE)
|
||||
image4x: imageURL(size: QUADRUPLE)
|
||||
}
|
||||
}
|
165
src/sites/twitch-clips/modules/chat/index.js
Normal file
165
src/sites/twitch-clips/modules/chat/index.js
Normal file
|
@ -0,0 +1,165 @@
|
|||
'use strict';
|
||||
|
||||
// ============================================================================
|
||||
// Chat Hooks
|
||||
// ============================================================================
|
||||
|
||||
import {get} from 'utilities/object';
|
||||
import {ColorAdjuster, Color} from 'utilities/color';
|
||||
|
||||
import Module from 'utilities/module';
|
||||
|
||||
import Line from './line';
|
||||
import BADGE_QUERY from './get_badges.gql';
|
||||
|
||||
|
||||
export default class Chat extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.should_enable = true;
|
||||
|
||||
this.colors = new ColorAdjuster;
|
||||
this.inverse_colors = new ColorAdjuster;
|
||||
|
||||
this.inject('settings');
|
||||
this.inject('i18n');
|
||||
|
||||
this.inject('chat');
|
||||
|
||||
this.inject('site');
|
||||
this.inject('site.fine');
|
||||
|
||||
this.inject(Line);
|
||||
|
||||
this.ChatController = this.fine.define(
|
||||
'clip-chat-controller',
|
||||
n => n.filterChatLines
|
||||
);
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
this.chat.context.on('changed:chat.adjustment-mode', this.updateColors, this);
|
||||
this.chat.context.on('changed:chat.adjustment-contrast', this.updateColors, this);
|
||||
this.chat.context.on('changed:theme.is-dark', this.updateColors, this);
|
||||
|
||||
this.ChatController.on('mount', this.chatMounted, this);
|
||||
this.ChatController.on('unmount', this.chatMounted, this);
|
||||
this.ChatController.on('receive-props', this.chatUpdated, this);
|
||||
|
||||
this.ChatController.ready((cls, instances) => {
|
||||
for(const inst of instances)
|
||||
this.chatMounted(inst);
|
||||
});
|
||||
|
||||
this.loadBadges();
|
||||
this.updateColors();
|
||||
}
|
||||
|
||||
|
||||
updateColors() {
|
||||
const is_dark = this.chat.context.get('theme.is-dark'),
|
||||
mode = this.chat.context.get('chat.adjustment-mode'),
|
||||
contrast = this.chat.context.get('chat.adjustment-contrast'),
|
||||
c = this.colors,
|
||||
ic = this.inverse_colors;
|
||||
|
||||
// TODO: Get the background color from the theme system.
|
||||
// Updated: Use the lightest/darkest colors from alternating rows for better readibility.
|
||||
c._base = is_dark ? '#191919' : '#e0e0e0'; //#0e0c13' : '#faf9fa';
|
||||
c.mode = mode;
|
||||
c.contrast = contrast;
|
||||
|
||||
ic._base = is_dark ? '#dad8de' : '#19171c';
|
||||
ic.mode = mode;
|
||||
ic.contrast = contrast;
|
||||
|
||||
this.line.updateLines();
|
||||
}
|
||||
|
||||
|
||||
async loadBadges() {
|
||||
let data;
|
||||
try {
|
||||
data = await this.resolve('site.apollo').client.query({
|
||||
query: BADGE_QUERY
|
||||
});
|
||||
} catch(err) {
|
||||
this.log.warn('Error loading badge data.', err);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( data && data.data && data.data.badges )
|
||||
this.chat.badges.updateTwitchBadges(data.data.badges);
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Room Handling
|
||||
// ========================================================================
|
||||
|
||||
addRoom(thing, props) {
|
||||
if ( ! props )
|
||||
props = thing.props;
|
||||
|
||||
const channel_id = get('data.clip.broadcaster.id', props);
|
||||
if ( ! channel_id )
|
||||
return null;
|
||||
|
||||
const room = thing._ffz_room = this.chat.getRoom(channel_id, null, false, true);
|
||||
room.ref(thing);
|
||||
return room;
|
||||
}
|
||||
|
||||
|
||||
removeRoom(thing) { // eslint-disable-line class-methods-use-this
|
||||
if ( ! thing._ffz_room )
|
||||
return;
|
||||
|
||||
thing._ffz_room.unref(thing);
|
||||
thing._ffz_room = null;
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Chat Controller
|
||||
// ========================================================================
|
||||
|
||||
chatMounted(chat, props) {
|
||||
if ( ! props )
|
||||
props = chat.props;
|
||||
|
||||
if ( ! this.addRoom(chat, props) )
|
||||
return;
|
||||
|
||||
this.updateRoomBadges(chat, get('data.clip.video.owner.broadcastBadges', props));
|
||||
}
|
||||
|
||||
|
||||
chatUmounted(chat) {
|
||||
this.removeRoom(chat);
|
||||
}
|
||||
|
||||
|
||||
chatUpdated(chat, props) {
|
||||
if ( get('data.clip.broadcaster.id', props) !== get('data.clip.broadcaster.id', chat.props) ) {
|
||||
this.chatUmounted(chat);
|
||||
this.chatMounted(chat, props);
|
||||
return;
|
||||
}
|
||||
|
||||
const new_room_badges = get('data.clip.video.owner.broadcastBadges', props),
|
||||
old_room_badges = get('data.clip.video.owner.broadcastBadges', chat.props);
|
||||
|
||||
if ( new_room_badges !== old_room_badges )
|
||||
this.updateRoomBadges(chat, new_room_badges);
|
||||
}
|
||||
|
||||
updateRoomBadges(chat, badges) { // eslint-disable-line class-methods-use-this
|
||||
const room = chat._ffz_room;
|
||||
if ( ! room )
|
||||
return;
|
||||
|
||||
room.updateBadges(badges);
|
||||
}
|
||||
}
|
166
src/sites/twitch-clips/modules/chat/line.jsx
Normal file
166
src/sites/twitch-clips/modules/chat/line.jsx
Normal file
|
@ -0,0 +1,166 @@
|
|||
'use strict';
|
||||
|
||||
// ============================================================================
|
||||
// Chat Line
|
||||
// ============================================================================
|
||||
|
||||
import Module from 'utilities/module';
|
||||
|
||||
import {createElement} from 'react';
|
||||
import { split_chars } from '../../../../utilities/object';
|
||||
|
||||
|
||||
export default class Line extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.inject('settings');
|
||||
this.inject('i18n');
|
||||
|
||||
this.inject('chat');
|
||||
|
||||
this.inject('site');
|
||||
this.inject('site.fine');
|
||||
|
||||
this.ChatLine = this.fine.define(
|
||||
'clip-chat-line',
|
||||
n => n.renderFragments && n.renderUserBadges
|
||||
);
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
this.chat.context.on('changed:chat.emoji.style', this.updateLines, this);
|
||||
this.chat.context.on('changed:chat.bits.stack', this.updateLines, this);
|
||||
this.chat.context.on('changed:chat.badges.style', this.updateLines, this);
|
||||
this.chat.context.on('changed:chat.badges.hidden', this.updateLines, this);
|
||||
this.chat.context.on('changed:chat.badges.custom-mod', this.updateLines, this);
|
||||
this.chat.context.on('changed:chat.rich.enabled', this.updateLines, this);
|
||||
this.chat.context.on('changed:chat.rich.hide-tokens', this.updateLines, this);
|
||||
|
||||
this.ChatLine.ready((cls, instances) => {
|
||||
const t = this,
|
||||
old_render = cls.prototype.render;
|
||||
|
||||
cls.prototype.render = function() {
|
||||
try {
|
||||
const msg = t.standardizeMessage(this.props.node, this.props.video),
|
||||
is_action = msg.is_action,
|
||||
user = msg.user,
|
||||
color = t.parent.colors.process(user.color),
|
||||
|
||||
tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, user);
|
||||
|
||||
return (<div class="tw-mg-b-1 tw-font-size-5 tw-c-text-alt clip-chat__message">
|
||||
<div class="tw-animation tw-animation--animate tw-animation--duration-short tw-animation--fill-mode-both tw-animation--slide-in-bottom tw-animation--timing-ease" data-room-id={msg.roomID} data-room={msg.roomLogin} data-user-id={user.id} data-user={user.login}>
|
||||
<span class="chat-line__message--badges">{
|
||||
t.chat.badges.render(msg, createElement)
|
||||
}</span>
|
||||
<a
|
||||
class="tw-font-size-5 tw-strong clip-chat__message-author notranslate"
|
||||
href={`https://www.twitch.tv/${user.login}/clips`}
|
||||
style={{color}}
|
||||
>
|
||||
<span class="chat-author__display-name">{ user.displayName }</span>
|
||||
{user.isIntl && <span class="chat-author__intl-login"> ({user.login})</span>}
|
||||
</a>
|
||||
<span>{is_action ? ' ' : ': '}</span>
|
||||
<span class="message" style={{color: is_action ? color : null}}>{
|
||||
t.chat.renderTokens(tokens, createElement)
|
||||
}</span>
|
||||
</div>
|
||||
</div>)
|
||||
|
||||
} catch(err) {
|
||||
t.log.error(err);
|
||||
t.log.capture(err, {extra:{props: this.props}});
|
||||
}
|
||||
|
||||
return old_render.call(this);
|
||||
}
|
||||
|
||||
this.ChatLine.forceUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
updateLines() {
|
||||
for(const inst of this.ChatLine.instances) {
|
||||
const msg = inst.props.node;
|
||||
if ( msg )
|
||||
msg._ffz_message = null;
|
||||
}
|
||||
|
||||
this.ChatLine.forceUpdate();
|
||||
}
|
||||
|
||||
|
||||
standardizeMessage(msg, video) {
|
||||
if ( ! msg || ! msg.message )
|
||||
return msg;
|
||||
|
||||
if ( msg._ffz_message )
|
||||
return msg._ffz_message;
|
||||
|
||||
const room = this.chat.getRoom(video.owner.id, null, true, true),
|
||||
author = msg.commenter,
|
||||
badges = {};
|
||||
|
||||
if ( msg.message.userBadges )
|
||||
for(const badge of msg.message.userBadges)
|
||||
if ( badge )
|
||||
badges[badge.setID] = badge.version;
|
||||
|
||||
const out = msg._ffz_message = {
|
||||
user: {
|
||||
color: author.chatColor,
|
||||
id: author.id,
|
||||
login: author.login,
|
||||
displayName: author.displayName,
|
||||
isIntl: author.name && author.displayName && author.displayName.trim().toLowerCase() !== author.name,
|
||||
type: 'user'
|
||||
},
|
||||
roomLogin: room && room.login,
|
||||
roomID: room && room.id,
|
||||
badges,
|
||||
messageParts: msg.message.fragments
|
||||
};
|
||||
|
||||
this.detokenizeMessage(out, msg);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
detokenizeMessage(msg) { // eslint-disable-line class-methods-use-this
|
||||
const out = [],
|
||||
parts = msg.messageParts,
|
||||
l = parts.length,
|
||||
emotes = {};
|
||||
|
||||
let idx = 0;
|
||||
|
||||
for(let i=0; i < l; i++) {
|
||||
const part = parts[i],
|
||||
text = part && part.text;
|
||||
|
||||
if ( ! text || ! text.length )
|
||||
continue;
|
||||
|
||||
const len = split_chars(text).length;
|
||||
|
||||
if ( part.emote ) {
|
||||
const id = part.emote.emoteID,
|
||||
em = emotes[id] = emotes[id] || [];
|
||||
|
||||
em.push({startIndex: idx, endIndex: idx + len - 1});
|
||||
}
|
||||
|
||||
out.push(text);
|
||||
idx += len;
|
||||
}
|
||||
|
||||
msg.message = out.join('');
|
||||
msg.ffz_emotes = emotes;
|
||||
|
||||
return msg;
|
||||
}
|
||||
}
|
102
src/sites/twitch-clips/modules/theme/index.js
Normal file
102
src/sites/twitch-clips/modules/theme/index.js
Normal file
|
@ -0,0 +1,102 @@
|
|||
'use strict';
|
||||
|
||||
// ============================================================================
|
||||
// Menu Module
|
||||
// ============================================================================
|
||||
|
||||
import Module from 'utilities/module';
|
||||
import {createElement} from 'utilities/dom';
|
||||
|
||||
//import THEME_CSS_URL from 'site/styles/theme.scss';
|
||||
|
||||
|
||||
export default class ThemeEngine extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.inject('settings');
|
||||
|
||||
this.inject('site');
|
||||
|
||||
this.should_enable = true;
|
||||
|
||||
this.settings.add('theme.dark', {
|
||||
requires: ['theme.is-dark'],
|
||||
default: false,
|
||||
process(ctx, val) {
|
||||
return ctx.get('theme.is-dark') ? val : false
|
||||
},
|
||||
|
||||
ui: {
|
||||
path: 'Appearance @{"description": "Personalize the appearance of Twitch. Change the color scheme and fonts and tune the layout to optimize your experience."} > Theme >> General',
|
||||
title: 'Gray (no Purple)',
|
||||
description: '<em>Requires Dark Theme to be Enabled.</em><br>I see my website and I want it painted black...<br>This is a very early feature and will change as there is time.',
|
||||
component: 'setting-check-box'
|
||||
},
|
||||
|
||||
changed: val => this.updateSetting(val)
|
||||
});
|
||||
|
||||
this.settings.add('theme.can-dark', {
|
||||
requires: ['context.route.name'],
|
||||
process(ctx) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('theme.is-dark', {
|
||||
requires: ['theme.can-dark', 'context.ui.theme'],
|
||||
process(ctx) {
|
||||
return ctx.get('theme.can-dark') && ctx.get('context.ui.theme') === 1;
|
||||
},
|
||||
changed: () => this.updateCSS()
|
||||
});
|
||||
|
||||
this.settings.add('theme.tooltips-dark', {
|
||||
requires: ['theme.is-dark'],
|
||||
process(ctx) {
|
||||
return ! ctx.get('theme.is-dark')
|
||||
}
|
||||
});
|
||||
|
||||
this._style = null;
|
||||
}
|
||||
|
||||
|
||||
updateCSS() {
|
||||
const dark = this.settings.get('theme.is-dark'),
|
||||
gray = this.settings.get('theme.dark');
|
||||
|
||||
document.body.classList.toggle('tw-theme--dark', dark);
|
||||
document.body.classList.toggle('tw-theme--ffz', gray);
|
||||
|
||||
}
|
||||
|
||||
|
||||
toggleStyle(enable) {
|
||||
if ( ! this._style ) {
|
||||
if ( ! enable )
|
||||
return;
|
||||
|
||||
this._style = createElement('link', {
|
||||
rel: 'stylesheet',
|
||||
type: 'text/css',
|
||||
//href: THEME_CSS_URL
|
||||
});
|
||||
|
||||
} else if ( ! enable ) {
|
||||
this._style.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
document.head.appendChild(this._style);
|
||||
}
|
||||
|
||||
updateSetting(enable) {
|
||||
this.toggleStyle(enable);
|
||||
this.updateCSS();
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
this.updateSetting(this.settings.get('theme.dark'));
|
||||
}
|
||||
}
|
25
src/sites/twitch-clips/styles/chat.scss
Normal file
25
src/sites/twitch-clips/styles/chat.scss
Normal file
|
@ -0,0 +1,25 @@
|
|||
.chat-line__message--emote {
|
||||
vertical-align: middle;
|
||||
margin: -.5rem 0;
|
||||
}
|
||||
|
||||
.chat-author__display-name,
|
||||
.chat-author__intl-login {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ffz-emoji {
|
||||
width: calc(var(--ffz-chat-font-size) * 1.5);
|
||||
height: calc(var(--ffz-chat-font-size) * 1.5);
|
||||
|
||||
&.preview-image {
|
||||
width: 7.2rem;
|
||||
height: 7.2rem;
|
||||
}
|
||||
|
||||
&.emote-autocomplete-provider__image {
|
||||
width: 1.8rem;
|
||||
height: 1.8rem;
|
||||
margin: .5rem;
|
||||
}
|
||||
}
|
3
src/sites/twitch-clips/styles/main.scss
Normal file
3
src/sites/twitch-clips/styles/main.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
@import 'styles/main.scss';
|
||||
|
||||
@import 'chat.scss';
|
80
src/sites/twitch-clips/switchboard.js
Normal file
80
src/sites/twitch-clips/switchboard.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
'use strict';
|
||||
|
||||
// ============================================================================
|
||||
// Switchboard
|
||||
// A hack for React Router to make it load a module.
|
||||
// ============================================================================
|
||||
|
||||
import Module from 'utilities/module';
|
||||
import pathToRegexp from 'path-to-regexp';
|
||||
|
||||
|
||||
export default class Switchboard extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.inject('site.web_munch');
|
||||
this.inject('site.fine');
|
||||
}
|
||||
|
||||
async onEnable() {
|
||||
await this.parent.awaitElement('.clips-root');
|
||||
if ( this.web_munch._require || this.web_munch.v4 === false )
|
||||
return;
|
||||
|
||||
const da_switch = this.fine.searchTree(null, n =>
|
||||
n.context && n.context.router &&
|
||||
n.props && n.props.children &&
|
||||
n.componentWillMount && n.componentWillMount.toString().includes('Switch')
|
||||
);
|
||||
|
||||
if ( ! da_switch )
|
||||
return new Promise(r => setTimeout(r, 50)).then(() => this.onEnable());
|
||||
|
||||
|
||||
// Identify Router
|
||||
this.log.info(`Found Switch with ${da_switch.props.children.length} routes.`);
|
||||
|
||||
const location = da_switch.context.router.route.location.pathname;
|
||||
|
||||
for(const route of da_switch.props.children) {
|
||||
if ( ! route.props || ! route.props.component )
|
||||
continue;
|
||||
|
||||
try {
|
||||
const reg = pathToRegexp(route.props.path);
|
||||
if ( ! reg.exec || reg.exec(location) )
|
||||
continue;
|
||||
|
||||
} catch(err) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.log.info('Found Non-Matching Route', route.props.path);
|
||||
|
||||
let component;
|
||||
|
||||
try {
|
||||
component = new route.props.component;
|
||||
} catch(err) {
|
||||
this.log.error('Error instantiating component for forced chunk loading.', err);
|
||||
component = null;
|
||||
}
|
||||
|
||||
if ( ! component || ! component.props || ! component.props.children || ! component.props.children.props || ! component.props.children.props.loader )
|
||||
continue;
|
||||
|
||||
try {
|
||||
component.props.children.props.loader().then(() => {
|
||||
this.log.info('Successfully forced a chunk to load using route', route.props.path)
|
||||
});
|
||||
} catch(err) {
|
||||
this.log.warn('Unexpected result trying to use component loader to force loading of another chunk.');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.log.warn('Unable to use any of the available routes.');
|
||||
}
|
||||
}
|
|
@ -180,5 +180,6 @@ Twilight.ROUTES = {
|
|||
'user-followers': '/:userName/followers',
|
||||
'user-following': '/:userName/following',
|
||||
'product': '/products/:productName',
|
||||
'prime': '/prime',
|
||||
'user': '/:userName'
|
||||
}
|
|
@ -9,7 +9,7 @@ import {createElement} from 'utilities/dom';
|
|||
|
||||
import THEME_CSS_URL from 'site/styles/theme.scss';
|
||||
|
||||
const BAD_ROUTES = ['product'];
|
||||
const BAD_ROUTES = ['product', 'prime'];
|
||||
|
||||
|
||||
export default class ThemeEngine extends Module {
|
||||
|
|
|
@ -195,8 +195,6 @@ export default class VideoChatHook extends Module {
|
|||
if ( this.state.showReplyForm || ! t.chat.context.get('chat.video-chat.enabled') )
|
||||
return old_render.call(this);
|
||||
|
||||
t.log.info('Video Chat', this);
|
||||
|
||||
const context = this.props.messageContext,
|
||||
msg = t.standardizeMessage(context.comment, context.author),
|
||||
main_message = this.ffzRenderMessage(msg),
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
@import 'styles/main.scss';
|
||||
|
||||
@import 'menu_button';
|
||||
@import 'main_menu';
|
||||
@import 'player';
|
||||
@import 'channel';
|
||||
|
||||
|
|
28
src/std-components/markdown.vue
Normal file
28
src/std-components/markdown.vue
Normal file
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<div v-html="output" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import MD from 'markdown-it';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
source: String
|
||||
},
|
||||
|
||||
computed: {
|
||||
md() {
|
||||
return new MD({
|
||||
html: false,
|
||||
linkify: true
|
||||
});
|
||||
},
|
||||
|
||||
output() {
|
||||
return this.md.render(this.source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
|
@ -1,6 +1,7 @@
|
|||
@import 'icons';
|
||||
@import 'tooltips';
|
||||
@import 'widgets';
|
||||
@import 'main_menu';
|
||||
|
||||
@import 'chat';
|
||||
|
||||
|
|
82
webpack.clips.babel.js
Normal file
82
webpack.clips.babel.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
const webpack = require('webpack');
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.clips.common.js');
|
||||
const path = require('path');
|
||||
|
||||
const CleanPlugin = require('clean-webpack-plugin');
|
||||
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||
|
||||
const commit_hash = require('child_process').execSync('git rev-parse HEAD').toString().trim();
|
||||
|
||||
/* global module __dirname */
|
||||
|
||||
const config = module.exports = merge(common, {
|
||||
devtool: 'source-map',
|
||||
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
plugins: ['transform-es2015-classes']
|
||||
}
|
||||
}
|
||||
}]
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new CleanPlugin(['dist/clips/babel']),
|
||||
new UglifyJSPlugin({
|
||||
sourceMap: true,
|
||||
uglifyOptions: {
|
||||
compress: {
|
||||
keep_fnames: true,
|
||||
keep_classnames: true
|
||||
},
|
||||
mangle: {
|
||||
keep_classnames: true,
|
||||
keep_fnames: true
|
||||
}
|
||||
}
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
__git_commit__: JSON.stringify(commit_hash)
|
||||
}),
|
||||
new ManifestPlugin({
|
||||
basePath: 'clips/babel/',
|
||||
publicPath: 'clips/babel/',
|
||||
map: data => {
|
||||
if ( data.name.endsWith('.scss') )
|
||||
data.name = `${data.name.substr(0,data.name.length - 5)}.css`;
|
||||
|
||||
return data;
|
||||
}
|
||||
})
|
||||
],
|
||||
|
||||
output: {
|
||||
publicPath: '//cdn.frankerfacez.com/static/clips/babel/',
|
||||
path: path.resolve(__dirname, 'dist/clips/babel'),
|
||||
filename: '[name].[hash].js'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// This is why we can't have nice things.
|
||||
// Why can't I just access process.env.NODE_ENV from
|
||||
// one of these files when I set it with webpack's
|
||||
// CLI? So stupid.
|
||||
//
|
||||
// So here we go.
|
||||
// This is crap.
|
||||
// But it works.
|
||||
|
||||
for(const rule of config.module.rules) {
|
||||
if ( Array.isArray(rule.use) )
|
||||
for(const use of rule.use)
|
||||
if ( use.options && use.options.name && use.options.name.startsWith('[name].') )
|
||||
use.options.name = `[name].[hash].${use.options.name.slice(7)}`;
|
||||
}
|
13
webpack.clips.common.js
Normal file
13
webpack.clips.common.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
const path = require('path');
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
|
||||
/* global module __dirname */
|
||||
|
||||
module.exports = merge(common, {
|
||||
resolve: {
|
||||
alias: {
|
||||
site: path.resolve(__dirname, 'src/sites/twitch-clips/')
|
||||
}
|
||||
}
|
||||
});
|
73
webpack.clips.dev.js
Normal file
73
webpack.clips.dev.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
/* eslint-disable */
|
||||
const path = require('path');
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.clips.common.js');
|
||||
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const webpack = require('webpack');
|
||||
|
||||
/* global module */
|
||||
|
||||
module.exports = merge(common, {
|
||||
devtool: 'inline-source-map',
|
||||
|
||||
plugins: [
|
||||
new CopyPlugin([
|
||||
{
|
||||
from: './src/entry.js',
|
||||
to: 'script.js'
|
||||
}
|
||||
]),
|
||||
new webpack.DefinePlugin({
|
||||
__git_commit__: null
|
||||
})
|
||||
],
|
||||
|
||||
devServer: {
|
||||
https: true,
|
||||
port: 8000,
|
||||
compress: true,
|
||||
inline: false,
|
||||
|
||||
allowedHosts: [
|
||||
'.twitch.tv',
|
||||
'.frankerfacez.com'
|
||||
],
|
||||
|
||||
contentBase: path.join(__dirname, 'dev_cdn'),
|
||||
publicPath: '/script/clips/',
|
||||
|
||||
proxy: {
|
||||
'**': {
|
||||
target: 'http://cdn.frankerfacez.com/',
|
||||
changeOrigin: true
|
||||
}
|
||||
},
|
||||
|
||||
before(app) {
|
||||
// Because the headers config option is broken.
|
||||
app.get('/*', (req, res, next) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/script/script.js', (req, res, next) => {
|
||||
req.url = req.url.replace(/^\/script/, '/script/clips');
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/dev_server', (req, res) => {
|
||||
res.json({
|
||||
path: process.cwd(),
|
||||
version: 2
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
output: {
|
||||
publicPath: '//localhost:8000/script/clips/',
|
||||
filename: '[name].js',
|
||||
jsonpFunction: 'ffzWebpackJsonp'
|
||||
}
|
||||
})
|
85
webpack.clips.prod.js
Normal file
85
webpack.clips.prod.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
const webpack = require('webpack');
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.clips.common.js');
|
||||
const path = require('path');
|
||||
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||
const CleanPlugin = require('clean-webpack-plugin');
|
||||
|
||||
const uglify = require('uglify-es');
|
||||
|
||||
// Get Git info
|
||||
|
||||
const commit_hash = require('child_process').execSync('git rev-parse HEAD').toString().trim();
|
||||
|
||||
/* global module Buffer __dirname */
|
||||
|
||||
const config = module.exports = merge(common, {
|
||||
devtool: 'source-map',
|
||||
|
||||
plugins: [
|
||||
new CleanPlugin(['dist/clips']),
|
||||
new UglifyJSPlugin({
|
||||
sourceMap: true,
|
||||
uglifyOptions: {
|
||||
compress: {
|
||||
keep_fnames: true,
|
||||
keep_classnames: true
|
||||
},
|
||||
mangle: {
|
||||
keep_classnames: true,
|
||||
keep_fnames: true
|
||||
}
|
||||
}
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
__git_commit__: JSON.stringify(commit_hash)
|
||||
}),
|
||||
new CopyPlugin([
|
||||
{
|
||||
from: './src/entry.js',
|
||||
to: 'script.min.js',
|
||||
transform: content => {
|
||||
const text = content.toString('utf8');
|
||||
const minified = uglify.minify(text);
|
||||
return (minified && minified.code) ? Buffer.from(minified.code) : content;
|
||||
}
|
||||
}
|
||||
]),
|
||||
new ManifestPlugin({
|
||||
basePath: 'clips/',
|
||||
publicPath: 'clips/',
|
||||
map: data => {
|
||||
if ( data.name.endsWith('.scss') )
|
||||
data.name = `${data.name.substr(0,data.name.length - 5)}.css`;
|
||||
|
||||
return data;
|
||||
}
|
||||
})
|
||||
],
|
||||
|
||||
output: {
|
||||
publicPath: '//cdn.frankerfacez.com/static/clips/',
|
||||
path: path.resolve(__dirname, 'dist/clips'),
|
||||
filename: '[name].[hash].js'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// This is why we can't have nice things.
|
||||
// Why can't I just access process.env.NODE_ENV from
|
||||
// one of these files when I set it with webpack's
|
||||
// CLI? So stupid.
|
||||
//
|
||||
// So here we go.
|
||||
// This is crap.
|
||||
// But it works.
|
||||
|
||||
for(const rule of config.module.rules) {
|
||||
if ( Array.isArray(rule.use) )
|
||||
for(const use of rule.use)
|
||||
if ( use.options && use.options.name && use.options.name.startsWith('[name].') )
|
||||
use.options.name = `[name].[hash].${use.options.name.slice(7)}`;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue