feat: migrate to jsxc v4.0.0-alpha

- use webpack to build packages
- use typescript
This commit is contained in:
sualko 2018-09-05 11:05:56 +02:00
Родитель e5214aa68c
Коммит 8d04c91ee2
31 изменённых файлов: 4856 добавлений и 936 удалений

15
.editorconfig Normal file
Просмотреть файл

@ -0,0 +1,15 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[ts/**.ts]
charset = utf-8
indent_style = space
indent_size = 3
quote_type = single

3
.gitignore поставляемый
Просмотреть файл

@ -18,3 +18,6 @@ coverage
.phpstorm_helpers
.phpcomplete_extended
.php_cs.cache
/js/
yarn-error.log
/dist/

Просмотреть файл

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013 Klaus Herberth <klaus@jsxc.org>
Copyright (c) 2018 Klaus Herberth <klaus@jsxc.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -18,4 +18,4 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
THE SOFTWARE.

Просмотреть файл

@ -5,10 +5,6 @@ use OCA\OJSXC\Hooks;
\OCP\App::registerPersonal('ojsxc', 'settings/personal');
$isDevEnv = \OC::$server->getConfig()->getSystemValue('jsxc.environment') === 'dev';
$jsxc_root = ($isDevEnv)? 'jsxc/dev/' : 'jsxc/';
$jsProdSuffix = (!$isDevEnv)? '.min' : '';
$linkToGeneralConfig = \OC::$server->getURLGenerator()->linkToRoute('ojsxc.javascript.generalConfig');
\OCP\Util::addHeader(
@ -19,14 +15,11 @@ $linkToGeneralConfig = \OC::$server->getURLGenerator()->linkToRoute('ojsxc.javas
], ''
);
OCP\Util::addScript ( 'ojsxc', $jsxc_root.'lib/jquery.slimscroll' );
OCP\Util::addScript ( 'ojsxc', $jsxc_root.'lib/jquery.fullscreen' );
OCP\Util::addScript ( 'ojsxc', $jsxc_root.'lib/jsxc.dep'.$jsProdSuffix );
OCP\Util::addScript ( 'ojsxc', $jsxc_root.'jsxc'.$jsProdSuffix );
OCP\Util::addScript ( 'ojsxc', 'ojsxc');
OCP\Util::addScript ( 'ojsxc', 'jsxc/jsxc.bundle' );
OCP\Util::addScript ( 'ojsxc', 'bundle');
// ############# CSS #############
OCP\Util::addStyle ( 'ojsxc', 'jsxc.oc' );
OCP\Util::addStyle ( 'ojsxc', '../js/jsxc/styles/jsxc.bundle' );
OCP\Util::addStyle ( 'ojsxc', 'bundle' );
if(class_exists('\\OCP\\AppFramework\\Http\\EmptyContentSecurityPolicy')) {
$manager = \OC::$server->getContentSecurityPolicyManager();

Просмотреть файл

@ -37,7 +37,7 @@
<lib>xmlreader</lib>
<lib>xmlwriter</lib>
<lib>dom</lib>
<nextcloud min-version="12" max-version="14"/>
<nextcloud min-version="12" max-version="15"/>
</dependencies>
<repair-steps>

9
custom.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,9 @@
declare var OC: {
generateUrl(string, any?): string,
};
declare var oc_requesttoken: string;
declare var oc_current_user: string;
declare var jsxc: any;
declare var OJSXC_CONFIG: {
defaultLoginFormEnable: boolean,
};

Просмотреть файл

@ -1,4 +0,0 @@
/**
* This is a helper file for the concatenation.
*/
;

@ -1 +0,0 @@
Subproject commit 5c7c7a4cff6ea306833713bfd8c6b1a71fe1a9f3

Просмотреть файл

@ -1,523 +0,0 @@
/* global jsxc, oc_appswebroots, OC, oc_requesttoken, dijit, oc_config, OJSXC_CONFIG */
/* jshint latedef: nofunc */
(function($) {
"use strict";
var serverTypes = {
INTERNAL: 0,
EXTERNAL: 1,
MANAGED: 2
};
var forceLoginFormEnable;
function observeContactsMenu() {
var target = document.getElementById('contactsmenu');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.target.id !== 'contactsmenu-contacts') {
return;
}
$(mutation.target).find('[href^="xmpp:"]').addClass('jsxc_statusIndicator');
$(mutation.target).find('.contact').each(function() {
updateContactItem($(this));
});
jsxc.gui.detectUriScheme(mutation.target);
});
});
var config = {
attributes: true,
childList: true,
characterData: true,
subtree: true
};
observer.observe(target, config);
}
function updateContactItem(contactElement) {
var xmppAddresses = contactElement.find('[href^="xmpp:"]').map(function() {
return $(this).attr('href').replace(/^xmpp:/, '');
});
if (xmppAddresses.length === 0) {
return;
}
var lastMessages = [];
var highestPresent = jsxc.CONST.STATUS.indexOf('offline');
var highestPresentBid = xmppAddresses.get(0);
xmppAddresses.each(function(index, bid) {
var lastMsg = jsxc.getLastMsg(bid);
if (lastMsg) {
lastMessages.push(lastMsg);
}
var data = jsxc.storage.getUserItem('buddy', bid) || {};
if (data.status > highestPresent) {
highestPresent = data.status;
highestPresentBid = bid;
}
});
var latestMsg = {
date: 0
};
$(lastMessages).each(function(index, msg) {
if (msg.date > latestMsg.date) {
latestMsg = msg;
}
});
if (latestMsg.date > 0) {
// replace emoticons from XEP-0038 and pidgin with shortnames
$.each(jsxc.gui.emotions, function(i, val) {
latestMsg.text = latestMsg.text.replace(val[2], ':' + val[1] + ':');
});
// translate shortnames to images
latestMsg.text = jsxc.gui.shortnameToImage(latestMsg.text);
contactElement.find('.last-message').html(latestMsg.text);
}
if (highestPresent > 0) {
var status = jsxc.CONST.STATUS[highestPresent];
contactElement.removeClass('jsxc_' + jsxc.CONST.STATUS.join(' jsxc_')).addClass('jsxc_' + status);
}
if (highestPresentBid) {
contactElement.find('.avatar').click(function() {
jsxc.gui.queryActions.message(highestPresentBid);
});
}
}
function injectChatIcon() {
var div = $('<div/>');
div.addClass('jsxc_chatIcon');
div.click(function() {
jsxc.gui.roster.toggle();
});
$('#header form.searchbox').after(div);
}
function onRosterToggle(ev, state, duration) {
$('body').removeClass('jsxc-roster-hidden jsxc-roster-shown').addClass('jsxc-roster-' + state);
// trigger nextcloud/owncloud triggers
setTimeout(function() {
$(window).resize();
}, duration + 50);
}
function onRosterReady(ev, rosterState) {
injectChatIcon();
$('body').removeClass('jsxc-roster-hidden jsxc-roster-shown').addClass('jsxc-roster-' + rosterState);
// update webodf
$(window).on('hashchange', function() {
if (window.location.pathname.match(/\/documents\/$/)) {
var docNo = window.location.hash.replace(/^#/, '');
if (docNo.match(/[0-9]+/) && typeof dijit !== 'undefined') {
dijit.byId("mainContainer").resize();
}
}
});
}
function defaultAvatar(element, jid) {
var adminSettings = jsxc.options.get('adminSettings') || {};
var cache = jsxc.storage.getUserItem('defaultAvatars') || {};
var data = jsxc.storage.getUserItem('buddy', jsxc.jidToBid(jid)) || {};
var node = Strophe.getNodeFromJid(jid);
var domain = Strophe.getDomainFromJid(jid);
var user = Strophe.unescapeNode(node);
$(element).each(function() {
var $div = $(this).find('.jsxc_avatar');
var size = $div.width();
var key = user + '@' + size;
var handleResponse = function(result) {
if (typeof(result) === 'object') {
if (result.data && result.data.displayname) {
$div.imageplaceholder(user, result.data.displayname);
} else {
$div.imageplaceholder(user);
}
} else {
$div.css('backgroundImage', 'url(' + result + ')');
$div.text('');
}
};
if (domain !== adminSettings.xmppDomain) {
// probably external user, don't request avatar
$div.imageplaceholder(user);
} else if (typeof cache[key] === 'undefined' || cache[key] === null) {
if (data.status === 0) {
// don't query avatar for offline users
$div.imageplaceholder(user, data.name);
return;
}
var url;
url = OC.generateUrl('/avatar/' + encodeURIComponent(user) + '/' + size + '?requesttoken={requesttoken}', {
user: user,
size: size,
requesttoken: oc_requesttoken
});
$.get(url, function(result) {
var val = (typeof result === 'object') ? result : url;
handleResponse(val);
jsxc.storage.updateItem('defaultAvatars', key, val, true);
});
} else {
handleResponse(cache[key]);
}
});
}
function loadSettings(username, password, cb) {
$.ajax({
type: 'POST',
url: OC.generateUrl('apps/ojsxc/settings'),
data: {
username: username,
password: password
},
success: function(d) {
if (d.result === 'success' && d.data && d.data.serverType !== 'internal' && d.data.xmpp.url !== '' && d.data.xmpp.url !== null) {
jsxc.storage.setItem('serverType', serverTypes[d.data.serverType.toUpperCase()]);
if (forceLoginFormEnable) {
d.data.loginForm.enable = true;
}
cb(d.data);
} else if (d.data && d.data.serverType === 'internal') {
jsxc.storage.setItem('serverType', serverTypes.INTERNAL);
var node = username || OC.currentUser;
jsxc.bid = node.toLowerCase() + '@' + window.location.host;
jsxc.options.set('adminSettings', d.data.adminSettings);
if (d.data.loginForm) {
jsxc.options.set('loginForm', {
startMinimized: d.data.loginForm.startMinimized
});
}
cb(false);
} else {
cb(false);
}
},
error: function(xhr) {
jsxc.error('XHR error on getSettings.php');
if (xhr.responseJSON && xhr.responseJSON.message) {
jsxc.debug('Error message: ' + xhr.responseJSON.message);
}
if (xhr.status === 412) {
jsxc.debug('Refresh page to get a new CSRF token');
window.location.href = window.location.href;
return;
}
cb(false);
}
});
}
function saveSettinsPermanent(data, cb) {
$.ajax({
type: 'POST',
url: OC.generateUrl('apps/ojsxc/settings/user'),
data: data,
success: function(data) {
cb(data && data.status === 'success');
},
error: function() {
cb(false);
}
});
}
function getUsers(search, cb) {
$.ajax({
type: 'GET',
url: OC.generateUrl('apps/ojsxc/settings/users'),
data: {
search: search
},
success: cb,
error: function() {
jsxc.error('XHR error on getUsers.php');
}
});
}
function getViewportSize() {
var w = $(window).width() - $('#jsxc_windowListSB').width();
var h = $(window).height() - $('#header').height() - 10;
if (jsxc.storage.getUserItem('roster') === 'shown') {
w -= $('#jsxc_roster').outerWidth(true);
}
return {
width: w,
height: h
};
}
function addChatSubmitButton() {
var defaultEnable = OJSXC_CONFIG.defaultLoginFormEnable;
var jsxcSubmitWrapperElement = $('<div>');
jsxcSubmitWrapperElement.attr('id', 'jsxc_submit_wrapper');
var submitElement = $('<input>');
submitElement.attr({
type: 'button',
id: 'jsxc_submit',
});
submitElement.addClass('login primary');
submitElement.val(defaultEnable ? $.t('Log_in_without_chat') : $.t('Log_in_with_chat'));
submitElement.click(function() {
jsxc.storage.setItem('login_without_chat', defaultEnable);
if (defaultEnable) { // log in without chat
jsxc.submitLoginForm();
} else { // log in with chat
forceLoginFormEnable = true;
$(jsxc.options.loginForm.form).submit();
}
});
jsxcSubmitWrapperElement.append(submitElement);
$('.login-additional').prepend(jsxcSubmitWrapperElement);
$('#lost-password').mouseup(function(ev) {
ev.preventDefault();
jsxcSubmitWrapperElement.slideUp().fadeOut();
});
$('#lost-password-back').mouseup(function(ev) {
ev.preventDefault();
jsxcSubmitWrapperElement.slideDown().fadeIn();
});
}
function addServerTypetoBodyTag() {
let type = parseInt(jsxc.storage.getItem('serverType'));
if (parseInt(type) === serverTypes.INTERNAL) {
$('body').addClass('jsxc-internal-server');
}
}
// initialization
$(function() {
if (location.pathname.substring(location.pathname.lastIndexOf("/") + 1) === 'public.php') {
// abort on shares
return;
}
if (window.parent && window !== window.parent) {
// abort if inside a frame
return;
}
if (typeof jsxc === 'undefined' || typeof emojione === 'undefined') {
// abort if core or dependencies threw an error
return;
}
if (typeof oc_config === 'undefined' || typeof oc_appswebroots === 'undefined' || typeof OC === 'undefined') {
// abort if a dependency is missing
return;
}
if (OC.generateUrl('login/flow') === window.location.pathname) {
// abort on login flow
return;
}
addServerTypetoBodyTag();
$(document).one('ready-roster-jsxc', onRosterReady);
$(document).on('toggle.roster.jsxc', onRosterToggle);
$(document).on('connected.jsxc', function() {
// reset default avatar cache
jsxc.storage.removeUserItem('defaultAvatars');
// when we are connected it doesn't matter anymore whether we logged in without chat since the user
// must have manually logged in
jsxc.storage.setItem('login_without_chat', false);
});
$(document).on('connfail.jsxc', function(ev, condition) {
if (condition === 'x-nc-not_allowed_to_chat') {
jsxc.gui.roster.toggle(jsxc.CONST.HIDDEN);
$('.jsxc_chatIcon').remove();
jsxc.storage.removeItem('jid');
jsxc.storage.removeItem('sid');
jsxc.storage.removeItem('rid');
}
});
$(document).on('status.contacts.count status.contact.updated', function() {
if (jsxc.restoreCompleted) {
setTimeout(function() {
jsxc.gui.detectEmail($('table#contactlist'));
}, 500);
} else {
$(document).on('restoreCompleted.jsxc', function() {
jsxc.gui.detectEmail($('table#contactlist'));
});
}
});
jsxc.init({
app_name: 'Nextcloud',
loginForm: {
form: '#body-login form',
jid: '#user',
pass: '#password',
ifFound: 'force',
onConnecting: (oc_config.version.match(/^([8-9]|[0-9]{2,})+\./)) ? 'quiet' : 'dialog'
},
logoutElement: $('#logout'),
rosterAppend: 'body',
root: oc_appswebroots.ojsxc + '/js/jsxc',
RTCPeerConfig: {
url: OC.generateUrl('apps/ojsxc/settings/iceServers')
},
displayRosterMinimized: function() {
return OC.currentUser != null;
},
defaultAvatar: function(jid) {
defaultAvatar(this, jid);
},
loadSettings: loadSettings,
saveSettinsPermanent: saveSettinsPermanent,
getUsers: getUsers,
viewport: {
getSize: getViewportSize
}
});
// Add submit link without chat functionality
if (jsxc.el_exists(jsxc.options.loginForm.form) && jsxc.el_exists(jsxc.options.loginForm.jid) && jsxc.el_exists(jsxc.options.loginForm.pass)) {
addChatSubmitButton();
Strophe.log = function(level, msg) {
if (level === 3 && /^request id/.test(msg)) {
console.warn('Something went wrong during BOSH connection establishment. Continue without chat.');
jsxc.submitLoginForm();
}
};
}
if ($('#contactsmenu').length > 0) {
observeContactsMenu();
}
});
$(document).on('click', '#jsxc_roster p', function() {
if (jsxc.storage.getItem('serverType') === serverTypes.INTERNAL) {
startInternalBackend();
}
});
function startInternalBackend() {
var currentUser = OC.currentUser;
if (!currentUser) {
return;
}
jsxc.bid = currentUser.toLowerCase() + '@' + window.location.host;
jsxc.options.set('xmpp', {
url: OC.generateUrl('apps/ojsxc/http-bind')
});
$(document).one('attached.jsxc', function() {
if (jsxc.options.get('loginForm').startMinimized !== true) {
jsxc.gui.roster.toggle(jsxc.CONST.SHOWN);
}
});
jsxc.start(jsxc.bid + '/internal', 'internal', '123456');
}
if (jsxc.storage.getItem('serverType') === serverTypes.INTERNAL) {
jsxc.gui.showLoginBox = function() {};
}
$(document).on('stateChange.jsxc', function _handler(event, state) {
if (state === jsxc.CONST.STATE.SUSPEND) {
/**
* The first time we go into suspend mode we check if we are using the internal backend.
* If this is the case and the user explicitly press the "login_without_chat" button when logging
* into Nextcloud we know we are using another authentication mechanism (like SAML/SSO) and thus have
* to manually start the connection.
*/
var chatDisabledByUser = jsxc.storage.getUserItem('forcedLogout') || jsxc.storage.getItem('login_without_chat');
$(document).off('stateChange.jsxc', _handler);
if (jsxc.storage.getItem('serverType') === null) {
$.ajax({
url: OC.generateUrl('apps/ojsxc/settings/servertype'),
success: function(data) {
jsxc.storage.setItem('serverType', serverTypes[data.serverType.toUpperCase()]);
if (data.serverType === 'internal' && !chatDisabledByUser) {
jsxc.gui.showLoginBox = function() {};
startInternalBackend();
}
}
});
} else if (jsxc.storage.getItem('serverType') === serverTypes.INTERNAL && !chatDisabledByUser) {
jsxc.gui.showLoginBox = function() {};
startInternalBackend();
}
} else if (state === jsxc.CONST.STATE.READY) {
// if JSXC is ready this means we successfully connected and thus don't have to listen to the suspend state
$(document).off('stateChange.jsxc', _handler);
}
});
}(jQuery));

Просмотреть файл

@ -1,290 +0,0 @@
/* global $, OC */
$(document).ready(function() {
/**
* Test if bosh server is up and running.
*
* @param {string} url BOSH url
* @param {string} domain host domain for BOSH server
* @param {Function} cb called if test is done
*/
function testBoshServer(url, domain, cb) {
var rid = jsxc.storage.getItem('rid') || '123456';
function fail(m) {
var msg = 'BOSH server NOT reachable or misconfigured.';
if (typeof m === 'string') {
msg += '<br /><br />' + m;
}
cb({
status: 'fail',
msg: msg
});
}
$.ajax({
type: 'POST',
url: url,
data: "<body rid='" + rid + "' xmlns='http://jabber.org/protocol/httpbind' to='" + domain + "' xml:lang='en' wait='60' hold='1' content='text/xml; charset=utf-8' ver='1.6' xmpp:version='1.0' xmlns:xmpp='urn:xmpp:xbosh'/>",
global: false,
dataType: 'xml'
}).done(function(stanza) {
if (typeof stanza === 'string') {
// shouldn't be needed anymore, because of dataType
stanza = $.parseXML(stanza);
}
var body = $(stanza).find('body[xmlns="http://jabber.org/protocol/httpbind"]');
var condition = (body) ? body.attr('condition') : null;
var type = (body) ? body.attr('type') : null;
// we got a valid xml response, but we have test for errors
if (body.length > 0 && type !== 'terminate') {
cb({
status: 'success',
msg: 'BOSH Server reachable.'
});
} else {
if (condition === 'internal-server-error') {
fail('Internal server error: ' + body.text());
} else if (condition === 'host-unknown') {
if (url) {
fail('Host unknown: ' + domain + ' is unknown to your XMPP server.');
} else {
fail('Host unknown: Please provide a XMPP domain.');
}
} else {
fail(condition);
}
}
}).fail(function(xhr, textStatus) {
// no valid xml, not found or csp issue
var fullurl;
if (url.match(/^https?:\/\//)) {
fullurl = url;
} else {
fullurl = window.location.protocol + '//' + window.location.host;
if (url.match(/^\//)) {
fullurl += url;
} else {
fullurl += window.location.pathname.replace(/[^/]+$/, "") + url;
}
}
if (xhr.status === 0) {
// cross-side
fail('Cross domain request was not possible. Either your BOSH server does not send any ' +
'Access-Control-Allow-Origin header or the content-security-policy (CSP) blocks your request. ' +
'Starting from Owncloud 9.0 your CSP will be updated in any app which uses the appframework (e.g. files) ' +
'after you save these settings and reload.' +
'The savest way is still to use Apache ProxyRequest or Nginx proxy_pass.');
} else if (xhr.status === 404) {
// not found
fail('Your server responded with "404 Not Found". Please check if your BOSH server is running and reachable via ' + fullurl + '.');
} else if (textStatus === 'parsererror') {
fail('Invalid XML received. Maybe ' + fullurl + ' was redirected. You should use an absolute url.');
} else {
fail(xhr.status + ' ' + xhr.statusText);
}
});
}
$('#ojsxc [name=serverType]').change(function() {
$('#ojsxc .ojsxc-external, #ojsxc .ojsxc-internal, #ojsxc .ojsxc-managed').hide();
$('#ojsxc .ojsxc-external, #ojsxc .ojsxc-internal, #ojsxc .ojsxc-managed').find('.required').removeAttr('required');
$('#ojsxc .ojsxc-' + $(this).val()).show();
$('#ojsxc .ojsxc-' + $(this).val()).find('.required').attr('required', 'true');
});
$('#ojsxc [name=serverType]:checked').change();
$('#boshUrl, #xmppDomain').on('input', function() {
var self = $(this);
var timeout = self.data('timeout');
if (timeout) {
clearTimeout(timeout);
}
var url = $('#boshUrl').val();
var domain = $('#xmppDomain').val();
if (!url || !domain) {
// we need url and domain to test BOSH server
return;
}
$('#ojsxc .boshUrl-msg').html('<div></div>');
var status = $('#ojsxc .boshUrl-msg div');
status.html('<img src="' + jsxc.options.root + '/img/loading.gif" alt="wait" width="16px" height="16px" /> Testing BOSH Server...');
// test only every 2 seconds
timeout = setTimeout(function() {
testBoshServer(url, domain, function(res) {
status.addClass('jsxc_' + res.status);
status.html(res.msg);
});
}, 2000);
self.data('timeout', timeout);
});
function saveAdminSettings() {
if (OC.PasswordConfirmation && OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(saveAdminSettings);
return;
}
var post = $('#ojsxc').serialize();
$('#ojsxc .msg').html('<div>');
var status = $('#ojsxc .msg div');
status.html('<img src="' + jsxc.options.root + '/img/loading.gif" alt="wait" width="16px" height="16px" /> Saving...');
$.post(OC.generateUrl('apps/ojsxc/settings/admin'), post, function(data) {
if (data && data.status === 'success') {
status.addClass('jsxc_success').text('Settings saved. Please log out and in again.');
} else {
status.addClass('jsxc_fail').text('Error!');
}
setTimeout(function() {
status.hide('slow');
}, 3000);
});
}
$('#ojsxc').submit(function(event) {
event.preventDefault();
saveAdminSettings();
});
$('#ojsxc .add-input').click(function(ev) {
ev.preventDefault();
var clone = $(this).prev().clone();
clone.val('');
$(this).before(clone);
});
$('#insert-upload-service').click(function(ev) {
ev.preventDefault();
if (!jsxc.xmpp.conn || !jsxc.xmpp.conn.connected) {
console.warn('Not connected to any XMPP server.');
return;
}
var options = jsxc.options.get('httpUpload') || {};
var services = $('[name="externalServices[]"]').map(function() {
var inputField = $(this);
return inputField.val() || null;
});
if (options.server && services.toArray().indexOf(options.server) < 0) {
// insert service
var emptyInputFields = $('[name="externalServices[]"]').filter(function() {
return $(this).val() === '';
});
var targetInputField;
if (emptyInputFields.length === 0) {
$(this).parents('.form-group').find('.add-input').click();
targetInputField = $('[name="externalServices[]"]').last();
} else {
targetInputField = $(emptyInputFields[0]);
}
targetInputField.val(options.server);
}
});
$('#ojsxc input[readonly]').focus(function() {
if (typeof this.select === 'function') {
this.select();
}
});
$('#ojsxc-register').click(function() {
var el = $(this);
var msgEl = el.parents('.ojsxc-managed').find('.msg');
var promotionCode = $('#ojsxc-managed-promotion-code').val();
if (promotionCode.length > 0 && !/^[0-9a-z]+$/i.test(promotionCode)) {
msgEl.addClass('jsxc_fail');
msgEl.text('Your promotion code is invalid.');
$('#ojsxc-managed-promotion-code').one('input', function() {
msgEl.removeClass('jsxc_fail');
msgEl.text('');
});
return;
}
el.prop('disabled', 'disabled');
el.val(el.attr('data-toggle-value'));
el.addClass('jsxc-loading');
$.ajax({
method: 'POST',
url: OC.generateUrl('apps/ojsxc/managedServer/register'),
data: {
promotionCode: promotionCode
}
}).always(function(responseJSON) {
el.removeClass('jsxc-loading');
if (responseJSON && responseJSON.result === 'success') {
$('.ojsxc-managed-registration').hide();
msgEl.addClass('jsxc_success');
msgEl.text('Congratulations! You got your own XMPP server. Please log out and in again.');
var submitEl = $('#ojsxc input[type="submit"]');
submitEl.prop('disabled', 'disabled');
submitEl.val('Please reload this page to continue');
return;
}
if (responseJSON.responseJSON) {
responseJSON = responseJSON.responseJSON;
}
var errorMsg = (responseJSON && responseJSON.data) ? responseJSON.data.msg : 'unknown error';
msgEl.addClass('jsxc_fail');
msgEl.append($('<span>').text('Sorry we couldn\'t complete your registration.'));
msgEl.append($('<br><br>'));
msgEl.append($('<span>').text(errorMsg));
msgEl.append($('<br><br>'));
msgEl.append($('<span>').html('Please report this to our <a href="https://jsxc.ch/managed-issue-tracker" target="_blank">issue tracker</a> and mention the request id ' + responseJSON.data.requestId + '.'));
el.val('Registration failed');
});
});
$('.ojsxc-refresh-registration').click(function(ev) {
ev.preventDefault();
var msgEl = $(this).parents('.msg');
msgEl.removeClass('jsxc_success');
msgEl.empty();
$('.ojsxc-managed-registration').show();
});
$('#ojsxc-legal, #ojsxc-dp').on('change', function() {
$('#ojsxc-register').prop('disabled', !$('#ojsxc-legal').prop('checked') || !$('#ojsxc-dp').prop('checked'));
})
});

Просмотреть файл

@ -1,71 +0,0 @@
/* global OC */
(function($) {
"use strict";
$(function() {
$('#ojsxc-settings [name="loginFormEnable"]').change(function() {
var loginFormData = {
enable: $(this).val().match(/^true|false$/) ? JSON.parse($(this).val()) : null
};
if (jsxc.bid) {
var options = jsxc.storage.getUserItem('options');
if (loginFormData.enable === null && options.loginForm) {
delete options.loginForm.enable;
jsxc.storage.setUserItem('options', options);
} else {
loginFormData = $.extend(jsxc.options.get('loginForm'), loginFormData);
jsxc.options.set('loginForm', loginFormData);
}
}
$.ajax({
method: 'POST',
url: OC.generateUrl('apps/ojsxc/settings/user'),
data: {
loginForm: loginFormData
},
success: function(data) {
if (data && data.status === 'success') {
jsxc.debug('loginFormEnable saved.');
}
}
});
});
function savePersonalSettings() {
if (OC.PasswordConfirmation && OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(savePersonalSettings);
return;
}
var post = $('#ojsxc').serialize();
$('#ojsxc .msg').html('<div>');
var status = $('#ojsxc .msg div');
status.html('<img src="' + jsxc.options.root + '/img/loading.gif" alt="wait" width="16px" height="16px" /> Saving...');
$.post(OC.generateUrl('apps/ojsxc/settings/user'), post, function(data) {
if (data && data.status === 'success') {
status.addClass('jsxc_success').text('Settings saved. Please log out and in again.');
} else {
status.addClass('jsxc_fail').text('Error!');
}
setTimeout(function() {
status.hide('slow');
}, 3000);
});
}
$('#ojsxc').submit(function(ev) {
ev.preventDefault()
savePersonalSettings();
})
});
}(jQuery));

Просмотреть файл

@ -10,23 +10,28 @@
"type": "git",
"url": "https://github.com/jsxc/jsxc.nextcloud"
},
"scripts": {
"start": "webpack --mode production",
"dev": "mkdir node_modules/jsxc/dist/; webpack --watch --mode development --config webpack.config.js",
"jsxc": "cd node_modules/jsxc/ && webpack --watch --mode development",
"fix-typescript-format": "node_modules/.bin/tsfmt -r",
"checking-typescript-format": "node_modules/.bin/tsfmt --verify"
},
"devDependencies": {
"grunt": "~1.0.1",
"grunt-autoprefixer": "^3.0.4",
"grunt-banner": "~0.6.0",
"grunt-cli": "~1.2.0",
"grunt-contrib-clean": "~1.1.0",
"grunt-contrib-compress": "^1.4.3",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-jshint": "~1.1.0",
"grunt-contrib-watch": "^1.0.0",
"grunt-data-uri": "^0.3.0",
"grunt-exec": "^2.0.0",
"grunt-jsbeautifier": "^0.2.13",
"grunt-prettysass": "^0.2.3",
"grunt-sass": "2.0.0",
"grunt-search": "^0.1.6",
"grunt-text-replace": "~0.4.0",
"node-sass": "4.5.3"
"@types/jquery": "^3.3.6",
"clean-webpack-plugin": "^0.1.19",
"copy-webpack-plugin": "^4.5.3",
"css-loader": "^1.0.0",
"mini-css-extract-plugin": "^0.4.4",
"node-sass": "4.9.4",
"sass-loader": "^7.1.0",
"ts-loader": "^5.1.0",
"ts-node": "^7.0.1",
"typescript": "^3.1.3",
"webpack": "^4.20.2",
"webpack-cli": "^3.1.2"
},
"dependencies": {
"typescript-formatter": "^7.2.2"
}
}

Просмотреть файл

@ -12,7 +12,7 @@
}
}
#jsxc_roster {
#jsxc-roster {
padding-top: 5px; // NC >= 14
top: 45px;
z-index: 1500;
@ -22,10 +22,14 @@
width: 8px;
}
#jsxc_windowList, #jsxc_windowListSB {
#jsxc-window-list, #jsxc_windowListSB {
z-index: 1500;
}
.jsxc-dialog-wrapper {
z-index: 1600;
}
#jsxc {
padding: 20px;
@ -298,4 +302,4 @@
display: none;
}
}
}
}

Просмотреть файл

@ -1,6 +1,3 @@
@import "js/jsxc/scss/colors";
@import "js/jsxc/scss/dep";
//fonts
$font_sans: Arial, sans-serif;
$font_serif: serif;
@ -9,15 +6,13 @@ $window_bar_bg: #0082c9;
$window_bar_color: #c0dff1;
$window_bar_color_hover: #fff;
@import "js/jsxc/scss/modules";
@import "js/jsxc/scss/buddylist";
@import "js/jsxc/scss/state";
@import "js/jsxc/scss/emoticons";
@import "js/jsxc/scss/roster";
@import "js/jsxc/scss/window";
@import "js/jsxc/scss/muc";
@import "oc";
@import "js/jsxc/scss/_jsxc.scss";
@import "js/jsxc/scss/webrtc";
.jsxc-bar--window, .jsxc-window-item .jsxc-memberlist {
background-color: $window_bar_bg;
color: $window_bar_color;
@import "_oc";
:hover {
// color: $window_bar_color_hover;
}
}

33
ts/Bootstrap.ts Normal file
Просмотреть файл

@ -0,0 +1,33 @@
import { DEPENDENCIES } from './CONST'
export default class Bootstrap {
public static start() {
Bootstrap.checkDependencies();
Bootstrap.checkFrame();
Bootstrap.checkSpecialPage();
}
private static checkDependencies() {
for (let dependency of DEPENDENCIES) {
if (typeof (<any>window)[dependency] === 'undefined') {
throw `Dependency "${dependency}" is missing.`;
}
}
}
private static checkFrame() {
if (window.parent && window !== window.parent) {
throw `Abort, because we are running inside a frame.`;
}
}
private static checkSpecialPage() {
if (/^(\/index.php)?\/s\//.test(location.pathname)) {
throw `Abort, because we dont want to start chat on public shares.`;
}
if (OC.generateUrl('login/flow') === window.location.pathname) {
throw `Abort, because chat is not needed on flow login.`;
}
}
}

7
ts/CONST.ts Normal file
Просмотреть файл

@ -0,0 +1,7 @@
export const SERVER_TYPE = {
INTERNAL: 0,
EXTERNAL: 1,
MANAGED: 2
};
export const DEPENDENCIES = ['jsxc', 'oc_config', 'oc_appswebroots', 'OC'];

11
ts/ChatIconInjector.ts Normal file
Просмотреть файл

@ -0,0 +1,11 @@
export default function injectChatIcon() {
var div = $('<div/>');
div.addClass('jsxc_chatIcon');
div.click(function() {
jsxc.toggleRoster();
});
$('#header form.searchbox').after(div);
}

Просмотреть файл

@ -0,0 +1,41 @@
import Storage from './Storage'
function addChatSubmitButton(formElement: JQuery<any>) {
let storage = Storage.get();
let defaultEnable = OJSXC_CONFIG.defaultLoginFormEnable;
let submitWrapperElement = $('<div>');
submitWrapperElement.attr('id', 'jsxc-submit-wrapper');
let submitElement = $('<input>');
submitElement.attr({
type: 'button',
id: 'jsxc-submit',
});
submitElement.addClass('login primary');
if (defaultEnable) {
submitElement.val('Log_in_without_chat'); //@TODO translate
submitElement.click(function() {
// submit form without login
});
} else {
submitElement.val('Log_in_with_chat');
submitElement.click(function() {
let forceLoginFormEnable = true;
formElement.submit();
});
}
submitWrapperElement.append(submitElement);
$('.login-additional').prepend(submitWrapperElement);
$('#lost-password').mouseup(function(ev) {
ev.preventDefault();
submitWrapperElement.slideUp().fadeOut();
});
$('#lost-password-back').mouseup(function(ev) {
ev.preventDefault();
submitWrapperElement.slideDown().fadeIn();
});
}

