diff --git a/.eslintrc b/.eslintrc index b824e9df..dec82d92 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,15 +7,14 @@ "root": true, "parser": "vue-eslint-parser", "parserOptions": { - "parser": "@typescript-eslint/parser", + "parser": "@typescript-eslint/parser" }, "extends": [ - "plugin:vue/strongly-recommended", "eslint:recommended", - "@vue/typescript/recommended", - "prettier", + "plugin:vue/vue3-recommended", + "prettier" ], - "plugins": ["@typescript-eslint", "prettier"], + "plugins": ["prettier"], "rules": { "prettier/prettier": [ "error", @@ -31,5 +30,8 @@ "endOfLine": "auto" } ] + }, + "globals": { + "NodeJS": true } } diff --git a/README.md b/README.md index 735950a6..8b0e607b 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,7 @@ The `config/preferences.json` file specifies application preferences. The availa | field | type | default | description | |-------|------|---------|-------------| +| minRightForPublicMessages | number | -1 | Min. right to send public messages | | minRightForPrivateMessages | number | -1 | Min. right to send private messages | | minRightForShortTermMessageHistory | number | -1 | Min. right to access short term room message history | | minRightForMessageHistory | number | -1 | Min. right to access full room message history | @@ -116,6 +117,7 @@ The `config/preferences.json` file specifies application preferences. The availa | maxNewlinesPerMessage | number | 20 | Max. number of newlines per message | | maxConsecutiveMessages | number | 1 | Max. number of consecutive messages in a room | | maxMessageMergeDelayMin | number | 10 | Max. minutes before not merging consecutive messages | +| daysBeforeMessageFuzz | number | 7 | Number of days before messages are fuzzed, if ExtraSecurityPluginGroup is enabled | | invertedBlacklist | boolean | false | Whether blacklisted users can not see messages from users who blacklisted them | | messagesCooldown | ([number, number])[] | [ [ -1, 0] ] | Minimum duration between two consecutive messages for each right level, sorted by ascending right number | @@ -153,6 +155,11 @@ When the source files change, the build processes re-runs automatically. ### Add features +Refer to [the Wiki](https://github.com/skychatorg/skychat/wiki) guides to contribute: +- [Write a plugin](https://github.com/skychatorg/skychat/wiki/SkyChat-Plugin-Development-Documentation) +- [Example: Write the TypingList Plugin](https://github.com/skychatorg/skychat/wiki/Room-Plugin-Example:-Writing-the-TypingList-Plugin) +- [Plugin hooks](https://github.com/skychatorg/skychat/wiki/SkyChat-Plugin-Hooks-Documentation) + Please use only one of the following to suggest new features (or bug fixes): - Create a pull request - Open an issue with your proposal diff --git a/app/api/SkyChatClient.ts b/app/api/SkyChatClient.ts index 7659aefc..a18fc307 100644 --- a/app/api/SkyChatClient.ts +++ b/app/api/SkyChatClient.ts @@ -69,6 +69,7 @@ export class SkyChatClient extends EventEmitter { static readonly CURSOR_DECAY_DELAY = 10 * 1e3; static readonly LOCAL_STORAGE_TOKEN_KEY = 'skychat-token'; + static readonly LOCAL_STORAGE_ROOM_ID = 'skychat-room-id'; private _websocket: WebSocket | null = null; @@ -96,7 +97,11 @@ export class SkyChatClient extends EventEmitter { private _currentPlayerChannelId: number | null = null; private _currentPlayerChannel: SanitizedPlayerChannel | null = null; private _playerApiSearchResult: { type: string; items: Array } | null = null; - private _player: { current: QueuedVideoInfo | null; queue: QueuedVideoInfo[]; cursor: number } = { current: null, queue: [], cursor: 0 }; + private _player: { current: QueuedVideoInfo | null; queue: QueuedVideoInfo[]; cursor: number } = { + current: null, + queue: [], + cursor: 0, + }; private _playerLastUpdate: Date | null = null; constructor(public readonly url: string) { @@ -230,7 +235,8 @@ export class SkyChatClient extends EventEmitter { this._user = ownUser.user; } ({ messageIdToLastSeenUsers: this._messageIdToLastSeenUsers } = this._generateMessageIdToLastSeenUsers()); - ({ roomConnectedUsers: this._roomConnectedUsers, playerChannelUsers: this._playerChannelUsers } = this._generateRoomConnectedUsersAndPlayerChannelUsers()); + ({ roomConnectedUsers: this._roomConnectedUsers, playerChannelUsers: this._playerChannelUsers } = + this._generateRoomConnectedUsersAndPlayerChannelUsers()); } private _onRoomList(rooms: Array) { @@ -238,8 +244,11 @@ export class SkyChatClient extends EventEmitter { this.emit('update', this.state); } - private _onCurrentRoomId(currentRoomId: number) { + private _onCurrentRoomId(currentRoomId: number | null) { this._currentRoomId = currentRoomId; + if (typeof localStorage !== 'undefined' && currentRoomId !== null) { + localStorage.setItem(SkyChatClient.LOCAL_STORAGE_ROOM_ID, currentRoomId.toString()); + } this.emit('update', this.state); // Ask for message history if joined a room currentRoomId !== null && this.sendMessage('/messagehistory'); @@ -507,7 +516,12 @@ export class SkyChatClient extends EventEmitter { if (typeof localStorage !== 'undefined') { const authToken = localStorage.getItem(SkyChatClient.LOCAL_STORAGE_TOKEN_KEY); if (authToken) { - this._sendEvent('set-token', JSON.parse(authToken)); + const rawRoomId = localStorage.getItem(SkyChatClient.LOCAL_STORAGE_ROOM_ID); + const roomId = rawRoomId ? parseInt(rawRoomId) : null; + this._sendEvent('set-token', { + ...JSON.parse(authToken), + roomId: roomId ?? undefined, + }); } } this.emit('update', this.state); diff --git a/app/client/src/App.vue b/app/client/src/App.vue index 0c5a4f9a..32ac9c50 100644 --- a/app/client/src/App.vue +++ b/app/client/src/App.vue @@ -7,6 +7,7 @@ import ProfileModal from '@/components/modal/ProfileModal.vue'; import PlayerQueueModal from '@/components/modal/PlayerQueueModal.vue'; import OngoingConvertsModal from '@/components/modal/OngoingConvertsModal.vue'; import VideoConverterModal from '@/components/modal/VideoConverterModal.vue'; +import ManageRoomsModal from '@/components/modal/ManageRoomsModal.vue'; diff --git a/app/client/src/components/message/NewMessageForm.vue b/app/client/src/components/message/NewMessageForm.vue index 19c9b74a..0ad382d8 100644 --- a/app/client/src/components/message/NewMessageForm.vue +++ b/app/client/src/components/message/NewMessageForm.vue @@ -4,7 +4,6 @@ import { useAppStore } from '@/stores/app'; import { useClientStore } from '@/stores/client'; import { AudioRecorder } from '@/lib/AudioRecorder'; import { RisiBank } from 'risibank-web-api'; -import HoverCard from '@/components/util/HoverCard.vue'; const MESSAGE_HISTORY_LENGTH = 500; @@ -22,7 +21,7 @@ onMounted(() => { message.value.focus(); }); -const typingListStr = computed(() => { +const typingListText = computed(() => { let typingUsers = client.state.typingList; typingUsers = typingUsers.filter((user) => user.username.toLowerCase() !== client.state.user.username.toLowerCase()); @@ -43,10 +42,21 @@ const typingListStr = computed(() => { return 'multiple users are currently typing..'; }); +const textAreaPlaceholder = computed(() => { + if (!client.state.currentRoom) { + return '❌ Disconnected'; + } + let placeholder = `New message / ${client.state.currentRoom.name}`; + if (client.state.currentRoom.plugins.messagelimiter) { + placeholder += ` (char limit: ${client.state.currentRoom.plugins.messagelimiter})`; + } + return placeholder; +}); + // Watch when the new message input changes. The change does not necessarily come from this component, as the message input can be prepared elsewhere. watch( () => app.newMessage, - (newValue, oldValue) => { + (newValue) => { // Auto-set the message value if (newValue !== message.value.value) { message.value.value = newValue; @@ -188,7 +198,7 @@ const recordingAudioStopCb = ref(null); const uploadAudio = async function () { if (recordingAudio.value) { // Stop recording - const { blob, uri, audio } = await recordingAudioStopCb.value(); + const { blob } = await recordingAudioStopCb.value(); client.sendAudio(blob); } else { // Start recording @@ -229,7 +239,7 @@ const cancelAudio = function () { - + @@ -246,7 +256,7 @@ const cancelAudio = function () {
-
@@ -266,15 +276,16 @@ const cancelAudio = function () {

- {{ typingListStr }} + {{ typingListText }}