Sanitized public release v1.0.3
Source-Tag: v1.0.3 Manifest-SHA256: b7b09bf48f9e092ed8ad82f99c0319d6c3837d0a1549bd0cc4a374276c0f3896
This commit is contained in:
Executable
+524
@@ -0,0 +1,524 @@
|
||||
#!/usr/bin/perl
|
||||
use strict;
|
||||
use warnings;
|
||||
use File::Copy qw(copy);
|
||||
use File::Path qw(make_path);
|
||||
|
||||
my ($source_dir, $assets_dir) = @ARGV;
|
||||
die "usage: $0 SOURCE_DIR ASSETS_DIR\n" unless $source_dir && $assets_dir;
|
||||
|
||||
sub read_file {
|
||||
my ($path) = @_;
|
||||
open my $fh, '<', $path or die "open $path: $!\n";
|
||||
local $/;
|
||||
return <$fh>;
|
||||
}
|
||||
|
||||
sub write_file {
|
||||
my ($path, $content) = @_;
|
||||
open my $fh, '>', $path or die "write $path: $!\n";
|
||||
print {$fh} $content;
|
||||
close $fh or die "close $path: $!\n";
|
||||
}
|
||||
|
||||
my $index_path = "$source_dir/src/index.html";
|
||||
my $index = read_file($index_path);
|
||||
my $hero = read_file("$assets_dir/login-hero.html");
|
||||
my $core_welcome = $hero;
|
||||
$core_welcome =~ s{<section class="login-hero"}{<section class="login-hero core-login-hero" ng-show="activeBuffer().fullName === 'core.weechat'"} or
|
||||
die "could not prepare core login hero\n";
|
||||
$core_welcome =~ s{neorelay-login-title}{neorelay-core-title}g;
|
||||
my $drop_settings = read_file("$assets_dir/drop-settings.html");
|
||||
my $colour_settings = read_file("$assets_dir/colour-settings.html");
|
||||
my $colour_settings_js = read_file("$assets_dir/colour-settings.js.inc");
|
||||
my $weechat_settings = read_file("$assets_dir/weechat-settings.html");
|
||||
my $weechat_settings_js = read_file("$assets_dir/weechat-settings.js.inc");
|
||||
my $drop_plugin = read_file("$assets_dir/neodrop-plugin.js.inc");
|
||||
my $connection_panel = read_file("$assets_dir/connection-panel.html");
|
||||
my $sidebar_resize = read_file("$assets_dir/sidebar-resize.js.inc");
|
||||
my $command_menu = read_file("$assets_dir/command-menu.html");
|
||||
my $command_menu_js = read_file("$assets_dir/command-menu.js.inc");
|
||||
my $context_menu = read_file("$assets_dir/context-menu.html");
|
||||
my $server_manager = read_file("$assets_dir/server-manager.html");
|
||||
|
||||
my $count = ($index =~ s{
|
||||
<div\ ng-hide="connected"\ class="container">\s*
|
||||
<h2>.*?</h2>
|
||||
}{<div ng-hide="connected" class="container login-shell">\n$hero}sx);
|
||||
die "could not replace Glowing Bear login header\n" unless $count == 1;
|
||||
|
||||
$count = ($index =~ s{
|
||||
(<link\ href="css/glowingbear\.css"\ rel="stylesheet"\ media="screen">)
|
||||
}{$1\n <link href="css/neorelay-cyberpunk.css?v=31" rel="stylesheet" media="screen">}x);
|
||||
die "could not inject cyberpunk stylesheet\n" unless $count == 1;
|
||||
|
||||
$count = ($index =~ s{
|
||||
<div\ class="panel"\ data-state="active">\s*
|
||||
<div\ class="panel-heading">\s*<h4\ class="panel-title">\s*
|
||||
<a\ class="accordion-toggle"\ ng-click="toggleAccordion\(\$event\)">\s*
|
||||
Connection\ settings.*?
|
||||
(?=<div\ class="panel"\ data-state="collapsed"\ id="gettingStartedAccordion">)
|
||||
}{$connection_panel}sx);
|
||||
die "could not replace connection panel\n" unless $count == 1;
|
||||
|
||||
$count = ($index =~ s{
|
||||
<div\ class="panel"[^>]*id="gettingStartedAccordion">.*?
|
||||
(?=\s*</div>\s*</div>\s*<div\ class="content"\ id="content")
|
||||
}{}sx);
|
||||
die "could not remove public help panels\n" unless $count == 1;
|
||||
|
||||
$index =~ s{assets/img/glowing_bear_128x128\.png}{assets/neorelay-avatar-icon.png}g;
|
||||
$index =~ s{assets/img/favicon\.png}{assets/neorelay-avatar-icon.png}g;
|
||||
$index =~ s{<meta name="theme-color" content="#[0-9a-fA-F]+">}{<meta name="theme-color" content="#150a24">};
|
||||
$index =~ s{Image upload failed\.}{\{\{ uploadErrorMessage || 'Encrypted upload failed.' \}\}} or
|
||||
die "could not customize upload error\n";
|
||||
|
||||
$count = ($index =~ s{
|
||||
<li\ class="standard-labels">\s*<form\ class="form-horizontal"\ role="form">\s*
|
||||
<div\ class="form-group">\s*<label[^>]+for="iToken".*?</form>\s*</li>
|
||||
}{$drop_settings}sx);
|
||||
die "could not replace Imgur settings\n" unless $count == 1;
|
||||
|
||||
$count = ($index =~ s{
|
||||
(<div\ id="settingsModal".*?<div\ class="modal-body">)\s*<ul\ class="">
|
||||
}{$1
|
||||
<nav class="neo-settings-tabs" role="tablist" aria-label="Settings sections">
|
||||
<button type="button" ng-class="{active: neoSettingsTab !== 'colours' && neoSettingsTab !== 'weechat'}" ng-click="neoSettingsTab = 'general'" role="tab">General</button>
|
||||
<button type="button" ng-class="{active: neoSettingsTab === 'colours'}" ng-click="neoSettingsTab = 'colours'; loadNeoWeeChatColours()" role="tab">Colours</button>
|
||||
<button type="button" ng-class="{active: neoSettingsTab === 'weechat'}" ng-click="neoSettingsTab = 'weechat'; loadNeoWeeChatSettings()" role="tab">WeeChat</button>
|
||||
</nav>
|
||||
<ul class="neo-settings-panel" ng-show="neoSettingsTab !== 'colours' && neoSettingsTab !== 'weechat'">}sx);
|
||||
die "could not add settings tabs\n" unless $count == 1;
|
||||
$index =~ s{Settings\ will\ be\ stored\ in\ your\ browser\.}{Browser preferences stay local; Colours and WeeChat settings are saved to the relay.} or
|
||||
die "could not clarify settings persistence\n";
|
||||
$count = ($index =~ s{
|
||||
</ul>\s*(</div>\s*<div\ class="modal-footer">)
|
||||
}{</ul>
|
||||
$colour_settings
|
||||
$weechat_settings
|
||||
$1}x);
|
||||
die "could not add colour settings panel\n" unless $count == 1;
|
||||
|
||||
$index =~ s{ng-class="\{'no-overflow':\ connected\}"}{ng-class="{'no-overflow': connected, 'neo-preferred-font': connected}"} or
|
||||
die "could not scope preferred typography to the connected client\n";
|
||||
$index =~ s{<input\ type="text"\ ng-model="settings\.fontfamily"\ class="form-control"\ id="font">}{<div class="neo-font-picker" id="font">
|
||||
<button type="button" class="form-control neo-font-trigger" ng-click="neoFontPickerOpen = !neoFontPickerOpen" ng-style="{'font-family': settings.fontfamily}" aria-haspopup="listbox" aria-expanded="{{ neoFontPickerOpen }}">
|
||||
<span class="neo-font-value">{{ settings.fontfamily }}</span><span class="caret" aria-hidden="true"></span>
|
||||
</button>
|
||||
<div class="neo-font-menu" ng-show="neoFontPickerOpen" role="listbox" aria-label="Available fonts">
|
||||
<button type="button" class="neo-font-option" role="option" ng-click="settings.fontfamily = 'OpenDyslexic, Atkinson Hyperlegible, monospace'; neoFontPickerOpen = false" style="font-family:'OpenDyslexic',sans-serif"><strong>OpenDyslexic</strong><span>Aa Bb Cc 123</span></button>
|
||||
<button type="button" class="neo-font-option" role="option" ng-click="settings.fontfamily = 'system-ui, sans-serif'; neoFontPickerOpen = false" style="font-family:system-ui,sans-serif"><strong>System Sans</strong><span>Aa Bb Cc 123</span></button>
|
||||
<button type="button" class="neo-font-option" role="option" ng-click="settings.fontfamily = 'ui-serif, Georgia, serif'; neoFontPickerOpen = false" style="font-family:Georgia,serif"><strong>System Serif</strong><span>Aa Bb Cc 123</span></button>
|
||||
<button type="button" class="neo-font-option" role="option" ng-click="settings.fontfamily = 'ui-monospace, SFMono-Regular, Consolas, monospace'; neoFontPickerOpen = false" style="font-family:monospace"><strong>System Monospace</strong><span>Aa Bb Cc 123</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="neo-font-preview" ng-style="{'font-family': settings.fontfamily, 'font-size': settings.fontsize}">The quick brown fox jumps over the lazy dog. 0123456789</p>} or
|
||||
die "could not add preferred font previews\n";
|
||||
|
||||
$count = ($index =~ s{
|
||||
(<div\ class="title"[^>]*>\s*)
|
||||
(<span\ class="desktop"\ ng-repeat=)
|
||||
}{$1<span class="desktop neorelay-core-title" ng-show="activeBuffer().fullName === 'core.weechat'">{{PRODUCT_NAME}} <small>private IRC gateway</small></span>\n <span class="desktop" ng-hide="activeBuffer().fullName === 'core.weechat'" ng-repeat=}x);
|
||||
die "could not replace core buffer title\n" unless $count == 1;
|
||||
|
||||
$count = ($index =~ s{
|
||||
(<span\ class="mobile")
|
||||
}{<span class="mobile neorelay-core-title" ng-show="activeBuffer().fullName === 'core.weechat'">{{PRODUCT_NAME}}</span>\n $1 ng-hide="activeBuffer().fullName === 'core.weechat'"}x);
|
||||
die "could not guard mobile core title\n" unless $count == 1;
|
||||
|
||||
$count = ($index =~ s{
|
||||
<div\ class="actions\ pull-right\ vertical-line-left">.*?</div>
|
||||
}{}sx);
|
||||
die "could not remove topbar actions\n" unless $count == 1;
|
||||
|
||||
my $core_label = q!{{ buffer.fullName === 'core.weechat' ? '{{PRODUCT_NAME}}' : (buffer.trimmedName || buffer.fullName) }}!;
|
||||
$count = ($index =~ s!(class="buffername" ng-class="::buffer\.nameClasses">)\{\{ buffer\.trimmedName \|\| buffer\.fullName \}\}!$1$core_label!);
|
||||
die "could not rename visible core buffer\n" unless $count == 1;
|
||||
|
||||
$count = ($index =~ s{
|
||||
(<div\ id="bufferlines"[^>]*>)\s*<table>
|
||||
}{$1\n $core_welcome\n <table>}x);
|
||||
die "could not replace core buffer contents\n" unless $count == 1;
|
||||
$index =~ s{('private': buffer\.type === 'private')}{$1,
|
||||
'server': buffer.type === 'server'} or
|
||||
die "could not mark server buffers\n";
|
||||
$index =~ s{<li\ class="buffer"\ ng-class=}{<li class="buffer" neo-context="{kind:'buffer',buffer:buffer}" ng-class=} or
|
||||
die "could not add buffer context menu\n";
|
||||
$index =~ s{<a\ ng-click="openBuffer\(nick\.name\)"><span\ ng-class="::nick\.prefixClasses"\ ng-bind="::nick\.prefix"></span><span\ ng-class="::nick\.nameClasses"\ ng-bind="::nick\.name"></span></a>}{<a ng-click="insertAtCaret(nick.name)" neo-context="{kind:'nick',nick:nick,buffer:activeBuffer()}" title="Insert nickname; right-click for actions"><span ng-class="::nick.prefixClasses" ng-bind="::nick.prefix"></span><span ng-class="::nick.displayClasses" ng-bind="::nick.name"></span></a>} or
|
||||
die "could not add nick context menu\n";
|
||||
$index =~ s{<span\ ng-repeat="part\ in\ ::bufferline\.prefix"\ ng-class="::part\.classes"}{<span ng-repeat="part in ::bufferline.prefix" ng-class="part.classes.concat(activeBuffer().getNickColourClasses(bufferline.nick))"} or
|
||||
die "could not apply WeeChat nick colours to chat prefixes\n";
|
||||
$count = ($index =~ s{
|
||||
</ul>\s*</div>\s*<div\ id="nicklist"
|
||||
}{</ul>
|
||||
<div class="sidebar-actions">
|
||||
<button type="button" ng-click="showModal('settingsModal')" title="Settings"><i class="glyphicon glyphicon-cog"></i><span>Settings</span></button>
|
||||
<button type="button" ng-click="openServerManager()" title="Manage IRC servers"><i class="glyphicon glyphicon-plus"></i><span>Servers</span></button>
|
||||
<button type="button" ng-if="pwaInstallAvailable" ng-click="installPwa()" title="Install NeoBear app"><i class="glyphicon glyphicon-download-alt"></i><span>Install</span></button>
|
||||
<button type="button" ng-click="disconnect()" title="Disconnect"><i class="glyphicon glyphicon-off"></i><span>Logout</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="nicklist"}x);
|
||||
die "could not add sidebar actions\n" unless $count == 1;
|
||||
$index =~ s{\s*</body>}{
|
||||
<neo-context-menu-view></neo-context-menu-view>
|
||||
<neo-server-manager></neo-server-manager>
|
||||
</body>} or die "could not add NeoBear UI overlays\n";
|
||||
write_file($index_path, $index);
|
||||
|
||||
my $js_path = "$source_dir/src/js/glowingbear.js";
|
||||
my $js = read_file($js_path);
|
||||
$js =~ s/'hostField': 'localhost'/'hostField': '{{RELAY_HOST}}'/ or
|
||||
die "could not set relay host default\n";
|
||||
$js =~ s/'port': 9001/'port': 443/ or die "could not set relay port default\n";
|
||||
$js =~ s/'customCSS': '',/'customCSS': '',
|
||||
'dropProfile': 'small',
|
||||
'neorelaySidebarWidth': 0,/ or
|
||||
die "could not add NeoDrop profile setting\n";
|
||||
$js =~ s{(\s+\$scope\.settings\s*=\s*settings;)}{$1\n\n$weechat_settings_js\n$colour_settings_js\n$sidebar_resize} or
|
||||
die "could not add desktop sidebar resizing\n";
|
||||
$js =~ s{
|
||||
'fontfamily':\s*\(utils\.isMobileUi\(\)\s*\?\s*'sans-serif'\s*:\s*'[^']+'\)
|
||||
}{'fontfamily': 'OpenDyslexic, Atkinson Hyperlegible, monospace'}x or
|
||||
die "could not set font default\n";
|
||||
$js =~ s{
|
||||
(\$scope\.command\s*=\s*'';)
|
||||
}{$1
|
||||
|
||||
\$scope.maskPassword = function(value) {
|
||||
return value ? new Array(value.length + 1).join('*') : '';
|
||||
};
|
||||
|
||||
\$scope.openServerManager = function() {
|
||||
\$scope.\$broadcast('neoServerManagerOpen');
|
||||
};
|
||||
|
||||
var neoPwaInstallPrompt = null;
|
||||
\$scope.pwaInstallAvailable = false;
|
||||
window.addEventListener('beforeinstallprompt', function(event) {
|
||||
event.preventDefault();
|
||||
neoPwaInstallPrompt = event;
|
||||
\$scope.\$applyAsync(function() { \$scope.pwaInstallAvailable = true; });
|
||||
});
|
||||
window.addEventListener('appinstalled', function() {
|
||||
neoPwaInstallPrompt = null;
|
||||
\$scope.\$applyAsync(function() { \$scope.pwaInstallAvailable = false; });
|
||||
});
|
||||
\$scope.installPwa = function() {
|
||||
if (!neoPwaInstallPrompt) return;
|
||||
neoPwaInstallPrompt.prompt();
|
||||
neoPwaInstallPrompt.userChoice.finally(function() {
|
||||
neoPwaInstallPrompt = null;
|
||||
\$scope.\$applyAsync(function() { \$scope.pwaInstallAvailable = false; });
|
||||
});
|
||||
};}x or die "could not add password masker\n";
|
||||
$js =~ s{
|
||||
\$scope\.init\s*=\s*function\(\)\s*\{\s*
|
||||
\$scope\.parseHost\(\);\s*
|
||||
\$scope\.parseHash\(\);\s*
|
||||
\};
|
||||
}{\$scope.init = function() {
|
||||
settings.hostField = '{{RELAY_HOST}}';
|
||||
settings.host = '{{RELAY_HOST}}';
|
||||
settings.port = 443;
|
||||
settings.path = 'weechat';
|
||||
settings.tls = true;
|
||||
\$scope.parseHost();
|
||||
};}x or die "could not hardcode relay initialization\n";
|
||||
$js =~ s{
|
||||
(\$scope\.connect\s*=\s*function\(\)\s*\{)
|
||||
}{$1
|
||||
settings.hostField = '{{RELAY_HOST}}';
|
||||
settings.host = '{{RELAY_HOST}}';
|
||||
settings.port = 443;
|
||||
settings.path = 'weechat';
|
||||
settings.tls = true;}x or die "could not hardcode relay connection\n";
|
||||
$js =~ s{
|
||||
document\.getElementById\('audioNotificationInitializer'\)\.play\(\);
|
||||
}{var audioPlay = document.getElementById('audioNotificationInitializer').play();
|
||||
if (audioPlay && audioPlay.catch) {
|
||||
audioPlay.catch(function() {});
|
||||
}}x or die "could not handle blocked notification audio\n";
|
||||
$js =~ s{
|
||||
(\$rootScope\.bufferlistfilter\s*=\s*function\(buffer\)\s*\{)
|
||||
}{$1\n if (buffer.fullName === 'relay.relay.list') {\n return false;\n }}x or
|
||||
die "could not hide relay list buffer\n";
|
||||
$js =~ s{
|
||||
(\$rootScope\.\$on\('relayDisconnect',\ function\(\)\ \{)
|
||||
}{$1\n \$rootScope.loadingLines = false;}x or
|
||||
die "could not reset line loading on disconnect\n";
|
||||
$js =~ s{
|
||||
(settings\.addCallback\('fontfamily',\ function\(fontfamily\)\ \{\s*
|
||||
utils\.changeClassStyle\('favorite-font',\ 'fontFamily',\ fontfamily\);)
|
||||
}{$1
|
||||
document.documentElement.style.setProperty('--neo-preferred-font', fontfamily);}x or
|
||||
die "could not apply preferred font to connected controls\n";
|
||||
$js =~ s{
|
||||
(utils\.changeClassStyle\('favorite-font',\ 'fontSize',\ fontsize\);\s*
|
||||
\}\);)
|
||||
}{utils.changeClassStyle('favorite-font', 'fontSize', fontsize);
|
||||
document.documentElement.style.setProperty('--neo-preferred-font-size', fontsize);
|
||||
\});}x or
|
||||
die "could not apply preferred font size to connected controls\n";
|
||||
write_file($js_path, $js);
|
||||
|
||||
my $models_path = "$source_dir/src/js/models.js";
|
||||
my $models = read_file($models_path);
|
||||
$models =~ s{(var\ nicklist\ =\ \{\};)}{$1\n var nickColours = {};} or
|
||||
die "could not add nick colour index\n";
|
||||
$models =~ s{(nicklist\[group\]\.nicks\.push\(nick\);)}{$1\n nickColours[nick.name] = nick.displayClasses;} or
|
||||
die "could not index added nick colour\n";
|
||||
$models =~ s{(if\ \(group\ ===\ undefined\)\ \{.*?\n\s*\})\s*(for\ \(i\ in\ group\.nicks\)\ \{)}{$1\n $2}sx or
|
||||
die "could not locate nick deletion loop\n";
|
||||
$models =~ s{(if\ \(group\.nicks\[i\]\.name\ ==\ nick\.name\)\ \{)}{$1\n delete nickColours[nick.name];} or
|
||||
die "could not remove deleted nick colour\n";
|
||||
$models =~ s{(var\ clearNicklist\ =\ function\(\)\ \{)}{$1\n nickColours = {};} or
|
||||
die "could not clear nick colour index\n";
|
||||
$models =~ s{(if\ \(group\.nicks\[i\]\.name\ ===\ nick\.name\)\ \{\s*group\.nicks\[i\]\ =\ nick;)}{$1\n nickColours[nick.name] = nick.displayClasses;}x or
|
||||
die "could not update nick colour index\n";
|
||||
$models =~ s{
|
||||
(//\ Check\ whether\ a\ particular\ nick\ is\ in\ the\ nicklist)
|
||||
}{$1
|
||||
var getNickColourClasses = function(nick) {
|
||||
return nick ? (nickColours[nick] || ['neo-nick']) : [];
|
||||
};
|
||||
}x or die "could not add nick colour lookup\n";
|
||||
$models =~ s{(nicklistRequested:\ nicklistRequested,)}{$1\n getNickColourClasses: getNickColourClasses,} or
|
||||
die "could not export nick colour lookup\n";
|
||||
$models =~ s{(for\ \(var\ pti\ =\ 0;\ pti\ <\ prefix\.length;\ \+\+pti\)\ \{\s*prefixtext\ \+=\ prefix\[pti\]\.text;\s*\})}{$1\n var nick = showHiddenBrackets && prefix.length ? prefix[prefix.length - 1].text : '';}x or
|
||||
die "could not identify chat prefix nick\n";
|
||||
$models =~ s{(prefixtext:\ prefixtext,)}{$1\n nick: nick,} or
|
||||
die "could not export chat prefix nick\n";
|
||||
$models =~ s{
|
||||
(var\ colorClasses\ =\ nickGetClasses\(message\);)\s*
|
||||
return\ \{\s*
|
||||
prefix:\ prefix,\s*
|
||||
visible:\ visible,\s*
|
||||
name:\ name,\s*
|
||||
prefixClasses:\ colorClasses\.prefix,\s*
|
||||
nameClasses:\ colorClasses\.name\s*
|
||||
\};
|
||||
}{$1
|
||||
var roleClass = 'neo-nick';
|
||||
if (colorClasses.name.indexOf('cof-nicklist_away') >= 0) roleClass = 'neo-nick-away';
|
||||
else if (['~', '&', String.fromCharCode(64)].indexOf(prefix.charAt(0)) >= 0) roleClass = 'neo-nick-op';
|
||||
else if (prefix.charAt(0) === '%') roleClass = 'neo-nick-halfop';
|
||||
else if (prefix.charAt(0) === '+') roleClass = 'neo-nick-voice';
|
||||
var isSelf = colorClasses.name.indexOf('cof-chat_nick_self') >= 0;
|
||||
var isAway = colorClasses.name.indexOf('cof-nicklist_away') >= 0;
|
||||
var hasWeeChatRoleColour = prefix && !(
|
||||
colorClasses.prefix.length === 1 && colorClasses.prefix[0] === 'cwf-default'
|
||||
);
|
||||
var displayClasses = colorClasses.name.slice();
|
||||
if (!isSelf && !isAway && hasWeeChatRoleColour) displayClasses = [roleClass];
|
||||
else if (!isSelf && isAway) displayClasses.push('neo-nick-away');
|
||||
else if (!isSelf && prefix && !hasWeeChatRoleColour) displayClasses.push(roleClass);
|
||||
|
||||
return {
|
||||
prefix: prefix,
|
||||
visible: visible,
|
||||
name: name,
|
||||
prefixClasses: colorClasses.prefix,
|
||||
nameClasses: colorClasses.name,
|
||||
roleClass: roleClass,
|
||||
displayClasses: displayClasses
|
||||
};
|
||||
}x or die "could not add semantic nick colours\n";
|
||||
write_file($models_path, $models);
|
||||
|
||||
my $connection_path = "$source_dir/src/js/connection.js";
|
||||
my $connection = read_file($connection_path);
|
||||
$count = ($connection =~ s!\Qconnect(d[0], d[1], d[2], d[3], d[4], false, "", d[5], function() {\E!connect(d[0], d[1], d[2], d[3], d[4], d[5], function() {!);
|
||||
die "could not fix reconnect callback arguments\n" unless $count == 1;
|
||||
write_file($connection_path, $connection);
|
||||
|
||||
my $websockets_path = "$source_dir/src/js/websockets.js";
|
||||
my $websockets = read_file($websockets_path);
|
||||
$count = ($websockets =~ s{
|
||||
if\ \(ws\ !==\ null\ &&\ ws\.readyState\ !==\ WebSocket\.CLOSED\)\ \{\s*
|
||||
ws\.close\(\);\s*
|
||||
\}
|
||||
}{if (ws !== null && ws.readyState !== WebSocket.CLOSED) {
|
||||
ws.onclose = null;
|
||||
ws.onerror = null;
|
||||
ws.onmessage = null;
|
||||
ws.close();
|
||||
}}x);
|
||||
die "could not detach stale WebSocket handlers\n" unless $count == 1;
|
||||
write_file($websockets_path, $websockets);
|
||||
|
||||
my $input_path = "$source_dir/src/directives/input.html";
|
||||
my $input = read_file($input_path);
|
||||
$input =~ s/title="Send image"/title="Upload encrypted file"/g;
|
||||
$input =~ s/ accept="image\/\*"//;
|
||||
$input =~ s{(<form\ class="form\ form-horizontal"\ id="inputform"[^>]*>)}{$1\n$command_menu} or
|
||||
die "could not add command menu template\n";
|
||||
$input =~ s{(ng-paste="inputPasted\(\$event\)")}{$1 aria-autocomplete="list" aria-controls="{{inputId}}-command-menu" aria-expanded="{{commandMenuOpen}}"} or
|
||||
die "could not add command menu accessibility attributes\n";
|
||||
write_file($input_path, $input);
|
||||
|
||||
my $inputbar_path = "$source_dir/src/js/inputbar.js";
|
||||
my $inputbar = read_file($inputbar_path);
|
||||
$inputbar =~ s{(\s+\$scope\.utils\s*=\s*utils;)}{$1\n\n$command_menu_js} or
|
||||
die "could not add command menu behavior\n";
|
||||
$inputbar =~ s{
|
||||
(\s+//\ Cancel\ any\ command\ completion\ that\ was\ still\ ongoing)
|
||||
}{\n updateCommandMenu();\n$1}x or
|
||||
die "could not update command menu on input\n";
|
||||
$inputbar =~ s{
|
||||
(\s+//\ Support\ different\ browser\ quirks\s*
|
||||
var\ code\ =\ \$event\.keyCode\ \?\ \$event\.keyCode\ :\ \$event\.charCode;)
|
||||
}{$1
|
||||
|
||||
if (\$scope.handleCommandMenuKey(\$event, code, inputNode)) {
|
||||
return true;
|
||||
}}x or die "could not add command menu keyboard handling\n";
|
||||
$count = ($inputbar =~ s{
|
||||
(\s+\$rootScope\.insertAtCaret\s*=\s*function\(toInsert\)\s*\{)
|
||||
}{
|
||||
\$rootScope.registerNeoDrop = function(readUrl, deleteCapability) {
|
||||
connection.sendMessage('/drops register ' + readUrl + ' ' + deleteCapability);
|
||||
};\n$1
|
||||
}x);
|
||||
die "could not add NeoDrop manager registration callback\n" unless $count == 1;
|
||||
|
||||
$count = ($inputbar =~ s{
|
||||
\s*//\ Show\ toast\ with\ delete\ link
|
||||
\s*var\ toastImgur\s*=.*?
|
||||
\s*console\.log\('An\ image\ was\ uploaded\ to\ imgur,\ delete\ it\ with\ .*?;
|
||||
}{}sx);
|
||||
die "could not remove exposed Imgur deletion capability\n" unless $count == 1;
|
||||
write_file($inputbar_path, $inputbar);
|
||||
|
||||
my $connection_source_path = "$source_dir/src/js/connection.js";
|
||||
my $connection_source = read_file($connection_source_path);
|
||||
$count = ($connection_source =~ s{
|
||||
(\s+var\ requestCompletion\ =\ function\(bufferId,\ position,\ data\)\ \{)
|
||||
}{
|
||||
var requestInfolist = function(name, pointer, args) {
|
||||
return ngWebsockets.send(
|
||||
weeChat.Protocol.formatInfolist({name: name, pointer: pointer || 0, args: args || ''})
|
||||
).then(function(message) {
|
||||
if (!message.objects || !message.objects[0]) return [];
|
||||
return message.objects[0].content.map(function(fields) {
|
||||
var item = {};
|
||||
fields.forEach(function(field) {
|
||||
Object.keys(field).forEach(function(key) { item[key] = field[key]; });
|
||||
});
|
||||
return item;
|
||||
});
|
||||
});
|
||||
};
|
||||
var requestCommandHooks = function() {
|
||||
return requestInfolist('hook', 0, 'command').then(function(items) {
|
||||
return items.map(function(hook) {
|
||||
return {
|
||||
command: hook.command || '',
|
||||
args: hook.args_nls || hook.args || '',
|
||||
description: hook.description_nls || hook.description || '',
|
||||
priority: hook.priority || 0
|
||||
};
|
||||
}).filter(function(hook) { return hook.command; });
|
||||
});
|
||||
};
|
||||
var sendMessageToBuffer = function(bufferId, message) {
|
||||
ngWebsockets.send(weeChat.Protocol.formatInput({buffer: '0x' + bufferId, data: message}));
|
||||
};$1
|
||||
}x);
|
||||
die "could not add command hook request\n" unless $count == 1;
|
||||
$connection_source =~ s{
|
||||
(\s+requestCompletion:\ requestCompletion)
|
||||
}{$1,\n requestCommandHooks: requestCommandHooks,\n requestInfolist: requestInfolist,\n sendMessageToBuffer: sendMessageToBuffer}x or
|
||||
die "could not export command hook request\n";
|
||||
$connection_source =~ s{
|
||||
(\s+\$rootScope\.connected\s*=\s*true;)
|
||||
}{$1
|
||||
\$rootScope.reconnecting = false;
|
||||
\$rootScope.loadingLines = false;
|
||||
\$rootScope.errorMessage = false;}x or
|
||||
die "could not reset successful connection state\n";
|
||||
write_file($connection_source_path, $connection_source);
|
||||
|
||||
my $plugin_directive_path = "$source_dir/src/js/plugin-directive.js";
|
||||
my $plugin_directive = read_file($plugin_directive_path);
|
||||
$plugin_directive =~ s{
|
||||
\$scope\.plugin\.visible\s*=\s*!settings\.noembed\s*&&\s*!\$scope\.plugin\.nsfw;
|
||||
}{\$scope.plugin.visible = !\$scope.plugin.nsfw &&
|
||||
(\$scope.plugin.name === 'NeoDrop media' || !settings.noembed);}x or
|
||||
die "could not enable automatic NeoDrop media previews\n";
|
||||
write_file($plugin_directive_path, $plugin_directive);
|
||||
|
||||
my $plugins_path = "$source_dir/src/js/plugins.js";
|
||||
my $plugins = read_file($plugins_path);
|
||||
$plugins =~ s{(\s+return \{\s*plugins: \[)}{$drop_plugin$1neoDropMediaPlugin, neoDropTextPlugin, } or
|
||||
die "could not register NeoDrop preview plugin\n";
|
||||
write_file($plugins_path, $plugins);
|
||||
|
||||
my $main_path = "$source_dir/src/main.js";
|
||||
my $main = read_file($main_path);
|
||||
$main =~ s{(import "\./js/glowingbear\.js";)}{$1
|
||||
import "./js/context-menu.js";
|
||||
import "./js/server-manager.js";} or
|
||||
die "could not import NeoBear UI modules\n";
|
||||
$main .= q!
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function() {
|
||||
navigator.serviceWorker.register('serviceworker.js');
|
||||
});
|
||||
}
|
||||
!;
|
||||
write_file($main_path, $main);
|
||||
|
||||
my $webpack_path = "$source_dir/webpack.config.js";
|
||||
my $webpack = read_file($webpack_path);
|
||||
$webpack =~ s{
|
||||
(output:\s*\{\s*path:\s*path\.resolve\(__dirname,\s*'build'\),)
|
||||
}{$1\n filename: 'main.neorelay.27.js',}x or
|
||||
die "could not version JavaScript bundle\n";
|
||||
$webpack =~ s{"\*\*/\*\.png",}{"**/*.png",\n "**/*.jpg",} or
|
||||
die "could not enable JPEG asset copying\n";
|
||||
$webpack =~ s{"\*\*/\*\.jpg",}{"**/*.jpg",\n "**/*.woff2",\n "assets/fonts/OFL.txt",} or
|
||||
die "could not enable OpenDyslexic asset copying\n";
|
||||
write_file($webpack_path, $webpack);
|
||||
|
||||
for my $manifest (qw(webapp.manifest.json manifest.json manifest.webapp)) {
|
||||
my $path = "$source_dir/src/$manifest";
|
||||
next unless -f $path;
|
||||
my $content = read_file($path);
|
||||
$content =~ s{assets/img/glowing_bear_(?:60x60|90x90|128x128)\.png}{assets/neorelay-avatar-icon.png}g;
|
||||
$content =~ s{assets/img/favicon\.png}{assets/neorelay-avatar-icon.png}g;
|
||||
$content =~ s/"theme_color"\s*:\s*"#[0-9a-fA-F]+"/"theme_color": "#150a24"/g;
|
||||
$content =~ s/"background_color"\s*:\s*"#[0-9a-fA-F]+"/"background_color": "#07030d"/g;
|
||||
write_file($path, $content);
|
||||
}
|
||||
|
||||
make_path("$source_dir/src/assets");
|
||||
make_path("$source_dir/src/assets/fonts");
|
||||
for my $font (qw(
|
||||
OpenDyslexic-Regular.woff2
|
||||
OpenDyslexic-Italic.woff2
|
||||
OpenDyslexic-Bold.woff2
|
||||
OpenDyslexic-BoldItalic.woff2
|
||||
OFL.txt
|
||||
)) {
|
||||
copy("$assets_dir/fonts/$font", "$source_dir/src/assets/fonts/$font") or
|
||||
die "copy OpenDyslexic asset $font: $!\n";
|
||||
}
|
||||
copy("$assets_dir/avatar-login.jpg", "$source_dir/src/assets/neorelay-avatar-login.jpg") or
|
||||
die "copy login avatar: $!\n";
|
||||
copy("$assets_dir/avatar-icon.png", "$source_dir/src/assets/neorelay-avatar-icon.png") or
|
||||
die "copy favicon: $!\n";
|
||||
copy("$assets_dir/cyberpunk.css", "$source_dir/src/css/neorelay-cyberpunk.css") or
|
||||
die "copy cyberpunk stylesheet: $!\n";
|
||||
copy("$assets_dir/neodrop.js", "$source_dir/src/js/imgur.js") or
|
||||
die "copy NeoDrop uploader: $!\n";
|
||||
copy("$assets_dir/neodrop-crypto.js", "$source_dir/src/js/neodrop-crypto.js") or
|
||||
die "copy NeoDrop crypto: $!\n";
|
||||
copy("$assets_dir/context-menu.js", "$source_dir/src/js/context-menu.js") or
|
||||
die "copy context menu: $!\n";
|
||||
copy("$assets_dir/context-menu.html", "$source_dir/src/directives/context-menu.html") or
|
||||
die "copy context menu template: $!\n";
|
||||
copy("$assets_dir/server-manager.js", "$source_dir/src/js/server-manager.js") or
|
||||
die "copy server manager: $!\n";
|
||||
copy("$assets_dir/server-manager.html", "$source_dir/src/directives/server-manager.html") or
|
||||
die "copy server manager template: $!\n";
|
||||
Reference in New Issue
Block a user