Просмотреть файл

@ -0,0 +1,90 @@
function observeContactsMenu() {
var target = document.getElementById('contactsmenu');
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.target.id !== 'contactsmenu-contacts') {
return;
}
$(mutation.target).find('[href^="xmpp:"]').addClass('jsxc_statusIndicator');
$(mutation.target).find('.contact').each(function () {
updateContactItem($(this));
});
jsxc.gui.detectUriScheme(mutation.target);
});
});
var config = {
attributes: true,
childList: true,
characterData: true,
subtree: true
};
observer.observe(target, config);
}
function updateContactItem(contactElement) {
var xmppAddresses = contactElement.find('[href^="xmpp:"]').map(function () {
return $(this).attr('href').replace(/^xmpp:/, '');
});
if (xmppAddresses.length === 0) {
return;
}
var lastMessages = [];
var highestPresent = jsxc.CONST.STATUS.indexOf('offline');
var highestPresentBid = xmppAddresses.get(0);
xmppAddresses.each(function (index, bid) {
var lastMsg = jsxc.getLastMsg(bid);
if (lastMsg) {
lastMessages.push(lastMsg);
}
var data = jsxc.storage.getUserItem('buddy', bid) || {};
if (data.status > highestPresent) {
highestPresent = data.status;
highestPresentBid = bid;
}
});
var latestMsg = {
date: 0
};
$(lastMessages).each(function (index, msg) {
if (msg.date > latestMsg.date) {
latestMsg = msg;
}
});
if (latestMsg.date > 0) {
// replace emoticons from XEP-0038 and pidgin with shortnames
$.each(jsxc.gui.emotions, function (i, val) {
latestMsg.text = latestMsg.text.replace(val[2], ':' + val[1] + ':');
});
// translate shortnames to images
latestMsg.text = jsxc.gui.shortnameToImage(latestMsg.text);
contactElement.find('.last-message').html(latestMsg.text);
}
if (highestPresent > 0) {
var status = jsxc.CONST.STATUS[highestPresent];
contactElement.removeClass('jsxc_' + jsxc.CONST.STATUS.join(' jsxc_')).addClass('jsxc_' + status);
}
if (highestPresentBid) {
contactElement.find('.avatar').click(function () {
jsxc.gui.queryActions.message(highestPresentBid);
});
}
}

84
ts/DefaultAvatar.ts Normal file
Просмотреть файл

