mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-22 18:13:09 +02:00
Compare commits
54 commits
a133ff0d9b
...
eb606d0634
Author | SHA1 | Date | |
---|---|---|---|
|
eb606d0634 | ||
|
e0afeff248 | ||
|
cfc8081cec | ||
|
011ccd3a9a | ||
|
28065fc1d1 | ||
|
960f4fcff7 | ||
|
92eec2178f | ||
|
56dca6dbf5 | ||
|
7730472c77 | ||
|
de80727cfc | ||
|
2a634e0309 | ||
|
e6970a0e7c | ||
|
571272a564 | ||
|
30bb0f60a2 | ||
|
328546c4cd | ||
|
459e684117 | ||
|
f4e89f2e6b | ||
|
ccf5659ac3 | ||
|
8f300cf460 | ||
|
395ea9ab9f | ||
|
a0c63c8cc4 | ||
|
2cad97c1ab | ||
|
6ecfe7c7c3 | ||
|
23f476dbf3 | ||
|
7a1251423f | ||
|
7f5492a395 | ||
|
11d9fd9199 | ||
|
6132bc3b3e | ||
|
fef7a7b99a | ||
|
1948ca9aa8 | ||
|
848e1f9a56 | ||
|
9c4353ee79 | ||
|
a6e257f502 | ||
|
310e1a1262 | ||
|
15f3c046d1 | ||
|
01d695428a | ||
|
acf3e3460f | ||
|
4c8116859c | ||
|
0e13397db7 | ||
|
ad8818508f | ||
|
d444ee662f | ||
|
4c354fff2d | ||
|
b81448edf6 | ||
|
134d2895f0 | ||
|
7ba8fde9b9 | ||
|
1022280465 | ||
|
021d3924e6 | ||
|
b6d50d781f | ||
|
1d411bb885 | ||
|
f7afd1ae4a | ||
|
1ef1f2a03c | ||
|
829ce4f86a | ||
|
6d5d863150 | ||
|
fc7d4bc420 |
15 changed files with 121 additions and 10 deletions
|
@ -55,6 +55,8 @@ getImageTag:
|
||||||
only:
|
only:
|
||||||
- stable
|
- stable
|
||||||
- develop
|
- develop
|
||||||
|
- tags
|
||||||
|
|
||||||
buildDocker:
|
buildDocker:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
needs:
|
needs:
|
||||||
|
@ -78,6 +80,8 @@ buildDocker:
|
||||||
only:
|
only:
|
||||||
- stable
|
- stable
|
||||||
- develop
|
- develop
|
||||||
|
- tags
|
||||||
|
|
||||||
mergeManifests:
|
mergeManifests:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
needs:
|
needs:
|
||||||
|
@ -103,3 +107,4 @@ mergeManifests:
|
||||||
only:
|
only:
|
||||||
- stable
|
- stable
|
||||||
- develop
|
- develop
|
||||||
|
- tags
|
||||||
|
|
|
@ -688,6 +688,7 @@ channel: "Channels"
|
||||||
create: "Create"
|
create: "Create"
|
||||||
notificationSetting: "Notification settings"
|
notificationSetting: "Notification settings"
|
||||||
notificationSettingDesc: "Select the types of notification to display."
|
notificationSettingDesc: "Select the types of notification to display."
|
||||||
|
enableFaviconNotificationDot: "Enable favicon notification dot"
|
||||||
useGlobalSetting: "Use global settings"
|
useGlobalSetting: "Use global settings"
|
||||||
useGlobalSettingDesc: "If turned on, your account's notification settings will be used. If turned off, individual configurations can be made."
|
useGlobalSettingDesc: "If turned on, your account's notification settings will be used. If turned off, individual configurations can be made."
|
||||||
other: "Other"
|
other: "Other"
|
||||||
|
|
4
locales/index.d.ts
vendored
4
locales/index.d.ts
vendored
|
@ -2764,6 +2764,10 @@ export interface Locale extends ILocale {
|
||||||
* 表示する通知の種別を選択してください。
|
* 表示する通知の種別を選択してください。
|
||||||
*/
|
*/
|
||||||
"notificationSettingDesc": string;
|
"notificationSettingDesc": string;
|
||||||
|
/**
|
||||||
|
* ファビコン通知ドットを有効にする
|
||||||
|
*/
|
||||||
|
"enableFaviconNotificationDot": string;
|
||||||
/**
|
/**
|
||||||
* グローバル設定を使う
|
* グローバル設定を使う
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -687,6 +687,7 @@ channel: "チャンネル"
|
||||||
create: "作成"
|
create: "作成"
|
||||||
notificationSetting: "通知設定"
|
notificationSetting: "通知設定"
|
||||||
notificationSettingDesc: "表示する通知の種別を選択してください。"
|
notificationSettingDesc: "表示する通知の種別を選択してください。"
|
||||||
|
enableFaviconNotificationDot: "ファビコン通知ドットを有効にする"
|
||||||
useGlobalSetting: "グローバル設定を使う"
|
useGlobalSetting: "グローバル設定を使う"
|
||||||
useGlobalSettingDesc: "オンにすると、アカウントの通知設定が使用されます。オフにすると、個別に設定できるようになります。"
|
useGlobalSettingDesc: "オンにすると、アカウントの通知設定が使用されます。オフにすると、個別に設定できるようになります。"
|
||||||
other: "その他"
|
other: "その他"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "sharkey",
|
"name": "sharkey",
|
||||||
"version": "2024.3.1",
|
"version": "2024.3.2-devel",
|
||||||
"codename": "shonk",
|
"codename": "shonk",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -172,7 +172,7 @@
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"systeminformation": "5.22.0",
|
"systeminformation": "5.22.0",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tmp": "0.2.2",
|
"tmp": "0.2.3",
|
||||||
"tsc-alias": "1.8.8",
|
"tsc-alias": "1.8.8",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typeorm": "0.3.20",
|
"typeorm": "0.3.20",
|
||||||
|
|
|
@ -192,6 +192,7 @@ export class FileServerService {
|
||||||
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
|
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
|
||||||
reply.header('Accept-Ranges', 'bytes');
|
reply.header('Accept-Ranges', 'bytes');
|
||||||
reply.header('Content-Length', chunksize);
|
reply.header('Content-Length', chunksize);
|
||||||
|
reply.code(206);
|
||||||
} else {
|
} else {
|
||||||
image = {
|
image = {
|
||||||
data: fs.createReadStream(file.path),
|
data: fs.createReadStream(file.path),
|
||||||
|
@ -261,7 +262,6 @@ export class FileServerService {
|
||||||
const parts = range.replace(/bytes=/, '').split('-');
|
const parts = range.replace(/bytes=/, '').split('-');
|
||||||
const start = parseInt(parts[0], 10);
|
const start = parseInt(parts[0], 10);
|
||||||
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
|
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
|
||||||
console.log(end);
|
|
||||||
if (end > file.file.size) {
|
if (end > file.file.size) {
|
||||||
end = file.file.size - 1;
|
end = file.file.size - 1;
|
||||||
}
|
}
|
||||||
|
@ -431,6 +431,7 @@ export class FileServerService {
|
||||||
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
|
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
|
||||||
reply.header('Accept-Ranges', 'bytes');
|
reply.header('Accept-Ranges', 'bytes');
|
||||||
reply.header('Content-Length', chunksize);
|
reply.header('Content-Length', chunksize);
|
||||||
|
reply.code(206);
|
||||||
} else {
|
} else {
|
||||||
image = {
|
image = {
|
||||||
data: fs.createReadStream(file.path),
|
data: fs.createReadStream(file.path),
|
||||||
|
@ -527,6 +528,9 @@ export class FileServerService {
|
||||||
if (!file.storedInternal) {
|
if (!file.storedInternal) {
|
||||||
if (!(file.isLink && file.uri)) return '204';
|
if (!(file.isLink && file.uri)) return '204';
|
||||||
const result = await this.downloadAndDetectTypeFromUrl(file.uri);
|
const result = await this.downloadAndDetectTypeFromUrl(file.uri);
|
||||||
|
if (!file.size) {
|
||||||
|
file.size = (await fs.promises.stat(result.path)).size;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
url: file.uri,
|
url: file.uri,
|
||||||
|
|
|
@ -5,8 +5,8 @@ block vars
|
||||||
- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
|
- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
|
||||||
- const url = `${config.url}/notes/${note.id}`;
|
- const url = `${config.url}/notes/${note.id}`;
|
||||||
- const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
- const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
||||||
- const images = (note.files || []).filter(file => file.type.startsWith('image/') && !file.isSensitive)
|
- const images = note.cw ? [] : (note.files || []).filter(file => file.type.startsWith('image/') && !file.isSensitive)
|
||||||
- const videos = (note.files || []).filter(file => file.type.startsWith('video/') && !file.isSensitive)
|
- const videos = note.cw ? [] : (note.files || []).filter(file => file.type.startsWith('video/') && !file.isSensitive)
|
||||||
|
|
||||||
block title
|
block title
|
||||||
= `${title} | ${instanceName}`
|
= `${title} | ${instanceName}`
|
||||||
|
|
|
@ -112,6 +112,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<MkSwitch v-model="useGroupedNotifications">{{ i18n.ts.useGroupedNotifications }}</MkSwitch>
|
<MkSwitch v-model="useGroupedNotifications">{{ i18n.ts.useGroupedNotifications }}</MkSwitch>
|
||||||
|
|
||||||
|
<MkSwitch v-model="enableFaviconNotificationDot">{{ i18n.ts.enableFaviconNotificationDot }}</MkSwitch>
|
||||||
|
|
||||||
<MkRadios v-model="notificationPosition">
|
<MkRadios v-model="notificationPosition">
|
||||||
<template #label>{{ i18n.ts.position }}</template>
|
<template #label>{{ i18n.ts.position }}</template>
|
||||||
<option value="leftTop"><i class="ph-arrow-up-left ph-bold ph-lg"></i> {{ i18n.ts.leftTop }}</option>
|
<option value="leftTop"><i class="ph-arrow-up-left ph-bold ph-lg"></i> {{ i18n.ts.leftTop }}</option>
|
||||||
|
@ -337,6 +339,7 @@ const oneko = computed(defaultStore.makeGetterSetter('oneko'));
|
||||||
const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
|
const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
|
||||||
const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia'));
|
const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia'));
|
||||||
const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
|
const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
|
||||||
|
const enableFaviconNotificationDot = computed(defaultStore.makeGetterSetter('enableFaviconNotificationDot'));
|
||||||
const warnMissingAltText = computed(defaultStore.makeGetterSetter('warnMissingAltText'));
|
const warnMissingAltText = computed(defaultStore.makeGetterSetter('warnMissingAltText'));
|
||||||
const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
|
const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
|
||||||
const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm'));
|
const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm'));
|
||||||
|
|
|
@ -72,6 +72,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
|
||||||
'advancedMfm',
|
'advancedMfm',
|
||||||
'loadRawImages',
|
'loadRawImages',
|
||||||
'warnMissingAltText',
|
'warnMissingAltText',
|
||||||
|
'enableFaviconNotificationDot',
|
||||||
'imageNewTab',
|
'imageNewTab',
|
||||||
'dataSaver',
|
'dataSaver',
|
||||||
'disableShowingAnimatedImages',
|
'disableShowingAnimatedImages',
|
||||||
|
|
75
packages/frontend/src/scripts/favicon-dot.ts
Normal file
75
packages/frontend/src/scripts/favicon-dot.ts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
class FavIconDot {
|
||||||
|
canvas : HTMLCanvasElement;
|
||||||
|
src : string | null = null;
|
||||||
|
ctx : CanvasRenderingContext2D | null = null;
|
||||||
|
favconImage : HTMLImageElement | null = null;
|
||||||
|
faviconEL : HTMLLinkElement;
|
||||||
|
hasLoaded : Promise<void>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.canvas = document.createElement('canvas');
|
||||||
|
this.faviconEL = document.querySelector<HTMLLinkElement>('link[rel$=icon]') ?? this._createFaviconElem();
|
||||||
|
|
||||||
|
this.src = this.faviconEL.getAttribute('href');
|
||||||
|
this.ctx = this.canvas.getContext('2d');
|
||||||
|
|
||||||
|
this.favconImage = document.createElement('img');
|
||||||
|
this.hasLoaded = new Promise((resolve, _reject) => {
|
||||||
|
if (this.favconImage != null) {
|
||||||
|
this.favconImage.onload = () => {
|
||||||
|
this.canvas.width = (this.favconImage as HTMLImageElement).width;
|
||||||
|
this.canvas.height = (this.favconImage as HTMLImageElement).height;
|
||||||
|
// resolve();
|
||||||
|
setTimeout(() => resolve(), 200);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.favconImage.src = this.faviconEL.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createFaviconElem() {
|
||||||
|
const newLink = document.createElement('link');
|
||||||
|
newLink.rel = 'icon';
|
||||||
|
newLink.href = '/favicon.ico';
|
||||||
|
document.head.appendChild(newLink);
|
||||||
|
return newLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _drawIcon() {
|
||||||
|
if (!this.ctx || !this.favconImage) return;
|
||||||
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||||
|
this.ctx.drawImage(this.favconImage, 0, 0, this.favconImage.width, this.favconImage.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _drawDot() {
|
||||||
|
if (!this.ctx || !this.favconImage) return;
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.arc(this.favconImage.width - 10, 10, 10, 0, 2 * Math.PI);
|
||||||
|
this.ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--navIndicator');
|
||||||
|
this.ctx.strokeStyle = 'white';
|
||||||
|
this.ctx.fill();
|
||||||
|
this.ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setFavicon() {
|
||||||
|
this.faviconEL.href = this.canvas.toDataURL('image/png');
|
||||||
|
}
|
||||||
|
|
||||||
|
async setVisible(isVisible : boolean) {
|
||||||
|
//Wait for it to have loaded the icon
|
||||||
|
await this.hasLoaded;
|
||||||
|
console.log(this.hasLoaded);
|
||||||
|
this._drawIcon();
|
||||||
|
if (isVisible) this._drawDot();
|
||||||
|
this._setFavicon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let icon: FavIconDot = new FavIconDot();
|
||||||
|
|
||||||
|
export function setFavIconDot(visible: boolean) {
|
||||||
|
if (!icon) {
|
||||||
|
icon = new FavIconDot();
|
||||||
|
}
|
||||||
|
icon.setVisible(visible);
|
||||||
|
}
|
|
@ -268,6 +268,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
enableFaviconNotificationDot: {
|
||||||
|
where: 'device',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
imageNewTab: {
|
imageNewTab: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: false,
|
default: false,
|
||||||
|
|
|
@ -47,8 +47,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent, ref } from 'vue';
|
import { defineAsyncComponent, ref, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { setFavIconDot } from '../../scripts/favicon-dot';
|
||||||
import { swInject } from './sw-inject.js';
|
import { swInject } from './sw-inject.js';
|
||||||
import XNotification from './notification.vue';
|
import XNotification from './notification.vue';
|
||||||
import { popups } from '@/os.js';
|
import { popups } from '@/os.js';
|
||||||
|
@ -93,6 +94,12 @@ function onNotification(notification: Misskey.entities.Notification, isClient =
|
||||||
if ($i) {
|
if ($i) {
|
||||||
const connection = useStream().useChannel('main', null, 'UI');
|
const connection = useStream().useChannel('main', null, 'UI');
|
||||||
connection.on('notification', onNotification);
|
connection.on('notification', onNotification);
|
||||||
|
|
||||||
|
//For the favicon notification dot
|
||||||
|
watch(() => $i?.hasUnreadNotification, (hasAny) => setFavIconDot((defaultStore.state.enableFaviconNotificationDot ? hasAny : false) ?? false));
|
||||||
|
|
||||||
|
if ($i.hasUnreadNotification && defaultStore.state.enableFaviconNotificationDot) setFavIconDot(true);
|
||||||
|
|
||||||
globalEvents.on('clientNotification', notification => onNotification(notification, true));
|
globalEvents.on('clientNotification', notification => onNotification(notification, true));
|
||||||
|
|
||||||
//#region Listen message from SW
|
//#region Listen message from SW
|
||||||
|
|
|
@ -392,8 +392,8 @@ importers:
|
||||||
specifier: 1.6.0
|
specifier: 1.6.0
|
||||||
version: 1.6.0
|
version: 1.6.0
|
||||||
tmp:
|
tmp:
|
||||||
specifier: 0.2.2
|
specifier: 0.2.3
|
||||||
version: 0.2.2
|
version: 0.2.3
|
||||||
tsc-alias:
|
tsc-alias:
|
||||||
specifier: 1.8.8
|
specifier: 1.8.8
|
||||||
version: 1.8.8
|
version: 1.8.8
|
||||||
|
@ -18813,6 +18813,12 @@ packages:
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
dependencies:
|
dependencies:
|
||||||
rimraf: 5.0.5
|
rimraf: 5.0.5
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/tmp@0.2.3:
|
||||||
|
resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==}
|
||||||
|
engines: {node: '>=14.14'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tmpl@1.0.5:
|
/tmpl@1.0.5:
|
||||||
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
|
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
|
||||||
|
|
Loading…
Reference in a new issue