mirror of
https://github.com/RetroShare/RSNewWebUI.git
synced 2026-09-12 11:40:06 +05:00
Merge pull request #122 from zelfroster/fix/webui-build-sources
This commit is contained in:
commit
fb80060595
356
README.md
356
README.md
@ -1,212 +1,206 @@
|
||||
# Web Interface for Retroshare
|
||||
# RetroShare Web Interface
|
||||
|
||||
A web-based frontend for [Retroshare](https://github.com/Retroshare/Retroshare)
|
||||
which communicates with the client through the JSON API.
|
||||
This project is the browser interface for
|
||||
[RetroShare](https://github.com/RetroShare/RetroShare). It talks to RetroShare
|
||||
through its JSON API.
|
||||
|
||||
## Requirements
|
||||
Recent RetroShare releases already include this interface. Follow this guide if
|
||||
you want to build it yourself, change it, or contribute to it.
|
||||
|
||||
- Retroshare v0.6.5+ with JSON API enabled(see instructions below)
|
||||
- A modern JavaScript-enabled web browser
|
||||
- [`qmake`](https://doc.qt.io/qt-5/qmake-manual.html)(optional)
|
||||
## What you need
|
||||
|
||||
## Installation
|
||||
- RetroShare 0.6.5 or newer, with the JSON API enabled
|
||||
- A modern web browser
|
||||
|
||||
> **Note:** The Web Interface is shipped by default in the latest release of
|
||||
> Retroshare. If you want to customise it or [contribute](#contributing) to it
|
||||
> then proceed with the following steps.
|
||||
## Set up RetroShare
|
||||
|
||||
### Install WebUI
|
||||
### Enable the JSON API
|
||||
|
||||
First, you need to download and install the web interface javascript code
|
||||
itself:
|
||||
1. Open RetroShare.
|
||||
2. Go to **Preferences > JSON API**.
|
||||
3. Enable **RetroShare JSON API Server**.
|
||||
|
||||
1. **Clone the repo**:
|
||||
You can clone using git, or download the zip file and extract it
|
||||
### Enable the Web Interface
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Retroshare/RSNewWebUI
|
||||
cd RSNewWebUI
|
||||
```
|
||||
1. Go to **Preferences > Web Interface**.
|
||||
2. Enable the Web Interface.
|
||||
3. Choose a password.
|
||||
4. Set **Web interface directory** to this repository's `webui/` directory if
|
||||
RetroShare does not find it automatically.
|
||||
5. Click **Apply**.
|
||||
|
||||
2. **Build the files**:
|
||||
If you have `qmake` installed, you just need to run it in the base directory:
|
||||
The JSON API page should now show an authenticated token named
|
||||
`webui:<your password>`.
|
||||
|
||||
```bash
|
||||
qmake .
|
||||
```
|
||||
## Open the interface
|
||||
|
||||
If you do not have `qmake`, go to `webui-src/make-src/` and run the build
|
||||
script. Note that qmake is enough to do the build.
|
||||
Open [https://localhost:9092/index.html](https://localhost:9092/index.html) in
|
||||
your browser. If you changed the JSON API port, replace `9092` with that port.
|
||||
|
||||
##### On Linux/MacOS:
|
||||
### Connect to a remote or headless server
|
||||
|
||||
```bash
|
||||
cd webui-src/make-src
|
||||
sh build.sh
|
||||
```
|
||||
The Web Interface listens on localhost by default. To reach RetroShare on a
|
||||
remote server, run this command on your local computer:
|
||||
|
||||
##### On Windows:
|
||||
```sh
|
||||
ssh -L 9092:localhost:9092 -N login@server
|
||||
```
|
||||
|
||||
```bash
|
||||
cd webui-src\make-src\
|
||||
./build.bat
|
||||
```
|
||||
Keep that command running, then open
|
||||
[https://localhost:9092/index.html](https://localhost:9092/index.html).
|
||||
|
||||
### Compile Retroshare with JSON API
|
||||
The Web Interface cannot create a new RetroShare node. Create the node with the
|
||||
desktop interface first, then copy its RetroShare data directory to the server.
|
||||
On Linux this directory is usually `.retroshare/`.
|
||||
|
||||
If you are on older versions of Retroshare then it needs to be compiled with
|
||||
non-default options as follows:
|
||||
Start the service on the server with:
|
||||
|
||||
```bash
|
||||
```sh
|
||||
./retroshare-service/src/retroshare-service -U list -W
|
||||
```
|
||||
|
||||
Follow the prompts to select the profile and enter its passwords.
|
||||
|
||||
## Contributing
|
||||
|
||||
To work on the source, you need:
|
||||
|
||||
- Node.js 24 or newer and npm
|
||||
- `sh` on macOS or Linux; Windows uses the included batch file
|
||||
- `qmake` only if you want to use RetroShare's qmake build integration
|
||||
|
||||
### First-time setup
|
||||
|
||||
Fork the repository, clone your fork, and install the locked dependencies:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/YOUR-USERNAME/RSNewWebUI.git
|
||||
cd RSNewWebUI/webui-src
|
||||
npm ci
|
||||
```
|
||||
|
||||
### Where to make changes
|
||||
|
||||
- JavaScript source is in `webui-src/app/`.
|
||||
- SCSS source is in `webui-src/app/scss/`.
|
||||
- `webui-src/styles.css` is generated from `app/scss/main.scss`. Do not edit it
|
||||
by hand, but commit it with the SCSS changes that generated it.
|
||||
- Everything in `webui/` is build output. Do not edit those files by hand.
|
||||
- `webui-src/app/mithril.js` contains Mithril 2.3.8. Change it only when
|
||||
intentionally upgrading Mithril.
|
||||
|
||||
### Normal development commands
|
||||
|
||||
Run these commands from `webui-src/`:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
npm run lint
|
||||
```
|
||||
|
||||
`npm run build` compiles the SCSS and creates the complete `webui/` directory.
|
||||
It automatically uses `build.bat` on Windows and `build.sh` on macOS or Linux.
|
||||
|
||||
`npm run lint` checks the source files and does not require `webui/` to exist.
|
||||
|
||||
To rebuild CSS whenever an SCSS file changes, run:
|
||||
|
||||
```sh
|
||||
npm run watch
|
||||
```
|
||||
|
||||
Watch mode updates `webui-src/styles.css` only. Run `npm run build` when you
|
||||
also need to update the files in `webui/`.
|
||||
|
||||
### Using qmake during development
|
||||
|
||||
Run qmake from the repository root:
|
||||
|
||||
```sh
|
||||
qmake .
|
||||
```
|
||||
|
||||
qmake copies the committed `webui-src/styles.css`; it does not compile SCSS.
|
||||
After changing SCSS, run `npm run build` first. Rerun `qmake .` when you need
|
||||
qmake to package newer WebUI files; `make` alone does not do that for this
|
||||
project.
|
||||
|
||||
A standalone checkout may show a warning about a missing `../retroshare.pri`.
|
||||
The WebUI files are still generated.
|
||||
|
||||
### Building without npm or qmake
|
||||
|
||||
These commands create `webui/` from the files already stored in the repository.
|
||||
They do not compile SCSS.
|
||||
|
||||
From the repository root on macOS or Linux:
|
||||
|
||||
```sh
|
||||
sh webui-src/make-src/build.sh
|
||||
```
|
||||
|
||||
From the repository root on Windows:
|
||||
|
||||
```bat
|
||||
webui-src\make-src\build.bat
|
||||
```
|
||||
|
||||
### Optional focused builds
|
||||
|
||||
The normal `npm run build` command is the easiest choice. On macOS or Linux,
|
||||
you can rebuild only one generated file from `webui-src/`:
|
||||
|
||||
```sh
|
||||
# JavaScript only
|
||||
sh make-src/build.sh "" app.js
|
||||
|
||||
# HTML only
|
||||
sh make-src/build.sh "" index.html
|
||||
|
||||
# CSS only
|
||||
./node_modules/.bin/sass --no-source-map --style=compressed app/scss/main.scss styles.css
|
||||
sh make-src/build.sh "" styles.css
|
||||
```
|
||||
|
||||
### Before opening a pull request
|
||||
|
||||
1. Run `npm run build`.
|
||||
2. Run `npm run lint`.
|
||||
3. Point RetroShare's **Web interface directory** to this repository's
|
||||
`webui/` directory.
|
||||
4. Manually test the screens you changed.
|
||||
5. If you changed SCSS, include the regenerated `webui-src/styles.css`.
|
||||
|
||||
There is no separate formatting command.
|
||||
|
||||
### Building older RetroShare versions
|
||||
|
||||
If you are testing with an older RetroShare version, you may need to build it
|
||||
with the JSON API and WebUI enabled:
|
||||
|
||||
```sh
|
||||
qmake CONFIG+="debug rs_jsonapi rs_webui"
|
||||
make
|
||||
```
|
||||
|
||||
See the [RetroShare repo](https://github.com/Retroshare/Retroshare) for more
|
||||
detailed instructions on compiling RetroShare. You should afterwards see a tab
|
||||
'JSON API' and a tab 'Web Interface' in the **Preferences**.
|
||||
See the [RetroShare repository](https://github.com/RetroShare/RetroShare) for
|
||||
complete build instructions. After building, RetroShare preferences should
|
||||
contain both **JSON API** and **Web Interface** pages.
|
||||
|
||||
### Enable JSON API
|
||||
### Useful references
|
||||
|
||||
You need to enable the JSON API, through which the web interface communicates
|
||||
with the client:
|
||||
- [Mithril documentation](https://mithril.js.org/)
|
||||
- [RetroShare source](https://github.com/RetroShare/RetroShare)
|
||||
|
||||
1. Open Retroshare, go to `Preferences > JSON API`.
|
||||
2. Make sure the **Enable Retroshare JSON API Server** box is checked.
|
||||
|
||||
### Enable Web Interface
|
||||
|
||||
1. Go to `Preferences > Webinterface`.
|
||||
2. Make sure the **Enable Retroshare WEB Interface** box is checked.
|
||||
3. Enter a password to protect access to the web interface.
|
||||
|
||||
If necessary, point the **Web interface directory** to the place where the webui
|
||||
files are compiled. This is usually `RSNewWebUI/webui/`.
|
||||
|
||||
In any case, click on "Apply settings" after making the changes. If everything
|
||||
goes ok, you should see a new token `webui:[your password]` under the
|
||||
**Authenticated Tokens** section in the **JSON API** preferences page.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
This is the default link to access the WebInterface.
|
||||
<br>
|
||||
Open this link your browser ->
|
||||
[https://localhost:9092/index.html](https://localhost:9092/index.html).
|
||||
|
||||
> Note: If you changed the port in the JSON API preferences pages, the port
|
||||
> in the above line needs to be changed accordingly.
|
||||
|
||||
### Advanced Usage
|
||||
|
||||
The Web interface is only accessible from localhost (127.0.0.1). If you want to
|
||||
access the web interface of a headless retroshare server, then you need to
|
||||
create a SSH tunnel as follows:
|
||||
|
||||
```
|
||||
ssh login@server -L 9092:localhost:9092 -N
|
||||
```
|
||||
|
||||
After that, the Web interface of the Retroshare running on 'server' is tunneled
|
||||
to your local machine and accessible through localhost:9092.
|
||||
|
||||
Running a headless retroshare server is one possibility. The Webinterface
|
||||
however does not allow you to create new nodes. Therefore the steps are:
|
||||
|
||||
1. Create a node using the standard Qt UI. That can be done in another machine.
|
||||
2. Copy the retroshare data directory (.retroshare/ on linux) on the server.
|
||||
3. On the server, launch a headless retroshare using that node:
|
||||
```
|
||||
./retroshare-service/src/retroshare-service -U list -W
|
||||
```
|
||||
|
||||
After that follow instructions to launch your profile (you need to choose a
|
||||
webui password and enter the ID and login password of your node).
|
||||
|
||||
## Contributing
|
||||
|
||||
For contributing, It is recommended that you read this entire section to have a
|
||||
better idea.
|
||||
|
||||
### Setup WebUI for contributing
|
||||
|
||||
Follow these steps to setup the project and make it ready for
|
||||
contribution/customisation :
|
||||
|
||||
- Fork and Clone this repository to your local machine.
|
||||
- `cd` into the cloned repo:
|
||||
```
|
||||
cd RSNewWebUI
|
||||
```
|
||||
- Run this command to install the dependecies for the project.
|
||||
```
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Run the WebUI
|
||||
|
||||
- Run this command to watch for any changes in the `scss` files and compile them
|
||||
to css.
|
||||
|
||||
```
|
||||
pnpm watch
|
||||
```
|
||||
|
||||
- You can now start to edit the source code. But you must run the below command
|
||||
to see the changes reflected in the browser everytime you edit the code for
|
||||
webui which is in the `webui-src/app/` directory.
|
||||
|
||||
```
|
||||
qmake .
|
||||
```
|
||||
|
||||
### Linting and Formatting
|
||||
|
||||
Linting and formatting can be done with editor/IDE plugins.
|
||||
|
||||
Or to do it manually,
|
||||
|
||||
Install `prettier` and `eslint` (required for linting & formatting code):
|
||||
To find RetroShare headers that expose JSON API methods, run this from
|
||||
`libretroshare/src/retroshare/` in the RetroShare source tree:
|
||||
|
||||
```sh
|
||||
npm install -g prettier eslint
|
||||
grep -c "@jsonapi" *.h | grep -v ":0"
|
||||
```
|
||||
|
||||
Next, run the following in the `webui-src` directory:
|
||||
## Get help or report a problem
|
||||
|
||||
To run the linter: `eslint app`
|
||||
|
||||
To run the formatter: `prettier -c app`
|
||||
|
||||
### References
|
||||
|
||||
Now, While contributing you can checkout these resources as you might need to
|
||||
look up for these often.
|
||||
|
||||
- [mithril](https://mithril.js.org/hyperscript.html)
|
||||
- You can list files with @jsonapi in libretroshare/src/retroshare of
|
||||
[retroshare](https://github.com/RetroShare/RetroShare):
|
||||
|
||||
```
|
||||
grep -c "@jsonapi" *.h|grep -v ":0"
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
And, that's it. You are more than welcome to contribute to this project. If you
|
||||
have any questions/difficulties in setting up or running the project, you can
|
||||
raise an issue and we will be more than willing to help you out.
|
||||
|
||||
### Bug Reports & Feature requests
|
||||
|
||||
Please create an [issue](https://github.com/Retroshare/RsNewWebUI/issues)
|
||||
concisely describing the bug you faced, or the feature you would like to see
|
||||
implemented.
|
||||
|
||||
### Development
|
||||
|
||||
Whether you are a JavaScript developer or a Web designer, you can help make the
|
||||
web interface better. Get in touch with us on the Developer forums in
|
||||
Retroshare.
|
||||
Open a [GitHub issue](https://github.com/RetroShare/RSNewWebUI/issues) and
|
||||
briefly explain what happened, what you expected, and how someone can reproduce
|
||||
it. You can also join the RetroShare developer forums to discuss development.
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly",
|
||||
"window": "readonly",
|
||||
"document": "readonly"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"ignorePatterns": ["mithril.js", "assets/*", "make-src/*"],
|
||||
"rules": {
|
||||
"linebreak-style": ["off", "unix"],
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"],
|
||||
"arrow-spacing": ["error", { "before": true, "after": true }],
|
||||
"arrow-parens": ["error", "always"],
|
||||
"comma-style": ["error", "last"],
|
||||
"comma-spacing": ["error", { "before": false, "after": true }],
|
||||
"camelcase": [
|
||||
"error",
|
||||
{
|
||||
"allow": ["^UNSAFE_"],
|
||||
"properties": "never",
|
||||
"ignoreGlobals": true
|
||||
}
|
||||
],
|
||||
"eol-last": "error",
|
||||
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
||||
"no-irregular-whitespace": ["error"],
|
||||
"no-trailing-spaces": ["error"],
|
||||
"no-unexpected-multiline": ["error"],
|
||||
"no-unreachable": ["error"],
|
||||
"no-var": "warn",
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"args": "none",
|
||||
"caughtErrors": "none",
|
||||
"ignoreRestSiblings": true,
|
||||
"vars": "all"
|
||||
}
|
||||
],
|
||||
"no-use-before-define": ["warn", { "functions": true, "classes": false }],
|
||||
"object-shorthand": "error",
|
||||
"prefer-const": ["error", { "destructuring": "all" }],
|
||||
"semi-style": ["warn", "last"],
|
||||
"spaced-comment": ["warn", "always"]
|
||||
}
|
||||
}
|
||||
1
webui-src/.nvmrc
Normal file
1
webui-src/.nvmrc
Normal file
@ -0,0 +1 @@
|
||||
v24.15.0
|
||||
@ -39,7 +39,7 @@ function createboard() {
|
||||
m('label[for=thumbnail]', 'Thumbnail: '),
|
||||
m('input[type=file][name=files][id=thumbnail][accept=image/*]', {
|
||||
onchange: async (e) => {
|
||||
let reader = new FileReader();
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = function () {
|
||||
thumbnail = reader.result.substring(reader.result.indexOf(',') + 1);
|
||||
};
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
const m = require('mithril');
|
||||
const rs = require('rswebui');
|
||||
|
||||
const GROUP_SUBSCRIBE_ADMIN = 0x01;// means: you have the admin key for this group
|
||||
const GROUP_SUBSCRIBE_PUBLISH = 0x02;// means: you have the publish key for thiss group. Typical use: publish key in channels are shared with specific friends.
|
||||
const GROUP_SUBSCRIBE_SUBSCRIBED = 0x04;// means: you are subscribed to a group, which makes you a source for this group to your friend nodes.
|
||||
const GROUP_SUBSCRIBE_ADMIN = 0x01; // means: you have the admin key for this group
|
||||
const GROUP_SUBSCRIBE_PUBLISH = 0x02; // means: you have the publish key for thiss group. Typical use: publish key in channels are shared with specific friends.
|
||||
const GROUP_SUBSCRIBE_SUBSCRIBED = 0x04; // means: you are subscribed to a group, which makes you a source for this group to your friend nodes.
|
||||
const GROUP_SUBSCRIBE_NOT_SUBSCRIBED = 0x08;
|
||||
const GROUP_MY_BOARD = GROUP_SUBSCRIBE_ADMIN + GROUP_SUBSCRIBE_SUBSCRIBED + GROUP_SUBSCRIBE_PUBLISH;
|
||||
const GXS_VOTE_DOWN = 0x0001;
|
||||
const GXS_VOTE_UP = 0x0002;
|
||||
|
||||
//rsgxscircles.h:50
|
||||
const PUBLIC = 1; /// Public distribution
|
||||
const EXTERNAL = 2; /// Restricted to an external circle, based on GxsIds
|
||||
// rsgxscircles.h:50
|
||||
const PUBLIC = 1; // Public distribution
|
||||
const EXTERNAL = 2; // Restricted to an external circle, based on GxsIds
|
||||
const NODES_GROUP = 3;
|
||||
|
||||
const Data = {
|
||||
@ -20,34 +20,6 @@ const Data = {
|
||||
Comments: {}, // threadID, msgID -> {Comment, showReplies}
|
||||
};
|
||||
|
||||
async function updateContent(content, boardid) {
|
||||
const res = await rs.rsJsonApiRequest('/rsPosted/getBoardContent', {
|
||||
boardId: boardid,
|
||||
contentsIds: [content.mMsgId],
|
||||
});
|
||||
if (res.body.retval && res.body.posts.length > 0) {
|
||||
Data.Posts[boardid][content.mMsgId] = { post: res.body.posts[0], isSearched: true };
|
||||
} else if (res.body.retval && res.body.comments.length > 0) {
|
||||
if (Data.Comments[content.mThreadId] === undefined) {
|
||||
Data.Comments[content.mThreadId] = {};
|
||||
}
|
||||
Data.Comments[content.mThreadId][content.mMsgId] = res.body.comments[0];
|
||||
} else if (res.body.retval && res.body.votes.length > 0) {
|
||||
const vote = res.body.votes[0];
|
||||
if (
|
||||
Data.Comments[vote.mMeta.mThreadId] &&
|
||||
Data.Comments[vote.mMeta.mThreadId][vote.mMeta.mParentId]
|
||||
) {
|
||||
if (vote.mVoteType === GXS_VOTE_UP) {
|
||||
Data.Comments[vote.mMeta.mThreadId][vote.mMeta.mParentId].mUpVotes += 1;
|
||||
}
|
||||
if (vote.mVoteType === GXS_VOTE_DOWN) {
|
||||
Data.Comments[vote.mMeta.mThreadId][vote.mMeta.mParentId].mDownVotes += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateDisplayBoards(keyid, details) {
|
||||
const res1 = await rs.rsJsonApiRequest('/rsPosted/getBoardsInfo', {
|
||||
boardsIds: [keyid],
|
||||
@ -72,7 +44,7 @@ async function updateDisplayBoards(keyid, details) {
|
||||
Data.Posts[keyid] = {};
|
||||
}
|
||||
|
||||
/*const res2 = await rs.rsJsonApiRequest('/rsPosted/getContentSummaries', {
|
||||
/* const res2 = await rs.rsJsonApiRequest('/rsPosted/getContentSummaries', {
|
||||
boardId: keyid,
|
||||
});
|
||||
|
||||
@ -81,7 +53,6 @@ async function updateDisplayBoards(keyid, details) {
|
||||
updateContent(content, keyid);
|
||||
});
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
const DisplayBoardsFromList = () => {
|
||||
|
||||
@ -109,7 +109,7 @@ function createchannel() {
|
||||
m('label[for=thumbnail]', 'Thumbnail: '),
|
||||
m('input[type=file][name=files][id=thumbnail][accept=image/*]', {
|
||||
onchange: async (e) => {
|
||||
let reader = new FileReader();
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = function () {
|
||||
thumbnail = reader.result.substring(reader.result.indexOf(',') + 1);
|
||||
};
|
||||
@ -238,7 +238,7 @@ const AddPost = () => {
|
||||
m('label[for=thumbnail]', 'Thumbnail: '),
|
||||
m('input[type=file][name=files][id=thumbnail][accept=image/*]', {
|
||||
onchange: async (e) => {
|
||||
let reader = new FileReader();
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = function () {
|
||||
pthumbnail = reader.result.substring(reader.result.indexOf(',') + 1);
|
||||
};
|
||||
|
||||
@ -7,18 +7,11 @@ const chatEmoji = require('chat/chat_emoji');
|
||||
const HistoryBrowserModal = require('people/people_history');
|
||||
|
||||
const {
|
||||
get64Num,
|
||||
loadLobbyDetails,
|
||||
loadDistantChatDetails,
|
||||
sortLobbies,
|
||||
getNicknameColor,
|
||||
getStatusColor,
|
||||
getStatusTooltip,
|
||||
renderTextWithEmoji,
|
||||
getSafeAvatar,
|
||||
MobileState,
|
||||
ChatRoomsModel,
|
||||
Message,
|
||||
ChatLobbyModel,
|
||||
ChatHubState,
|
||||
} = chatState;
|
||||
@ -123,13 +116,13 @@ function pollHashStatus(localpath) {
|
||||
const info = data.info;
|
||||
const sizeNum = info.size.xint64 || parseInt(info.size.xstr64) || info.size;
|
||||
const fileLink = `<a href="retroshare://file?name=${encodeURIComponent(info.name)}&size=${sizeNum}&hash=${info.hash}">${info.name}</a> (${rs.formatBytes(sizeNum)})`;
|
||||
|
||||
|
||||
const textarea = document.querySelector('.chat-hub-textarea');
|
||||
if (textarea) {
|
||||
const val = textarea.value;
|
||||
textarea.value = val ? val + '\n' + fileLink : fileLink;
|
||||
}
|
||||
|
||||
|
||||
ChatHubState.showAttachModal = false;
|
||||
ChatHubState.isHashing = false;
|
||||
ChatHubState.attachPath = '';
|
||||
@ -144,75 +137,6 @@ function pollHashStatus(localpath) {
|
||||
|
||||
// ************************* views ****************************
|
||||
|
||||
const Lobby = () => {
|
||||
return {
|
||||
view: (vnode) => {
|
||||
const { info, tagname, onclick, lobbytagname = 'mainname' } = vnode.attrs;
|
||||
return m(
|
||||
ChatLobbyModel.selected(info, '.selected-lobby', tagname),
|
||||
{
|
||||
key: rs.idToHex(info.lobby_id),
|
||||
onclick,
|
||||
},
|
||||
[
|
||||
m('h5', { class: lobbytagname }, info.lobby_name === '' ? '<unnamed>' : info.lobby_name),
|
||||
m('.topic', info.lobby_topic),
|
||||
]
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const LobbyList = {
|
||||
view(vnode) {
|
||||
const tagname = vnode.attrs.tagname;
|
||||
const lobbytagname = vnode.attrs.lobbytagname;
|
||||
const onclick = vnode.attrs.onclick || (() => null);
|
||||
return [
|
||||
vnode.attrs.rooms.map((info) =>
|
||||
m(Lobby, {
|
||||
info,
|
||||
tagname,
|
||||
lobbytagname,
|
||||
onclick: onclick(info),
|
||||
})
|
||||
),
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
const SubscribedLobbies = {
|
||||
view() {
|
||||
return m('.widget', [
|
||||
m('.widget__heading', m('h3', 'Subscribed chat rooms')),
|
||||
m('.widget__body', [
|
||||
m(LobbyList, {
|
||||
rooms: sortLobbies(Object.values(ChatRoomsModel.subscribedRooms)),
|
||||
tagname: '.lobby.subscribed',
|
||||
onclick: ChatLobbyModel.switchToEvent,
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
const PublicLobbies = {
|
||||
view() {
|
||||
return m('.widget', [
|
||||
m('.widget__heading', m('h3', 'Public chat rooms')),
|
||||
m('.widget__body', [
|
||||
m(LobbyList, {
|
||||
rooms: (ChatRoomsModel.allRooms || []).filter((info) => !ChatRoomsModel.subscribed(info)),
|
||||
tagname: '.lobby.public',
|
||||
onclick: ChatLobbyModel.setupEvent,
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
// ************************* Chat Hub Sub-Components ****************************
|
||||
|
||||
const ChatRoomHeader = () => {
|
||||
return {
|
||||
view: (vnode) => {
|
||||
@ -982,8 +906,6 @@ const ChatRoomDetailView = () => {
|
||||
const room = ChatHubState.selectedRoom;
|
||||
if (!room) return null;
|
||||
|
||||
let participantCount = 0;
|
||||
let participantNames = [];
|
||||
let participants = [];
|
||||
|
||||
if (room.gxs_ids) {
|
||||
@ -994,7 +916,7 @@ const ChatRoomDetailView = () => {
|
||||
}));
|
||||
} else if (typeof room.gxs_ids === 'object') {
|
||||
participants = Object.keys(room.gxs_ids).map((key) => ({
|
||||
key: key,
|
||||
key,
|
||||
name: rs.userList.username(key) || key
|
||||
}));
|
||||
}
|
||||
@ -1011,8 +933,8 @@ const ChatRoomDetailView = () => {
|
||||
}
|
||||
}
|
||||
|
||||
participantCount = participants.length;
|
||||
participantNames = participants.map((p) => p.name);
|
||||
const participantCount = participants.length;
|
||||
const participantNames = participants.map((p) => p.name);
|
||||
participantNames.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
const lobbyHexId = rs.idToHex(room.lobby_id);
|
||||
@ -1316,7 +1238,7 @@ const Layout = {
|
||||
ChatHubState.showCreateRoomModal && m('.attach-modal-overlay', [
|
||||
m('.attach-modal', [
|
||||
m('h4', 'Create New Chat Room'),
|
||||
|
||||
|
||||
m('.form-field', { style: 'display: flex; flex-direction: column; gap: 0.25rem;' }, [
|
||||
m('label', { style: 'font-weight: bold; font-size: 0.9rem; color: #475569;' }, 'Room Name:'),
|
||||
m('input[type=text]', {
|
||||
@ -1326,7 +1248,7 @@ const Layout = {
|
||||
style: 'padding: 0.5rem; border: 1px solid #cbd5e1; border-radius: 0.25rem; font-size: 0.9rem;'
|
||||
})
|
||||
]),
|
||||
|
||||
|
||||
m('.form-field', { style: 'display: flex; flex-direction: column; gap: 0.25rem; margin-top: 0.5rem;' }, [
|
||||
m('label', { style: 'font-weight: bold; font-size: 0.9rem; color: #475569;' }, 'Topic:'),
|
||||
m('input[type=text]', {
|
||||
@ -1344,7 +1266,7 @@ const Layout = {
|
||||
onchange: (e) => { ChatHubState.newRoomIdentity = e.target.value; },
|
||||
style: 'padding: 0.5rem; border: 1px solid #cbd5e1; border-radius: 0.25rem; font-size: 0.9rem; background-color: #ffffff;'
|
||||
}, [
|
||||
ChatHubState.ownGxsIdentities && ChatHubState.ownGxsIdentities.map(id => {
|
||||
ChatHubState.ownGxsIdentities && ChatHubState.ownGxsIdentities.map((id) => {
|
||||
const details = ChatHubState.gxsDetails[id];
|
||||
const name = details ? (details.mNickname || details.mGroupName) : id;
|
||||
return m('option', { value: id }, name);
|
||||
@ -1386,7 +1308,7 @@ const Layout = {
|
||||
let flags = 0;
|
||||
if (isPublic) flags |= 4;
|
||||
if (isSigned) flags |= 8;
|
||||
|
||||
|
||||
rs.rsJsonApiRequest('/rsChats/createChatLobby', {
|
||||
lobby_name: name,
|
||||
lobby_identity: identity,
|
||||
@ -1601,74 +1523,6 @@ const Layout = {
|
||||
},
|
||||
};
|
||||
|
||||
const LayoutSingle = () => {
|
||||
const onResize = () => {
|
||||
const element = document.querySelector('.messages');
|
||||
if (element) element.scrollTop = element.scrollHeight;
|
||||
};
|
||||
return {
|
||||
oninit: () => {
|
||||
ChatLobbyModel.loadLobby(m.route.param('lobby'));
|
||||
window.addEventListener('resize', onResize);
|
||||
},
|
||||
onremove: () => window.removeEventListener('resize', onResize),
|
||||
view: (vnode) => {
|
||||
const chatType = ChatLobbyModel.currentLobby.chatType;
|
||||
const isPrivate = chatType === 1 || chatType === 2;
|
||||
const isRoom = chatType === 3;
|
||||
return m(
|
||||
'.node-panel.chat-panel.chat-room',
|
||||
{
|
||||
class:
|
||||
(MobileState.showLobbies ? 'show-lobbies ' : '') +
|
||||
(MobileState.showUsers ? 'show-users ' : '') +
|
||||
(isPrivate ? 'no-lobbies' : ''),
|
||||
},
|
||||
[
|
||||
m('.chat-overlay', { onclick: () => MobileState.closeAll() }),
|
||||
m(
|
||||
'.messages' + (isRoom ? '.compact-container' : ''),
|
||||
{ onclick: () => MobileState.closeAll() },
|
||||
ChatLobbyModel.messages
|
||||
),
|
||||
m(
|
||||
'.chatMessage',
|
||||
{},
|
||||
[
|
||||
m('textarea.chatMsg', {
|
||||
placeholder: 'Type a message...',
|
||||
enterkeyhint: 'send',
|
||||
onkeydown: (e) => {
|
||||
if ((e.key === 'Enter' || e.keyCode === 13) && !e.shiftKey) {
|
||||
const msg = e.target.value;
|
||||
if (msg.trim() === '') return false;
|
||||
e.target.value = ' sending ... ';
|
||||
ChatLobbyModel.sendMessage(msg, () => (e.target.value = ''));
|
||||
return false;
|
||||
}
|
||||
},
|
||||
}),
|
||||
m(
|
||||
'button.chat-send-btn',
|
||||
{
|
||||
onclick: (e) => {
|
||||
const textarea = e.target.closest('.chatMessage').querySelector('textarea');
|
||||
const msg = textarea.value;
|
||||
if (msg.trim() === '') return;
|
||||
textarea.value = ' sending ... ';
|
||||
ChatLobbyModel.sendMessage(msg, () => (textarea.value = ''));
|
||||
},
|
||||
},
|
||||
m('i.fas.fa-paper-plane')
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
/rsChats/initiateDistantChatConnexion
|
||||
* @param[in] to_pid RsGxsId to start the connection
|
||||
|
||||
@ -7,72 +7,72 @@ const EMOJI_ICONS = {
|
||||
};
|
||||
const EMOJI_DATA = {
|
||||
Smileys: [
|
||||
'😀','😁','😂','🤣','😃','😄','😅','😆','😉','😊','😋','😎','😍','😘','🥰','😗','😙','😚',
|
||||
'🙂','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😯','😪','😫','🥱',
|
||||
'😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','☹️','🙁','😖','😞','😟',
|
||||
'😤','😢','😭','😦','😧','😨','😩','🤯','😬','😰','😱','🥵','🥶','😳','🤪','😵','😡','😠',
|
||||
'🤬','😷','🤒','🤕','🤢','🤮','🤧','🥴','😇','🥳','🥺','🤠','🤡','🤥','🤫','🤭','🧐','🤓',
|
||||
'😈','👿','👹','👺','💀','☠️','👻','👽','👾','🤖','😺','😸','😹','😻','😼','😽','🙀','😿','😾',
|
||||
'😀', '😁', '😂', '🤣', '😃', '😄', '😅', '😆', '😉', '😊', '😋', '😎', '😍', '😘', '🥰', '😗', '😙', '😚',
|
||||
'🙂', '🤗', '🤩', '🤔', '🤨', '😐', '😑', '😶', '🙄', '😏', '😣', '😥', '😮', '🤐', '😯', '😪', '😫', '🥱',
|
||||
'😴', '😌', '😛', '😜', '😝', '🤤', '😒', '😓', '😔', '😕', '🙃', '🤑', '😲', '☹️', '🙁', '😖', '😞', '😟',
|
||||
'😤', '😢', '😭', '😦', '😧', '😨', '😩', '🤯', '😬', '😰', '😱', '🥵', '🥶', '😳', '🤪', '😵', '😡', '😠',
|
||||
'🤬', '😷', '🤒', '🤕', '🤢', '🤮', '🤧', '🥴', '😇', '🥳', '🥺', '🤠', '🤡', '🤥', '🤫', '🤭', '🧐', '🤓',
|
||||
'😈', '👿', '👹', '👺', '💀', '☠️', '👻', '👽', '👾', '🤖', '😺', '😸', '😹', '😻', '😼', '😽', '🙀', '😿', '😾',
|
||||
],
|
||||
People: [
|
||||
'👋','🤚','🖐️','✋','🖖','👌','🤌','🤏','✌️','🤞','🤟','🤘','🤙','👈','👉','👆','🖕','👇',
|
||||
'☝️','👍','👎','✊','👊','🤛','🤜','👏','🙌','👐','🤲','🤝','🙏','✍️','💅','🤳','💪','🦾',
|
||||
'🦿','🦵','🦶','👂','🦻','👃','🫀','🫁','🧠','🦷','🦴','👀','👁️','👅','👄','🫦','👶','🧒',
|
||||
'👦','👧','🧑','👱','👨','🧔','👩','🧓','👴','👵','🙍','🙎','🙅','🙆','💁','🙋','🧏','🙇',
|
||||
'🤦','🤷','👮','🕵️','💂','🥷','👷','🫅','🤴','👸','👲','🧕','🤵','👰','🤰','🫃','🫄','🤱',
|
||||
'👼','🎅','🤶','🧑🎄','🦸','🦹','🧙','🧝','🧛','🧟','🧞','🧜','🧚','🧑🤝🧑','👫','👬','👭','💏','💑','👪',
|
||||
'👋', '🤚', '🖐️', '✋', '🖖', '👌', '🤌', '🤏', '✌️', '🤞', '🤟', '🤘', '🤙', '👈', '👉', '👆', '🖕', '👇',
|
||||
'☝️', '👍', '👎', '✊', '👊', '🤛', '🤜', '👏', '🙌', '👐', '🤲', '🤝', '🙏', '✍️', '💅', '🤳', '💪', '🦾',
|
||||
'🦿', '🦵', '🦶', '👂', '🦻', '👃', '🫀', '🫁', '🧠', '🦷', '🦴', '👀', '👁️', '👅', '👄', '🫦', '👶', '🧒',
|
||||
'👦', '👧', '🧑', '👱', '👨', '🧔', '👩', '🧓', '👴', '👵', '🙍', '🙎', '🙅', '🙆', '💁', '🙋', '🧏', '🙇',
|
||||
'🤦', '🤷', '👮', '🕵️', '💂', '🥷', '👷', '🫅', '🤴', '👸', '👲', '🧕', '🤵', '👰', '🤰', '🫃', '🫄', '🤱',
|
||||
'👼', '🎅', '🤶', '🧑🎄', '🦸', '🦹', '🧙', '🧝', '🧛', '🧟', '🧞', '🧜', '🧚', '🧑🤝🧑', '👫', '👬', '👭', '💏', '💑', '👪',
|
||||
],
|
||||
Animals: [
|
||||
'🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼','🐻❄️','🐨','🐯','🦁','🐮','🐷','🐸','🐵','🙈','🙉',
|
||||
'🙊','🐒','🦆','🦅','🦉','🦇','🐝','🪱','🐛','🦋','🐌','🐞','🐜','🪲','🦗','🪳','🕷️','🦂',
|
||||
'🐢','🐍','🦎','🦖','🦕','🐙','🦑','🦐','🦞','🦀','🐡','🐠','🐟','🐬','🐳','🐋','🦈','🦭',
|
||||
'🐊','🐅','🐆','🦓','🦍','🦧','🦣','🐘','🦛','🦏','🐪','🐫','🦒','🦘','🦬','🐃','🐂','🐄',
|
||||
'🐎','🐖','🐏','🐑','🦙','🐐','🦌','🐕','🐩','🦮','🐕🦺','🐈','🐈⬛','🐓','🦃','🦤','🦚','🦜',
|
||||
'🦢','🦩','🕊️','🐇','🦝','🦨','🦡','🦫','🦦','🦥','🐁','🐀','🐿️','🦔','🐾','🐉','🐲','🌵',
|
||||
'🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐻❄️', '🐨', '🐯', '🦁', '🐮', '🐷', '🐸', '🐵', '🙈', '🙉',
|
||||
'🙊', '🐒', '🦆', '🦅', '🦉', '🦇', '🐝', '🪱', '🐛', '🦋', '🐌', '🐞', '🐜', '🪲', '🦗', '🪳', '🕷️', '🦂',
|
||||
'🐢', '🐍', '🦎', '🦖', '🦕', '🐙', '🦑', '🦐', '🦞', '🦀', '🐡', '🐠', '🐟', '🐬', '🐳', '🐋', '🦈', '🦭',
|
||||
'🐊', '🐅', '🐆', '🦓', '🦍', '🦧', '🦣', '🐘', '🦛', '🦏', '🐪', '🐫', '🦒', '🦘', '🦬', '🐃', '🐂', '🐄',
|
||||
'🐎', '🐖', '🐏', '🐑', '🦙', '🐐', '🦌', '🐕', '🐩', '🦮', '🐕🦺', '🐈', '🐈⬛', '🐓', '🦃', '🦤', '🦚', '🦜',
|
||||
'🦢', '🦩', '🕊️', '🐇', '🦝', '🦨', '🦡', '🦫', '🦦', '🦥', '🐁', '🐀', '🐿️', '🦔', '🐾', '🐉', '🐲', '🌵',
|
||||
],
|
||||
Food: [
|
||||
'🍎','🍊','🍋','🍌','🍍','🥭','🍓','🍒','🍑','🥝','🍅','🥥','🥑','🍆','🥔','🥕','🌽','🌶️',
|
||||
'🫑','🥒','🥬','🥦','🧄','🧅','🍄','🥜','🌰','🍞','🥐','🥖','🫓','🥨','🧀','🥚','🍳','🧈',
|
||||
'🥞','🧇','🥓','🥩','🍗','🍖','🦴','🌭','🍔','🍟','🍕','🫔','🌮','🌯','🥙','🧆','🥚','🍱',
|
||||
'🍘','🍙','🍚','🍛','🍜','🍝','🍠','🍢','🍣','🍤','🍥','🥮','🍡','🥟','🥠','🥡','🦪','🍦',
|
||||
'🍧','🍨','🍩','🍪','🎂','🍰','🧁','🥧','🍫','🍬','🍭','🍮','🍯','🍼','🥛','☕','🫖','🍵',
|
||||
'🧃','🥤','🧋','🍶','🍺','🍻','🥂','🍷','🥃','🍸','🍹','🧉','🍾','🧊','🥄','🍴','🍽️','🥢',
|
||||
'🍎', '🍊', '🍋', '🍌', '🍍', '🥭', '🍓', '🍒', '🍑', '🥝', '🍅', '🥥', '🥑', '🍆', '🥔', '🥕', '🌽', '🌶️',
|
||||
'🫑', '🥒', '🥬', '🥦', '🧄', '🧅', '🍄', '🥜', '🌰', '🍞', '🥐', '🥖', '🫓', '🥨', '🧀', '🥚', '🍳', '🧈',
|
||||
'🥞', '🧇', '🥓', '🥩', '🍗', '🍖', '🦴', '🌭', '🍔', '🍟', '🍕', '🫔', '🌮', '🌯', '🥙', '🧆', '🥚', '🍱',
|
||||
'🍘', '🍙', '🍚', '🍛', '🍜', '🍝', '🍠', '🍢', '🍣', '🍤', '🍥', '🥮', '🍡', '🥟', '🥠', '🥡', '🦪', '🍦',
|
||||
'🍧', '🍨', '🍩', '🍪', '🎂', '🍰', '🧁', '🥧', '🍫', '🍬', '🍭', '🍮', '🍯', '🍼', '🥛', '☕', '🫖', '🍵',
|
||||
'🧃', '🥤', '🧋', '🍶', '🍺', '🍻', '🥂', '🍷', '🥃', '🍸', '🍹', '🧉', '🍾', '🧊', '🥄', '🍴', '🍽️', '🥢',
|
||||
],
|
||||
Travel: [
|
||||
'🚗','🚕','🚙','🚌','🚎','🏎️','🚓','🚑','🚒','🚐','🛻','🚚','🚛','🚜','🦯','🦽','🦼','🛺',
|
||||
'🚲','🛴','🛵','🏍️','🛺','🚨','🚔','🚍','🚘','🚖','🚡','🚠','🚟','🚃','🚋','🚞','🚝','🚄',
|
||||
'🚅','🚈','🚂','🚆','🚇','🚊','🚉','✈️','🛫','🛬','🛩️','💺','🛸','🚁','🛶','⛵','🚤','🛥️',
|
||||
'🛳️','⛴️','🚢','⚓','🗺️','🧭','🏔️','⛰️','🌋','🗻','🏕️','🏖️','🏜️','🏝️','🏞️','🏟️','🏛️','🏗️',
|
||||
'🧱','🪨','🪵','🛖','🏘️','🏚️','🏠','🏡','🏢','🏣','🏤','🏥','🏦','🏨','🏩','🏪','🏫','🏬',
|
||||
'🏭','🏯','🏰','💒','🗼','🗽','⛪','🕌','🛕','🕍','⛩️','🕋','⛲','⛺','🌁','🌃','🏙️','🌄',
|
||||
'🚗', '🚕', '🚙', '🚌', '🚎', '🏎️', '🚓', '🚑', '🚒', '🚐', '🛻', '🚚', '🚛', '🚜', '🦯', '🦽', '🦼', '🛺',
|
||||
'🚲', '🛴', '🛵', '🏍️', '🛺', '🚨', '🚔', '🚍', '🚘', '🚖', '🚡', '🚠', '🚟', '🚃', '🚋', '🚞', '🚝', '🚄',
|
||||
'🚅', '🚈', '🚂', '🚆', '🚇', '🚊', '🚉', '✈️', '🛫', '🛬', '🛩️', '💺', '🛸', '🚁', '🛶', '⛵', '🚤', '🛥️',
|
||||
'🛳️', '⛴️', '🚢', '⚓', '🗺️', '🧭', '🏔️', '⛰️', '🌋', '🗻', '🏕️', '🏖️', '🏜️', '🏝️', '🏞️', '🏟️', '🏛️', '🏗️',
|
||||
'🧱', '🪨', '🪵', '🛖', '🏘️', '🏚️', '🏠', '🏡', '🏢', '🏣', '🏤', '🏥', '🏦', '🏨', '🏩', '🏪', '🏫', '🏬',
|
||||
'🏭', '🏯', '🏰', '💒', '🗼', '🗽', '⛪', '🕌', '🛕', '🕍', '⛩️', '🕋', '⛲', '⛺', '🌁', '🌃', '🏙️', '🌄',
|
||||
],
|
||||
Activities: [
|
||||
'⚽','🏀','🏈','⚾','🥎','🎾','🏐','🏉','🥏','🎱','🏓','🏸','🏒','🏑','🥍','🏏','🪃','🥅',
|
||||
'⛳','🪁','🛝','🏹','🎣','🤿','🥊','🥋','🎽','🛹','🛷','⛸️','🥌','🎿','⛷️','🏂','🪂','🏋️',
|
||||
'🤼','🤸','⛹️','🤺','🏇','🧘','🏄','🏊','🤽','🚣','🧗','🚵','🚴','🏆','🥇','🥈','🥉','🏅',
|
||||
'🎖️','🏵️','🎗️','🎫','🎟️','🎪','🤹','🎭','🩰','🎨','🖼️','🎰','🎲','🧩','🪄','🎯','🪅','🎮',
|
||||
'🕹️','🎳','🎻','🎷','🥁','🪘','🎺','🎸','🪗','🎹','🎵','🎶','🎼','🎤','🎧','📻','🎙️','🎚️',
|
||||
'🎬','📽️','🎞️','📱','📲','☎️','📞','📟','📠','🔋','🪫','🔌','💡','🔦','🕯️','💸','💵','🪙',
|
||||
'⚽', '🏀', '🏈', '⚾', '🥎', '🎾', '🏐', '🏉', '🥏', '🎱', '🏓', '🏸', '🏒', '🏑', '🥍', '🏏', '🪃', '🥅',
|
||||
'⛳', '🪁', '🛝', '🏹', '🎣', '🤿', '🥊', '🥋', '🎽', '🛹', '🛷', '⛸️', '🥌', '🎿', '⛷️', '🏂', '🪂', '🏋️',
|
||||
'🤼', '🤸', '⛹️', '🤺', '🏇', '🧘', '🏄', '🏊', '🤽', '🚣', '🧗', '🚵', '🚴', '🏆', '🥇', '🥈', '🥉', '🏅',
|
||||
'🎖️', '🏵️', '🎗️', '🎫', '🎟️', '🎪', '🤹', '🎭', '🩰', '🎨', '🖼️', '🎰', '🎲', '🧩', '🪄', '🎯', '🪅', '🎮',
|
||||
'🕹️', '🎳', '🎻', '🎷', '🥁', '🪘', '🎺', '🎸', '🪗', '🎹', '🎵', '🎶', '🎼', '🎤', '🎧', '📻', '🎙️', '🎚️',
|
||||
'🎬', '📽️', '🎞️', '📱', '📲', '☎️', '📞', '📟', '📠', '🔋', '🪫', '🔌', '💡', '🔦', '🕯️', '💸', '💵', '🪙',
|
||||
],
|
||||
Objects: [
|
||||
'⌚','📱','📲','💻','⌨️','🖥️','🖨️','🖱️','🖲️','💾','💿','📀','🧮','📷','📸','📹','🎥','📽️',
|
||||
'📞','☎️','📟','📠','📺','📻','🧭','⏱️','⏲️','⏰','🕰️','⌛','⏳','📡','🔋','🪫','🔌','💡',
|
||||
'🔦','🕯️','🪔','🧱','💰','💴','💵','💶','💷','💸','💳','🪙','💹','✉️','📧','📨','📩','📤',
|
||||
'📥','📦','📫','📪','📬','📭','📮','🗳️','✏️','✒️','🖊️','🖋️','📝','📁','📂','🗂️','📅','📆',
|
||||
'🗒️','🗓️','📇','📈','📉','📊','📋','📌','📍','🗺️','📏','📐','✂️','🗃️','🗄️','🗑️','🔒','🔓',
|
||||
'🔏','🔐','🔑','🗝️','🔨','🪓','⛏️','⚒️','🛠️','🗡️','⚔️','🔫','🪃','🏹','🛡️','🪚','🔧','🪛',
|
||||
'⌚', '📱', '📲', '💻', '⌨️', '🖥️', '🖨️', '🖱️', '🖲️', '💾', '💿', '📀', '🧮', '📷', '📸', '📹', '🎥', '📽️',
|
||||
'📞', '☎️', '📟', '📠', '📺', '📻', '🧭', '⏱️', '⏲️', '⏰', '🕰️', '⌛', '⏳', '📡', '🔋', '🪫', '🔌', '💡',
|
||||
'🔦', '🕯️', '🪔', '🧱', '💰', '💴', '💵', '💶', '💷', '💸', '💳', '🪙', '💹', '✉️', '📧', '📨', '📩', '📤',
|
||||
'📥', '📦', '📫', '📪', '📬', '📭', '📮', '🗳️', '✏️', '✒️', '🖊️', '🖋️', '📝', '📁', '📂', '🗂️', '📅', '📆',
|
||||
'🗒️', '🗓️', '📇', '📈', '📉', '📊', '📋', '📌', '📍', '🗺️', '📏', '📐', '✂️', '🗃️', '🗄️', '🗑️', '🔒', '🔓',
|
||||
'🔏', '🔐', '🔑', '🗝️', '🔨', '🪓', '⛏️', '⚒️', '🛠️', '🗡️', '⚔️', '🔫', '🪃', '🏹', '🛡️', '🪚', '🔧', '🪛',
|
||||
],
|
||||
Symbols: [
|
||||
'❤️','🧡','💛','💚','💙','💜','🖤','🤍','🤎','💔','❣️','💕','💞','💓','💗','💖','💘','💝',
|
||||
'💟','☮️','✝️','☪️','🕉️','☸️','✡️','🔯','🕎','☯️','☦️','🛐','⛎','♈','♉','♊','♋','♌',
|
||||
'♍','♎','♏','♐','♑','♒','♓','🆔','⚛️','🉑','☢️','☣️','📴','📳','🈶','🈚','🈸','🈺',
|
||||
'🈷️','✴️','🆚','💮','🉐','㊙️','㊗️','🈴','🈵','🈹','🈲','🅰️','🅱️','🆎','🆑','🅾️','🆘',
|
||||
'❌','⭕','🛑','⛔','📛','🚫','💯','💢','♨️','🚷','🚯','🚳','🚱','🔞','📵','🚭','❗','❕',
|
||||
'❓','❔','‼️','⁉️','🔅','🔆','📶','🛜','📳','📴','🔱','📛','🔰','♻️','✅','🈯','💹','❎',
|
||||
'🌐','💠','Ⓜ️','🌀','💤','🏧','🚾','♿','🅿️','🛗','🈳','🈹','🚰','🔤','🔡','🔠','🆖','🆗',
|
||||
'🆙','🆒','🆕','🆓','🔟','📊','🔣','✔️','☑️','🔘','🔲','🔳','⬛','⬜','◼️','◻️','◾','◽',
|
||||
'▪️','▫️','🔶','🔷','🔸','🔹','🔺','🔻','💠','🔘','🔲','🔳','🏁','🚩','🎌','🏴','🏳️','⭐',
|
||||
'🌟','💫','✨','🌈','☀️','🌤️','⛅','🌥️','☁️','🌦️','🌧️','⛈️','🌩️','🌨️','❄️','☃️','⛄','🌬️',
|
||||
'❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔', '❣️', '💕', '💞', '💓', '💗', '💖', '💘', '💝',
|
||||
'💟', '☮️', '✝️', '☪️', '🕉️', '☸️', '✡️', '🔯', '🕎', '☯️', '☦️', '🛐', '⛎', '♈', '♉', '♊', '♋', '♌',
|
||||
'♍', '♎', '♏', '♐', '♑', '♒', '♓', '🆔', '⚛️', '🉑', '☢️', '☣️', '📴', '📳', '🈶', '🈚', '🈸', '🈺',
|
||||
'🈷️', '✴️', '🆚', '💮', '🉐', '㊙️', '㊗️', '🈴', '🈵', '🈹', '🈲', '🅰️', '🅱️', '🆎', '🆑', '🅾️', '🆘',
|
||||
'❌', '⭕', '🛑', '⛔', '📛', '🚫', '💯', '💢', '♨️', '🚷', '🚯', '🚳', '🚱', '🔞', '📵', '🚭', '❗', '❕',
|
||||
'❓', '❔', '‼️', '⁉️', '🔅', '🔆', '📶', '🛜', '📳', '📴', '🔱', '📛', '🔰', '♻️', '✅', '🈯', '💹', '❎',
|
||||
'🌐', '💠', 'Ⓜ️', '🌀', '💤', '🏧', '🚾', '♿', '🅿️', '🛗', '🈳', '🈹', '🚰', '🔤', '🔡', '🔠', '🆖', '🆗',
|
||||
'🆙', '🆒', '🆕', '🆓', '🔟', '📊', '🔣', '✔️', '☑️', '🔘', '🔲', '🔳', '⬛', '⬜', '◼️', '◻️', '◾', '◽',
|
||||
'▪️', '▫️', '🔶', '🔷', '🔸', '🔹', '🔺', '🔻', '💠', '🔘', '🔲', '🔳', '🏁', '🚩', '🎌', '🏴', '🏳️', '⭐',
|
||||
'🌟', '💫', '✨', '🌈', '☀️', '🌤️', '⛅', '🌥️', '☁️', '🌦️', '🌧️', '⛈️', '🌩️', '🌨️', '❄️', '☃️', '⛄', '🌬️',
|
||||
],
|
||||
};
|
||||
|
||||
@ -115,14 +115,14 @@ const EmojiPicker = () => ({
|
||||
onclick: () => { ChatHubState.emojiSearch = ''; },
|
||||
}, m('i.fas.fa-times')),
|
||||
]),
|
||||
!search && m('.emoji-categories', EMOJI_CATEGORIES.map(c =>
|
||||
!search && m('.emoji-categories', EMOJI_CATEGORIES.map((c) =>
|
||||
m('button.emoji-cat-btn' + (c === cat ? '.active' : ''), {
|
||||
title: c,
|
||||
onclick: () => { ChatHubState.emojiCategory = c; },
|
||||
}, EMOJI_ICONS[c])
|
||||
)),
|
||||
m('.emoji-grid',
|
||||
emojis.map(e =>
|
||||
emojis.map((e) =>
|
||||
m('button.emoji-btn', {
|
||||
onclick: () => {
|
||||
insertEmojiIntoTextarea(e, onSelect);
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
const m = require('mithril');
|
||||
const rs = require('rswebui');
|
||||
const peopleUtil = require('people/people_util');
|
||||
const people = require('people/people');
|
||||
|
||||
// **************** utility functions ********************
|
||||
|
||||
@ -35,7 +33,7 @@ function loadDistantChatDetails(pid, apply) {
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsChats/getDistantChatStatus',
|
||||
{
|
||||
pid: pid,
|
||||
pid,
|
||||
},
|
||||
(detail, success) => {
|
||||
if (success && detail.retval) {
|
||||
@ -118,7 +116,7 @@ function renderChatMessage(rawText) {
|
||||
if (src) {
|
||||
parts.push(
|
||||
m('img.chat-embedded-image', {
|
||||
src: src,
|
||||
src,
|
||||
style: {
|
||||
maxWidth: '100%',
|
||||
maxHeight: '300px',
|
||||
@ -160,7 +158,7 @@ function renderChatMessage(rawText) {
|
||||
if (rawText.trim().startsWith('data:image/')) {
|
||||
const src = rawText.trim();
|
||||
return m('img.chat-embedded-image', {
|
||||
src: src,
|
||||
src,
|
||||
style: {
|
||||
maxWidth: '100%',
|
||||
maxHeight: '300px',
|
||||
@ -248,7 +246,7 @@ function renderTextWithEmoji(text) {
|
||||
const parts = [];
|
||||
let last = 0;
|
||||
let match;
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
|
||||
while ((match = emojiRegex.exec(text)) !== null) {
|
||||
if (match[0].length === 0) { emojiRegex.lastIndex++; continue; }
|
||||
if (match.index > last) parts.push(text.slice(last, match.index));
|
||||
@ -442,8 +440,8 @@ const Message = () => {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
messageText: targetText,
|
||||
username: username,
|
||||
gxsId: gxsId,
|
||||
username,
|
||||
gxsId,
|
||||
};
|
||||
m.redraw();
|
||||
};
|
||||
@ -581,7 +579,7 @@ const ChatLobbyModel = {
|
||||
loadHistory(id, type) {
|
||||
const chatPeerId = {
|
||||
broadcast_status_peer_id: '00000000000000000000000000000000',
|
||||
type: type,
|
||||
type,
|
||||
peer_id: '00000000000000000000000000000000',
|
||||
distant_chat_id: '00000000000000000000000000000000',
|
||||
lobby_id: { xstr64: '0' },
|
||||
@ -594,7 +592,7 @@ const ChatLobbyModel = {
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsHistory/getMessages',
|
||||
{
|
||||
chatPeerId: chatPeerId,
|
||||
chatPeerId,
|
||||
loadCount: 20,
|
||||
},
|
||||
(data, success) => {
|
||||
@ -623,11 +621,11 @@ const ChatLobbyModel = {
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsHistory/getMessages',
|
||||
{
|
||||
chatPeerId: chatPeerId,
|
||||
chatPeerId,
|
||||
loadCount: 0,
|
||||
},
|
||||
(data, success) => {
|
||||
let msgs = (success && data && data.msgs) ? data.msgs : [];
|
||||
const msgs = (success && data && data.msgs) ? data.msgs : [];
|
||||
msgs.sort((a, b) => (a.sendTime || a.recvTime) - (b.sendTime || b.recvTime));
|
||||
ChatHubState.fullHistoryMessages = msgs;
|
||||
ChatHubState.isHistoryLoading = false;
|
||||
@ -642,7 +640,7 @@ const ChatLobbyModel = {
|
||||
'/rsChats/setIdentityForChatLobby',
|
||||
{
|
||||
lobby_id: { xstr64: lobbyId },
|
||||
nick: nick,
|
||||
nick,
|
||||
},
|
||||
() => m.route.set('/chat/:lobby', { lobby: lobbyId }),
|
||||
true
|
||||
@ -685,7 +683,7 @@ const ChatLobbyModel = {
|
||||
const id = this.lastLobbyId || m.route.param('lobby');
|
||||
const cid = {
|
||||
broadcast_status_peer_id: '00000000000000000000000000000000',
|
||||
type: type,
|
||||
type,
|
||||
peer_id: '00000000000000000000000000000000',
|
||||
distant_chat_id: '00000000000000000000000000000000',
|
||||
lobby_id: { xstr64: '0' },
|
||||
@ -805,13 +803,13 @@ const ChatLobbyModel = {
|
||||
'/rsChats/sendChat',
|
||||
{
|
||||
id: cid,
|
||||
msg: msg,
|
||||
msg,
|
||||
},
|
||||
(data, success) => {
|
||||
if (success) {
|
||||
const echoMsg = {
|
||||
chat_id: cid,
|
||||
msg: msg,
|
||||
msg,
|
||||
sendTime: Math.floor(Date.now() / 1000),
|
||||
lobby_peer_gxs_id: this.currentLobby.gxs_id,
|
||||
};
|
||||
|
||||
@ -10,8 +10,8 @@ const ConfigChat = () => {
|
||||
let maxStorageDays = 10;
|
||||
|
||||
// History states
|
||||
let historyEnable = { private: true, distant: true, lobby: true };
|
||||
let historySaveCount = { private: 500, distant: 500, lobby: 500 };
|
||||
const historyEnable = { private: true, distant: true, lobby: true };
|
||||
const historySaveCount = { private: 500, distant: 500, lobby: 500 };
|
||||
|
||||
function loadSettings() {
|
||||
// Load Own Identities
|
||||
|
||||
@ -579,8 +579,8 @@ const displayHiddenServiceInfo = () => {
|
||||
details && details.hiddenNodeAddress &&
|
||||
m('.hidden-service-info', { style: 'display: flex; flex-direction: column; gap: 0.75rem; width: 100%;' }, [
|
||||
m('p.proxy-description', { style: 'margin-bottom: 0.5rem; color: #475569;' }, details.hiddenType === 4
|
||||
? "I2P has been automatically configured by Retroshare. You shouldn't need to change anything here."
|
||||
: "Tor has been automatically configured by Retroshare. You shouldn't need to change anything here."
|
||||
? 'I2P has been automatically configured by Retroshare. You shouldn\'t need to change anything here.'
|
||||
: 'Tor has been automatically configured by Retroshare. You shouldn\'t need to change anything here.'
|
||||
),
|
||||
// Local Address + Local Port row
|
||||
m('.nw-config-row', { style: 'display: grid; grid-template-columns: 200px 1fr; gap: 1rem; align-items: center;' }, [
|
||||
|
||||
@ -2,7 +2,7 @@ const m = require('mithril');
|
||||
const rs = require('rswebui');
|
||||
const util = require('forums/forums_util');
|
||||
const peopleUtil = require('people/people_util');
|
||||
const { updatedisplayforums, loadPostContent, getTimestampValue, formatTimestamp } = require('./forums_util');
|
||||
const { loadPostContent, getTimestampValue, formatTimestamp } = require('./forums_util');
|
||||
|
||||
function createforum() {
|
||||
let title;
|
||||
@ -75,75 +75,6 @@ function createforum() {
|
||||
]),
|
||||
};
|
||||
}
|
||||
const EditThread = () => {
|
||||
let title = '';
|
||||
let body = '';
|
||||
return {
|
||||
oninit: (vnode) => {
|
||||
title = vnode.attrs.current_title;
|
||||
body = vnode.attrs.current_body;
|
||||
},
|
||||
view: (vnode) =>
|
||||
m('.widget', [
|
||||
m('h3', 'Edit Thread'),
|
||||
m('hr'),
|
||||
|
||||
m(
|
||||
'iddisplay',
|
||||
{
|
||||
style: { display: 'block ruby' }, // same line block ruby
|
||||
},
|
||||
[
|
||||
'Identity: ',
|
||||
m('h5[id=authid]', rs.userList.username(vnode.attrs.authorId)),
|
||||
]
|
||||
),
|
||||
m(
|
||||
'titledisplay',
|
||||
{
|
||||
style: { display: 'block ruby' },
|
||||
},
|
||||
[
|
||||
'Title: ',
|
||||
m('input[type=text][placeholder=Title]', {
|
||||
value: vnode.attrs.current_title,
|
||||
oninput: (e) => (title = e.target.value),
|
||||
}),
|
||||
]
|
||||
),
|
||||
m('textarea[rows=5]', {
|
||||
style: { width: '90%', display: 'block' },
|
||||
oninput: (e) => (body = e.target.value),
|
||||
value: vnode.attrs.current_body,
|
||||
}),
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
onclick: async () => {
|
||||
const res = await rs.rsJsonApiRequest('/rsgxsforums/createPost', {
|
||||
forumId: vnode.attrs.forumId,
|
||||
mBody: body,
|
||||
title,
|
||||
authorId: vnode.attrs.authorId,
|
||||
parentId: vnode.attrs.current_parent,
|
||||
origPostId: vnode.attrs.current_msgid,
|
||||
});
|
||||
res.body.retval === false
|
||||
? util.popupmessage([m('h3', 'Error'), m('hr'), m('p', res.body.errorMessage)])
|
||||
: util.popupmessage([
|
||||
m('h3', 'Success'),
|
||||
m('hr'),
|
||||
m('p', 'Thread edited successfully'),
|
||||
]);
|
||||
util.updatedisplayforums(vnode.attrs.forumId);
|
||||
m.redraw();
|
||||
},
|
||||
},
|
||||
'Add'
|
||||
),
|
||||
]),
|
||||
};
|
||||
};
|
||||
const AddThread = () => {
|
||||
let title = '';
|
||||
let body = '';
|
||||
@ -228,166 +159,6 @@ const AddThread = () => {
|
||||
|
||||
// getTimestampValue and formatTimestamp are imported from forums_util.js
|
||||
|
||||
function displaythread() {
|
||||
// recursive function to display all the threads
|
||||
let groupmessagepair;
|
||||
let unread;
|
||||
let editpermission = false;
|
||||
return {
|
||||
view: (v) => {
|
||||
const thread = v.attrs.threadStruct.thread;
|
||||
groupmessagepair = { first: thread.mMeta.mGroupId, second: thread.mMeta.mOrigMsgId };
|
||||
let parMap = [];
|
||||
if (util.Data.ParentThreadMap[thread.mMeta.mOrigMsgId]) {
|
||||
parMap = util.Data.ParentThreadMap[thread.mMeta.mOrigMsgId];
|
||||
}
|
||||
unread = thread.mMeta.mMsgStatus === util.THREAD_UNREAD;
|
||||
v.attrs.identity &&
|
||||
v.attrs.identity.map((val) => {
|
||||
if (val.localeCompare(thread.mMeta.mAuthorId) === 0) {
|
||||
// if the author of the thread matches one of our own ids
|
||||
editpermission = true;
|
||||
}
|
||||
});
|
||||
return [
|
||||
m(
|
||||
'tr',
|
||||
{
|
||||
style: unread ? { fontWeight: 'bold' } : '',
|
||||
},
|
||||
[
|
||||
Object.keys(parMap).length // if this thread has some replies
|
||||
? m(
|
||||
'td',
|
||||
m('i.fas.fa-angle-right', {
|
||||
class: 'fa-rotate-' + (v.attrs.threadStruct.showReplies ? '90' : '0'),
|
||||
style: 'margin-top:12px',
|
||||
onclick: () => {
|
||||
v.attrs.threadStruct.showReplies = !v.attrs.threadStruct.showReplies;
|
||||
},
|
||||
})
|
||||
)
|
||||
: m('td', ''),
|
||||
|
||||
m(
|
||||
'td',
|
||||
{
|
||||
style: {
|
||||
position: 'relative',
|
||||
'--replyDepth': v.attrs.replyDepth,
|
||||
left: 'calc(30px*var(--replyDepth))', // shifts reply by 30 px
|
||||
padding: '10px 0',
|
||||
},
|
||||
},
|
||||
[
|
||||
m('div.date', { style: { fontSize: '0.8em', color: '#888' } },
|
||||
formatTimestamp(thread.mMeta.mPublishTs)
|
||||
),
|
||||
m('div.title', {
|
||||
style: { fontWeight: 'bold', fontSize: '1.1em', cursor: 'pointer', margin: '5px 0' },
|
||||
onclick: async () => {
|
||||
v.attrs.changeThread(thread.mMeta.mOrigMsgId);
|
||||
if (unread) {
|
||||
const res = await rs.rsJsonApiRequest('/rsgxsforums/markRead', {
|
||||
messageId: groupmessagepair,
|
||||
read: true,
|
||||
});
|
||||
if (res.body.retval) {
|
||||
updatedisplayforums(thread.mMeta.mGroupId);
|
||||
m.redraw();
|
||||
}
|
||||
}
|
||||
},
|
||||
ondblclick: () =>
|
||||
(v.attrs.threadStruct.showReplies = !v.attrs.threadStruct.showReplies),
|
||||
}, [
|
||||
thread.mMeta.mMsgName,
|
||||
m('options', { style: 'display:block; margin-top: 5px;' }, [
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
style: 'font-size:12px; margin-right: 5px;',
|
||||
onclick: (e) => {
|
||||
e.stopPropagation();
|
||||
util.popupmessage(
|
||||
m(AddThread, {
|
||||
parent_thread: thread.mMeta.mMsgName,
|
||||
forumId: thread.mMeta.mGroupId,
|
||||
authorId: v.attrs.identity,
|
||||
parentId: thread.mMeta.mMsgId,
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
'Reply'
|
||||
),
|
||||
editpermission &&
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
style: 'font-size:12px; margin-right: 5px;',
|
||||
onclick: async (e) => {
|
||||
e.stopPropagation();
|
||||
const body = await loadPostContent(
|
||||
thread.mMeta.mGroupId,
|
||||
thread.mMeta.mOrigMsgId
|
||||
);
|
||||
util.popupmessage(
|
||||
m(EditThread, {
|
||||
current_thread: thread.mMeta.mMsgName,
|
||||
forumId: thread.mMeta.mGroupId,
|
||||
current_title: thread.mMeta.mMsgName,
|
||||
current_body: body || '',
|
||||
authorId: thread.mMeta.mAuthorId,
|
||||
current_parent: thread.mMeta.mParentId,
|
||||
current_msgid: thread.mMeta.mOrigMsgId,
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
'Edit'
|
||||
),
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
style: { fontSize: '12px' },
|
||||
onclick: async (e) => {
|
||||
e.stopPropagation();
|
||||
const res = await rs.rsJsonApiRequest('/rsgxsforums/markRead', {
|
||||
messageId: groupmessagepair,
|
||||
read: !unread ? true : false,
|
||||
});
|
||||
|
||||
if (res.body.retval) {
|
||||
updatedisplayforums(thread.mMeta.mGroupId);
|
||||
m.redraw();
|
||||
}
|
||||
},
|
||||
},
|
||||
unread ? 'Mark Read' : 'Mark Unread'
|
||||
),
|
||||
]),
|
||||
]),
|
||||
m('div.author', { style: { fontSize: '0.9em', fontStyle: 'italic' } }, rs.userList.username(thread.mMeta.mAuthorId)),
|
||||
]
|
||||
),
|
||||
]
|
||||
),
|
||||
v.attrs.threadStruct.showReplies &&
|
||||
Object.keys(parMap).map((key, index) =>
|
||||
m(displaythread, {
|
||||
// recursive call to all replies
|
||||
threadStruct: util.Data.Threads[parMap[key].mGroupId][parMap[key].mOrigMsgId],
|
||||
replyDepth: v.attrs.replyDepth + 1,
|
||||
identity: v.attrs.identity,
|
||||
changeThread: v.attrs.changeThread,
|
||||
})
|
||||
),
|
||||
];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const ThreadView = () => {
|
||||
let ownId;
|
||||
return {
|
||||
@ -449,7 +220,7 @@ const ThreadView = () => {
|
||||
style: { marginRight: '10px' },
|
||||
onclick: () => util.popupmessage(m(AddThread, {
|
||||
parent_thread: meta.mMsgName,
|
||||
forumId: forumId,
|
||||
forumId,
|
||||
authorId: ownId,
|
||||
parentId: msgId,
|
||||
}))
|
||||
|
||||
@ -106,7 +106,7 @@ async function updatedisplayforums(keyid) {
|
||||
|
||||
const threadStruct = {
|
||||
thread: { mMeta: meta, mMsg: null },
|
||||
replies: replies,
|
||||
replies,
|
||||
showReplies: false,
|
||||
};
|
||||
|
||||
@ -156,7 +156,7 @@ async function loadPostContent(forumId, msgId) {
|
||||
|
||||
try {
|
||||
const res = await rs.rsJsonApiRequest('/rsgxsforums/getForumContent', {
|
||||
forumId: forumId,
|
||||
forumId,
|
||||
msgsIds: [msgId],
|
||||
});
|
||||
if (res && res.body && res.body.retval && res.body.msgs && res.body.msgs.length > 0) {
|
||||
|
||||
@ -41,7 +41,7 @@ function renderPolygon(shapePoints, x, y, angle, shapeAngle, size, color) {
|
||||
|
||||
function toSvg(hash, width) {
|
||||
if (!hash || hash.length < 18) {
|
||||
hash = "00000000000000000000000000000000";
|
||||
hash = '00000000000000000000000000000000';
|
||||
}
|
||||
|
||||
const csh = parseInt(hash.substr(0, 1), 16);
|
||||
@ -99,4 +99,4 @@ function toSvg(hash, width) {
|
||||
|
||||
module.exports = {
|
||||
toSvg
|
||||
};
|
||||
};
|
||||
|
||||
@ -18,7 +18,7 @@ function formatFileSize(bytes) {
|
||||
const Layout = () => {
|
||||
let showCc = false;
|
||||
let showBcc = false;
|
||||
let ownAvatars = {};
|
||||
const ownAvatars = {};
|
||||
let attachments = [];
|
||||
let showEmojiPicker = false;
|
||||
let emojiSearch = '';
|
||||
@ -397,7 +397,7 @@ const Layout = () => {
|
||||
const attHtml = `
|
||||
<br/><hr style="border:none;border-top:1px solid #e2e8f0;margin:1rem 0;"/><div style="margin-top:10px;font-weight:bold;color:#475569;font-size:0.9rem;">Attachments (${attachments.length}):</div>
|
||||
<ul style="list-style:none;padding:0;margin:6px 0;">
|
||||
${attachments.map(att => `<li style="padding:4px 0;color:#1e293b;font-size:0.875rem;">📎 <b>${att.name}</b> <span style="color:#94a3b8;font-size:0.8em;">(${att.size})</span></li>`).join('')}
|
||||
${attachments.map((att) => `<li style="padding:4px 0;color:#1e293b;font-size:0.875rem;">📎 <b>${att.name}</b> <span style="color:#94a3b8;font-size:0.8em;">(${att.size})</span></li>`).join('')}
|
||||
</ul>
|
||||
`;
|
||||
fullMailBody += attHtml;
|
||||
@ -701,7 +701,7 @@ const Layout = () => {
|
||||
}),
|
||||
]),
|
||||
!emojiSearch && m('.emoji-cat-bar', { style: 'display: flex; background: #f8fafc; border-bottom: 1px solid #e2e8f0; padding: 0.25rem; overflow-x: auto;' },
|
||||
chatEmoji.EMOJI_CATEGORIES.map(c =>
|
||||
chatEmoji.EMOJI_CATEGORIES.map((c) =>
|
||||
m('button', {
|
||||
style: `border: none; background: ${c === emojiCategory ? '#ffffff' : 'transparent'}; border-radius: 0.25rem; padding: 0.3rem 0.4rem; cursor: pointer; font-size: 1rem; box-shadow: ${c === emojiCategory ? '0 1px 2px rgba(0,0,0,0.1)' : 'none'};`,
|
||||
title: c,
|
||||
@ -711,9 +711,9 @@ const Layout = () => {
|
||||
),
|
||||
m('.emoji-grid-body', { style: 'padding: 0.5rem; display: grid; grid-template-columns: repeat(7, 1fr); gap: 0.25rem; max-height: 230px; overflow-y: auto;' },
|
||||
(emojiSearch
|
||||
? Object.values(chatEmoji.EMOJI_DATA).flat().filter(e => e.includes(emojiSearch))
|
||||
? Object.values(chatEmoji.EMOJI_DATA).flat().filter((e) => e.includes(emojiSearch))
|
||||
: (chatEmoji.EMOJI_DATA[emojiCategory] || [])
|
||||
).map(e =>
|
||||
).map((e) =>
|
||||
m('button', {
|
||||
style: 'border: none; background: transparent; font-size: 1.25rem; cursor: pointer; padding: 0.25rem; border-radius: 0.25rem; transition: background 0.15s ease;',
|
||||
onmouseenter: (ev) => (ev.currentTarget.style.background = '#f1f5f9'),
|
||||
|
||||
@ -195,7 +195,7 @@ const GenericMailList = () => {
|
||||
m(util.MessageSummary, {
|
||||
key: msg.msgId,
|
||||
details: msg,
|
||||
category: category,
|
||||
category,
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
@ -622,11 +622,11 @@ const Table = () => {
|
||||
};
|
||||
|
||||
let totalItems = 0;
|
||||
let tbody = v.children[0];
|
||||
const tbody = v.children[0];
|
||||
if (tbody && tbody.children) {
|
||||
const flatChildren = Array.isArray(tbody.children) ? tbody.children.flat().filter(Boolean) : [tbody.children].filter(Boolean);
|
||||
totalItems = flatChildren.length;
|
||||
|
||||
|
||||
const start = currentPage * pageSize;
|
||||
const end = start + pageSize;
|
||||
tbody.children = flatChildren.slice(start, end);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -149,7 +149,7 @@ function sendDirectChatMessage() {
|
||||
'/rsChats/sendChat',
|
||||
{
|
||||
id: { type: 1, peer_id: State.currentChatPeerId },
|
||||
msg: msg,
|
||||
msg,
|
||||
},
|
||||
(data, success) => {
|
||||
if (success) {
|
||||
|
||||
@ -100,14 +100,6 @@ const ChatTab = () => {
|
||||
|
||||
const canTalk = State.distantChatStatus && State.distantChatStatus.status === 2;
|
||||
|
||||
// Filter history by search query for history modal
|
||||
const query = (State.historySearchQuery || '').toLowerCase();
|
||||
const filteredHistory = (State.fullHistoryMessages || []).filter((msg) => {
|
||||
if (!query) return true;
|
||||
const text = (msg.msg || msg.message || '').toLowerCase();
|
||||
return text.includes(query);
|
||||
});
|
||||
|
||||
return m('.network-chat-view', [
|
||||
m('.chat-identity-select-container', {
|
||||
style: 'padding: 0.5rem 1rem; background-color: #ffffff; border-bottom: 1px solid #cbd5e1; display: flex; align-items: center; justify-content: space-between; font-size: 0.85rem;',
|
||||
|
||||
@ -33,7 +33,7 @@ const PeopleSidebar = () => {
|
||||
},
|
||||
view: () => {
|
||||
// 1. Determine list based on mainTab ('people' vs 'chats')
|
||||
let displayItems = [];
|
||||
let displayItems;
|
||||
|
||||
// 0. Compute active chats count (conversations with real message history)
|
||||
const allUserGroupIds = new Set((rs.userList.users || []).map((u) => u.mGroupId));
|
||||
@ -47,7 +47,7 @@ const PeopleSidebar = () => {
|
||||
});
|
||||
|
||||
if (State.mainTab === 'people') {
|
||||
let baseList = [];
|
||||
let baseList;
|
||||
if (State.activeFilter === 'own') {
|
||||
baseList = peopleUtil.sortIds(State.ownGxsIds) || [];
|
||||
} else if (State.activeFilter === 'contacts') {
|
||||
@ -57,13 +57,13 @@ const PeopleSidebar = () => {
|
||||
}
|
||||
|
||||
displayItems = baseList.filter((item) => {
|
||||
let name = State.activeFilter === 'own' ? (rs.userList.username(item) || 'Unknown') : (item.mGroupName || 'Unknown');
|
||||
const name = State.activeFilter === 'own' ? (rs.userList.username(item) || 'Unknown') : (item.mGroupName || 'Unknown');
|
||||
return name.toLowerCase().includes(State.searchString.toLowerCase());
|
||||
});
|
||||
|
||||
displayItems.sort((a, b) => {
|
||||
let nameA = State.activeFilter === 'own' ? (rs.userList.username(a) || '') : (a.mGroupName || '');
|
||||
let nameB = State.activeFilter === 'own' ? (rs.userList.username(b) || '') : (b.mGroupName || '');
|
||||
const nameA = State.activeFilter === 'own' ? (rs.userList.username(a) || '') : (a.mGroupName || '');
|
||||
const nameB = State.activeFilter === 'own' ? (rs.userList.username(b) || '') : (b.mGroupName || '');
|
||||
return nameA.localeCompare(nameB);
|
||||
});
|
||||
} else {
|
||||
|
||||
@ -359,7 +359,7 @@ function loadChatMessages() {
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsHistory/getMessages',
|
||||
{
|
||||
chatPeerId: chatPeerId,
|
||||
chatPeerId,
|
||||
loadCount: 50,
|
||||
},
|
||||
(data, success) => {
|
||||
@ -509,7 +509,7 @@ function preloadAllChatHistory() {
|
||||
if (!existing || lastTime > existing.lastTime) {
|
||||
State.chatHistoryMap[gxsId] = {
|
||||
lastMsg: last.message || last.msg || '',
|
||||
lastTime: lastTime,
|
||||
lastTime,
|
||||
};
|
||||
m.redraw();
|
||||
}
|
||||
@ -573,7 +573,7 @@ function loadAllHistoryForSelectedPeer(callback) {
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsHistory/getMessages',
|
||||
{
|
||||
chatPeerId: chatPeerId,
|
||||
chatPeerId,
|
||||
loadCount: 0, // 0 = load all messages in C++
|
||||
},
|
||||
(msgData, success) => {
|
||||
@ -588,7 +588,7 @@ function loadAllHistoryForSelectedPeer(callback) {
|
||||
const key = `${mItem.sendTime || mItem.recvTime}_${text}`;
|
||||
if (!map.has(key)) map.set(key, mItem);
|
||||
});
|
||||
let uniqueMsgs = Array.from(map.values());
|
||||
const uniqueMsgs = Array.from(map.values());
|
||||
uniqueMsgs.sort((a, b) => (a.sendTime || a.recvTime) - (b.sendTime || b.recvTime));
|
||||
State.fullHistoryMessages = uniqueMsgs;
|
||||
State.isHistoryLoading = false;
|
||||
|
||||
@ -70,7 +70,7 @@ const UserAvatar = () => ({
|
||||
width: sizeStr,
|
||||
height: sizeStr,
|
||||
borderRadius: isSquare ? '0' : '50%',
|
||||
backgroundColor: backgroundColor,
|
||||
backgroundColor,
|
||||
}
|
||||
},
|
||||
m('p', {
|
||||
|
||||
@ -15,11 +15,11 @@
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
box-shadow: inset -3px -3px 0 darken($color: $bg-color, $amount: 20);
|
||||
box-shadow: inset -3px -3px 0 color.adjust($bg-color, $lightness: -20%);
|
||||
|
||||
&:active {
|
||||
outline: none;
|
||||
box-shadow: inset 3px 3px 0 darken($color: $bg-color, $amount: 20);
|
||||
box-shadow: inset 3px 3px 0 color.adjust($bg-color, $lightness: -20%);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -372,6 +372,7 @@ textarea.chatMsg {
|
||||
padding: 1.25rem;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||||
position: relative; // anchors the absolutely-positioned .chat-create-lobby-btn
|
||||
|
||||
.profile-header {
|
||||
display: flex;
|
||||
@ -896,6 +897,7 @@ textarea.chatMsg {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
position: relative; // anchors the hovered-participant .user-tooltip (top offset is measured against this)
|
||||
}
|
||||
|
||||
.chat-hub-rightbar .rightbar-title {
|
||||
@ -926,6 +928,7 @@ textarea.chatMsg {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chat-hub-rightbar .user:hover {
|
||||
@ -1125,4 +1128,490 @@ textarea.chatMsg {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Chat-hub extras: user tooltip, right-bar context menu, attach-file modal,
|
||||
// emoji picker, create-lobby button (chat.js). Recovered from compiled styles.css.
|
||||
|
||||
.chat-create-lobby-btn {
|
||||
position: absolute;
|
||||
bottom: 0.5rem;
|
||||
right: 1.25rem;
|
||||
background-color: #0084ff;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.35rem 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 132, 255, 0.2), 0 2px 4px -1px rgba(0, 132, 255, 0.1);
|
||||
transition: background-color 0.2s, transform 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.chat-create-lobby-btn:hover {
|
||||
background-color: #0073e6;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.chat-create-lobby-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.chat-hub-rightbar .user .user-name {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-tooltip {
|
||||
position: absolute;
|
||||
width: 260px;
|
||||
background-color: #ffffe1;
|
||||
border: 1px solid #7f7f7f;
|
||||
box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.25);
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
z-index: 10000;
|
||||
white-space: normal;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.chat-hub-rightbar .user-tooltip {
|
||||
left: -275px;
|
||||
transform: translateY(-50%);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.user-tooltip .tooltip-avatar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-tooltip .tooltip-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.8rem;
|
||||
color: #000000;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.user-tooltip .tooltip-row {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.user-tooltip .tooltip-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.user-tooltip .tooltip-value {
|
||||
font-weight: normal;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.user-tooltip .tooltip-value.tooltip-id {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.chat-hub-rightbar .rightbar-context-menu {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
width: 210px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e2e8f0;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 0.375rem;
|
||||
z-index: 1010;
|
||||
padding: 0.25rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chat-hub-rightbar .rightbar-context-menu .menu-item {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.85rem;
|
||||
color: #334155;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.chat-hub-rightbar .rightbar-context-menu .menu-item:hover {
|
||||
background-color: #f1f5f9;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.chat-emoji {
|
||||
font-size: 1.45em;
|
||||
line-height: 1;
|
||||
vertical-align: -0.15em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.chat-hub-attach-btn,
|
||||
.chat-hub-action-btn {
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
font-size: 1.15rem !important;
|
||||
color: #64748b !important;
|
||||
cursor: pointer !important;
|
||||
padding: 0.4rem 0.5rem !important;
|
||||
border-radius: 0.375rem !important;
|
||||
flex-shrink: 0 !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
transition: all 0.2s !important;
|
||||
box-shadow: none !important;
|
||||
margin: 0 !important;
|
||||
line-height: 1 !important;
|
||||
height: 36px !important;
|
||||
width: 36px !important;
|
||||
}
|
||||
|
||||
.chat-hub-attach-btn:hover,
|
||||
.chat-hub-action-btn:hover {
|
||||
background-color: #f1f5f9 !important;
|
||||
color: #3b82f6 !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.attach-modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(15, 23, 42, 0.4);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.attach-modal {
|
||||
background-color: #ffffff;
|
||||
border-radius: 0.5rem;
|
||||
width: 450px;
|
||||
max-width: 90%;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.attach-modal .attach-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.attach-modal .attach-modal-icon {
|
||||
font-size: 1.2rem;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.attach-modal h4 {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.attach-modal p {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.attach-modal .attach-path-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.attach-modal .attach-path-row input[type="text"] {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.9rem;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.attach-modal .attach-path-row input[type="text"]:focus {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
|
||||
.attach-browse-btn {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.625rem 0.9rem;
|
||||
font-size: 0.875rem;
|
||||
background-color: #f1f5f9;
|
||||
color: #334155;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
box-shadow: none;
|
||||
transition: background-color 0.2s, border-color 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.attach-browse-btn:hover {
|
||||
background-color: #e2e8f0;
|
||||
border-color: #94a3b8;
|
||||
}
|
||||
|
||||
.attach-path-hint {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
padding: 0.6rem 0.75rem;
|
||||
background-color: #fffbeb;
|
||||
border: 1px solid #fcd34d;
|
||||
border-left: 3px solid #f59e0b;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.825rem;
|
||||
color: #92400e;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.attach-path-hint i {
|
||||
color: #f59e0b;
|
||||
margin-top: 0.1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.attach-path-hint code {
|
||||
font-family: monospace;
|
||||
background-color: rgba(245, 158, 11, 0.15);
|
||||
padding: 0.05rem 0.25rem;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
.attach-modal .hashing-spinner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.attach-modal .error-text {
|
||||
color: #ef4444;
|
||||
font-size: 0.85rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.attach-modal .modal-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.75rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.attach-modal .modal-buttons button {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
border-radius: 0.25rem;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.attach-modal .modal-buttons button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.chat-hub-emoji-btn {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
font-size: 1.3rem;
|
||||
cursor: pointer;
|
||||
padding: 0.35rem 0.4rem;
|
||||
margin-right: 0.25rem;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0.375rem;
|
||||
line-height: 1;
|
||||
transition: background-color 0.15s, transform 0.15s;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.chat-hub-emoji-btn:hover {
|
||||
background-color: #f1f5f9;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.emoji-picker-wrapper {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.emoji-picker {
|
||||
position: absolute;
|
||||
bottom: calc(100% + 0.5rem);
|
||||
left: 0;
|
||||
width: 320px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 0.625rem;
|
||||
box-shadow: 0 8px 30px -4px rgba(0, 0, 0, 0.18), 0 4px 12px -2px rgba(0, 0, 0, 0.1);
|
||||
z-index: 3000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
animation: emoji-pop 0.15s ease-out;
|
||||
}
|
||||
|
||||
.emoji-search-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.6rem 0.75rem 0.4rem;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
}
|
||||
|
||||
.emoji-search-icon {
|
||||
color: #94a3b8;
|
||||
font-size: 0.8rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.emoji-search-input {
|
||||
flex: 1;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.3rem 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
outline: none;
|
||||
background-color: #f8fafc;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
|
||||
.emoji-search-input:focus {
|
||||
border-color: #3ba4d7;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.emoji-search-clear {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #94a3b8;
|
||||
padding: 0.2rem;
|
||||
font-size: 0.8rem;
|
||||
box-shadow: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.emoji-search-clear:hover {
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.emoji-categories {
|
||||
display: flex;
|
||||
gap: 0.1rem;
|
||||
padding: 0.35rem 0.5rem;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.emoji-categories::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.emoji-cat-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
padding: 0.3rem 0.35rem;
|
||||
border-radius: 0.375rem;
|
||||
line-height: 1;
|
||||
box-shadow: none;
|
||||
transition: background-color 0.1s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.emoji-cat-btn:hover {
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
|
||||
.emoji-cat-btn.active {
|
||||
background-color: #e0f2fe;
|
||||
box-shadow: inset 0 -2px 0 #3ba4d7;
|
||||
}
|
||||
|
||||
.emoji-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 0;
|
||||
padding: 0.4rem 0.35rem;
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #cbd5e1 transparent;
|
||||
}
|
||||
|
||||
.emoji-grid::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.emoji-grid::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.emoji-grid::-webkit-scrollbar-thumb {
|
||||
background-color: #cbd5e1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.emoji-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1.7rem;
|
||||
padding: 0.25rem;
|
||||
border-radius: 0.3rem;
|
||||
line-height: 1;
|
||||
box-shadow: none;
|
||||
text-align: center;
|
||||
transition: background-color 0.1s, transform 0.1s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.emoji-btn:hover {
|
||||
background-color: #f1f5f9;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
@keyframes emoji-pop {
|
||||
from { opacity: 0; transform: scale(0.92) translateY(6px); }
|
||||
to { opacity: 1; transform: scale(1) translateY(0); }
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
@use 'sass:color';
|
||||
@use '../abstracts' as *;
|
||||
|
||||
.mail {
|
||||
@ -7,13 +8,13 @@
|
||||
}
|
||||
&-tags {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid transparentize($dark-color, 0.8);
|
||||
border: 1px solid color.adjust($dark-color, $alpha: -0.8);
|
||||
border-radius: 6px;
|
||||
&__container {
|
||||
@include flex(column);
|
||||
& .tag-item {
|
||||
@include flex($align: center, $gap: 4px);
|
||||
border-bottom: 1px solid transparentize($dark-color, 0.9);
|
||||
border-bottom: 1px solid color.adjust($dark-color, $alpha: -0.9);
|
||||
padding: 2px 0;
|
||||
|
||||
&:last-child {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
@use 'sass:color';
|
||||
@use '../abstracts/' as *;
|
||||
|
||||
.file-view {
|
||||
@ -71,7 +72,7 @@ table.friendsfiles td:nth-child(2) {
|
||||
margin-top: 1rem;
|
||||
padding: 8px;
|
||||
@include flex($gap: 8px);
|
||||
border: 1px solid transparentize($dark-color, 0.8);
|
||||
border: 1px solid color.adjust($dark-color, $alpha: -0.8);
|
||||
border-radius: 6px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
@ -79,11 +80,11 @@ table.friendsfiles td:nth-child(2) {
|
||||
&__keywords {
|
||||
flex-basis: 15%;
|
||||
padding-right: 0.25rem;
|
||||
border-right: 1px solid transparentize($dark-color, 0.9);
|
||||
border-right: 1px solid color.adjust($dark-color, $alpha: -0.9);
|
||||
|
||||
& .keywords-container {
|
||||
@include flex(column);
|
||||
border-top: 2.5px solid transparentize($dark-color, 0.92);
|
||||
border-top: 2.5px solid color.adjust($dark-color, $alpha: -0.92);
|
||||
margin-top: 0.125rem;
|
||||
padding-top: 0.25rem;
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
@use 'sass:color';
|
||||
@use '../abstracts/' as *;
|
||||
|
||||
.side-bar {
|
||||
@ -182,7 +183,7 @@ table.mails {
|
||||
/* attachments */
|
||||
&:nth-child(2) {
|
||||
width: 5%;
|
||||
color: darken($light-color, 50%);
|
||||
color: color.adjust($light-color, $lightness: -50%);
|
||||
}
|
||||
/* subject */
|
||||
&:nth-child(3) {
|
||||
@ -239,7 +240,7 @@ table.mails {
|
||||
|
||||
&:hover {
|
||||
background-color: $light-color;
|
||||
color: darken($light-color, 80%);
|
||||
color: color.adjust($light-color, $lightness: -80%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,6 +104,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: relative; // anchors the absolutely-positioned .people-context-menu
|
||||
|
||||
.searchbar-container {
|
||||
padding: 0.75rem 1rem;
|
||||
|
||||
@ -195,3 +195,134 @@ img.avatar {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Recent-chats split-pane + friends context menu (people_sidebar.js).
|
||||
// Recovered from compiled styles.css; hard-coded colors kept as-was.
|
||||
|
||||
.friends-list-container .people-context-menu {
|
||||
position: absolute;
|
||||
left: 2rem;
|
||||
width: 210px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e2e8f0;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 0.375rem;
|
||||
z-index: 1010;
|
||||
padding: 0.25rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.friends-list-container .people-context-menu .menu-item {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.85rem;
|
||||
color: #334155;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.friends-list-container .people-context-menu .menu-item:hover {
|
||||
background-color: #f1f5f9;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.people-container {
|
||||
display: flex;
|
||||
height: calc(100vh - 55px);
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.people-left-pane {
|
||||
width: 320px;
|
||||
border-right: 1px solid #cbd5e1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #ffffff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.people-right-pane {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
.people-left-pane .chat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
gap: 0.75rem;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.people-left-pane .chat-item:hover {
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
.people-left-pane .chat-item.selected {
|
||||
background-color: #eff6ff;
|
||||
border-left: 3px solid #3b82f6;
|
||||
}
|
||||
|
||||
.people-left-pane .chat-item .chat-avatar-wrapper {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.people-left-pane .chat-item .chat-avatar-wrapper .status-dot {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #ffffff;
|
||||
}
|
||||
|
||||
.people-left-pane .chat-item .chat-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
|
||||
.people-left-pane .chat-item .chat-info .chat-name {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.people-left-pane .chat-item .chat-info .chat-last-msg {
|
||||
font-size: 0.8rem;
|
||||
color: #64748b;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.people-left-pane .chat-item .chat-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 0.25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.people-left-pane .chat-item .chat-meta .chat-time {
|
||||
font-size: 0.75rem;
|
||||
color: #94a3b8;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
51
webui-src/eslint.config.mjs
Normal file
51
webui-src/eslint.config.mjs
Normal file
@ -0,0 +1,51 @@
|
||||
import js from '@eslint/js';
|
||||
import globals from 'globals';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['app/mithril.js', 'assets/**', 'make-src/**'],
|
||||
},
|
||||
{
|
||||
files: ['app/**/*.js'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'commonjs',
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.commonjs,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
'linebreak-style': ['off', 'unix'],
|
||||
quotes: ['error', 'single'],
|
||||
semi: ['error', 'always'],
|
||||
'arrow-spacing': ['error', { before: true, after: true }],
|
||||
'arrow-parens': ['error', 'always'],
|
||||
'comma-style': ['error', 'last'],
|
||||
'comma-spacing': ['error', { before: false, after: true }],
|
||||
camelcase: ['error', {
|
||||
allow: ['^UNSAFE_'],
|
||||
properties: 'never',
|
||||
ignoreGlobals: true,
|
||||
}],
|
||||
'eol-last': 'error',
|
||||
eqeqeq: ['error', 'always', { null: 'ignore' }],
|
||||
'no-irregular-whitespace': 'error',
|
||||
'no-trailing-spaces': 'error',
|
||||
'no-unexpected-multiline': 'error',
|
||||
'no-unreachable': 'error',
|
||||
'no-var': 'warn',
|
||||
'no-unused-vars': ['error', {
|
||||
args: 'none',
|
||||
caughtErrors: 'none',
|
||||
ignoreRestSiblings: true,
|
||||
vars: 'all',
|
||||
}],
|
||||
'object-shorthand': 'error',
|
||||
'prefer-const': ['error', { destructuring: 'all' }],
|
||||
'semi-style': ['warn', 'last'],
|
||||
'spaced-comment': ['warn', 'always'],
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -5,59 +5,71 @@ setlocal enabledelayedexpansion
|
||||
|
||||
echo "### Starting WebUI build ###"
|
||||
|
||||
set src=%~dp0..\..\webui-src
|
||||
set "src=%~dp0..\..\webui-src"
|
||||
|
||||
rem Output destination
|
||||
if "%~1" == "" (
|
||||
set publicdest=%~dp0..\..\webui
|
||||
if "%~1"=="" (
|
||||
set "publicdest=%~dp0..\..\webui"
|
||||
) else (
|
||||
set publicdest=%~1\webui
|
||||
set "publicdest=%~1\webui"
|
||||
)
|
||||
|
||||
if exist "%publicdest%" echo removing existing %publicdest%&&rd %publicdest% /S /Q
|
||||
if exist "%publicdest%" (
|
||||
echo removing existing %publicdest%
|
||||
rd "%publicdest%" /S /Q
|
||||
)
|
||||
|
||||
echo creating %publicdest%
|
||||
md %publicdest%
|
||||
md "%publicdest%"
|
||||
|
||||
rem Make full path
|
||||
pushd %publicdest%
|
||||
set publicdest=%cd%
|
||||
pushd "%publicdest%"
|
||||
set "publicdest=%cd%"
|
||||
popd
|
||||
|
||||
echo copying html file
|
||||
xcopy /s %src%\index.html %publicdest%
|
||||
copy /Y "%src%\index.html" "%publicdest%\index.html" >nul
|
||||
|
||||
echo copying css file
|
||||
xcopy /s %src%\styles.css %publicdest%
|
||||
copy /Y "%src%\styles.css" "%publicdest%\styles.css" >nul
|
||||
|
||||
echo building app.js
|
||||
echo - copying template.js ...
|
||||
copy %src%\make-src\template.js %publicdest%\app.js
|
||||
copy /Y "%src%\make-src\template.js" "%publicdest%\app.js" >nul
|
||||
|
||||
pushd %src%\app
|
||||
pushd "%src%\app"
|
||||
set "basefolder=%cd%\"
|
||||
set "lastsection="
|
||||
for /R %%F in (*.js) do call :addfile-js "%basefolder%" "%%F"
|
||||
popd
|
||||
|
||||
echo copying assets folder
|
||||
xcopy /s %src%\assets\ %publicdest%
|
||||
xcopy "%src%\assets\*" "%publicdest%\" /E /I /Y >nul
|
||||
|
||||
echo "### WebUI build complete ###"
|
||||
|
||||
goto :EOF
|
||||
|
||||
:addfile-js
|
||||
set basefolder=%~1
|
||||
set fname=%~2
|
||||
set "basefolder=%~1"
|
||||
set "fname=%~2"
|
||||
|
||||
set registername=%~dpn2
|
||||
set registername=!registername:%basefolder%=!
|
||||
set registername=%registername:\=/%
|
||||
set "registername=%~dpn2"
|
||||
set "registername=!registername:%basefolder%=!"
|
||||
set "registername=%registername:\=/%"
|
||||
|
||||
echo - adding %registername% ...
|
||||
echo require.register("%registername%", function(exports, require, module) { >> %publicdest%\app.js
|
||||
type %fname% >> %publicdest%\app.js
|
||||
echo. >> %publicdest%\app.js
|
||||
echo }); >> %publicdest%\app.js
|
||||
for /f "tokens=1,2 delims=/" %%A in ("!registername!") do (
|
||||
if "%%B"=="" (
|
||||
echo - adding !registername! ...
|
||||
set "lastsection="
|
||||
) else if not "!lastsection!"=="%%A" (
|
||||
echo - adding %%A/* ...
|
||||
set "lastsection=%%A"
|
||||
)
|
||||
)
|
||||
echo require.register("%registername%", function(exports, require, module) { >>"%publicdest%\app.js"
|
||||
type "%fname%" >>"%publicdest%\app.js"
|
||||
echo. >>"%publicdest%\app.js"
|
||||
echo }); >>"%publicdest%\app.js"
|
||||
|
||||
:EOF
|
||||
|
||||
35
webui-src/make-src/build.js
Normal file
35
webui-src/make-src/build.js
Normal file
@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
|
||||
const shellScript = path.join(__dirname, 'build.sh');
|
||||
const batchScript = path.join(__dirname, 'build.bat');
|
||||
|
||||
function getRunner(platform, commandShell = 'cmd.exe') {
|
||||
return platform === 'win32'
|
||||
? { command: commandShell, args: ['/d', '/c', batchScript] }
|
||||
: { command: 'sh', args: [shellScript] };
|
||||
}
|
||||
|
||||
function run(runner) {
|
||||
const result = spawnSync(runner.command, runner.args, { stdio: 'inherit' });
|
||||
if (result.error) {
|
||||
console.error(result.error.message);
|
||||
return 1;
|
||||
}
|
||||
return result.status ?? 1;
|
||||
}
|
||||
|
||||
if (process.argv[2] === '--check') {
|
||||
assert.equal(path.basename(getRunner('win32').args[2]), 'build.bat');
|
||||
assert.equal(path.basename(getRunner('linux').args[0]), 'build.sh');
|
||||
const status = process.platform === 'win32'
|
||||
? 0
|
||||
: run({ command: 'sh', args: ['-n', shellScript] });
|
||||
if (status === 0) console.log('Build dispatcher checks passed.');
|
||||
process.exitCode = status;
|
||||
} else {
|
||||
process.exitCode = run(getRunner(process.platform, process.env.ComSpec));
|
||||
}
|
||||
@ -1,68 +1,85 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
# create webfiles from sources at compile time (works without npm/node.js)
|
||||
# Create webfiles from sources at compile time (works without npm/node.js)
|
||||
#
|
||||
# Usage: build.sh [DEST_PARENT] [TARGET_FILE] [EXTRA_COPY_DIR]
|
||||
# DEST_PARENT parent dir of the generated webui/ (default: repo root)
|
||||
# TARGET_FILE build only this file (index.html|styles.css|app.js); default: all
|
||||
# EXTRA_COPY_DIR also copy TARGET_FILE into EXTRA_COPY_DIR/webui/
|
||||
|
||||
set -eu
|
||||
|
||||
echo "### Starting WebUI build ###"
|
||||
|
||||
src=$(readlink -f $(dirname $0))/../../webui-src
|
||||
# Resolve the script's own directory portably.
|
||||
script_dir=$(cd -- "$(dirname -- "$0")" && pwd -P)
|
||||
src="$script_dir/../../webui-src"
|
||||
|
||||
if [ "$1" = "" ]; then
|
||||
publicdest=$(readlink -f $(dirname $0))/../../webui
|
||||
dest_parent="${1:-}"
|
||||
target="${2:-}"
|
||||
extra_copy_dir="${3:-}"
|
||||
|
||||
if [ -z "$dest_parent" ]; then
|
||||
publicdest="$script_dir/../../webui"
|
||||
else
|
||||
publicdest=$1/webui
|
||||
publicdest="$dest_parent/webui"
|
||||
fi
|
||||
|
||||
if [ "$2" = "" ]; then
|
||||
if [ -d "$publicdest" ]; then
|
||||
echo removing existing $publicdest
|
||||
rm $publicdest -R
|
||||
fi
|
||||
# Full rebuild (no specific target): remove any existing output first.
|
||||
if [ -z "$target" ] && [ -d "$publicdest" ]; then
|
||||
echo "removing existing $publicdest"
|
||||
rm -rf -- "$publicdest"
|
||||
fi
|
||||
|
||||
if [ ! -d "$publicdest" ]; then
|
||||
echo creating $publicdest
|
||||
mkdir $publicdest
|
||||
mkdir -p -- "$publicdest"
|
||||
|
||||
if [ -z "$target" ] || [ "$target" = "index.html" ]; then
|
||||
echo "copying html file"
|
||||
cp -- "$src/index.html" "$publicdest/"
|
||||
fi
|
||||
|
||||
# For using recursive directory search(requires bash v4+)
|
||||
shopt -s globstar
|
||||
|
||||
if [ "$2" = "" ]||[ "$2" = "index.html" ]; then
|
||||
echo copying html file
|
||||
cp $src/index.html $publicdest/
|
||||
if [ -z "$target" ] || [ "$target" = "styles.css" ]; then
|
||||
echo "copying css file"
|
||||
cp -- "$src/styles.css" "$publicdest/"
|
||||
fi
|
||||
|
||||
if [ "$2" = "" ]||[ "$2" = "styles.css" ]; then
|
||||
echo copying css file
|
||||
cp $src/styles.css $publicdest/
|
||||
if [ -z "$target" ] || [ "$target" = "app.js" ]; then
|
||||
echo "building app.js:"
|
||||
echo "- copying template.js ..."
|
||||
cp -- "$src/make-src/template.js" "$publicdest/app.js"
|
||||
|
||||
js_root="$src/app"
|
||||
find "$js_root" -type f -name '*.js' | LC_ALL=C sort | while IFS= read -r filename; do
|
||||
fname="${filename#"$js_root/"}"
|
||||
fname="${fname%.*}"
|
||||
case "$fname" in
|
||||
*/*)
|
||||
section="${fname%%/*}/*"
|
||||
if [ "$section" != "${last_section:-}" ]; then
|
||||
echo "- adding $section ..."
|
||||
last_section="$section"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "- adding $fname ..."
|
||||
last_section=
|
||||
;;
|
||||
esac
|
||||
{
|
||||
printf 'require.register("%s", function(exports, require, module) {\n' "$fname"
|
||||
cat -- "$filename"
|
||||
printf '\n});\n'
|
||||
} >>"$publicdest/app.js"
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$2" = "" ]||[ "$2" = "app.js" ]; then
|
||||
echo building app.js:
|
||||
echo - copying template.js ...
|
||||
cp $src/make-src/template.js $publicdest/app.js
|
||||
echo "copying assets folder"
|
||||
cp -R -- "$src/assets/." "$publicdest/"
|
||||
|
||||
js_source=$src/app/
|
||||
for filename in $src/app/**/*.js; do
|
||||
fname="${filename#$js_source}"
|
||||
fname="${fname%.*}"
|
||||
echo - adding $fname ...
|
||||
echo require.register\(\"$fname\", function\(exports, require, module\) { >> $publicdest/app.js
|
||||
cat $filename >> $publicdest/app.js
|
||||
echo >> $publicdest/app.js
|
||||
echo }\)\; >> $publicdest/app.js
|
||||
done
|
||||
if [ -n "$target" ] && [ -n "$extra_copy_dir" ]; then
|
||||
mkdir -p -- "$extra_copy_dir/webui"
|
||||
echo "copying $target to $extra_copy_dir/webui/$target"
|
||||
cp -- "$publicdest/$target" "$extra_copy_dir/webui/$target"
|
||||
fi
|
||||
|
||||
echo copying assets folder
|
||||
cp -r $src/assets/* $publicdest/
|
||||
|
||||
if [ "$2" != "" ]&&[ "$3" != "" ]; then
|
||||
if [ ! -d "$3/webui" ]; then
|
||||
echo creating $3/webui
|
||||
mkdir $3/webui
|
||||
fi
|
||||
echo copying $2 nach $3/webui/$2
|
||||
cp $publicdest/$2 $3/webui/$2
|
||||
fi
|
||||
echo "### WebUI build complete ###"
|
||||
|
||||
1311
webui-src/package-lock.json
generated
Normal file
1311
webui-src/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -3,11 +3,18 @@
|
||||
"version": "1.0.0",
|
||||
"description": "Retroshare's Web Interface",
|
||||
"scripts": {
|
||||
"watch": "rm ./styles.css && sass --watch --embed-sources --embed-source-map ./app/scss/main.scss ./styles.css",
|
||||
"build": "sass --no-source-map --style=compressed ./app/scss/main.scss ./styles.css"
|
||||
"watch": "sass --watch --embed-sources --embed-source-map ./app/scss/main.scss ./styles.css",
|
||||
"build": "sass --no-source-map --style=compressed ./app/scss/main.scss ./styles.css && node ./make-src/build.js",
|
||||
"lint": "eslint app && node ./make-src/build.js --check && echo Lint checks passed."
|
||||
},
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=24"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sass": "^1.60.0"
|
||||
"@eslint/js": "^10.0.1",
|
||||
"eslint": "^10.8.0",
|
||||
"globals": "^17.8.0",
|
||||
"sass": "1.97.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,240 +0,0 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
sass:
|
||||
specifier: ^1.60.0
|
||||
version: 1.97.3
|
||||
|
||||
packages:
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@parcel/watcher-darwin-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@parcel/watcher-darwin-x64@2.5.6':
|
||||
resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@parcel/watcher-freebsd-x64@2.5.6':
|
||||
resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@parcel/watcher-linux-arm-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.6':
|
||||
resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.6':
|
||||
resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.6':
|
||||
resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher-win32-ia32@2.5.6':
|
||||
resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher-win32-x64@2.5.6':
|
||||
resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher@2.5.6':
|
||||
resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
immutable@5.1.5:
|
||||
resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-glob@4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
picomatch@4.0.3:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
readdirp@4.1.2:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
|
||||
sass@1.97.3:
|
||||
resolution: {integrity: sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-darwin-arm64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-darwin-x64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-freebsd-x64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm-glibc@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-ia32@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-x64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher@2.5.6':
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
is-glob: 4.0.3
|
||||
node-addon-api: 7.1.1
|
||||
picomatch: 4.0.3
|
||||
optionalDependencies:
|
||||
'@parcel/watcher-android-arm64': 2.5.6
|
||||
'@parcel/watcher-darwin-arm64': 2.5.6
|
||||
'@parcel/watcher-darwin-x64': 2.5.6
|
||||
'@parcel/watcher-freebsd-x64': 2.5.6
|
||||
'@parcel/watcher-linux-arm-glibc': 2.5.6
|
||||
'@parcel/watcher-linux-arm-musl': 2.5.6
|
||||
'@parcel/watcher-linux-arm64-glibc': 2.5.6
|
||||
'@parcel/watcher-linux-arm64-musl': 2.5.6
|
||||
'@parcel/watcher-linux-x64-glibc': 2.5.6
|
||||
'@parcel/watcher-linux-x64-musl': 2.5.6
|
||||
'@parcel/watcher-win32-arm64': 2.5.6
|
||||
'@parcel/watcher-win32-ia32': 2.5.6
|
||||
'@parcel/watcher-win32-x64': 2.5.6
|
||||
optional: true
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
|
||||
detect-libc@2.1.2:
|
||||
optional: true
|
||||
|
||||
immutable@5.1.5: {}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
optional: true
|
||||
|
||||
is-glob@4.0.3:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
optional: true
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
optional: true
|
||||
|
||||
picomatch@4.0.3:
|
||||
optional: true
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
sass@1.97.3:
|
||||
dependencies:
|
||||
chokidar: 4.0.3
|
||||
immutable: 5.1.5
|
||||
source-map-js: 1.2.1
|
||||
optionalDependencies:
|
||||
'@parcel/watcher': 2.5.6
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
File diff suppressed because one or more lines are too long
10
webui.pro
10
webui.pro
@ -33,7 +33,7 @@ WEBUI_SRC_CSS = $$PWD/webui-src/styles.css
|
||||
|
||||
WEBUI_SRC_IMAGE = $$PWD/data/retroshare.svg
|
||||
|
||||
win32-g++ {
|
||||
win32 {
|
||||
isEmpty(QMAKE_SH) {
|
||||
# Windows native build
|
||||
WEBUI_SRC_SCRIPT = $$PWD/webui-src/make-src/build.bat
|
||||
@ -72,7 +72,7 @@ win32-g++ {
|
||||
} else {
|
||||
# create dummy files, we need it to include files on first try
|
||||
|
||||
system(bash webui-src/make-src/build.sh .)
|
||||
system(sh webui-src/make-src/build.sh .)
|
||||
|
||||
WEBUI_SRC_SCRIPT = webui-src/make-src/build.sh
|
||||
|
||||
@ -80,19 +80,19 @@ win32-g++ {
|
||||
|
||||
create_webfiles_html.output = webui/index.html
|
||||
create_webfiles_html.input = WEBUI_SRC_HTML
|
||||
create_webfiles_html.commands = bash $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ index.html .
|
||||
create_webfiles_html.commands = sh $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ index.html .
|
||||
create_webfiles_html.variable_out = JUNK
|
||||
create_webfiles_html.CONFIG = combine no_link
|
||||
|
||||
create_webfiles_js.output = webui/app.js
|
||||
create_webfiles_js.input = WEBUI_SRC_JS
|
||||
create_webfiles_js.commands = bash $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ app.js .
|
||||
create_webfiles_js.commands = sh $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ app.js .
|
||||
create_webfiles_js.variable_out = JUNK
|
||||
create_webfiles_js.CONFIG = combine no_link
|
||||
|
||||
create_webfiles_css.output = webui/styles.css
|
||||
create_webfiles_css.input = WEBUI_SRC_CSS
|
||||
create_webfiles_css.commands = bash $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ styles.css .
|
||||
create_webfiles_css.commands = sh $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ styles.css .
|
||||
create_webfiles_css.variable_out = JUNK
|
||||
create_webfiles_css.CONFIG = combine no_link
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user