@ -0,0 +1,84 @@
import Storage from "./Storage";
import { IJID } from "jsxc/src/JID.interface";
enum Presence {
online,
chat,
away,
xa,
dnd,
offline
}
interface Avatar {
username: string,
type: 'url' | 'placeholder',
displayname?: string,
url?: string;
}
export default function defaultAvatar(elements, jid: IJID, name: string, presence) {
let storage = Storage.get();
let adminSettings = storage.getItem('adminSettings') || {};
$(elements).each(function() {
let element = $(this);
if (element.length === 0) {
return;
}
let size = <number>element.width();
let username = jid.node;
let key = username + '@' + size;
let cache = storage.getItem('avatar:' + key);
let isExternalUser = jid.domain !== adminSettings.xmppDomain;
if (cache) {
displayAvatar(element, cache);
} else if (isExternalUser || presence === Presence.offline) {
setPlaceholder(element, username);
} else {
requestAvatar(element, username, size);
}
});
}
function requestAvatar(element: JQuery<any>, username: string, size: number) {
let url = getAvatarUrl(username, size);
$.get(url, function(result) {
let avatar: Avatar = {
username: username,
type: typeof result === 'string' ? 'url' : 'placeholder',
displayname: result.data && result.data.displayname ? result.data.displayname : undefined,
url: typeof result === 'string' ? result : undefined,
};
displayAvatar(element, avatar);
Storage.get().setItem('avatar:' + username + '@' + size, avatar);
});
}
function displayAvatar(element, avatar: Avatar) {
if (avatar.type === 'url') {
element.css('backgroundImage', 'url(' + avatar.url + ')');
element.text('');
} else {
setPlaceholder(element, avatar.username, avatar.displayname);
}
}
function getAvatarUrl(username: string, size: number) {
return OC.generateUrl('/avatar/' + encodeURIComponent(username) + '/' + size + '?requesttoken={requesttoken}', {
user: username,
size: size,
requesttoken: oc_requesttoken
})
}
function setPlaceholder(element, username: string, displayname?: string) {
(<any>element).imageplaceholder(username, displayname);
}

55
ts/FormWatcher.ts Normal file
Просмотреть файл

@ -0,0 +1,55 @@
export default class FormWatcher {
public static callback(username: string, password: string) {
return new Promise(resolve => {
$.ajax({
type: 'POST',
url: OC.generateUrl('apps/ojsxc/settings'),
data: {
username: username,
password: password
},
success: response => FormWatcher.success(response, resolve),
error: xhr => FormWatcher.error(xhr, resolve),
});
});
}
private static success(response, resolve) {
if (response.result !== 'success') {
resolve(false);
return;
}
let xmpp = response.data.xmpp || {};
if (!xmpp.url) {
resolve(false);
return;
}
resolve({
xmpp: {
url: xmpp.url,
domain: xmpp.domain,
}
});
}
private static error(xhr, resolve) {
if (xhr.responseJSON && xhr.responseJSON.message) {
throw 'Error message: ' + xhr.responseJSON.message;
}
if (xhr.status === 412) {
console.log('Refresh page to get a new CSRF token');
window.location.href = window.location.href;
return;
}
resolve(false);
}
}

64
ts/InternalBackend.todo Normal file
Просмотреть файл

@ -0,0 +1,64 @@
$(document).on('click', '#jsxc_roster p', function () {
if (jsxc.storage.getItem('serverType') === serverTypes.INTERNAL) {
startInternalBackend();
}
});
function startInternalBackend() {
var currentUser = OC.currentUser;
if (!currentUser) {
return;
}
jsxc.bid = currentUser.toLowerCase() + '@' + window.location.host;
jsxc.options.set('xmpp', {
url: OC.generateUrl('apps/ojsxc/http-bind')
});
$(document).one('attached.jsxc', function () {
if (jsxc.options.get('loginForm').startMinimized !== true) {
jsxc.gui.roster.toggle(jsxc.CONST.SHOWN);
}
});
jsxc.start(jsxc.bid + '/internal', 'internal', '123456');
}
if (jsxc.storage.getItem('serverType') === serverTypes.INTERNAL) {
jsxc.gui.showLoginBox = function() {};
}
$(document).on('stateChange.jsxc', function _handler(event, state) {
if (state === jsxc.CONST.STATE.SUSPEND) {
/**
* The first time we go into suspend mode we check if we are using the internal backend.
* If this is the case and the user explicitly press the "login_without_chat" button when logging
* into Nextcloud we know we are using another authentication mechanism (like SAML/SSO) and thus have
* to manually start the connection.
*/
var chatDisabledByUser = jsxc.storage.getUserItem('forcedLogout') || jsxc.storage.getItem('login_without_chat');
$(document).off('stateChange.jsxc', _handler);
if (jsxc.storage.getItem('serverType') === null) {
$.ajax({
url: OC.generateUrl('apps/ojsxc/settings/servertype'),
success: function(data) {
jsxc.storage.setItem('serverType', serverTypes[data.serverType.toUpperCase()]);
if (data.serverType === 'internal' && !chatDisabledByUser) {
jsxc.gui.showLoginBox = function() {};
startInternalBackend();
}
}
});
} else if (jsxc.storage.getItem('serverType') === serverTypes.INTERNAL && !chatDisabledByUser) {
jsxc.gui.showLoginBox = function() {};
startInternalBackend();
}
} else if (state === jsxc.CONST.STATE.READY) {
// if JSXC is ready this means we successfully connected and thus don't have to listen to the suspend state
$(document).off('stateChange.jsxc', _handler);
}
});

78
ts/SettingsLoader.ts Normal file
Просмотреть файл

@ -0,0 +1,78 @@
import { SERVER_TYPE } from "./CONST";
import Storage from "./Storage";
export function loadSettings(username, password) {
return new Promise((resolve, reject) => {
$.ajax({
type: 'POST',
url: OC.generateUrl('apps/ojsxc/settings'),
data: {
username: username,
password: password
},
success: (d) => resolve(d),
error: xhr => reject(xhr),
});
})
.then(handleResponse)
.catch(handleError);
}
function handleResponse(response) {
if (response.result !== 'success' || !response.data) {
throw 'Received unsuccessful response.';
}
let data = response.data;
let serverType = SERVER_TYPE[data.serverType.toUpperCase()];
let xmpp = data.xmpp || {};
Storage.get().setItem('serverType', serverType);
if (serverType !== SERVER_TYPE.INTERNAL && xmpp.url) {
// if (forceLoginFormEnable) {
// response.data.loginForm.enable = true;
// }
return {
xmpp: {
url: xmpp.url,
domain: xmpp.domain,
}
};
} else if (serverType === SERVER_TYPE.INTERNAL) {
// var node = username || OC.currentUser;
// jsxc.bid = node.toLowerCase() + '@' + window.location.host;
// jsxc.options.set('adminSettings', response.data.adminSettings);
// if (response.data.loginForm) {
// jsxc.options.set('loginForm', {
// startMinimized: response.data.loginForm.startMinimized
// });
// }
}
Storage.get().removeItem('serverType');
return false;
}
function handleError(xhr) {
console.warn('XHR error on getSettings.php');
if (xhr.responseJSON && xhr.responseJSON.message) {
console.log('Error message: ' + xhr.responseJSON.message);
}
if (xhr.status === 412) {
console.log('Refresh page to get a new CSRF token');
window.location.href = window.location.href;
return new Promise(() => { });
}
return false;
}

