Merge branch 'develop' of codeberg.org:thatonecalculator/calckey into develop
This commit is contained in:
commit
0683529145
|
@ -89,6 +89,8 @@
|
||||||
- Patron list
|
- Patron list
|
||||||
- Animations respect reduced motion
|
- Animations respect reduced motion
|
||||||
- Obliteration of Ai-chan
|
- Obliteration of Ai-chan
|
||||||
|
- Undo renote button inside original note
|
||||||
|
- MissV: [fix Misskey Forkbomb](https://code.vtopia.live/Vtopia/MissV/commit/40b23c070bd4adbb3188c73546c6c625138fb3c1)
|
||||||
- [Make showing ads optional](https://github.com/misskey-dev/misskey/pull/8996)
|
- [Make showing ads optional](https://github.com/misskey-dev/misskey/pull/8996)
|
||||||
- [Tapping avatar in mobile opens account modal](https://github.com/misskey-dev/misskey/pull/9056)
|
- [Tapping avatar in mobile opens account modal](https://github.com/misskey-dev/misskey/pull/9056)
|
||||||
- [OAuth bearer token authentication](https://github.com/misskey-dev/misskey/pull/9021)
|
- [OAuth bearer token authentication](https://github.com/misskey-dev/misskey/pull/9021)
|
||||||
|
|
|
@ -96,8 +96,9 @@ psql postgres -c "create database calckey with encoding = 'UTF8';"
|
||||||
|
|
||||||
## 💅 Customize
|
## 💅 Customize
|
||||||
|
|
||||||
- To add custom CSS for all users, edit `./custom/instance.css`.
|
- 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/` directory. They'll then be avaliable 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 avaliable on `https://yourinstance.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 update custom assets without rebuilding, just run `yarn run gulp`.
|
- To update custom assets without rebuilding, just run `yarn run gulp`.
|
||||||
|
|
||||||
## 🧑🔬 Configuring a new instance
|
## 🧑🔬 Configuring a new instance
|
||||||
|
|
|
@ -16,7 +16,7 @@ gulp.task('copy:backend:views', () =>
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task('copy:backend:custom', () =>
|
gulp.task('copy:backend:custom', () =>
|
||||||
gulp.src('./custom/*').pipe(gulp.dest('./packages/backend/assets/'))
|
gulp.src('./custom/assets/*').pipe(gulp.dest('./packages/backend/assets/'))
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task('copy:client:fonts', () =>
|
gulp.task('copy:client:fonts', () =>
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
|
let languages = []
|
||||||
|
let languages_custom = []
|
||||||
|
|
||||||
const merge = (...args) => args.reduce((a, c) => ({
|
const merge = (...args) => args.reduce((a, c) => ({
|
||||||
...a,
|
...a,
|
||||||
|
@ -13,33 +15,20 @@ const merge = (...args) => args.reduce((a, c) => ({
|
||||||
.reduce((a, [k, v]) => (a[k] = merge(v, c[k]), a), {})
|
.reduce((a, [k, v]) => (a[k] = merge(v, c[k]), a), {})
|
||||||
}), {});
|
}), {});
|
||||||
|
|
||||||
const languages = [
|
|
||||||
'ar-SA',
|
fs.readdirSync(__dirname).forEach((file) => {
|
||||||
'cs-CZ',
|
if (file.includes('.yml')){
|
||||||
'da-DK',
|
file = file.slice(0, file.indexOf('.'))
|
||||||
'de-DE',
|
languages.push(file);
|
||||||
'en-US',
|
}
|
||||||
'es-ES',
|
})
|
||||||
'fr-FR',
|
|
||||||
'id-ID',
|
fs.readdirSync(__dirname + '/../custom/locales').forEach((file) => {
|
||||||
'it-IT',
|
if (file.includes('.yml')){
|
||||||
'ja-JP',
|
file = file.slice(0, file.indexOf('.'))
|
||||||
'ja-KS',
|
languages_custom.push(file);
|
||||||
'kab-KAB',
|
}
|
||||||
'kn-IN',
|
})
|
||||||
'ko-KR',
|
|
||||||
'nl-NL',
|
|
||||||
'no-NO',
|
|
||||||
'pl-PL',
|
|
||||||
'pt-PT',
|
|
||||||
'ru-RU',
|
|
||||||
'sk-SK',
|
|
||||||
'ug-CN',
|
|
||||||
'uk-UA',
|
|
||||||
'vi-VN',
|
|
||||||
'zh-CN',
|
|
||||||
'zh-TW',
|
|
||||||
];
|
|
||||||
|
|
||||||
const primaries = {
|
const primaries = {
|
||||||
'en': 'US',
|
'en': 'US',
|
||||||
|
@ -51,6 +40,8 @@ const primaries = {
|
||||||
const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), '');
|
const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), '');
|
||||||
|
|
||||||
const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(`${__dirname}/${c}.yml`, 'utf-8'))) || {}, a), {});
|
const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(`${__dirname}/${c}.yml`, 'utf-8'))) || {}, a), {});
|
||||||
|
const locales_custom = languages_custom.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(`${__dirname}/../custom/locales/${c}.yml`, 'utf-8'))) || {}, a), {});
|
||||||
|
Object.assign(locales, locales_custom)
|
||||||
|
|
||||||
module.exports = Object.entries(locales)
|
module.exports = Object.entries(locales)
|
||||||
.reduce((a, [k ,v]) => (a[k] = (() => {
|
.reduce((a, [k ,v]) => (a[k] = (() => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "calckey",
|
"name": "calckey",
|
||||||
"version": "12.119.0-calc.18-rc.6",
|
"version": "12.119.0-calc.18-rc.11",
|
||||||
"codename": "aqua",
|
"codename": "aqua",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise<st
|
||||||
await updatePerson(actor.uri!, resolver, object);
|
await updatePerson(actor.uri!, resolver, object);
|
||||||
return `ok: Person updated`;
|
return `ok: Person updated`;
|
||||||
} else if (getApType(object) === 'Question') {
|
} else if (getApType(object) === 'Question') {
|
||||||
await updateQuestion(object).catch(e => console.log(e));
|
await updateQuestion(object, resolver).catch(e => console.log(e));
|
||||||
return `ok: Question updated`;
|
return `ok: Question updated`;
|
||||||
} else {
|
} else {
|
||||||
return `skip: Unknown type: ${getApType(object)}`;
|
return `skip: Unknown type: ${getApType(object)}`;
|
||||||
|
|
|
@ -271,7 +271,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
await updateFeatured(user!.id).catch(err => logger.error(err));
|
await updateFeatured(user!.id, resolver).catch(err => logger.error(err));
|
||||||
|
|
||||||
return user!;
|
return user!;
|
||||||
}
|
}
|
||||||
|
@ -384,7 +384,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
|
||||||
followerSharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
followerSharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||||
});
|
});
|
||||||
|
|
||||||
await updateFeatured(exist.id).catch(err => logger.error(err));
|
await updateFeatured(exist.id, resolver).catch(err => logger.error(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -462,14 +462,14 @@ export function analyzeAttachments(attachments: IObject | IObject[] | undefined)
|
||||||
return { fields, services };
|
return { fields, services };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateFeatured(userId: User['id']) {
|
export async function updateFeatured(userId: User['id'], resolver?: Resolver) {
|
||||||
const user = await Users.findOneByOrFail({ id: userId });
|
const user = await Users.findOneByOrFail({ id: userId });
|
||||||
if (!Users.isRemoteUser(user)) return;
|
if (!Users.isRemoteUser(user)) return;
|
||||||
if (!user.featured) return;
|
if (!user.featured) return;
|
||||||
|
|
||||||
logger.info(`Updating the featured: ${user.uri}`);
|
logger.info(`Updating the featured: ${user.uri}`);
|
||||||
|
|
||||||
const resolver = new Resolver();
|
if (resolver == null) resolver = new Resolver();
|
||||||
|
|
||||||
// Resolve to (Ordered)Collection Object
|
// Resolve to (Ordered)Collection Object
|
||||||
const collection = await resolver.resolveCollection(user.featured);
|
const collection = await resolver.resolveCollection(user.featured);
|
||||||
|
|
|
@ -40,7 +40,7 @@ export async function extractPollFromQuestion(source: string | IObject, resolver
|
||||||
* @param uri URI of AP Question object
|
* @param uri URI of AP Question object
|
||||||
* @returns true if updated
|
* @returns true if updated
|
||||||
*/
|
*/
|
||||||
export async function updateQuestion(value: any) {
|
export async function updateQuestion(value: any, resolver?: Resolver) {
|
||||||
const uri = typeof value === 'string' ? value : value.id;
|
const uri = typeof value === 'string' ? value : value.id;
|
||||||
|
|
||||||
// URIがこのサーバーを指しているならスキップ
|
// URIがこのサーバーを指しているならスキップ
|
||||||
|
@ -55,7 +55,7 @@ export async function updateQuestion(value: any) {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// resolve new Question object
|
// resolve new Question object
|
||||||
const resolver = new Resolver();
|
if (resolver == null) resolver = new Resolver();
|
||||||
const question = await resolver.resolve(value) as IQuestion;
|
const question = await resolver.resolve(value) as IQuestion;
|
||||||
apLogger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`);
|
apLogger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`);
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,11 @@ import renderFollow from '@/remote/activitypub/renderer/follow.js';
|
||||||
export default class Resolver {
|
export default class Resolver {
|
||||||
private history: Set<string>;
|
private history: Set<string>;
|
||||||
private user?: ILocalUser;
|
private user?: ILocalUser;
|
||||||
|
private recursionLimit?: number;
|
||||||
|
|
||||||
constructor() {
|
constructor(recursionLimit = 100) {
|
||||||
this.history = new Set();
|
this.history = new Set();
|
||||||
|
this.recursionLimit = recursionLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getHistory(): string[] {
|
public getHistory(): string[] {
|
||||||
|
@ -59,7 +61,9 @@ export default class Resolver {
|
||||||
if (this.history.has(value)) {
|
if (this.history.has(value)) {
|
||||||
throw new Error('cannot resolve already resolved one');
|
throw new Error('cannot resolve already resolved one');
|
||||||
}
|
}
|
||||||
|
if (this.recursionLimit && this.history.size > this.recursionLimit) {
|
||||||
|
throw new Error('hit recursion limit');
|
||||||
|
}
|
||||||
this.history.add(value);
|
this.history.add(value);
|
||||||
|
|
||||||
const host = extractDbHost(value);
|
const host = extractDbHost(value);
|
||||||
|
|
|
@ -232,8 +232,43 @@ const getFeed = async (acct: string) => {
|
||||||
return user && await packFeed(user);
|
return user && await packFeed(user);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// As the /@user[.json|.rss|.atom]/sub endpoint is complicated, we will use a regex to switch between them.
|
||||||
|
const reUser = new RegExp(`^/@(?<user>[^/]+?)(?:\.(?<feed>json|rss|atom))?(?:/(?<sub>[^/]+))?$`);
|
||||||
|
router.get(reUser, async (ctx, next) => {
|
||||||
|
const groups = reUser.exec(ctx.originalUrl)?.groups;
|
||||||
|
if (!groups) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.params = groups;
|
||||||
|
|
||||||
|
console.log(ctx, ctx.params)
|
||||||
|
if (groups.feed) {
|
||||||
|
if (groups.sub) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (groups.feed) {
|
||||||
|
case 'json':
|
||||||
|
await jsonFeed(ctx, next);
|
||||||
|
break;
|
||||||
|
case 'rss':
|
||||||
|
await rssFeed(ctx, next);
|
||||||
|
break;
|
||||||
|
case 'atom':
|
||||||
|
await atomFeed(ctx, next);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await userPage(ctx, next);
|
||||||
|
});
|
||||||
|
|
||||||
// Atom
|
// Atom
|
||||||
router.get('/@:user.atom', async ctx => {
|
const atomFeed: Router.Middleware = async ctx => {
|
||||||
const feed = await getFeed(ctx.params.user);
|
const feed = await getFeed(ctx.params.user);
|
||||||
|
|
||||||
if (feed) {
|
if (feed) {
|
||||||
|
@ -242,10 +277,10 @@ router.get('/@:user.atom', async ctx => {
|
||||||
} else {
|
} else {
|
||||||
ctx.status = 404;
|
ctx.status = 404;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
// RSS
|
// RSS
|
||||||
router.get('/@:user.rss', async ctx => {
|
const rssFeed: Router.Middleware = async ctx => {
|
||||||
const feed = await getFeed(ctx.params.user);
|
const feed = await getFeed(ctx.params.user);
|
||||||
|
|
||||||
if (feed) {
|
if (feed) {
|
||||||
|
@ -254,10 +289,10 @@ router.get('/@:user.rss', async ctx => {
|
||||||
} else {
|
} else {
|
||||||
ctx.status = 404;
|
ctx.status = 404;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
// JSON
|
// JSON
|
||||||
router.get('/@:user.json', async ctx => {
|
const jsonFeed: Router.Middleware = async ctx => {
|
||||||
const feed = await getFeed(ctx.params.user);
|
const feed = await getFeed(ctx.params.user);
|
||||||
|
|
||||||
if (feed) {
|
if (feed) {
|
||||||
|
@ -266,43 +301,47 @@ router.get('/@:user.json', async ctx => {
|
||||||
} else {
|
} else {
|
||||||
ctx.status = 404;
|
ctx.status = 404;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
//#region SSR (for crawlers)
|
//#region SSR (for crawlers)
|
||||||
// User
|
// User
|
||||||
router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => {
|
const userPage: Router.Middleware = async (ctx, next) => {
|
||||||
const { username, host } = Acct.parse(ctx.params.user);
|
const userParam = ctx.params.user;
|
||||||
|
const subParam = ctx.params.sub;
|
||||||
|
const { username, host } = Acct.parse(userParam);
|
||||||
|
|
||||||
const user = await Users.findOneBy({
|
const user = await Users.findOneBy({
|
||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: host ?? IsNull(),
|
host: host ?? IsNull(),
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user != null) {
|
if (user === null) {
|
||||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
|
||||||
const meta = await fetchMeta();
|
|
||||||
const me = profile.fields
|
|
||||||
? profile.fields
|
|
||||||
.filter(filed => filed.value != null && filed.value.match(/^https?:/))
|
|
||||||
.map(field => field.value)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
await ctx.render('user', {
|
|
||||||
user, profile, me,
|
|
||||||
avatarUrl: await Users.getAvatarUrl(user),
|
|
||||||
sub: ctx.params.sub,
|
|
||||||
instanceName: meta.name || 'Calckey',
|
|
||||||
icon: meta.iconUrl,
|
|
||||||
themeColor: meta.themeColor,
|
|
||||||
privateMode: meta.privateMode,
|
|
||||||
});
|
|
||||||
ctx.set('Cache-Control', 'public, max-age=15');
|
|
||||||
} else {
|
|
||||||
// リモートユーザーなので
|
|
||||||
// モデレータがAPI経由で参照可能にするために404にはしない
|
|
||||||
await next();
|
await next();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
const me = profile.fields
|
||||||
|
? profile.fields
|
||||||
|
.filter(filed => filed.value != null && filed.value.match(/^https?:/))
|
||||||
|
.map(field => field.value)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const userDetail = {
|
||||||
|
user, profile, me,
|
||||||
|
avatarUrl: await Users.getAvatarUrl(user),
|
||||||
|
sub: subParam,
|
||||||
|
instanceName: meta.name || 'Calckey',
|
||||||
|
icon: meta.iconUrl,
|
||||||
|
themeColor: meta.themeColor,
|
||||||
|
privateMode: meta.privateMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
await ctx.render('user', userDetail);
|
||||||
|
ctx.set('Cache-Control', 'public, max-age=15');
|
||||||
|
};
|
||||||
|
|
||||||
router.get('/users/:user', async ctx => {
|
router.get('/users/:user', async ctx => {
|
||||||
const user = await Users.findOneBy({
|
const user = await Users.findOneBy({
|
||||||
|
|
|
@ -428,6 +428,10 @@ function readPromo() {
|
||||||
padding: 28px 32px 18px;
|
padding: 28px 32px 18px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
@media (pointer: coarse) {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
> .avatar {
|
> .avatar {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -348,6 +348,9 @@ if (appearNote.replyId) {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
@media (pointer: coarse) {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .renote {
|
> .renote {
|
||||||
|
@ -546,6 +549,10 @@ if (appearNote.replyId) {
|
||||||
> .reply {
|
> .reply {
|
||||||
border-top: solid 0.5px var(--divider);
|
border-top: solid 0.5px var(--divider);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
@media (pointer: coarse) {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .reply, .reply-to, .reply-to-more {
|
> .reply, .reply-to, .reply-to-more {
|
||||||
|
|
|
@ -89,6 +89,10 @@ const replies: misskey.entities.Note[] = props.conversation?.filter(item => item
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
@media (pointer: coarse) {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
> .header {
|
> .header {
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
cursor: auto;
|
cursor: auto;
|
||||||
|
|
|
@ -53,42 +53,62 @@ useTooltip(buttonRef, async (showing) => {
|
||||||
}, {}, 'closed');
|
}, {}, 'closed');
|
||||||
});
|
});
|
||||||
|
|
||||||
const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
pleaseLogin();
|
pleaseLogin();
|
||||||
if (defaultStore.state.seperateRenoteQuote) {
|
|
||||||
os.api('notes/create', {
|
const renotes = await os.api('notes/renotes', {
|
||||||
renoteId: props.note.id,
|
noteId: props.note.id,
|
||||||
visibility: props.note.visibility,
|
limit: 11,
|
||||||
});
|
});
|
||||||
const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined;
|
|
||||||
if (el) {
|
const users = renotes.map(x => x.user.id);
|
||||||
const rect = el.getBoundingClientRect();
|
const hasRenotedBefore = users.includes($i.id);
|
||||||
const x = rect.left + (el.offsetWidth / 2);
|
|
||||||
const y = rect.top + (el.offsetHeight / 2);
|
let buttonActions = [{
|
||||||
os.popup(Ripple, { x, y }, {}, 'end');
|
text: i18n.ts.renote,
|
||||||
}
|
icon: 'ph-repeat-bold ph-lg',
|
||||||
} else {
|
danger: false,
|
||||||
os.popupMenu([{
|
action: () => {
|
||||||
text: i18n.ts.renote,
|
os.api('notes/create', {
|
||||||
icon: 'ph-repeat-bold ph-lg',
|
renoteId: props.note.id,
|
||||||
action: () => {
|
visibility: props.note.visibility,
|
||||||
os.api('notes/create', {
|
});
|
||||||
renoteId: props.note.id,
|
const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined;
|
||||||
visibility: props.note.visibility,
|
if (el) {
|
||||||
});
|
const rect = el.getBoundingClientRect();
|
||||||
},
|
const x = rect.left + (el.offsetWidth / 2);
|
||||||
}, {
|
const y = rect.top + (el.offsetHeight / 2);
|
||||||
|
os.popup(Ripple, { x, y }, {}, 'end');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
|
||||||
|
if (!defaultStore.state.seperateRenoteQuote) {
|
||||||
|
buttonActions.push({
|
||||||
text: i18n.ts.quote,
|
text: i18n.ts.quote,
|
||||||
icon: 'ph-quotes-bold ph-lg',
|
icon: 'ph-quotes-bold ph-lg',
|
||||||
|
danger: false,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.post({
|
os.post({
|
||||||
renote: props.note,
|
renote: props.note,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}], buttonRef.value, {
|
|
||||||
viaKeyboard,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasRenotedBefore) {
|
||||||
|
buttonActions.push({
|
||||||
|
text: i18n.ts.unrenote,
|
||||||
|
icon: 'ph-trash-bold ph-lg',
|
||||||
|
danger: true,
|
||||||
|
action: () => {
|
||||||
|
os.api('notes/unrenote', {
|
||||||
|
noteId: props.note.id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
os.popupMenu(buttonActions, buttonRef.value, { viaKeyboard });
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,6 @@ function del(): void {
|
||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
margin-left: 4%;
|
|
||||||
|
|
||||||
& + * {
|
& + * {
|
||||||
clear: both;
|
clear: both;
|
||||||
|
@ -215,8 +214,6 @@ function del(): void {
|
||||||
|
|
||||||
> .balloon {
|
> .balloon {
|
||||||
$color: var(--X4);
|
$color: var(--X4);
|
||||||
margin-right: 4%;
|
|
||||||
margin-left: 0%;
|
|
||||||
background: $color;
|
background: $color;
|
||||||
|
|
||||||
&.noText {
|
&.noText {
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
"@shoq@newsroom.social",
|
"@shoq@newsroom.social",
|
||||||
"@pikadude@erisly.social",
|
"@pikadude@erisly.social",
|
||||||
"@sage@stop.voring.me",
|
"@sage@stop.voring.me",
|
||||||
"@sky@therian.club"
|
"@sky@therian.club",
|
||||||
|
"@panos@electricrequiem.com",
|
||||||
|
"@redhunt07@www.foxyhole.io"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue