Porównaj commity

...

12 Commity

Autor SHA1 Wiadomość Data
Alyx 51d6e458d8
Merge 9721925e28 into 7376cb1e99 2024-04-19 15:18:40 +00:00
Alyx 9721925e28
Add basic instruction to run the container 2024-04-19 17:18:31 +02:00
Alyx e694de6255
Add Docker GHA
- As the build now happens inside the container, I thought it was a good idea to extract it from the image instead of rebuilding it on the host
- QEMU and Buildx are just stuff for multiplatform builds (even if it's just a bunch of static files, I don't think docker is able to detect that the base image (nginx) exists in multiplatform, I might be wrong
- Add actions to extract metadata in order to tag the image
- Add actions to logins to Docker Hub and GitHub Registry
- Build and push to both registry, with our previously created tags
- Build only, locally, the "artifacts" stage, where we can extract the zip files. As it's reusing the previous stages, it won't be build twice.
- Extract the artifacts so the previous release workflow can keep on going (as it should!)
2024-04-19 17:08:30 +02:00
Alyx 0f5f8dfd0f
Update Dockerfile:
- The build now happens in the container itself, uisng node:20-alpine as the build stage
- A special artifacts stage is created (but won't be used unless specifically called) to create the archive files, used as the releases
- Switched from httpd to nginx

The image is still very light (16.9MB)
2024-04-19 16:59:27 +02:00
Alyx e2228bfc8f
Add .dockerignore 2024-04-19 16:55:39 +02:00
Lim Chee Aun 7376cb1e99 Fix muted="false" means still muted 🤦‍♂️🤦‍♂️🤦‍♂️ 2024-04-19 08:46:10 +08:00
Lim Chee Aun ffbae70178 Remove newline from regex for shortcode 2024-04-19 08:41:16 +08:00
Lim Chee Aun 9235d2c800 Hide poll button if maxOptions <= 1
It's not a poll if there's only 1 option
2024-04-18 23:12:29 +08:00
Lim Chee Aun 6ccefaebe1 Handle invalid date
Ugly solution for now, but it's already ugly
2024-04-18 23:11:18 +08:00
Lim Chee Aun 5a448c8049 Fix infinite reloading
Comment these out because this used to fix an old bug with instances not loaded properly
2024-04-18 23:10:26 +08:00
Lim Chee Aun 9bf77fa97a Mentions also need fixNotifications
It's also from notifications API
2024-04-18 17:15:51 +08:00
Alyx fa196a2c94
Add Dockerfile 2024-03-18 09:52:29 +01:00
10 zmienionych plików z 198 dodań i 34 usunięć

38
.dockerignore 100644
Wyświetl plik

@ -0,0 +1,38 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Custom
.env.dev
phanpy-dist.zip
phanpy-dist.tar.gz
dist/
node_modules/
.github/
readme-assets/
README.md
.gitignore
.prettierrc
Dockerfile

Wyświetl plik

@ -10,19 +10,84 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- uses: actions/checkout@v4
with:
ref: production
# - run: git tag "`date +%Y.%m.%d`.`git rev-parse --short HEAD`" $(git rev-parse HEAD)
# - run: git push --tags
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm ci && npm run build
- run: cd dist && zip -r ../phanpy-dist.zip . && tar -czf ../phanpy-dist.tar.gz . && cd ..
- id: tag_name
run: echo ::set-output name=tag_name::$(date +%Y.%m.%d).$(git rev-parse --short HEAD)
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# @cheeaun: If you want to check out other ways to tag your Docker image:
# https://github.com/docker/metadata-action/blob/master/README.md
# I kept "tag_name" as the tag name for the Docker image for now
- name: Extract metadata for the Docker image
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ github.repository }}
ghcr.io/${{ github.repository }}
tags: |
type=raw,value=${{ steps.tag_name.outputs.tag_name }}
# @cheeaun: I think deploying to Docker Hub and GitHub is a good idea, to always have a fallback
# - name: Login to Docker Hub
# uses: docker/login-action@v3
# with:
# username: ${{ secrets.DOCKERHUB_USERNAME }}
# password: ${{ secrets.DOCKERHUB_TOKEN }}
# Source: https://github.com/docker/login-action?tab=readme-ov-file#github-container-registry
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
# @cheeaun: I think this is a good idea to support multiple architectures
# Basically here: any Windows, Mac or Linux computers, and 32-bits Raspberry Pi
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
load: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Extract artifacts from the Docker image
uses: docker/build-push-action@v5
with:
context: .
# @cheeaun: And this is where we extract the artifacts from the Docker image
# The reason I'm extracting it this way, is that you don't depend on anything else than docker,
# and you don't always know if your CI runner will have the tools to zip or tar a directory.
push: false
load: true
tags: ${{ github.repository }}:artifacts-latest
cache-from: type=gha
cache-to: type=gha,mode=max
# Copy the artifacts files from the Docker container to the host
- run: |
docker create --name phanpy-artifacts ${{ github.repository }}:artifacts-latest
docker cp -q phanpy-artifacts:/root/phanpy/latest.zip ./dist/phanpy-dist.zip
docker cp -q phanpy-artifacts:/root/phanpy/latest.tar.gz ./dist/phanpy-dist.tar.gz
docker rm phanpy-artifacts
- uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.tag_name.outputs.tag_name }}

36
Dockerfile 100644
Wyświetl plik

@ -0,0 +1,36 @@
#############################################
# Install everything to build the application
#############################################
FROM node:20-alpine AS build
WORKDIR /root/phanpy
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
##################################################
# Special stage to easily extract the app as a zip
##################################################
FROM alpine:3 AS artifacts
WORKDIR /root/phanpy
RUN apk add zip
COPY --from=build /root/phanpy/dist /root/phanpy/dist
# Outputs:
# - /root/phanpy/latest.zip
# - /root/phanpy/latest.tar.gz
RUN zip -r /root/phanpy/latest.zip dist && \
tar -czf /root/phanpy/latest.tar.gz dist
#####################################################
# Copy the static files to a mininal web server image
#####################################################
FROM nginx:1-alpine-slim
ENV NGINX_ENTRYPOINT_QUIET_LOGS=1
COPY --chown=static:static --from=build /root/phanpy/dist /usr/share/nginx/html

Wyświetl plik

@ -1,10 +1,10 @@
<div align="center">
<img src="design/logo-4.svg" width="128" height="128" alt="">
Phanpy
===
# Phanpy
**Minimalistic opinionated Mastodon web client.**
</div>
![Fancy screenshot](readme-assets/fancy-screenshot.jpg)
@ -55,7 +55,7 @@ Everything is designed and engineered following my taste and vision. This is a p
- On the timeline, the user name is displayed as `[NAME] @[username]`.
- For the `@[username]`, always exclude the instance domain name.
- If the `[NAME]` *looks the same* as the `@[username]`, then the `@[username]` is excluded as well.
- If the `[NAME]` _looks the same_ as the `@[username]`, then the `@[username]` is excluded as well.
### Boosts Carousel
@ -123,17 +123,29 @@ Some of these may change in the future. The front-end world is ever-changing.
This is a **pure static web app**. You can host it anywhere you want.
Two ways (choose one):
Some examples:
### Easy way
### Using pre-built releases
Go to [Releases](https://github.com/cheeaun/phanpy/releases) and download the latest `phanpy-dist.zip` or `phanpy-dist.tar.gz`. It's pre-built so don't need to run any install/build commands. Extract it. Serve the folder of extracted files.
### Using a Docker image
In your terminal, run:
```
$ docker run -d -p 8080:80 cheeaun/phanpy
```
Go to http://localhost:8080 and 🎉
Make sure to deploy the web app using a reverse proxy that make the connection secure (using HTTPS).
### Custom-build way
Requires [Node.js](https://nodejs.org/).
Download or `git clone` this repository. Use `production` branch for *stable* releases, `main` for *latest*. Build it by running `npm run build` (after `npm install`). Serve the `dist` folder.
Download or `git clone` this repository. Use `production` branch for _stable_ releases, `main` for _latest_. Build it by running `npm run build` (after `npm install`). Serve the `dist` folder.
Customization can be done by passing environment variables to the build command. Examples:
@ -222,7 +234,7 @@ Costs involved in running and developing this web app:
## Mascot
[Phanpy](https://bulbapedia.bulbagarden.net/wiki/Phanpy_(Pok%C3%A9mon)) is a Ground-type Pokémon.
[Phanpy](<https://bulbapedia.bulbagarden.net/wiki/Phanpy_(Pok%C3%A9mon)>) is a Ground-type Pokémon.
## Maintainers + contributors

Wyświetl plik

@ -131,7 +131,7 @@ const HASHTAG_RE = new RegExp(
// https://github.com/mastodon/mastodon/blob/23e32a4b3031d1da8b911e0145d61b4dd47c4f96/app/models/custom_emoji.rb#L31
const SHORTCODE_RE_FRAGMENT = '[a-zA-Z0-9_]{2,}';
const SCAN_RE = new RegExp(
`([^A-Za-z0-9_:\\n]|^)(:${SHORTCODE_RE_FRAGMENT}:)(?=[^A-Za-z0-9_:]|$)`,
`(^|[^=\\/\\w])(:${SHORTCODE_RE_FRAGMENT}:)(?=[^A-Za-z0-9_:]|$)`,
'g',
);
@ -1219,22 +1219,30 @@ function Compose({
/>
<Icon icon="attachment" />
</label>{' '}
<button
type="button"
class="toolbar-button"
disabled={
uiState === 'loading' || !!poll || !!mediaAttachments.length
}
onClick={() => {
setPoll({
options: ['', ''],
expiresIn: 24 * 60 * 60, // 1 day
multiple: false,
});
}}
>
<Icon icon="poll" alt="Add poll" />
</button>{' '}
{/* If maxOptions is not defined or defined and is greater than 1, show poll button */}
{maxOptions == null ||
(maxOptions > 1 && (
<>
<button
type="button"
class="toolbar-button"
disabled={
uiState === 'loading' ||
!!poll ||
!!mediaAttachments.length
}
onClick={() => {
setPoll({
options: ['', ''],
expiresIn: 24 * 60 * 60, // 1 day
multiple: false,
});
}}
>
<Icon icon="poll" alt="Add poll" />
</button>{' '}
</>
))}
<button
type="button"
class="toolbar-button"

Wyświetl plik

@ -388,7 +388,7 @@ function Media({
data-orientation="${orientation}"
preload="auto"
autoplay
muted="${isGIF}"
${isGIF ? 'muted' : ''}
${isGIF ? '' : 'controls'}
playsinline
loop="${loopable}"

Wyświetl plik

@ -21,6 +21,7 @@ export default function RelativeTime({ datetime, format }) {
const [renderCount, rerender] = useReducer((x) => x + 1, 0);
const date = useMemo(() => dayjs(datetime), [datetime]);
const [dateStr, dt, title] = useMemo(() => {
if (!date.isValid()) return ['' + datetime, '', ''];
let str;
if (format === 'micro') {
// If date <= 1 day ago or day is within this year
@ -37,6 +38,7 @@ export default function RelativeTime({ datetime, format }) {
}, [date, format, renderCount]);
useEffect(() => {
if (!date.isValid()) return;
let timeout;
let raf;
function rafRerender() {

Wyświetl plik

@ -4,6 +4,7 @@ import { useSearchParams } from 'react-router-dom';
import Link from '../components/link';
import Timeline from '../components/timeline';
import { api } from '../utils/api';
import { fixNotifications } from '../utils/group-notifications';
import { saveStatus } from '../utils/states';
import useTitle from '../utils/useTitle';
@ -30,6 +31,8 @@ function Mentions({ columnMode, ...props }) {
const results = await mentionsIterator.current.next();
let { value } = results;
if (value?.length) {
value = fixNotifications(value);
if (firstLoad) {
latestItem.current = value[0].id;
console.log('First load', latestItem.current);

Wyświetl plik

@ -9,7 +9,7 @@ const notificationTypeKeys = {
poll: ['status'],
update: ['status'],
};
function fixNotifications(notifications) {
export function fixNotifications(notifications) {
return notifications.filter((notification) => {
const { type, id, createdAt } = notification;
if (!type) {

Wyświetl plik

@ -107,10 +107,10 @@ export function getCurrentInstance() {
return (currentInstance = instances[instance]);
} catch (e) {
console.error(e);
alert(`Failed to load instance configuration. Please try again.\n\n${e}`);
// alert(`Failed to load instance configuration. Please try again.\n\n${e}`);
// Temporary fix for corrupted data
store.local.del('instances');
location.reload();
// store.local.del('instances');
// location.reload();
return {};
}
}