Merge remote-tracking branch 'origin/develop' into webv2
This commit is contained in:
@@ -1,40 +0,0 @@
|
|||||||
# See https://docs.earthly.dev/ci-integration/vendor-specific-guides/gh-actions-integration
|
|
||||||
# for details.
|
|
||||||
|
|
||||||
name: Build nightly docker
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 2 * * *'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: earthly/actions-setup@v1
|
|
||||||
with:
|
|
||||||
version: 'latest' # or pin to an specific version, e.g. "v0.6.10"
|
|
||||||
|
|
||||||
- name: Earthly version
|
|
||||||
run: earthly --version
|
|
||||||
|
|
||||||
- name: Log into GitHub Container Registry
|
|
||||||
env:
|
|
||||||
GH_CR_PAT: ${{ secrets.GH_CR_PAT }}
|
|
||||||
run: echo "${{ secrets.GH_CR_PAT }}" | docker login https://ghcr.io -u ${{ github.actor }} --password-stdin
|
|
||||||
if: env.GH_CR_PAT != null
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
id: qemu
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
with:
|
|
||||||
image: tonistiigi/binfmt:latest
|
|
||||||
platforms: all
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Checkout and build
|
|
||||||
if: env.GH_CR_PAT != null
|
|
||||||
env:
|
|
||||||
GH_CR_PAT: ${{ secrets.GH_CR_PAT }}
|
|
||||||
run: cd build/release && ./docker-nightly-earthly.sh
|
|
||||||
@@ -1,14 +1,23 @@
|
|||||||
|
# See https://docs.earthly.dev/ci-integration/vendor-specific-guides/gh-actions-integration
|
||||||
|
# for details.
|
||||||
|
|
||||||
name: Build nightly docker
|
name: Build nightly docker
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 2 * * *"
|
- cron: '0 2 * * *'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Docker:
|
Docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- uses: earthly/actions-setup@v1
|
||||||
|
with:
|
||||||
|
version: 'latest' # or pin to an specific version, e.g. "v0.6.10"
|
||||||
|
|
||||||
|
- name: Earthly version
|
||||||
|
run: earthly --version
|
||||||
|
|
||||||
- name: Log into GitHub Container Registry
|
- name: Log into GitHub Container Registry
|
||||||
env:
|
env:
|
||||||
@@ -16,9 +25,16 @@ jobs:
|
|||||||
run: echo "${{ secrets.GH_CR_PAT }}" | docker login https://ghcr.io -u ${{ github.actor }} --password-stdin
|
run: echo "${{ secrets.GH_CR_PAT }}" | docker login https://ghcr.io -u ${{ github.actor }} --password-stdin
|
||||||
if: env.GH_CR_PAT != null
|
if: env.GH_CR_PAT != null
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
id: qemu
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
with:
|
||||||
|
image: tonistiigi/binfmt:latest
|
||||||
|
platforms: all
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Setup and run
|
- name: Checkout and build
|
||||||
|
if: env.GH_CR_PAT != null
|
||||||
env:
|
env:
|
||||||
GH_CR_PAT: ${{ secrets.GH_CR_PAT }}
|
GH_CR_PAT: ${{ secrets.GH_CR_PAT }}
|
||||||
run: cd build/release && ./docker-nightly.sh
|
run: cd build/release && ./docker-nightly.sh
|
||||||
if: env.GH_CR_PAT != null
|
|
||||||
|
|||||||
@@ -178,11 +178,11 @@ func GetResolvedPublicKeyFromIRI(publicKeyIRI string) (vocab.W3IDSecurityV1Publi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "error resolving publickey from iri")
|
err = errors.Wrap(err, "error resolving publickey from iri, actor may not be valid: "+publicKeyIRI)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !resolved {
|
if !resolved {
|
||||||
err = errors.New("error resolving publickey from iri")
|
err = errors.New("error resolving publickey from iri, actor may not be valid: " + publicKeyIRI)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pubkey, err
|
return pubkey, err
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Docker build
|
|
||||||
# Must authenticate first: https://docs.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-docker-for-use-with-github-packages#authenticating-to-github-packages
|
|
||||||
DOCKER_IMAGE="owncast-earthly"
|
|
||||||
DATE=$(date +"%Y%m%d")
|
|
||||||
VERSION="${DATE}-nightly"
|
|
||||||
|
|
||||||
echo "Building Docker image ${DOCKER_IMAGE}..."
|
|
||||||
|
|
||||||
# Change to the root directory of the repository
|
|
||||||
cd $(git rev-parse --show-toplevel)
|
|
||||||
|
|
||||||
earthly --ci --push +docker-all --image="ghcr.io/owncast/${DOCKER_IMAGE}" --tag=nightly --version="${VERSION}"
|
|
||||||
@@ -1,29 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
# Docker build
|
# Docker build
|
||||||
# Must authenticate first: https://docs.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-docker-for-use-with-github-packages#authenticating-to-github-packages
|
# Must authenticate first: https://docs.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-docker-for-use-with-github-packages#authenticating-to-github-packages
|
||||||
DOCKER_IMAGE="owncast"
|
DOCKER_IMAGE="owncast"
|
||||||
DATE=$(date +"%Y%m%d")
|
DATE=$(date +"%Y%m%d")
|
||||||
VERSION="${DATE}-nightly"
|
VERSION="${DATE}-nightly"
|
||||||
GIT_COMMIT=$(git rev-list -1 HEAD)
|
|
||||||
|
|
||||||
# Create production build of Tailwind CSS
|
|
||||||
pushd ../../build/javascript >> /dev/null
|
|
||||||
# Install the tailwind & postcss CLIs
|
|
||||||
npm install --quiet --no-progress
|
|
||||||
# Run the tailwind CLI and pipe it to postcss for minification.
|
|
||||||
# Save it to a temp directory that we will reference below.
|
|
||||||
NODE_ENV="production" ./node_modules/.bin/tailwind build | ./node_modules/.bin/postcss > "../../webroot/js/web_modules/tailwindcss/dist/tailwind.min.css"
|
|
||||||
popd
|
|
||||||
|
|
||||||
echo "Building Docker image ${DOCKER_IMAGE}..."
|
echo "Building Docker image ${DOCKER_IMAGE}..."
|
||||||
|
|
||||||
# Change to the root directory of the repository
|
# Change to the root directory of the repository
|
||||||
cd $(git rev-parse --show-toplevel)
|
cd $(git rev-parse --show-toplevel)
|
||||||
|
|
||||||
# Docker build
|
earthly --ci --push +docker-all --image="ghcr.io/owncast/${DOCKER_IMAGE}" --tag=nightly --version="${VERSION}"
|
||||||
docker build --build-arg NAME=docker --build-arg VERSION=${VERSION} --build-arg GIT_COMMIT=$GIT_COMMIT -t ghcr.io/owncast/${DOCKER_IMAGE}:nightly .
|
|
||||||
|
|
||||||
# Dockerhub
|
|
||||||
# You must be authenticated via `docker login` with your Dockerhub credentials first.
|
|
||||||
# docker push gabekangas/owncast:nightly
|
|
||||||
|
|
||||||
docker push ghcr.io/owncast/${DOCKER_IMAGE}:nightly
|
|
||||||
|
|||||||
+3
-3
@@ -686,7 +686,7 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
example: https://fediverse.biz/authorize_interaction?uri=https://my.owncast.server/federation/user/streamer
|
example: https://fediverse.biz/authorize_interaction?uri=https://my.owncast.server/federation/user/streamer
|
||||||
|
|
||||||
/api/chat/updatemessagevisibility:
|
/api/chat/messagevisibility:
|
||||||
post:
|
post:
|
||||||
summary: Update the visibility of chat messages.
|
summary: Update the visibility of chat messages.
|
||||||
description: Pass an array of IDs you want to change the chat visibility of.
|
description: Pass an array of IDs you want to change the chat visibility of.
|
||||||
@@ -951,7 +951,7 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
|
||||||
/api/admin/chat/updatemessagevisibility:
|
/api/admin/chat/messagevisibility:
|
||||||
post:
|
post:
|
||||||
summary: Update the visibility of chat messages.
|
summary: Update the visibility of chat messages.
|
||||||
description: Pass an array of IDs you want to change the chat visibility of.
|
description: Pass an array of IDs you want to change the chat visibility of.
|
||||||
@@ -1984,7 +1984,7 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
|
||||||
/api/integrations/chat/updatemessagevisibility:
|
/api/integrations/chat/messagevisibility:
|
||||||
post:
|
post:
|
||||||
summary: Update the visibility of chat messages.
|
summary: Update the visibility of chat messages.
|
||||||
description: Pass an array of IDs you want to change the chat visibility of.
|
description: Pass an array of IDs you want to change the chat visibility of.
|
||||||
|
|||||||
+2
-2
@@ -131,7 +131,7 @@ func Start() error {
|
|||||||
http.HandleFunc("/api/admin/chat/messages", middleware.RequireAdminAuth(admin.GetChatMessages))
|
http.HandleFunc("/api/admin/chat/messages", middleware.RequireAdminAuth(admin.GetChatMessages))
|
||||||
|
|
||||||
// Update chat message visibility
|
// Update chat message visibility
|
||||||
http.HandleFunc("/api/admin/chat/updatemessagevisibility", middleware.RequireAdminAuth(admin.UpdateMessageVisibility))
|
http.HandleFunc("/api/admin/chat/messagevisibility", middleware.RequireAdminAuth(admin.UpdateMessageVisibility))
|
||||||
|
|
||||||
// Enable/disable a user
|
// Enable/disable a user
|
||||||
http.HandleFunc("/api/admin/chat/users/setenabled", middleware.RequireAdminAuth(admin.UpdateUserEnabled))
|
http.HandleFunc("/api/admin/chat/users/setenabled", middleware.RequireAdminAuth(admin.UpdateUserEnabled))
|
||||||
@@ -318,7 +318,7 @@ func Start() error {
|
|||||||
// Inline chat moderation actions
|
// Inline chat moderation actions
|
||||||
|
|
||||||
// Update chat message visibility
|
// Update chat message visibility
|
||||||
http.HandleFunc("/api/chat/updatemessagevisibility", middleware.RequireUserModerationScopeAccesstoken(admin.UpdateMessageVisibility))
|
http.HandleFunc("/api/chat/messagevisibility", middleware.RequireUserModerationScopeAccesstoken(admin.UpdateMessageVisibility))
|
||||||
|
|
||||||
// Enable/disable a user
|
// Enable/disable a user
|
||||||
http.HandleFunc("/api/chat/users/setenabled", middleware.RequireUserModerationScopeAccesstoken(admin.UpdateUserEnabled))
|
http.HandleFunc("/api/chat/users/setenabled", middleware.RequireUserModerationScopeAccesstoken(admin.UpdateUserEnabled))
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -7,112 +7,112 @@ const registerChat = require('./lib/chat').registerChat;
|
|||||||
const sendChatMessage = require('./lib/chat').sendChatMessage;
|
const sendChatMessage = require('./lib/chat').sendChatMessage;
|
||||||
|
|
||||||
const testVisibilityMessage = {
|
const testVisibilityMessage = {
|
||||||
body: 'message ' + Math.floor(Math.random() * 100),
|
body: 'message ' + Math.floor(Math.random() * 100),
|
||||||
type: 'CHAT',
|
type: 'CHAT',
|
||||||
};
|
};
|
||||||
|
|
||||||
var messageId;
|
var messageId;
|
||||||
|
|
||||||
const establishedUserFailedChatMessage = {
|
const establishedUserFailedChatMessage = {
|
||||||
body: 'this message should fail to send ' + Math.floor(Math.random() * 100),
|
body: 'this message should fail to send ' + Math.floor(Math.random() * 100),
|
||||||
type: 'CHAT',
|
type: 'CHAT',
|
||||||
};
|
};
|
||||||
|
|
||||||
test('can send a chat message', async (done) => {
|
test('can send a chat message', async (done) => {
|
||||||
const registration = await registerChat();
|
const registration = await registerChat();
|
||||||
const accessToken = registration.accessToken;
|
const accessToken = registration.accessToken;
|
||||||
|
|
||||||
sendChatMessage(testVisibilityMessage, accessToken, done);
|
sendChatMessage(testVisibilityMessage, accessToken, done);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('verify we can make API call to mark message as hidden', async (done) => {
|
test('verify we can make API call to mark message as hidden', async (done) => {
|
||||||
const registration = await registerChat();
|
const registration = await registerChat();
|
||||||
const accessToken = registration.accessToken;
|
const accessToken = registration.accessToken;
|
||||||
const ws = new WebSocket(
|
const ws = new WebSocket(
|
||||||
`ws://localhost:8080/ws?accessToken=${accessToken}`,
|
`ws://localhost:8080/ws?accessToken=${accessToken}`,
|
||||||
{
|
{
|
||||||
origin: 'http://localhost:8080',
|
origin: 'http://localhost:8080',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify the visibility change comes through the websocket
|
// Verify the visibility change comes through the websocket
|
||||||
ws.on('message', async function incoming(message) {
|
ws.on('message', async function incoming(message) {
|
||||||
const messages = message.split('\n');
|
const messages = message.split('\n');
|
||||||
messages.forEach(async function (message) {
|
messages.forEach(async function (message) {
|
||||||
const event = JSON.parse(message);
|
const event = JSON.parse(message);
|
||||||
|
|
||||||
if (event.type === 'VISIBILITY-UPDATE') {
|
if (event.type === 'VISIBILITY-UPDATE') {
|
||||||
ws.close();
|
ws.close();
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.get('/api/admin/chat/messages')
|
.get('/api/admin/chat/messages')
|
||||||
.auth('admin', 'abc123')
|
.auth('admin', 'abc123')
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
const message = res.body[0];
|
const message = res.body[0];
|
||||||
messageId = message.id;
|
messageId = message.id;
|
||||||
await request
|
await request
|
||||||
.post('/api/admin/chat/updatemessagevisibility')
|
.post('/api/admin/chat/messagevisibility')
|
||||||
.auth('admin', 'abc123')
|
.auth('admin', 'abc123')
|
||||||
.send({ idArray: [messageId], visible: false })
|
.send({ idArray: [messageId], visible: false })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('verify message has become hidden', async (done) => {
|
test('verify message has become hidden', async (done) => {
|
||||||
await new Promise((r) => setTimeout(r, 2000));
|
await new Promise((r) => setTimeout(r, 2000));
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.get('/api/admin/chat/messages')
|
.get('/api/admin/chat/messages')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.auth('admin', 'abc123');
|
.auth('admin', 'abc123');
|
||||||
|
|
||||||
const message = res.body.filter((obj) => {
|
const message = res.body.filter((obj) => {
|
||||||
return obj.id === messageId;
|
return obj.id === messageId;
|
||||||
});
|
});
|
||||||
expect(message.length).toBe(1);
|
expect(message.length).toBe(1);
|
||||||
// expect(message[0].hiddenAt).toBeTruthy();
|
// expect(message[0].hiddenAt).toBeTruthy();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can enable established chat user mode', async (done) => {
|
test('can enable established chat user mode', async (done) => {
|
||||||
await request
|
await request
|
||||||
.post('/api/admin/config/chat/establishedusermode')
|
.post('/api/admin/config/chat/establishedusermode')
|
||||||
.auth('admin', 'abc123')
|
.auth('admin', 'abc123')
|
||||||
.send({ value: true })
|
.send({ value: true })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can send a message after established user mode is enabled', async (done) => {
|
test('can send a message after established user mode is enabled', async (done) => {
|
||||||
const registration = await registerChat();
|
const registration = await registerChat();
|
||||||
const accessToken = registration.accessToken;
|
const accessToken = registration.accessToken;
|
||||||
|
|
||||||
sendChatMessage(establishedUserFailedChatMessage, accessToken, done);
|
sendChatMessage(establishedUserFailedChatMessage, accessToken, done);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('verify rejected message is not in the chat feed', async (done) => {
|
test('verify rejected message is not in the chat feed', async (done) => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.get('/api/admin/chat/messages')
|
.get('/api/admin/chat/messages')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.auth('admin', 'abc123');
|
.auth('admin', 'abc123');
|
||||||
|
|
||||||
const message = res.body.filter((obj) => {
|
const message = res.body.filter((obj) => {
|
||||||
return obj.body === establishedUserFailedChatMessage.body;
|
return obj.body === establishedUserFailedChatMessage.body;
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(message.length).toBe(0);
|
expect(message.length).toBe(0);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can disable established chat user mode', async (done) => {
|
test('can disable established chat user mode', async (done) => {
|
||||||
await request
|
await request
|
||||||
.post('/api/admin/config/chat/establishedusermode')
|
.post('/api/admin/config/chat/establishedusermode')
|
||||||
.auth('admin', 'abc123')
|
.auth('admin', 'abc123')
|
||||||
.send({ value: false })
|
.send({ value: false })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|||||||
Generated
+216
-872
File diff suppressed because it is too large
Load Diff
Generated
+254
-897
File diff suppressed because it is too large
Load Diff
Generated
+3
-3
@@ -1188,9 +1188,9 @@
|
|||||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||||
},
|
},
|
||||||
"moment": {
|
"moment": {
|
||||||
"version": "2.29.2",
|
"version": "2.29.4",
|
||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||||
"integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg=="
|
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
|
||||||
},
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const URL_CONFIG = `/api/config`;
|
|||||||
export const URL_VIEWER_PING = `/api/ping`;
|
export const URL_VIEWER_PING = `/api/ping`;
|
||||||
|
|
||||||
// inline moderation actions
|
// inline moderation actions
|
||||||
export const URL_HIDE_MESSAGE = `/api/chat/updatemessagevisibility`;
|
export const URL_HIDE_MESSAGE = `/api/chat/messagevisibility`;
|
||||||
export const URL_BAN_USER = `/api/chat/users/setenabled`;
|
export const URL_BAN_USER = `/api/chat/users/setenabled`;
|
||||||
|
|
||||||
// TODO: This directory is customizable in the config. So we should expose this via the config API.
|
// TODO: This directory is customizable in the config. So we should expose this via the config API.
|
||||||
|
|||||||
@@ -3,132 +3,133 @@ import htm from '/js/web_modules/htm.js';
|
|||||||
import { URL_FOLLOWERS } from '/js/utils/constants.js';
|
import { URL_FOLLOWERS } from '/js/utils/constants.js';
|
||||||
const html = htm.bind(h);
|
const html = htm.bind(h);
|
||||||
export default class FollowerList extends Component {
|
export default class FollowerList extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
followers: [],
|
followers: [],
|
||||||
followersPage: 0,
|
followersPage: 0,
|
||||||
currentPage: 0,
|
currentPage: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
try {
|
try {
|
||||||
this.getFollowers();
|
this.getFollowers();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('followers error: ', e);
|
console.error('followers error: ', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFollowers() {
|
async getFollowers(requestedPage) {
|
||||||
const { currentPage } = this.state;
|
const limit = 24;
|
||||||
const limit = 24;
|
const offset = requestedPage * limit;
|
||||||
const offset = currentPage * limit;
|
const u = `${URL_FOLLOWERS}?offset=${offset || 0}&limit=${limit}`;
|
||||||
const u = `${URL_FOLLOWERS}?offset=${offset}&limit=${limit}`;
|
const response = await fetch(u);
|
||||||
const response = await fetch(u);
|
const followers = await response.json();
|
||||||
const followers = await response.json();
|
const pages = Math.ceil(followers.total / limit);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
followers: followers.results,
|
followers: followers.results,
|
||||||
total: response.total,
|
total: followers.total,
|
||||||
});
|
pages: pages,
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
changeFollowersPage(page) {
|
changeFollowersPage(requestedPage) {
|
||||||
this.setState({ currentPage: page });
|
this.setState({ currentPage: requestedPage });
|
||||||
this.getFollowers();
|
this.getFollowers(requestedPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { followers, total, currentPage } = this.state;
|
const { followers, total, pages, currentPage } = this.state;
|
||||||
if (!followers) {
|
if (!followers) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const noFollowersInfo = html`<div class="col-span-4">
|
const noFollowersInfo = html`<div class="col-span-4">
|
||||||
<p class="mb-5 text-2xl">Be the first to follow this live stream.</p>
|
<p class="mb-5 text-2xl">Be the first to follow this live stream.</p>
|
||||||
<p class="text-md">
|
<p class="text-md">
|
||||||
By following this stream you'll get updates when it goes live, receive
|
By following this stream you'll get updates when it goes live, receive
|
||||||
posts from the streamer, and be featured here as a follower.
|
posts from the streamer, and be featured here as a follower.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-md mt-5">
|
<p class="text-md mt-5">
|
||||||
Learn more about ${' '}
|
Learn more about ${' '}
|
||||||
<a class="underline" href="https://en.wikipedia.org/wiki/Fediverse"
|
<a class="underline" href="https://en.wikipedia.org/wiki/Fediverse"
|
||||||
>The Fediverse</a
|
>The Fediverse</a
|
||||||
>, where you can follow this server as well as so much more.
|
>, where you can follow this server as well as so much more.
|
||||||
</p>
|
</p>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
const paginationControls =
|
const paginationControls =
|
||||||
total > 1 &&
|
pages > 1 &&
|
||||||
Array(total)
|
Array(pages)
|
||||||
.fill()
|
.fill()
|
||||||
.map((x, n) => {
|
.map((x, n) => {
|
||||||
const activePageClass =
|
const activePageClass =
|
||||||
n === currentPage &&
|
n === currentPage &&
|
||||||
'bg-indigo-600 rounded-full shadow-md focus:shadow-md text-white';
|
'bg-indigo-600 rounded-full shadow-md focus:shadow-md text-white';
|
||||||
return html` <li class="page-item active w-10">
|
return html` <li class="page-item active w-10">
|
||||||
<a
|
<a
|
||||||
class="page-link relative block cursor-pointer hover:no-underline py-1.5 px-3 border-0 rounded-full hover:text-gray-800 hover:bg-gray-200 outline-none transition-all duration-300 ${activePageClass}"
|
class="page-link relative block cursor-pointer hover:no-underline py-1.5 px-3 border-0 rounded-full hover:text-gray-800 hover:bg-gray-200 outline-none transition-all duration-300 ${activePageClass}"
|
||||||
onClick=${() => this.changeFollowersPage(n)}
|
onClick=${() => this.changeFollowersPage(n)}
|
||||||
>
|
>
|
||||||
${n + 1}
|
${n + 1}
|
||||||
</a>
|
</a>
|
||||||
</li>`;
|
</li>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
|
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
|
||||||
>
|
>
|
||||||
${followers.length === 0 && noFollowersInfo}
|
${followers.length === 0 && noFollowersInfo}
|
||||||
${followers.map((follower) => {
|
${followers.map((follower) => {
|
||||||
return html` <${SingleFollower} user=${follower} /> `;
|
return html` <${SingleFollower} user=${follower} /> `;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<nav aria-label="Tab pages">
|
<nav aria-label="Tab pages">
|
||||||
<ul class="flex list-style-none flex-wrap">
|
<ul class="flex list-style-none flex-wrap">
|
||||||
${paginationControls}
|
${paginationControls}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function SingleFollower(props) {
|
function SingleFollower(props) {
|
||||||
const { user } = props;
|
const { user } = props;
|
||||||
const { name, username, link, image } = user;
|
const { name, username, link, image } = user;
|
||||||
|
|
||||||
var displayName = name;
|
var displayName = name;
|
||||||
var displayUsername = username;
|
var displayUsername = username;
|
||||||
|
|
||||||
if (!displayName) {
|
if (!displayName) {
|
||||||
displayName = displayUsername.split('@', 1)[0];
|
displayName = displayUsername.split('@', 1)[0];
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<a
|
<a
|
||||||
href=${link}
|
href=${link}
|
||||||
class="following-list-follower block bg-white flex p-2 rounded-xl shadow border hover:no-underline mb-3 mr-3"
|
class="following-list-follower block bg-white flex p-2 rounded-xl shadow border hover:no-underline mb-3 mr-3"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="${image || '/img/logo.svg'}"
|
src="${image || '/img/logo.svg'}"
|
||||||
class="w-16 h-16 rounded-full"
|
class="w-16 h-16 rounded-full"
|
||||||
onError=${({ currentTarget }) => {
|
onError=${({ currentTarget }) => {
|
||||||
currentTarget.onerror = null;
|
currentTarget.onerror = null;
|
||||||
currentTarget.src = '/img/logo.svg';
|
currentTarget.src = '/img/logo.svg';
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div class="p-3 truncate flex-grow">
|
<div class="p-3 truncate flex-grow">
|
||||||
<p class="font-semibold text-gray-700 truncate">${displayName}</p>
|
<p class="font-semibold text-gray-700 truncate">${displayName}</p>
|
||||||
<p class="text-sm text-gray-500 truncate">${displayUsername}</p>
|
<p class="text-sm text-gray-500 truncate">${displayUsername}</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user