var neoBasicColours = ['default','black','darkgray','red','lightred','green','lightgreen','brown','yellow','blue','lightblue','magenta','lightmagenta','cyan','lightcyan','gray','white']; $scope.neoWeeChatColourChoices = neoBasicColours.concat(Array.from({length: 256}, function(_, index) { return String(index); })); var neoColourHex = {default:'#f1eaff',black:'#000000',darkgray:'#555555',red:'#aa0000',lightred:'#ff5555',green:'#00aa00',lightgreen:'#55ff55',brown:'#aa5500',yellow:'#ffff55',blue:'#0000aa',lightblue:'#5555ff',magenta:'#aa00aa',lightmagenta:'#ff55ff',cyan:'#00aaaa',lightcyan:'#55ffff',gray:'#aaaaaa',white:'#ffffff'}; var neoAllowedSections = [ {label:'WeeChat appearance',mask:'weechat.look.*'}, {label:'WeeChat colours',mask:'weechat.color.*'}, {label:'Completion',mask:'weechat.completion.*'}, {label:'History',mask:'weechat.history.*'}, {label:'Network',mask:'weechat.network.*'}, {label:'Plugins',mask:'weechat.plugin.*'}, {label:'IRC appearance',mask:'irc.look.*'}, {label:'IRC colours',mask:'irc.color.*'}, {label:'IRC network',mask:'irc.network.*'} ]; var neoAllowedMasks = neoAllowedSections.reduce(function(result, item) { result[item.mask] = true; return result; }, {}); var neoLoadedOptions = {}; function neoResolveWeeChatColour(value) { value = String(value || 'default').replace(/^[*!/_|]+/, ''); if (neoColourHex[value]) return neoColourHex[value]; var number = Number(value); if (!Number.isInteger(number) || number < 0 || number > 255) return neoColourHex.default; if (number < 16) return neoColourHex[neoBasicColours[number + 1] || 'default']; if (number >= 232) { var gray = 8 + (number - 232) * 10; return 'rgb(' + gray + ',' + gray + ',' + gray + ')'; } number -= 16; var levels = [0,95,135,175,215,255]; return 'rgb(' + levels[Math.floor(number / 36)] + ',' + levels[Math.floor(number / 6) % 6] + ',' + levels[number % 6] + ')'; } function neoValidWeeChatColour(value) { value = String(value || '').replace(/^[*!/_|]+/, ''); return neoBasicColours.indexOf(value) >= 0 || (/^(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/.test(value)); } function neoEffectiveOptionValue(row) { if (!row.value_is_null) return Object.prototype.hasOwnProperty.call(row, 'value') ? String(row.value) : ''; if (Object.prototype.hasOwnProperty.call(row, 'parent_value')) return String(row.parent_value); return Object.prototype.hasOwnProperty.call(row, 'default_value') ? String(row.default_value) : ''; } function neoNormalizeOption(row) { var choices = row.string_values ? String(row.string_values).split('|').filter(Boolean) : []; var type = String(row.type || 'string'); var value = neoEffectiveOptionValue(row); return { name: String(row.full_name || ''), configName: String(row.config_name || '').replace(/[^A-Za-z0-9_-]/g, ''), description: String(row.description_nls || row.description || ''), type: type, value: value, editValue: type === 'integer' && !choices.length && /^-?[0-9]+$/.test(value) ? Number(value) : value, defaultValue: Object.prototype.hasOwnProperty.call(row, 'default_value') ? String(row.default_value) : '', min: Number(row.min), max: Number(row.max), choices: choices, saving: false }; } function neoSafeOption(row) { return row && /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,511}$/.test(row.name) && /^(weechat|irc)$/.test(row.configName) && !/(?:password|passphrase|secret|token)(?:$|[._-])/i.test(row.name); } function neoValidateOption(row, value) { value = String(value); if (!neoSafeOption(row) || neoLoadedOptions[row.name] !== row) throw new Error('Setting is not editable.'); if (value.length > 4096 || /[\u0000-\u001f\u007f-\u009f"\\\u2028\u2029]/.test(value)) throw new Error('Value contains unsupported characters.'); if (row.type === 'boolean' && !/^(?:on|off)$/.test(value)) throw new Error('Choose On or Off.'); if (row.choices.length && row.choices.indexOf(value) < 0) throw new Error('Choose a listed value.'); if (row.type === 'integer' && !row.choices.length) { if (!/^-?[0-9]+$/.test(value)) throw new Error('Enter an integer.'); var number = Number(value); if ((Number.isFinite(row.min) && number < row.min) || (Number.isFinite(row.max) && row.max > row.min && number > row.max)) throw new Error('Integer is outside the allowed range.'); } if (row.type === 'color' && !neoValidWeeChatColour(value)) throw new Error('Choose a WeeChat colour.'); if (row.name === 'irc.color.nick_prefixes') { var prefixEntries = value.split(';').filter(Boolean); if (!prefixEntries.length || prefixEntries.some(function(entry) { var match = entry.match(/^[A-Za-z*]:([*!/_|]*)([^;]+)$/); return !match || !neoValidWeeChatColour(match[2]); })) throw new Error('Invalid IRC prefix colour map.'); } return value; } function neoRefreshExact(row, expected, success, failure, attempt) { connection.requestInfolist('option', 0, row.name).then(function(rows) { var updated = rows[0] ? neoNormalizeOption(rows[0]) : null; if (updated && (expected === null || updated.value === expected)) { Object.assign(row, updated); neoLoadedOptions[row.name] = row; connection.sendCoreCommand('/save ' + updated.configName); $scope.$applyAsync(function() { success(row); }); } else if (attempt < 5) { $timeout(function() { neoRefreshExact(row, expected, success, failure, attempt + 1); }, 200 * attempt); } else { $scope.$applyAsync(function() { failure('WeeChat did not confirm the setting change.'); }); } }, function() { $scope.$applyAsync(function() { failure('Unable to verify the setting change.'); }); }); } function neoWriteOption(row, value, success, failure) { try { value = neoValidateOption(row, value); } catch (error) { failure(error.message); return; } connection.sendCoreCommand('/set ' + row.name + ' "' + value + '"'); $timeout(function() { neoRefreshExact(row, value, success, failure, 1); }, 150); } $scope.neoWeeChatSettings = {sections: neoAllowedSections, section: neoAllowedSections[0].mask, filter: '', options: [], loading: false, status: ''}; $scope.loadNeoWeeChatSettings = function() { var state = $scope.neoWeeChatSettings; if (!$rootScope.connected || state.loading || !neoAllowedMasks[state.section]) return; state.loading = true; state.status = 'Loading settings from WeeChat...'; connection.requestInfolist('option', 0, state.section).then(function(rows) { $scope.$applyAsync(function() { state.options = rows.map(neoNormalizeOption).filter(neoSafeOption).sort(function(a, b) { return a.name.localeCompare(b.name); }); state.options.forEach(function(row) { neoLoadedOptions[row.name] = row; }); state.loading = false; state.status = 'Loaded ' + state.options.length + ' editable settings.'; }); }, function() { $scope.$applyAsync(function() { state.loading = false; state.status = 'Unable to load settings.'; }); }); }; $scope.saveNeoWeeChatOption = function(row) { row.saving = true; neoWriteOption(row, row.editValue, function(updated) { Object.assign(row, updated); row.saving = false; $scope.neoWeeChatSettings.status = 'Saved ' + row.name + '.'; }, function(message) { row.saving = false; $scope.neoWeeChatSettings.status = message; }); }; $scope.resetNeoWeeChatOption = function(row) { if (!neoSafeOption(row) || neoLoadedOptions[row.name] !== row) return; row.saving = true; connection.sendCoreCommand('/unset ' + row.name); $timeout(function() { neoRefreshExact(row, null, function(updated) { Object.assign(row, updated); row.saving = false; $scope.neoWeeChatSettings.status = 'Reset ' + row.name + '.'; }, function(message) { row.saving = false; $scope.neoWeeChatSettings.status = message; }, 1); }, 150); };