mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-14 14:07:38 -07:00
Merge branch 'develop' of codeberg.org:calckey/calckey into develop
This commit is contained in:
commit
179bf05af2
12 changed files with 135 additions and 68 deletions
12
CALCKEY.md
12
CALCKEY.md
|
@ -11,7 +11,7 @@
|
|||
- Federate with note edits
|
||||
- User "choices" (recommended users) like Mastodon and Soapbox
|
||||
- Join Reason system like Mastodon/Pleroma
|
||||
- Option to publicize instance blocks
|
||||
- Option to publicize server blocks
|
||||
- Build flag to remove NSFW/AI stuff
|
||||
- Filter notifications by user
|
||||
- Exclude self from antenna
|
||||
|
@ -19,7 +19,7 @@
|
|||
- MFM button
|
||||
- Personal notes for all accounts
|
||||
- Fully revamp non-logged-in screen
|
||||
- Lookup/details for post/file/instance
|
||||
- Lookup/details for post/file/server
|
||||
- [Rat mode?](https://stop.voring.me/notes/933fx97bmd)
|
||||
|
||||
## Work in progress
|
||||
|
@ -43,7 +43,7 @@
|
|||
- Upgrade packages with security vunrabilities
|
||||
- Saner defaults
|
||||
- Fediverse account migration
|
||||
- Recommended instances timeline
|
||||
- Recommended servers timeline
|
||||
- OCR image captioning
|
||||
- Improve mobile UX
|
||||
- Swipe through pages on mobile
|
||||
|
@ -71,7 +71,7 @@
|
|||
- Better welcome screen (not logged in)
|
||||
- vue-plyr as video/audio player
|
||||
- Ability to turn off "Connection lost" message
|
||||
- Raw instance info only for moderators
|
||||
- Raw server info only for moderators
|
||||
- New spinner animation
|
||||
- Spinner instead of "Loading..."
|
||||
- SearchX instead of Google
|
||||
|
@ -98,7 +98,7 @@
|
|||
- Obliteration of Ai-chan
|
||||
- Switch to [Calckey.js](https://codeberg.org/calckey/calckey.js)
|
||||
- Woozy mode 🥴
|
||||
- Improve blocking instances
|
||||
- Improve blocking servers
|
||||
- Release notes
|
||||
- New post style
|
||||
- Admins set default reaction emoji
|
||||
|
@ -117,7 +117,7 @@
|
|||
- Sonic search
|
||||
- Popular color schemes, including Nord, Gruvbox, and Catppuccin
|
||||
- Non-nyaify cat mode
|
||||
- Post imports from other Calckey/Misskey/Mastodon/Pleroma/Akkoma instances
|
||||
- Post imports from other Calckey/Misskey/Mastodon/Pleroma/Akkoma servers
|
||||
- Improve Classic mode
|
||||
- Proper Helm/Kubernetes config
|
||||
- Multiple boost visibilities
|
||||
|
|
18
README.md
18
README.md
|
@ -23,15 +23,15 @@
|
|||
# ✨ About Calckey
|
||||
|
||||
- Calckey is based off of Misskey, a powerful microblogging server on ActivityPub with features such as emoji reactions, a customizable web UI, rich chatting, and much more!
|
||||
- Calckey adds many quality of life changes and bug fixes for users and instance admins alike.
|
||||
- Calckey adds many quality of life changes and bug fixes for users and server admins alike.
|
||||
- Read **[this document](./CALCKEY.md)** all for current and future differences.
|
||||
- Notable differences:
|
||||
- Improved UI/UX (especially on mobile)
|
||||
- Improved notifications
|
||||
- Improved instance security
|
||||
- Improved server security
|
||||
- Improved accessibility
|
||||
- Improved threads
|
||||
- Recommended Instances timeline
|
||||
- Recommended Servers timeline
|
||||
- OCR image captioning
|
||||
- New and improved Groups
|
||||
- Better intro tutorial
|
||||
|
@ -50,10 +50,10 @@
|
|||
- 💸 OpenCollective: <https://opencollective.com/Calckey>
|
||||
- 💸 Liberapay: <https://liberapay.com/ThatOneCalculator>
|
||||
- Donate publicly to get your name on the Patron list!
|
||||
- 🚢 Flagship instance: <https://calckey.social>
|
||||
- 🚢 Flagship server: <https://calckey.social>
|
||||
- 📣 Official account: <https://i.calckey.cloud/@calckey>
|
||||
- 💁 Matrix support room: <https://matrix.to/#/#calckey:matrix.fedibird.com>
|
||||
- 📜 Instance list: <https://calckey.fediverse.observer/list>
|
||||
- 📜 Server list: <https://calckey.fediverse.observer/list>
|
||||
- 📖 JoinFediverse Wiki: <https://joinfediverse.wiki/What_is_Calckey%3F>
|
||||
- 🐋 Docker Hub: <https://hub.docker.com/r/thatonecalculator/calckey>
|
||||
- ✍️ Weblate: <https://hosted.weblate.org/engage/calckey/>
|
||||
|
@ -177,13 +177,13 @@ Please don't use ElasticSearch unless you already have an ElasticSearch setup an
|
|||
## 💅 Customize
|
||||
|
||||
- To add custom CSS for all users, edit `./custom/assets/instance.css`.
|
||||
- To add static assets (such as images for the splash screen), place them in the `./custom/assets/` directory. They'll then be available on `https://yourinstance.tld/static-assets/filename.ext`.
|
||||
- To add static assets (such as images for the splash screen), place them in the `./custom/assets/` directory. They'll then be available on `https://yourserver.tld/static-assets/filename.ext`.
|
||||
- To add custom locales, place them in the `./custom/locales/` directory. If you name your custom locale the same as an existing locale, it will overwrite it. If you give it a unique name, it will be added to the list. Also make sure that the first part of the filename matches the locale you're basing it on. (Example: `en-FOO.yml`)
|
||||
- To add custom error images, place them in the `./custom/assets/badges` directory, replacing the files already there.
|
||||
- To add custom sounds, place only mp3 files in the `./custom/assets/sounds` directory.
|
||||
- To update custom assets without rebuilding, just run `pnpm run gulp`.
|
||||
|
||||
## 🧑🔬 Configuring a new instance
|
||||
## 🧑🔬 Configuring a new server
|
||||
|
||||
- Run `cp .config/example.yml .config/default.yml`
|
||||
- Edit `.config/default.yml`, making sure to fill out required fields.
|
||||
|
@ -198,7 +198,7 @@ For migrating from Misskey v13, Misskey v12, and Foundkey, read [this document](
|
|||
### 🍀 Nginx (recommended)
|
||||
|
||||
- Run `sudo cp ./calckey.nginx.conf /etc/nginx/sites-available/ && cd /etc/nginx/sites-available/`
|
||||
- Edit `calckey.nginx.conf` to reflect your instance properly
|
||||
- Edit `calckey.nginx.conf` to reflect your server properly
|
||||
- Run `sudo ln -s ./calckey.nginx.conf ../sites-enabled/calckey.nginx.conf`
|
||||
- Run `sudo nginx -t` to validate that the config is valid, then restart the NGINX service.
|
||||
|
||||
|
@ -218,7 +218,7 @@ example.tld {
|
|||
> Apache has some known problems with Calckey. Only use it if you have to.
|
||||
|
||||
- Run `sudo cp ./calckey.apache.conf /etc/apache2/sites-available/ && cd /etc/apache2/sites-available/`
|
||||
- Edit `calckey.apache.conf` to reflect your instance properly
|
||||
- Edit `calckey.apache.conf` to reflect your server properly
|
||||
- Run `sudo a2ensite calckey.apache` to enable the site
|
||||
- Run `sudo service apache2 restart` to reload apache2 configuration
|
||||
## 🚀 Build and launch!
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# 🐳 Running a Calckey instance with Docker
|
||||
# 🐳 Running a Calckey server with Docker
|
||||
|
||||
## Pre-built docker container
|
||||
[thatonecalculator/calckey](https://hub.docker.com/r/thatonecalculator/calckey)
|
||||
|
@ -8,7 +8,7 @@
|
|||
There is a `docker-compose.yml` in the root of the project that you can use to build the container from source
|
||||
|
||||
- .config/docker.env (**db config settings**)
|
||||
- .config/default.yml (**calckey instance settings**)
|
||||
- .config/default.yml (**calckey server settings**)
|
||||
|
||||
## Configuring
|
||||
|
||||
|
@ -20,7 +20,7 @@ Rename the files:
|
|||
|
||||
then edit them according to your environment.
|
||||
You can configure `docker.env` with anything you like, but you will have to pay attention to the `default.yml` file:
|
||||
- `url` should be set to the URL you will be hosting the web interface for the instance at.
|
||||
- `url` should be set to the URL you will be hosting the web interface for the server at.
|
||||
- `host`, `db`, `user`, `pass` will have to be configured in the `PostgreSQL configuration` section - `host` is the name of the postgres container (eg: *calckey_db_1*), and the others should match your `docker.env`.
|
||||
- `host`will need to be configured in the *Redis configuration* section - it is the name of the redis container (eg: *calckey_redis_1*)
|
||||
- `auth` will need to be configured in the *Sonic* section - cannot be the default `SecretPassword`
|
||||
|
@ -36,7 +36,7 @@ Copy `docker-compose.yml` and the `config/` to a directory, then run the **docke
|
|||
|
||||
NOTE: This will take some time to come fully online, even after download and extracting the container images, and it may emit some error messages before completing successfully. Specifically, the `db` container needs to initialize and so isn't available to the `web` container right away. Only once the `db` container comes online does the `web` container start building and initializing the calckey tables.
|
||||
|
||||
Once the instance is up you can use a web browser to access the web interface at `http://serverip:3000` (where `serverip` is the IP of the server you are running the calckey instance on).
|
||||
Once the server is up you can use a web browser to access the web interface at `http://serverip:3000` (where `serverip` is the IP of the server you are running the calckey server on).
|
||||
|
||||
## Docker for development
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Running a Calckey instance with Kubernetes and Helm
|
||||
# Running a Calckey server with Kubernetes and Helm
|
||||
|
||||
This is a [Helm](https://helm.sh/) chart directory in the root of the project
|
||||
that you can use to deploy calckey to a Kubernetes cluster
|
||||
|
@ -27,7 +27,7 @@ helm upgrade \
|
|||
-f .config/helm_values.yml
|
||||
```
|
||||
|
||||
4. Watch your calckey instance spin up:
|
||||
4. Watch your calckey server spin up:
|
||||
```shell
|
||||
kubectl -n calckey get po -w
|
||||
```
|
||||
|
|
|
@ -598,6 +598,8 @@ scratchpadDescription: "The scratchpad provides an environment for AiScript expe
|
|||
output: "Output"
|
||||
script: "Script"
|
||||
disablePagesScript: "Disable AiScript on Pages"
|
||||
expandOnNoteClick: "Open post on click"
|
||||
expandOnNoteClickDesc: "If disabled, you can still open posts in the right-click menu or by clicking the timestamp."
|
||||
updateRemoteUser: "Update remote user information"
|
||||
deleteAllFiles: "Delete all files"
|
||||
deleteAllFilesConfirm: "Are you sure that you want to delete all files?"
|
||||
|
|
|
@ -11,6 +11,7 @@ import { addFile } from "@/services/drive/add-file.js";
|
|||
import { genId } from "@/misc/gen-id.js";
|
||||
import { db } from "@/db/postgre.js";
|
||||
import probeImageSize from "probe-image-size";
|
||||
import * as path from "path";
|
||||
|
||||
const logger = queueLogger.createSubLogger("import-custom-emojis");
|
||||
|
||||
|
@ -29,11 +30,11 @@ export async function importCustomEmojis(
|
|||
return;
|
||||
}
|
||||
|
||||
const [path, cleanup] = await createTempDir();
|
||||
const [tempPath, cleanup] = await createTempDir();
|
||||
|
||||
logger.info(`Temp dir is ${path}`);
|
||||
logger.info(`Temp dir is ${tempPath}`);
|
||||
|
||||
const destPath = `${path}/emojis.zip`;
|
||||
const destPath = `${tempPath}/emojis.zip`;
|
||||
|
||||
try {
|
||||
fs.writeFileSync(destPath, "", "binary");
|
||||
|
@ -46,44 +47,96 @@ export async function importCustomEmojis(
|
|||
throw e;
|
||||
}
|
||||
|
||||
const outputPath = `${path}/emojis`;
|
||||
const outputPath = `${tempPath}/emojis`;
|
||||
const unzipStream = fs.createReadStream(destPath);
|
||||
const zip = new AdmZip(destPath);
|
||||
zip.extractAllToAsync(outputPath, true, false, async (error) => {
|
||||
if (error) throw error;
|
||||
const metaRaw = fs.readFileSync(`${outputPath}/meta.json`, "utf-8");
|
||||
const meta = JSON.parse(metaRaw);
|
||||
|
||||
for (const record of meta.emojis) {
|
||||
if (!record.downloaded) continue;
|
||||
const emojiInfo = record.emoji;
|
||||
const emojiPath = `${outputPath}/${record.fileName}`;
|
||||
await Emojis.delete({
|
||||
name: emojiInfo.name,
|
||||
});
|
||||
const driveFile = await addFile({
|
||||
user: null,
|
||||
path: emojiPath,
|
||||
name: record.fileName,
|
||||
force: true,
|
||||
});
|
||||
const file = fs.createReadStream(emojiPath);
|
||||
const size = await probeImageSize(file);
|
||||
file.destroy();
|
||||
await Emojis.insert({
|
||||
id: genId(),
|
||||
updatedAt: new Date(),
|
||||
name: emojiInfo.name,
|
||||
category: emojiInfo.category,
|
||||
host: null,
|
||||
aliases: emojiInfo.aliases,
|
||||
originalUrl: driveFile.url,
|
||||
publicUrl: driveFile.webpublicUrl ?? driveFile.url,
|
||||
type: driveFile.webpublicType ?? driveFile.type,
|
||||
license: emojiInfo.license,
|
||||
width: size.width || null,
|
||||
height: size.height || null,
|
||||
}).then((x) => Emojis.findOneByOrFail(x.identifiers[0]));
|
||||
if (fs.existsSync(`${outputPath}/meta.json`)) {
|
||||
logger.info("starting emoji import with metadata");
|
||||
const metaRaw = fs.readFileSync(`${outputPath}/meta.json`, "utf-8");
|
||||
const meta = JSON.parse(metaRaw);
|
||||
|
||||
for (const record of meta.emojis) {
|
||||
if (!record.downloaded) continue;
|
||||
const emojiInfo = record.emoji;
|
||||
const emojiPath = `${outputPath}/${record.fileName}`;
|
||||
await Emojis.delete({
|
||||
name: emojiInfo.name,
|
||||
});
|
||||
const driveFile = await addFile({
|
||||
user: null,
|
||||
path: emojiPath,
|
||||
name: record.fileName,
|
||||
force: true,
|
||||
});
|
||||
const file = fs.createReadStream(emojiPath);
|
||||
const size = await probeImageSize(file);
|
||||
file.destroy();
|
||||
await Emojis.insert({
|
||||
id: genId(),
|
||||
updatedAt: new Date(),
|
||||
name: emojiInfo.name,
|
||||
category: emojiInfo.category,
|
||||
host: null,
|
||||
aliases: emojiInfo.aliases,
|
||||
originalUrl: driveFile.url,
|
||||
publicUrl: driveFile.webpublicUrl ?? driveFile.url,
|
||||
type: driveFile.webpublicType ?? driveFile.type,
|
||||
license: emojiInfo.license,
|
||||
width: size.width || null,
|
||||
height: size.height || null,
|
||||
}).then((x) => Emojis.findOneByOrFail(x.identifiers[0]));
|
||||
}
|
||||
} else {
|
||||
logger.info("starting emoji import without metadata");
|
||||
// Since we lack metadata, we import into a randomized category name instead
|
||||
let categoryName = genId();
|
||||
|
||||
let containedEmojis = fs.readdirSync(outputPath);
|
||||
|
||||
// Filter out accidental JSON files
|
||||
containedEmojis = containedEmojis.filter(
|
||||
(emoji) => !emoji.match(/\.(json)$/i),
|
||||
);
|
||||
|
||||
for (const emojiFilename of containedEmojis) {
|
||||
// strip extension and get filename to use as name
|
||||
const name = path.basename(emojiFilename, path.extname(emojiFilename));
|
||||
const emojiPath = `${outputPath}/${emojiFilename}`;
|
||||
|
||||
logger.info(`importing ${name}`);
|
||||
|
||||
await Emojis.delete({
|
||||
name: name,
|
||||
});
|
||||
const driveFile = await addFile({
|
||||
user: null,
|
||||
path: emojiPath,
|
||||
name: path.basename(emojiFilename),
|
||||
force: true,
|
||||
});
|
||||
const file = fs.createReadStream(emojiPath);
|
||||
const size = await probeImageSize(file);
|
||||
file.destroy();
|
||||
logger.info(`emoji size: ${size.width}x${size.height}`);
|
||||
|
||||
await Emojis.insert({
|
||||
id: genId(),
|
||||
updatedAt: new Date(),
|
||||
name: name,
|
||||
category: categoryName,
|
||||
host: null,
|
||||
aliases: [],
|
||||
originalUrl: driveFile.url,
|
||||
publicUrl: driveFile.webpublicUrl ?? driveFile.url,
|
||||
type: driveFile.webpublicType ?? driveFile.type,
|
||||
license: null,
|
||||
width: size.width || null,
|
||||
height: size.height || null,
|
||||
}).then((x) => Emojis.findOneByOrFail(x.identifiers[0]));
|
||||
}
|
||||
}
|
||||
|
||||
await db.queryResultCache!.remove(["meta_emojis"]);
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
class="article"
|
||||
@contextmenu.stop="onContextmenu"
|
||||
@click="noteClick"
|
||||
:style="{ cursor: expandOnNoteClick && !detailedView ? 'pointer' : '' }"
|
||||
>
|
||||
<div class="main">
|
||||
<div class="header-container">
|
||||
|
@ -313,6 +314,7 @@ const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords));
|
|||
const translation = ref(null);
|
||||
const translating = ref(false);
|
||||
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
||||
|
||||
const keymap = {
|
||||
r: () => reply(true),
|
||||
|
@ -501,7 +503,7 @@ function scrollIntoView() {
|
|||
}
|
||||
|
||||
function noteClick(e) {
|
||||
if (document.getSelection().type === "Range" || props.detailedView) {
|
||||
if (document.getSelection().type === "Range" || props.detailedView || !expandOnNoteClick) {
|
||||
e.stopPropagation();
|
||||
} else {
|
||||
router.push(notePage(appearNote));
|
||||
|
@ -704,7 +706,6 @@ defineExpose({
|
|||
position: relative;
|
||||
overflow: clip;
|
||||
padding: 4px 32px 10px;
|
||||
cursor: pointer;
|
||||
|
||||
&:first-child,
|
||||
&:nth-child(2) {
|
||||
|
|
|
@ -534,12 +534,8 @@ onUnmounted(() => {
|
|||
|
||||
> .reply {
|
||||
border-top: solid 0.5px var(--divider);
|
||||
cursor: pointer;
|
||||
padding-top: 24px;
|
||||
padding-bottom: 10px;
|
||||
@media (pointer: coarse) {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
// Hover
|
||||
|
|
|
@ -14,7 +14,10 @@
|
|||
@contextmenu.stop="onContextmenu"
|
||||
>
|
||||
<div v-if="conversation && depth > 1" class="line"></div>
|
||||
<div class="main" @click="noteClick">
|
||||
<div class="main"
|
||||
@click="noteClick"
|
||||
:style="{ cursor: expandOnNoteClick ? 'pointer' : '' }"
|
||||
>
|
||||
<div class="avatar-container">
|
||||
<MkAvatar class="avatar" :user="appearNote.user" />
|
||||
<div
|
||||
|
@ -258,6 +261,7 @@ const replies: misskey.entities.Note[] =
|
|||
)
|
||||
.reverse() ?? [];
|
||||
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
||||
|
||||
useNoteCapture({
|
||||
rootEl: el,
|
||||
|
@ -397,7 +401,7 @@ function blur() {
|
|||
}
|
||||
|
||||
function noteClick(e) {
|
||||
if (document.getSelection().type === "Range") {
|
||||
if (document.getSelection().type === "Range" || !expandOnNoteClick) {
|
||||
e.stopPropagation();
|
||||
} else {
|
||||
router.push(notePage(props.note));
|
||||
|
@ -422,7 +426,6 @@ function noteClick(e) {
|
|||
|
||||
> .main {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
|
||||
> .avatar-container {
|
||||
margin-right: 8px;
|
||||
|
|
|
@ -168,10 +168,10 @@
|
|||
<i class="ph-stop ph-bold"></i> {{ i18n.ts._mfm.stop }}
|
||||
</template>
|
||||
</MkButton>
|
||||
<div
|
||||
<!-- <div
|
||||
v-if="(isLong && !collapsed) || (props.note.cw && showContent)"
|
||||
class="fade"
|
||||
></div>
|
||||
></div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -54,6 +54,10 @@
|
|||
<FormSwitch v-model="disablePagesScript" class="_formBlock">{{
|
||||
i18n.ts.disablePagesScript
|
||||
}}</FormSwitch>
|
||||
<FormSwitch v-model="expandOnNoteClick" class="_formBlock">{{
|
||||
i18n.ts.expandOnNoteClick
|
||||
}}<template #caption>{{ i18n.ts.expandOnNoteClickDesc }}</template>
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model="profile.showTimelineReplies" class="_formBlock"
|
||||
>{{ i18n.ts.flagShowTimelineReplies
|
||||
}}<template #caption
|
||||
|
@ -299,6 +303,9 @@ const nsfw = computed(defaultStore.makeGetterSetter("nsfw"));
|
|||
const disablePagesScript = computed(
|
||||
defaultStore.makeGetterSetter("disablePagesScript")
|
||||
);
|
||||
const expandOnNoteClick = computed(
|
||||
defaultStore.makeGetterSetter("expandOnNoteClick")
|
||||
);
|
||||
const showFixedPostForm = computed(
|
||||
defaultStore.makeGetterSetter("showFixedPostForm")
|
||||
);
|
||||
|
@ -366,6 +373,7 @@ watch(
|
|||
seperateRenoteQuote,
|
||||
showAdminUpdates,
|
||||
autoplayMfm,
|
||||
expandOnNoteClick,
|
||||
],
|
||||
async () => {
|
||||
await reloadAsk();
|
||||
|
|
|
@ -162,6 +162,10 @@ export const defaultStore = markRaw(
|
|||
where: "device",
|
||||
default: true,
|
||||
},
|
||||
expandOnNoteClick: {
|
||||
where: "device",
|
||||
default: true,
|
||||
},
|
||||
nsfw: {
|
||||
where: "device",
|
||||
default: "respect" as "respect" | "force" | "ignore",
|
||||
|
|
Loading…
Reference in a new issue