13
ts/SettingsStore.ts Normal file
Просмотреть файл

@ -0,0 +1,13 @@
function saveSettinsPermanent(data, cb) {
$.ajax({
type: 'POST',
url: OC.generateUrl('apps/ojsxc/settings/user'),
data: data,
success: function(data) {
cb(data && data.status === 'success');
},
error: function() {
cb(false);
}
});
}

47
ts/Storage.ts Normal file
Просмотреть файл

@ -0,0 +1,47 @@
const PREFIX = 'ojsxc:';
const BACKEND = localStorage;
export default class Storage {
private static instance;
public static get(): Storage {
if (!Storage.instance) {
Storage.instance = new Storage();
}
return Storage.instance;
}
private constructor() {
}
public setItem(key: string, value: any): void {
try {
value = JSON.stringify(value);
} catch (err) {
console.warn('Error while stringifing', err);
return;
}
BACKEND.setItem(PREFIX + key, value);
}
public getItem(key: string): any {
let value = BACKEND.getItem(PREFIX + key);
if (typeof value === 'string') {
value = JSON.parse(value);
}
return value;
}
public removeItem(key: string): void {
BACKEND.removeItem(PREFIX + key);
}
}

13
ts/UsersLoader.ts Normal file
Просмотреть файл

@ -0,0 +1,13 @@
function getUsers(search, cb) {
$.ajax({
type: 'GET',
url: OC.generateUrl('apps/ojsxc/settings/users'),
data: {
search: search
},
success: cb,
error: function() {
console.warn('XHR error on getUsers.php');
}
});
}

33
ts/index.ts Normal file
Просмотреть файл

@ -0,0 +1,33 @@
// import jsxc from 'jsxc/src';
import FormWatcher from './FormWatcher';
import Bootstrap from './Bootstrap';
import { loadSettings } from './SettingsLoader';
import injectChatIcon from './ChatIconInjector';
(function() {
try {
Bootstrap.start();
} catch (err) {
console.warn('Abort JSXC', err);
return;
}
let numberOfCachedAccounts = jsxc.init({
loadSettings: loadSettings,
});
if (numberOfCachedAccounts === 0 && oc_current_user) {
jsxc.start();
}
injectChatIcon();
let formElement = $('#body-login form');
let usernameElement = $('#user');
let passwordElement = $('#password');
if (formElement.length > 0) {
jsxc.watchForm(formElement, usernameElement, passwordElement, FormWatcher.callback);
}
})();

18
ts/utils/Viewport.ts Normal file
Просмотреть файл

@ -0,0 +1,18 @@
import Storage from '../Storage'
export default class Viewport {
public static getSize(): { width: number, height: number } {
var w = <number>$(window).width() - <number>$('#jsxc_windowListSB').width();
var h = <number>$(window).height() - <number>$('#header').height() - 10;
//@TODO
if (Storage.get().getItem('roster') === 'shown') {
w -= <number>$('#jsxc-roster').outerWidth(true);
}
return {
width: w,
height: h
};
}
}

19
tsconfig.json Normal file
Просмотреть файл

@ -0,0 +1,19 @@
{
"include": [
"./ts/**/*.ts",
"./*.d.ts"
],
"compilerOptions": {
/* Basic Options */
"moduleResolution": "node",
"module": "commonjs",
"outDir": "./dist",
"target": "es5",
"lib": ["dom", "es2015", "es2015.promise", "es5"]
}
// "references": [
// { "path": "./node_modules/jsxc" }
// ]
}

129
webpack.config.js Normal file
Просмотреть файл

@ -0,0 +1,129 @@
/* jshint node:true */
const path = require("path");
const webpack = require("webpack");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const DESTINATION_DIR = 'dist/';
const fileLoader = {
loader: 'file-loader',
options: {
name: '[path][name]-[sha1:hash:hex:8].[ext]',
outputPath: 'assets/'
}
};
module.exports = {
entry: ['./scss/jsxc.oc.scss', './ts/index.ts'],
output: {
filename: 'js/bundle.js',
path: path.resolve(__dirname, DESTINATION_DIR),
publicPath: DESTINATION_DIR,
},
optimization: {
splitChunks: {
minSize: 10,
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true
}
}
},
},
node: {
fs: 'empty'
},
module: {
rules: [{
test: /\.ts$/,
loader: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.hbs$/,
loader: 'handlebars-loader',
exclude: /node_modules/,
options: {
helperDirs: [
path.resolve(__dirname, 'template', 'helpers')
],
partialDirs: [
path.resolve(__dirname, 'template', 'partials')
]
}
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader?importLoaders=1',
],
},
{
test: /\.(sass|scss)$/,
use: [
MiniCssExtractPlugin.loader, {
loader: 'css-loader',
options: {
url: false
}
},
'sass-loader'
]
},
{
test: /.*\.(svg|png|jpg|gif|mp3|wav)$/,
use: [fileLoader]
},
{
test: /.*\.(js)$/,
resourceQuery: /path/,
use: [fileLoader]
}
]
},
resolve: {
extensions: [".ts", ".js", ".hbs"],
alias: {}
},
externals: {
'jquery': 'jQuery',
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/bundle.css',
}),
new CleanWebpackPlugin([DESTINATION_DIR]),
new CopyWebpackPlugin([{
from: 'node_modules/jsxc/dist/',
to: 'js/jsxc/'
}, {
from: 'appinfo/',
to: 'appinfo/'
}, {
from: 'img/',
to: 'img/'
}, {
from: 'templates/',
to: 'templates/'
}, {
from: 'lib/',
to: 'lib/'
}, {
from: 'settings/',
to: 'settings/'
}, 'LICENSE']),
new webpack.LoaderOptionsPlugin({
options: {
handlebarsLoader: {}
}
}),
// new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, new RegExp(MOMENTJS_LOCALES.join('|'))),
// new BundleAnalyzerPlugin(),
]
};

4050
yarn.lock Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу