Merge branch 'develop' of codeberg.org:calckey/calckey into develop

This commit is contained in:
ThatOneCalculator 2023-06-02 14:52:58 -07:00
commit 179bf05af2
12 changed files with 135 additions and 68 deletions

View file

@ -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

View file

@ -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!

View file

@ -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

View file

@ -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
```

View file

@ -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?"

View file

@ -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"]);

View file

@ -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) {

View file

@ -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

View file

@ -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;

View file

@ -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>

View file

@ -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();

View file

@ -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",