0.2.4 - also fix theme building

This commit is contained in:
MayaWolf 2017-10-19 01:29:28 +02:00
parent 6d9fd35805
commit cf015bd4b7
23 changed files with 109 additions and 62 deletions

View File

@ -137,7 +137,7 @@
onKeyDown(e: KeyboardEvent): void {
const key = getKey(e);
if(e.ctrlKey && !e.shiftKey && key !== 'Control') { //tslint:disable-line:curly
if((e.metaKey || e.ctrlKey) && !e.shiftKey && key !== 'Control' && key !== 'Meta') { //tslint:disable-line:curly
for(const button of this.buttons)
if(button.key === key) {
e.stopPropagation();

View File

@ -16,10 +16,15 @@
<option :value="setting.False">{{l('conversationSettings.false')}}</option>
</select>
</div>
<div class="form-group">
<label class="control-label" for="defaultHighlights">
<input type="checkbox" id="defaultHighlights" v-model="defaultHighlights"/>
{{l('settings.defaultHighlights')}}
</label>
</div>
<div class="form-group">
<label class="control-label" :for="'highlightWords' + conversation.key">{{l('settings.highlightWords')}}</label>
<input :id="'highlightWords' + conversation.key" class="form-control" v-model="highlightWords"
:disabled="highlight == setting.Default"/>
<input :id="'highlightWords' + conversation.key" class="form-control" v-model="highlightWords"/>
</div>
<div class="form-group">
<label class="control-label" :for="'joinMessages' + conversation.key">{{l('settings.joinMessages')}}</label>
@ -52,6 +57,7 @@
highlight: Conversation.Setting;
highlightWords: string;
joinMessages: Conversation.Setting;
defaultHighlights: boolean;
constructor() {
super();
@ -64,6 +70,7 @@
this.highlight = settings.highlight;
this.highlightWords = settings.highlightWords.join(',');
this.joinMessages = settings.joinMessages;
this.defaultHighlights = settings.defaultHighlights;
};
@Watch('conversation')
@ -76,7 +83,8 @@
notify: this.notify,
highlight: this.highlight,
highlightWords: this.highlightWords.split(',').filter((x) => x.length),
joinMessages: this.joinMessages
joinMessages: this.joinMessages,
defaultHighlights: this.defaultHighlights
};
}
}

View File

@ -148,6 +148,7 @@
const touch = e instanceof TouchEvent ? e.changedTouches[0] : e;
let node = <Node & {character?: Character, channel?: Channel}>touch.target;
while(node !== document.body) {
if(e.type === 'touchstart' && node === this.$refs['menu']) return;
if(node.character !== undefined || node.parentNode === null) break;
node = node.parentNode;
}

View File

@ -46,6 +46,7 @@ export class ConversationSettings implements Conversation.Settings {
highlight = Conversation.Setting.Default;
highlightWords: string[] = [];
joinMessages = Conversation.Setting.Default;
defaultHighlights = true;
}
export function formatTime(this: void | never, date: Date): string {

View File

@ -439,19 +439,33 @@ export default function(this: void): Interfaces.State {
for(const item of state.pinned.private) state.getPrivate(core.characters.get(item));
queuedJoin(state.pinned.channels.slice());
});
core.channels.onEvent((type, channel) => {
core.channels.onEvent((type, channel, member) => {
const key = channel.id.toLowerCase();
if(type === 'join') {
const conv = new ChannelConversation(channel);
state.channelMap[key] = conv;
state.channelConversations.push(conv);
state.addRecent(conv);
} else {
if(type === 'join')
if(member === undefined) {
const conv = new ChannelConversation(channel);
state.channelMap[key] = conv;
state.channelConversations.push(conv);
state.addRecent(conv);
} else {
const conv = state.channelMap[channel.id]!;
if(conv.settings.joinMessages === Interfaces.Setting.False || conv.settings.joinMessages === Interfaces.Setting.Default &&
!core.state.settings.joinMessages) return;
const text = l('events.channelJoin', `[user]${member.character.name}[/user]`);
conv.addMessage(new EventMessage(text));
}
else if(member === undefined) {
const conv = state.channelMap[key]!;
state.channelConversations.splice(state.channelConversations.indexOf(conv), 1);
delete state.channelMap[key];
state.savePinned();
if(state.selectedConversation === conv) state.show(state.consoleTab);
} else {
const conv = state.channelMap[channel.id]!;
if(conv.settings.joinMessages === Interfaces.Setting.False || conv.settings.joinMessages === Interfaces.Setting.Default &&
!core.state.settings.joinMessages) return;
const text = l('events.channelLeave', `[user]${member.character.name}[/user]`);
conv.addMessage(new EventMessage(text));
}
});
@ -469,14 +483,10 @@ export default function(this: void): Interfaces.State {
const message = createMessage(MessageType.Message, char, decodeHTML(data.message), time);
conversation.addMessage(message);
let words: string[];
if(conversation.settings.highlight !== Interfaces.Setting.Default) {
words = conversation.settings.highlightWords.slice();
if(conversation.settings.highlight === Interfaces.Setting.True) words.push(core.connection.character);
} else {
words = core.state.settings.highlightWords.slice();
if(core.state.settings.highlight) words.push(core.connection.character);
}
const words = conversation.settings.highlightWords.slice();
if(conversation.settings.defaultHighlights) words.push(...core.state.settings.highlightWords);
if(conversation.settings.highlight === Interfaces.Setting.Default && core.state.settings.highlight ||
conversation.settings.highlight === Interfaces.Setting.True) words.push(core.connection.character);
//tslint:disable-next-line:no-null-keyword
const results = words.length > 0 ? message.text.match(new RegExp(`\\b(${words.join('|')})\\b`, 'i')) : null;
if(results !== null) {
@ -523,7 +533,7 @@ export default function(this: void): Interfaces.State {
const message = new EventMessage(l('events.login', `[user]${data.identity}[/user]`), time);
if(isOfInterest(core.characters.get(data.identity))) addEventMessage(message);
const conv = state.privateMap[data.identity.toLowerCase()];
if(conv !== undefined && core.state.settings.eventMessages && conv !== state.selectedConversation) conv.addMessage(message);
if(conv !== undefined && (!core.state.settings.eventMessages || conv !== state.selectedConversation)) conv.addMessage(message);
});
connection.onMessage('FLN', (data, time) => {
const message = new EventMessage(l('events.logout', `[user]${data.character}[/user]`), time);
@ -531,7 +541,7 @@ export default function(this: void): Interfaces.State {
const conv = state.privateMap[data.character.toLowerCase()];
if(conv === undefined) return;
conv.typingStatus = 'clear';
if(core.state.settings.eventMessages && conv !== state.selectedConversation) conv.addMessage(message);
if(!core.state.settings.eventMessages || conv !== state.selectedConversation) conv.addMessage(message);
});
connection.onMessage('TPN', (data) => {
const conv = state.privateMap[data.character.toLowerCase()];
@ -660,22 +670,6 @@ export default function(this: void): Interfaces.State {
state.selectedConversation.infoText = data.message;
addEventMessage(new EventMessage(data.message, time));
});
connection.onMessage('JCH', (data, time) => {
if(data.character.identity === core.connection.character) return;
const conv = state.channelMap[data.channel.toLowerCase()]!;
if(conv.settings.joinMessages === Interfaces.Setting.False || conv.settings.joinMessages === Interfaces.Setting.Default &&
!core.state.settings.joinMessages) return;
const text = l('events.channelJoin', `[user]${data.character.identity}[/user]`);
conv.addMessage(new EventMessage(text, time));
});
connection.onMessage('LCH', (data, time) => {
if(data.character === core.connection.character) return;
const conv = state.channelMap[data.channel.toLowerCase()]!;
if(conv.settings.joinMessages === Interfaces.Setting.False || conv.settings.joinMessages === Interfaces.Setting.Default &&
!core.state.settings.joinMessages) return;
const text = l('events.channelLeave', `[user]${data.character}[/user]`);
conv.addMessage(new EventMessage(text, time));
});
connection.onMessage('ZZZ', (data, time) => {
state.selectedConversation.infoText = data.message;
addEventMessage(new EventMessage(data.message, time));

View File

@ -92,6 +92,7 @@ export namespace Conversation {
readonly highlight: Setting;
readonly highlightWords: ReadonlyArray<string>;
readonly joinMessages: Setting;
readonly defaultHighlights: boolean;
}
export const enum UnreadState { None, Unread, Mention }

View File

@ -8,7 +8,7 @@ const strings: {[key: string]: string | undefined} = {
'action.copyLink': 'Copy Link',
'action.suggestions': 'Suggestions',
'action.open': 'Show',
'action.quit': 'Exit',
'action.quit': 'Quit',
'action.updateAvailable': 'UPDATE AVAILABLE',
'action.update': 'Restart now!',
'action.cancel': 'Cancel',
@ -18,6 +18,7 @@ const strings: {[key: string]: string | undefined} = {
'help.rules': 'F-List Rules',
'help.faq': 'F-List FAQ',
'help.report': 'How to report a user',
'help.changelog': 'Changelog',
'title': 'FChat 3.0',
'version': 'Version {0}',
'filter': 'Type to filter...',
@ -130,6 +131,7 @@ Are you sure?`,
'settings.theme': 'Theme',
'settings.logMessages': 'Log messages',
'settings.logAds': 'Log ads',
'settings.defaultHighlights': 'Use global highlight words',
'conversationSettings.title': 'Settings',
'conversationSettings.action': 'Edit settings for {0}',
'conversationSettings.default': 'Default',
@ -344,7 +346,7 @@ Are you sure?`,
'status.dnd': 'Do Not Disturb',
'status.idle': 'Idle',
'status.offline': 'Offline',
'status.crown': 'Rewarded by Admin',
'status.crown': 'Rewarded',
'importer.importGeneral': 'slimCat data has been detected on your computer.\nWould you like to import general settings?',
'importer.importCharacter': 'slimCat data for this character has been detected on your computer.\nWould you like to import settings and logs?\nThis may take a while.\nAny existing FChat 3.0 data for this character will be overwritten.',
'importer.importing': 'Importing data',

View File

@ -42,7 +42,7 @@ export function parse(this: void | never, input: string, context: CommandContext
switch(param.type) {
case ParamType.String:
if(i === command.params.length - 1) values[i] = args.substr(index);
continue;
break;
case ParamType.Enum:
if((param.options !== undefined ? param.options : []).indexOf(value) === -1)
return l('commands.invalidParam', l(`commands.${name}.param${i}`));
@ -62,7 +62,7 @@ export function parse(this: void | never, input: string, context: CommandContext
const char = core.characters.get(value);
if(char.status === 'offline') return l('commands.invalidCharacter');
}
index = endIndex + 1;
index = endIndex === -1 ? args.length : endIndex + 1;
}
if(command.context !== undefined)
return function(this: Conversation): void {

View File

@ -44,7 +44,8 @@ const UserView = Vue.extend({
else rankIcon = '';
} else rankIcon = '';
const html = (props.showStatus !== undefined ? `<span class="fa fa-fw ${getStatusIcon(character.status)}"></span>` : '') +
const html = (props.showStatus !== undefined || character.status === 'crown'
? `<span class="fa fa-fw ${getStatusIcon(character.status)}"></span>` : '') +
(rankIcon !== '' ? `<span class="fa ${rankIcon}"></span>` : '') + character.name;
return createElement('span', {
attrs: {class: `user-view gender-${character.gender !== undefined ? character.gender.toLowerCase() : 'none'}`},

View File

@ -59,6 +59,10 @@
import {GeneralSettings, getGeneralSettings, Logs, setGeneralSettings, SettingsStore} from './filesystem';
import Notifications from './notifications';
function confirmBack(): void {
if(confirm(l('chat.confirmLeave'))) (<Navigator & {app: {exitApp(): void}}>navigator).app.exitApp();
}
@Component({
components: {chat: Chat, modal: Modal}
})
@ -105,9 +109,11 @@
const connection = new Connection(Socket, this.settings!.account, this.settings!.password);
connection.onEvent('connected', () => {
Raven.setUserContext({username: core.connection.character});
document.addEventListener('backbutton', confirmBack);
});
connection.onEvent('closed', () => {
Raven.setUserContext();
document.removeEventListener('backbutton', confirmBack);
});
initCore(connection, Logs, SettingsStore, Notifications);
this.characters = data.characters.sort();

View File

@ -6,6 +6,7 @@
</description>
<author email="maya@f-list.net" href="https://www.f-list.net">The F-list Team</author>
<content src="index.html" />
<icon src="../electron/build/icon.png" />
<access origin="*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />

View File

@ -27,7 +27,7 @@ export class GeneralSettings {
account = '';
password = '';
host = 'wss://chat.f-list.net:9799';
theme = 'dark';
theme = 'default';
}
type Index = {[key: string]: {name: string, index: {[key: number]: number | undefined}} | undefined};
@ -99,12 +99,13 @@ function serializeMessage(message: Conversation.Message): Blob {
dv.setUint8(5, senderLength);
const textLength = getByteLength(message.text);
dv.setUint16(6, textLength);
return new Blob([buffer, name, message.text, String.fromCharCode(senderLength + textLength + 10)]);
const length = senderLength + textLength + 8;
return new Blob([buffer, name, message.text, String.fromCharCode(length >> 255), String.fromCharCode(length % 255)]);
}
function deserializeMessage(buffer: ArrayBuffer): {message: Conversation.Message, end: number} {
const dv = new DataView(buffer, 0, 8);
const time = dv.getUint32(0);
const time = dv.getUint32(0) * 1000;
const type = dv.getUint8(4);
const senderLength = dv.getUint8(5);
const messageLength = dv.getUint16(6);
@ -183,7 +184,7 @@ export class Logs implements Logging.Persistent {
let messages = new Array<Conversation.Message>(count);
let pos = file.size;
while(pos > 0 && count > 0) {
const length = new DataView(await readAsArrayBuffer(file)).getUint16(0);
const length = new DataView(await readAsArrayBuffer(file.slice(pos - 2, pos))).getUint16(0);
pos = pos - length - 2;
messages[--count] = deserializeMessage(await readAsArrayBuffer(file.slice(pos, pos + length))).message;
}

View File

@ -88,7 +88,6 @@
{label: l('action.open'), click: () => mainWindow!.show()},
{
label: l('action.quit'),
role: 'quit',
click: () => {
isClosing = true;
mainWindow!.close();
@ -201,7 +200,13 @@
},
{type: 'separator'},
{role: 'minimize'},
{role: 'quit'}
process.platform === 'darwin' ? {role: 'quit'} : {
label: l('action.quit'),
click(): void {
isClosing = true;
mainWindow!.close();
}
}
];
electron.remote.Menu.setApplicationMenu(electron.remote.Menu.buildFromTemplate(appMenu));
@ -220,6 +225,9 @@
electron.ipcRenderer.send('install-update');
}
}
}, {
label: l('help.changelog'),
click: () => electron.shell.openExternal('https://wiki.f-list.net/F-Chat_3.0#Changelog')
}])
}));
electron.remote.Menu.setApplicationMenu(menu);

View File

@ -1,6 +1,6 @@
{
"name": "fchat",
"version": "0.2.2",
"version": "0.2.4",
"author": "The F-List Team",
"description": "F-List.net Chat Client",
"main": "main.js",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 969 B

BIN
electron/build/tray@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -98,7 +98,7 @@ function createMessage(line: string, ownCharacter: string, name: string, isChann
let endIndex = line.indexOf('[', lineIndex += 6);
if(endIndex - lineIndex > 20) endIndex = lineIndex + 20;
sender = line.substring(lineIndex, endIndex);
text = line.substring(endIndex + 6, 65535);
text = line.substring(endIndex + 6, 50000);
} else {
if(lineIndex + ownCharacter.length <= line.length && line.substr(lineIndex, ownCharacter.length) === ownCharacter)
sender = ownCharacter;
@ -117,7 +117,7 @@ function createMessage(line: string, ownCharacter: string, name: string, isChann
lineIndex += 3;
}
} else type = Conversation.Message.Type.Action;
text = line.substr(lineIndex, 65535);
text = line.substr(lineIndex, 50000);
}
return {type, sender: {name: sender}, text, time: addMinutes(date, h * 60 + m)};
}

View File

@ -18,6 +18,10 @@ function sortMember(this: void | never, array: Interfaces.Member[], member: Inte
if(member.character.isChatOp && !other.character.isChatOp) break;
if(other.rank > member.rank) continue;
if(member.rank > other.rank) break;
if(other.character.isFriend && !member.character.isFriend) continue;
if(member.character.isFriend && !other.character.isFriend) break;
if(other.character.isBookmarked && !member.character.isBookmarked) continue;
if(member.character.isBookmarked && !other.character.isBookmarked) break;
if(name < other.character.name) break;
}
array.splice(i, 0, member);
@ -37,6 +41,7 @@ class Channel implements Interfaces.Channel {
addMember(member: Interfaces.Member): void {
this.members[member.character.name] = member;
sortMember(this.sortedMembers, member);
for(const handler of state.handlers) handler('join', this, member);
}
removeMember(name: string): void {
@ -44,6 +49,7 @@ class Channel implements Interfaces.Channel {
if(member !== undefined) {
delete this.members[name];
this.sortedMembers.splice(this.sortedMembers.indexOf(member), 1);
for(const handler of state.handlers) handler('leave', this, member);
}
}
@ -105,12 +111,17 @@ let state: State;
export default function(this: void, connection: Connection, characters: Character.State): Interfaces.State {
state = new State(connection);
let getChannelTimer: NodeJS.Timer | undefined;
connection.onEvent('connecting', () => {
let rejoin: string[] | undefined;
connection.onEvent('connecting', (isReconnect) => {
if(isReconnect) rejoin = Object.keys(state.joinedMap);
state.joinedChannels = [];
state.joinedMap = {};
});
connection.onEvent('connected', (isReconnect) => {
if(isReconnect) queuedJoin(Object.keys(state.joinedChannels));
connection.onEvent('connected', () => {
if(rejoin !== undefined) {
queuedJoin(rejoin);
rejoin = undefined;
}
const getChannels = () => {
connection.send('CHA');
connection.send('ORS');
@ -152,7 +163,8 @@ export default function(this: void, connection: Connection, characters: Characte
if(item !== undefined) item.isJoined = true;
} else {
const channel = state.getChannel(data.channel)!;
channel.addMember(channel.createMember(characters.get(data.character.identity)));
const member = channel.createMember(characters.get(data.character.identity));
channel.addMember(member);
if(item !== undefined) item.memberCount++;
}
});

View File

@ -128,22 +128,28 @@ export default function(this: void, connection: Connection): Interfaces.State {
char.isChatOp = false;
});
connection.onMessage('RTB', (data) => {
if(data.type !== 'trackadd' && data.type !== 'trackrem' && data.type !== 'friendadd' && data.type !== 'friendremove') return;
const character = state.get(data.name);
switch(data.type) {
case 'trackadd':
state.bookmarkList.push(data.name);
state.get(data.name).isBookmarked = true;
character.isBookmarked = true;
if(character.status !== 'offline') state.bookmarks.push(character);
break;
case 'trackrem':
state.bookmarkList.splice(state.bookmarkList.indexOf(data.name), 1);
state.get(data.name).isBookmarked = false;
character.isBookmarked = false;
if(character.status !== 'offline') state.bookmarks.splice(state.bookmarks.indexOf(character), 1);
break;
case 'friendadd':
state.friendList.push(data.name);
state.get(data.name).isFriend = true;
character.isFriend = true;
if(character.status !== 'offline') state.friends.push(character);
break;
case 'friendremove':
state.friendList.splice(state.friendList.indexOf(data.name), 1);
state.get(data.name).isFriend = false;
character.isFriend = false;
if(character.status !== 'offline') state.friends.splice(state.friends.indexOf(character), 1);
}
});
return state;

View File

@ -180,7 +180,7 @@ export namespace Character {
export type Character = Character.Character;
export namespace Channel {
export type EventHandler = (type: 'join' | 'leave', channel: Channel) => void;
export type EventHandler = (type: 'join' | 'leave', channel: Channel, member?: Member) => void;
export interface State {
readonly officialChannels: {readonly [key: string]: (ListItem | undefined)};

View File

@ -6,6 +6,7 @@
"license": "MIT",
"dependencies": {
"bootstrap": "^3.3.7",
"font-awesome": "^4.7.0",
"less": "^2.7.2",
"less-plugin-npm-import": "^2.1.0"
},

View File

@ -115,6 +115,10 @@ fast-deep-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
font-awesome@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133"
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"

View File

@ -31,9 +31,9 @@ See https://electron.atom.io/docs/tutorial/application-distribution/
## Building a custom theme
See [the wiki](https://wiki.f-list.net/F-Chat_3.0/Themes) for instructions on how to create a custom theme.
- Change into the `less/themes/chat` directory.
- Change into the `less` directory.
- Run `yarn install`.
- Run `yarn build {name}.less {name}.css`.
- Run `yarn build themes/chat/{name}.less {name}.css`.
## Dependencies
Note: Adding *and upgrading* dependencies should only be done with prior consideration and subsequent testing.