mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-11-04 07:24:13 +00:00 
			
		
		
		
	Merge branch 'develop' into merge/2024-02-03
# Conflicts: # locales/index.d.ts # packages/backend/src/core/entities/UserEntityService.ts # packages/frontend/src/_dev_boot_.ts # packages/misskey-js/src/autogen/types.ts # sharkey-locales/en-US.yml
This commit is contained in:
		
						commit
						f36029f795
					
				
					 24 changed files with 512 additions and 35 deletions
				
			
		| 
						 | 
					@ -3,10 +3,12 @@
 | 
				
			||||||
# **What does this MR do?**
 | 
					# **What does this MR do?**
 | 
				
			||||||
<!-- Please give us a brief description of what this PR does. -->
 | 
					<!-- Please give us a brief description of what this PR does. -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%{all_commits}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# **Contribution Guidelines**
 | 
					# **Contribution Guidelines**
 | 
				
			||||||
By submitting this merge request, you agree to follow our [Contribution Guidelines](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md)
 | 
					By submitting this merge request, you agree to follow our [Contribution Guidelines](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md)
 | 
				
			||||||
- [ ] I agree to follow this project's Contribution Guidelines
 | 
					- [ ] I agree to follow this project's Contribution Guidelines
 | 
				
			||||||
- [ ] I have made sure to test this merge request
 | 
					- [ ] I have made sure to test this merge request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<!-- Uncomment if your merge request has multiple authors -->
 | 
					<!-- Uncomment if your merge request has multiple authors -->
 | 
				
			||||||
<!-- Co-authored-by: Name <email@email.com> -->
 | 
					<!-- %{co_authored_by} -->
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										34
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										34
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -11977,6 +11977,40 @@ export interface Locale extends ILocale {
 | 
				
			||||||
     * Adding entries here will override the default robots.txt packaged with Sharkey.
 | 
					     * Adding entries here will override the default robots.txt packaged with Sharkey.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    "robotsTxtDescription": string;
 | 
					    "robotsTxtDescription": string;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Default content warning for new posts
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    "defaultCW": string;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The value here will be auto-filled as the content warning for all new posts and replies.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    "defaultCWDescription": string;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Automatic CW priority
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    "defaultCWPriority": string;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Select preferred action when default CW and keep CW settings are both enabled at the same time.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    "defaultCWPriorityDescription": string;
 | 
				
			||||||
 | 
					    "_defaultCWPriority": {
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * Use Default (use the default CW, ignoring the inherited CW)
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        "default": string;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * Use Parent (use the inherited CW, ignoring the default CW)
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        "parent": string;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * Use Default, then Parent (use the default CW, and append the inherited CW)
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        "defaultParent": string;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * Use Parent, then Default (use the inherited CW, and append the default CW)
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        "parentDefault": string;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * ID
 | 
					     * ID
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					export class AddUserProfileDefaultCw1738446745738 {
 | 
				
			||||||
 | 
						name = 'AddUserProfileDefaultCw1738446745738'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						async up(queryRunner) {
 | 
				
			||||||
 | 
							await queryRunner.query(`ALTER TABLE "user_profile" ADD "default_cw" text`);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						async down(queryRunner) {
 | 
				
			||||||
 | 
							await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "default_cw"`);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					export class AddUserProfileDefaultCwPriority1738468079662 {
 | 
				
			||||||
 | 
						name = 'AddUserProfileDefaultCwPriority1738468079662'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						async up(queryRunner) {
 | 
				
			||||||
 | 
							await queryRunner.query(`CREATE TYPE "public"."user_profile_default_cw_priority_enum" AS ENUM ('default', 'parent', 'defaultParent', 'parentDefault')`);
 | 
				
			||||||
 | 
							await queryRunner.query(`ALTER TABLE "user_profile" ADD "default_cw_priority" "public"."user_profile_default_cw_priority_enum" NOT NULL DEFAULT 'parent'`);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						async down(queryRunner) {
 | 
				
			||||||
 | 
							await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "default_cw_priority"`);
 | 
				
			||||||
 | 
							await queryRunner.query(`DROP TYPE "public"."user_profile_default_cw_priority_enum"`);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -263,6 +263,67 @@ export class MfmService {
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case 'rp': break;
 | 
				
			||||||
 | 
									case 'rt': {
 | 
				
			||||||
 | 
										appendChildren(node.childNodes);
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									case 'ruby': {
 | 
				
			||||||
 | 
										if (node.childNodes) {
 | 
				
			||||||
 | 
											/*
 | 
				
			||||||
 | 
												we get:
 | 
				
			||||||
 | 
												```
 | 
				
			||||||
 | 
												<ruby>
 | 
				
			||||||
 | 
												some text <rp>(</rp> <rt>annotation</rt> <rp>)</rp>
 | 
				
			||||||
 | 
												more text <rt>more annotation<rt>
 | 
				
			||||||
 | 
												</ruby>
 | 
				
			||||||
 | 
												```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												and we want to produce:
 | 
				
			||||||
 | 
												```
 | 
				
			||||||
 | 
												$[ruby $[group some text] annotation]
 | 
				
			||||||
 | 
												$[ruby $[group more text] more annotation]
 | 
				
			||||||
 | 
												```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												that `group` is a hack, because when the `ruby` render
 | 
				
			||||||
 | 
												sees just text inside the `$[ruby]`, it splits on
 | 
				
			||||||
 | 
												whitespace, considers the first "word" to be the main
 | 
				
			||||||
 | 
												content, and the rest the annotation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												with that `group`, we force it to consider the whole
 | 
				
			||||||
 | 
												group as the main content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												(note that the `rp` are to be ignored, they only exist
 | 
				
			||||||
 | 
												for browsers who don't understand ruby)
 | 
				
			||||||
 | 
											*/
 | 
				
			||||||
 | 
											let nonRtNodes = [];
 | 
				
			||||||
 | 
											// scan children, ignore `rp`, split on `rt`
 | 
				
			||||||
 | 
											for (const child of node.childNodes) {
 | 
				
			||||||
 | 
												if (treeAdapter.isTextNode(child)) {
 | 
				
			||||||
 | 
													nonRtNodes.push(child);
 | 
				
			||||||
 | 
													continue;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
												if (!treeAdapter.isElementNode(child)) {
 | 
				
			||||||
 | 
													continue;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
												if (child.nodeName === 'rp') {
 | 
				
			||||||
 | 
													continue;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
												if (child.nodeName === 'rt') {
 | 
				
			||||||
 | 
													text += '$[ruby $[group ';
 | 
				
			||||||
 | 
													appendChildren(nonRtNodes);
 | 
				
			||||||
 | 
													text += '] ';
 | 
				
			||||||
 | 
													analyze(child);
 | 
				
			||||||
 | 
													text += '] ';
 | 
				
			||||||
 | 
													nonRtNodes = [];
 | 
				
			||||||
 | 
													continue;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
												nonRtNodes.push(child);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				default:	// includes inline elements
 | 
									default:	// includes inline elements
 | 
				
			||||||
				{
 | 
									{
 | 
				
			||||||
					appendChildren(node.childNodes);
 | 
										appendChildren(node.childNodes);
 | 
				
			||||||
| 
						 | 
					@ -381,6 +442,14 @@ export class MfmService {
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// hack for ruby, should never be needed because we should
 | 
				
			||||||
 | 
										// never send this out to other instances
 | 
				
			||||||
 | 
										case 'group': {
 | 
				
			||||||
 | 
											const el = doc.createElement('span');
 | 
				
			||||||
 | 
											appendChildren(node.children, el);
 | 
				
			||||||
 | 
											return el;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					default: {
 | 
										default: {
 | 
				
			||||||
						return fnDefault(node);
 | 
											return fnDefault(node);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
| 
						 | 
					@ -559,11 +628,65 @@ export class MfmService {
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			async fn(node) {
 | 
								async fn(node) {
 | 
				
			||||||
				const el = doc.createElement('span');
 | 
									switch (node.props.name) {
 | 
				
			||||||
				el.textContent = '*';
 | 
										case 'group': { // hack for ruby
 | 
				
			||||||
				await appendChildren(node.children, el);
 | 
											const el = doc.createElement('span');
 | 
				
			||||||
				el.textContent += '*';
 | 
											await appendChildren(node.children, el);
 | 
				
			||||||
				return el;
 | 
											return el;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										case 'ruby': {
 | 
				
			||||||
 | 
											if (node.children.length === 1) {
 | 
				
			||||||
 | 
												const child = node.children[0];
 | 
				
			||||||
 | 
												const text = child.type === 'text' ? child.props.text : '';
 | 
				
			||||||
 | 
												const rubyEl = doc.createElement('ruby');
 | 
				
			||||||
 | 
												const rtEl = doc.createElement('rt');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												const rpStartEl = doc.createElement('rp');
 | 
				
			||||||
 | 
												rpStartEl.appendChild(doc.createTextNode('('));
 | 
				
			||||||
 | 
												const rpEndEl = doc.createElement('rp');
 | 
				
			||||||
 | 
												rpEndEl.appendChild(doc.createTextNode(')'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												rubyEl.appendChild(doc.createTextNode(text.split(' ')[0]));
 | 
				
			||||||
 | 
												rtEl.appendChild(doc.createTextNode(text.split(' ')[1]));
 | 
				
			||||||
 | 
												rubyEl.appendChild(rpStartEl);
 | 
				
			||||||
 | 
												rubyEl.appendChild(rtEl);
 | 
				
			||||||
 | 
												rubyEl.appendChild(rpEndEl);
 | 
				
			||||||
 | 
												return rubyEl;
 | 
				
			||||||
 | 
											} else {
 | 
				
			||||||
 | 
												const rt = node.children.at(-1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												if (!rt) {
 | 
				
			||||||
 | 
													const el = doc.createElement('span');
 | 
				
			||||||
 | 
													await appendChildren(node.children, el);
 | 
				
			||||||
 | 
													return el;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												const text = rt.type === 'text' ? rt.props.text : '';
 | 
				
			||||||
 | 
												const rubyEl = doc.createElement('ruby');
 | 
				
			||||||
 | 
												const rtEl = doc.createElement('rt');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												const rpStartEl = doc.createElement('rp');
 | 
				
			||||||
 | 
												rpStartEl.appendChild(doc.createTextNode('('));
 | 
				
			||||||
 | 
												const rpEndEl = doc.createElement('rp');
 | 
				
			||||||
 | 
												rpEndEl.appendChild(doc.createTextNode(')'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												await appendChildren(node.children.slice(0, node.children.length - 1), rubyEl);
 | 
				
			||||||
 | 
												rtEl.appendChild(doc.createTextNode(text.trim()));
 | 
				
			||||||
 | 
												rubyEl.appendChild(rpStartEl);
 | 
				
			||||||
 | 
												rubyEl.appendChild(rtEl);
 | 
				
			||||||
 | 
												rubyEl.appendChild(rpEndEl);
 | 
				
			||||||
 | 
												return rubyEl;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										default: {
 | 
				
			||||||
 | 
											const el = doc.createElement('span');
 | 
				
			||||||
 | 
											el.textContent = '*';
 | 
				
			||||||
 | 
											await appendChildren(node.children, el);
 | 
				
			||||||
 | 
											el.textContent += '*';
 | 
				
			||||||
 | 
											return el;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			blockCode(node) {
 | 
								blockCode(node) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,6 +55,8 @@ import type { NoteEntityService } from './NoteEntityService.js';
 | 
				
			||||||
import type { DriveFileEntityService } from './DriveFileEntityService.js';
 | 
					import type { DriveFileEntityService } from './DriveFileEntityService.js';
 | 
				
			||||||
import type { PageEntityService } from './PageEntityService.js';
 | 
					import type { PageEntityService } from './PageEntityService.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable @typescript-eslint/no-non-null-assertion */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Ajv = _Ajv.default;
 | 
					const Ajv = _Ajv.default;
 | 
				
			||||||
const ajv = new Ajv();
 | 
					const ajv = new Ajv();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -669,6 +671,8 @@ export class UserEntityService implements OnModuleInit {
 | 
				
			||||||
				achievements: profile!.achievements,
 | 
									achievements: profile!.achievements,
 | 
				
			||||||
				loggedInDays: profile!.loggedInDates.length,
 | 
									loggedInDays: profile!.loggedInDates.length,
 | 
				
			||||||
				policies: this.roleService.getUserPolicies(user.id),
 | 
									policies: this.roleService.getUserPolicies(user.id),
 | 
				
			||||||
 | 
									defaultCW: profile!.defaultCW,
 | 
				
			||||||
 | 
									defaultCWPriority: profile!.defaultCWPriority,
 | 
				
			||||||
			} : {}),
 | 
								} : {}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			...(opts.includeSecrets ? {
 | 
								...(opts.includeSecrets ? {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
 | 
					import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
 | 
				
			||||||
import { obsoleteNotificationTypes, followingVisibilities, followersVisibilities, notificationTypes } from '@/types.js';
 | 
					import { obsoleteNotificationTypes, followingVisibilities, followersVisibilities, notificationTypes, noteVisibilities, defaultCWPriorities } from '@/types.js';
 | 
				
			||||||
import { id } from './util/id.js';
 | 
					import { id } from './util/id.js';
 | 
				
			||||||
import { MiUser } from './User.js';
 | 
					import { MiUser } from './User.js';
 | 
				
			||||||
import { MiPage } from './Page.js';
 | 
					import { MiPage } from './Page.js';
 | 
				
			||||||
| 
						 | 
					@ -36,10 +36,10 @@ export class MiUserProfile {
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	public birthday: string | null;
 | 
						public birthday: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Column("varchar", {
 | 
						@Column('varchar', {
 | 
				
			||||||
		length: 128,
 | 
							length: 128,
 | 
				
			||||||
		nullable: true,
 | 
							nullable: true,
 | 
				
			||||||
		comment: "The ListenBrainz username of the User.",
 | 
							comment: 'The ListenBrainz username of the User.',
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	public listenbrainz: string | null;
 | 
						public listenbrainz: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -290,6 +290,19 @@ export class MiUserProfile {
 | 
				
			||||||
		unlockedAt: number;
 | 
							unlockedAt: number;
 | 
				
			||||||
	}[];
 | 
						}[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('text', {
 | 
				
			||||||
 | 
							name: 'default_cw',
 | 
				
			||||||
 | 
							nullable: true,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public defaultCW: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('enum', {
 | 
				
			||||||
 | 
							name: 'default_cw_priority',
 | 
				
			||||||
 | 
							enum: defaultCWPriorities,
 | 
				
			||||||
 | 
							default: 'parent',
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public defaultCWPriority: typeof defaultCWPriorities[number];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	//#region Denormalized fields
 | 
						//#region Denormalized fields
 | 
				
			||||||
	@Index()
 | 
						@Index()
 | 
				
			||||||
	@Column('varchar', {
 | 
						@Column('varchar', {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -752,6 +752,15 @@ export const packedMeDetailedOnlySchema = {
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		//#endregion
 | 
							//#endregion
 | 
				
			||||||
 | 
							defaultCW: {
 | 
				
			||||||
 | 
								type: 'string',
 | 
				
			||||||
 | 
								nullable: true, optional: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							defaultCWPriority: {
 | 
				
			||||||
 | 
								type: 'string',
 | 
				
			||||||
 | 
								enum: ['default', 'parent', 'defaultParent', 'parentDefault'],
 | 
				
			||||||
 | 
								nullable: false, optional: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,10 +13,11 @@ export const meta = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	requireCredential: false,
 | 
						requireCredential: false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 2 calls per second
 | 
						// Up to 10 calls, then 4 / second.
 | 
				
			||||||
 | 
						// This allows for reliable automation.
 | 
				
			||||||
	limit: {
 | 
						limit: {
 | 
				
			||||||
		duration: 1000,
 | 
							max: 10,
 | 
				
			||||||
		max: 2,
 | 
							dripRate: 250,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,10 +31,12 @@ export const meta = {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 3 calls per second
 | 
						// up to 20 calls, then 1 per second.
 | 
				
			||||||
 | 
						// This handles bursty traffic when all tabs reload as a group
 | 
				
			||||||
	limit: {
 | 
						limit: {
 | 
				
			||||||
		duration: 1000,
 | 
							max: 20,
 | 
				
			||||||
		max: 3,
 | 
							dripSize: 1,
 | 
				
			||||||
 | 
							dripRate: 1000,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -133,6 +133,12 @@ export const meta = {
 | 
				
			||||||
			id: '0b3f9f6a-2f4d-4b1f-9fb4-49d3a2fd7191',
 | 
								id: '0b3f9f6a-2f4d-4b1f-9fb4-49d3a2fd7191',
 | 
				
			||||||
			httpStatusCode: 422,
 | 
								httpStatusCode: 422,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							maxCwLength: {
 | 
				
			||||||
 | 
								message: 'You tried setting a default content warning which is too long.',
 | 
				
			||||||
 | 
								code: 'MAX_CW_LENGTH',
 | 
				
			||||||
 | 
								id: '7004c478-bda3-4b4f-acb2-4316398c9d52',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	res: {
 | 
						res: {
 | 
				
			||||||
| 
						 | 
					@ -243,6 +249,12 @@ export const paramDef = {
 | 
				
			||||||
			uniqueItems: true,
 | 
								uniqueItems: true,
 | 
				
			||||||
			items: { type: 'string' },
 | 
								items: { type: 'string' },
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							defaultCW: { type: 'string', nullable: true },
 | 
				
			||||||
 | 
							defaultCWPriority: {
 | 
				
			||||||
 | 
								type: 'string',
 | 
				
			||||||
 | 
								enum: ['default', 'parent', 'defaultParent', 'parentDefault'],
 | 
				
			||||||
 | 
								nullable: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -494,6 +506,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
				updates.alsoKnownAs = newAlsoKnownAs.size > 0 ? Array.from(newAlsoKnownAs) : null;
 | 
									updates.alsoKnownAs = newAlsoKnownAs.size > 0 ? Array.from(newAlsoKnownAs) : null;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								let defaultCW = ps.defaultCW;
 | 
				
			||||||
 | 
								if (defaultCW !== undefined) {
 | 
				
			||||||
 | 
									if (defaultCW === '') defaultCW = null;
 | 
				
			||||||
 | 
									if (defaultCW && defaultCW.length > this.config.maxCwLength) {
 | 
				
			||||||
 | 
										throw new ApiError(meta.errors.maxCwLength);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									profileUpdates.defaultCW = defaultCW;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if (ps.defaultCWPriority !== undefined) {
 | 
				
			||||||
 | 
									profileUpdates.defaultCWPriority = ps.defaultCWPriority;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			//#region emojis/tags
 | 
								//#region emojis/tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			let emojis = [] as string[];
 | 
								let emojis = [] as string[];
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,10 +57,10 @@ export const meta = {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 5 calls per 2 seconds
 | 
						// up to 50 calls @ 4 per second
 | 
				
			||||||
	limit: {
 | 
						limit: {
 | 
				
			||||||
		duration: 1000 * 2,
 | 
							max: 50,
 | 
				
			||||||
		max: 5,
 | 
							dripRate: 250,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,7 +48,7 @@
 | 
				
			||||||
			if (supportedLangs.includes(navigator.language)) {
 | 
								if (supportedLangs.includes(navigator.language)) {
 | 
				
			||||||
				lang = navigator.language;
 | 
									lang = navigator.language;
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
 | 
									lang = supportedLangs.find(x => x.split('-')[0] === navigator.language.split('-')[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Fallback
 | 
									// Fallback
 | 
				
			||||||
				if (lang == null) lang = 'en-US';
 | 
									if (lang == null) lang = 'en-US';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,7 +39,7 @@
 | 
				
			||||||
			if (supportedLangs.includes(navigator.language)) {
 | 
								if (supportedLangs.includes(navigator.language)) {
 | 
				
			||||||
				lang = navigator.language;
 | 
									lang = navigator.language;
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
 | 
									lang = supportedLangs.find(x => x.split('-')[0] === navigator.language.split('-')[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Fallback
 | 
									// Fallback
 | 
				
			||||||
				if (lang == null) lang = 'en-US';
 | 
									if (lang == null) lang = 'en-US';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,6 +58,8 @@ export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const;
 | 
				
			||||||
export const followingVisibilities = ['public', 'followers', 'private'] as const;
 | 
					export const followingVisibilities = ['public', 'followers', 'private'] as const;
 | 
				
			||||||
export const followersVisibilities = ['public', 'followers', 'private'] as const;
 | 
					export const followersVisibilities = ['public', 'followers', 'private'] as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const defaultCWPriorities = ['default', 'parent', 'defaultParent', 'parentDefault'] as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * ユーザーがエクスポートできるものの種類
 | 
					 * ユーザーがエクスポートできるものの種類
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,6 +45,50 @@ describe('MfmService', () => {
 | 
				
			||||||
			const output = '<p><pre><code><p>Hello, world!</p></code></pre></p>';
 | 
								const output = '<p><pre><code><p>Hello, world!</p></code></pre></p>';
 | 
				
			||||||
			assert.equal(mfmService.toHtml(mfm.parse(input)), output);
 | 
								assert.equal(mfmService.toHtml(mfm.parse(input)), output);
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							test('ruby', () => {
 | 
				
			||||||
 | 
								const input = '$[ruby some text ignore me]';
 | 
				
			||||||
 | 
								const output = '<p><ruby>some<rp>(</rp><rt>text</rt><rp>)</rp></ruby></p>';
 | 
				
			||||||
 | 
								assert.equal(mfmService.toHtml(mfm.parse(input)), output);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							test('ruby2', () => {
 | 
				
			||||||
 | 
								const input = '$[ruby *some text* ignore me]';
 | 
				
			||||||
 | 
								const output = '<p><ruby><i>some text</i><rp>(</rp><rt>ignore me</rt><rp>)</rp></ruby></p>';
 | 
				
			||||||
 | 
								assert.equal(mfmService.toHtml(mfm.parse(input)), output);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							test('ruby 3', () => {
 | 
				
			||||||
 | 
								const input = '$[ruby $[group *some* text] ignore me]';
 | 
				
			||||||
 | 
								const output = '<p><ruby><span><i>some</i> text</span><rp>(</rp><rt>ignore me</rt><rp>)</rp></ruby></p>';
 | 
				
			||||||
 | 
								assert.equal(mfmService.toHtml(mfm.parse(input)), output);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						describe('toMastoApiHtml', () => {
 | 
				
			||||||
 | 
							test('br', async () => {
 | 
				
			||||||
 | 
								const input = 'foo\nbar\nbaz';
 | 
				
			||||||
 | 
								const output = '<p><span>foo<br>bar<br>baz</span></p>';
 | 
				
			||||||
 | 
								assert.equal(await mfmService.toMastoApiHtml(mfm.parse(input)), output);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							test('br alt', async () => {
 | 
				
			||||||
 | 
								const input = 'foo\r\nbar\rbaz';
 | 
				
			||||||
 | 
								const output = '<p><span>foo<br>bar<br>baz</span></p>';
 | 
				
			||||||
 | 
								assert.equal(await mfmService.toMastoApiHtml(mfm.parse(input)), output);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							test('escape', async () => {
 | 
				
			||||||
 | 
								const input = '```\n<p>Hello, world!</p>\n```';
 | 
				
			||||||
 | 
								const output = '<p><pre><code><p>Hello, world!</p></code></pre></p>';
 | 
				
			||||||
 | 
								assert.equal(await mfmService.toMastoApiHtml(mfm.parse(input)), output);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							test('ruby', async () => {
 | 
				
			||||||
 | 
								const input = '$[ruby $[group *some* text] ignore me]';
 | 
				
			||||||
 | 
								const output = '<p><ruby><span><span>*some*</span><span> text</span></span><rp>(</rp><rt>ignore me</rt><rp>)</rp></ruby></p>';
 | 
				
			||||||
 | 
								assert.equal(await mfmService.toMastoApiHtml(mfm.parse(input)), output);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	describe('fromHtml', () => {
 | 
						describe('fromHtml', () => {
 | 
				
			||||||
| 
						 | 
					@ -133,5 +177,12 @@ describe('MfmService', () => {
 | 
				
			||||||
		test('hashtag', () => {
 | 
							test('hashtag', () => {
 | 
				
			||||||
			assert.deepStrictEqual(mfmService.fromHtml('<p>a <a href="https://example.com/tags/a">#a</a> d</p>', ['#a']), 'a #a d');
 | 
								assert.deepStrictEqual(mfmService.fromHtml('<p>a <a href="https://example.com/tags/a">#a</a> d</p>', ['#a']), 'a #a d');
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							test('ruby', () => {
 | 
				
			||||||
 | 
								assert.deepStrictEqual(
 | 
				
			||||||
 | 
									mfmService.fromHtml('<ruby> <i>some</i> text <rp>(</rp><rt>ignore me</rt><rp>)</rp> and <rt>more</rt></ruby>'),
 | 
				
			||||||
 | 
									'$[ruby $[group  <i>some</i> text ] ignore me] $[ruby $[group  and ] more]'
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
<button
 | 
					<button
 | 
				
			||||||
	class="_button"
 | 
						class="_button"
 | 
				
			||||||
	:class="[$style.root, { [$style.wait]: wait, [$style.active]: isFollowing || hasPendingFollowRequestFromYou, [$style.full]: full, [$style.large]: large }]"
 | 
						:class="[$style.root, { [$style.wait]: wait, [$style.active]: isFollowing || hasPendingFollowRequestFromYou, [$style.full]: full, [$style.large]: large }]"
 | 
				
			||||||
	:disabled="wait"
 | 
						:disabled="wait || disabled"
 | 
				
			||||||
	@click="onClick"
 | 
						@click="onClick"
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
	<template v-if="!wait">
 | 
						<template v-if="!wait">
 | 
				
			||||||
| 
						 | 
					@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { onBeforeUnmount, onMounted, ref } from 'vue';
 | 
					import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
 | 
				
			||||||
import * as Misskey from 'misskey-js';
 | 
					import * as Misskey from 'misskey-js';
 | 
				
			||||||
import { host } from '@@/js/config.js';
 | 
					import { host } from '@@/js/config.js';
 | 
				
			||||||
import * as os from '@/os.js';
 | 
					import * as os from '@/os.js';
 | 
				
			||||||
| 
						 | 
					@ -51,13 +51,16 @@ const props = withDefaults(defineProps<{
 | 
				
			||||||
	user: Misskey.entities.UserDetailed,
 | 
						user: Misskey.entities.UserDetailed,
 | 
				
			||||||
	full?: boolean,
 | 
						full?: boolean,
 | 
				
			||||||
	large?: boolean,
 | 
						large?: boolean,
 | 
				
			||||||
 | 
						disabled?: boolean,
 | 
				
			||||||
}>(), {
 | 
					}>(), {
 | 
				
			||||||
	full: false,
 | 
						full: false,
 | 
				
			||||||
	large: false,
 | 
						large: false,
 | 
				
			||||||
 | 
						disabled: false,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emit = defineEmits<{
 | 
					const emit = defineEmits<{
 | 
				
			||||||
	(_: 'update:user', value: Misskey.entities.UserDetailed): void
 | 
						(_: 'update:user', value: Misskey.entities.UserDetailed): void,
 | 
				
			||||||
 | 
						(_: 'update:wait', value: boolean): void,
 | 
				
			||||||
}>();
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isFollowing = ref(props.user.isFollowing);
 | 
					const isFollowing = ref(props.user.isFollowing);
 | 
				
			||||||
| 
						 | 
					@ -65,6 +68,9 @@ const hasPendingFollowRequestFromYou = ref(props.user.hasPendingFollowRequestFro
 | 
				
			||||||
const wait = ref(false);
 | 
					const wait = ref(false);
 | 
				
			||||||
const connection = useStream().useChannel('main');
 | 
					const connection = useStream().useChannel('main');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Emit the "wait" status so external components can synchronize state
 | 
				
			||||||
 | 
					watch(wait, value => emit('update:wait', value));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (props.user.isFollowing == null && $i) {
 | 
					if (props.user.isFollowing == null && $i) {
 | 
				
			||||||
	misskeyApi('users/show', {
 | 
						misskeyApi('users/show', {
 | 
				
			||||||
		userId: props.user.id,
 | 
							userId: props.user.id,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -122,6 +122,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<template v-else-if="notification.type === 'follow'">
 | 
								<template v-else-if="notification.type === 'follow'">
 | 
				
			||||||
				<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span>
 | 
									<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span>
 | 
				
			||||||
 | 
									<div v-if="full" :class="$style.followRequestCommands">
 | 
				
			||||||
 | 
										<MkFollowButton v-if="userDetailed" :class="$style.followCommandButton" :user="userDetailed" :transparent="false" :full="false"/>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
			</template>
 | 
								</template>
 | 
				
			||||||
			<template v-else-if="notification.type === 'followRequestAccepted'">
 | 
								<template v-else-if="notification.type === 'followRequestAccepted'">
 | 
				
			||||||
				<div :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</div>
 | 
									<div :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</div>
 | 
				
			||||||
| 
						 | 
					@ -136,6 +139,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
				<div v-if="full && !followRequestDone" :class="$style.followRequestCommands">
 | 
									<div v-if="full && !followRequestDone" :class="$style.followRequestCommands">
 | 
				
			||||||
					<MkButton :class="$style.followRequestCommandButton" rounded primary @click="acceptFollowRequest()"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton>
 | 
										<MkButton :class="$style.followRequestCommandButton" rounded primary @click="acceptFollowRequest()"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton>
 | 
				
			||||||
					<MkButton :class="$style.followRequestCommandButton" rounded danger @click="rejectFollowRequest()"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton>
 | 
										<MkButton :class="$style.followRequestCommandButton" rounded danger @click="rejectFollowRequest()"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton>
 | 
				
			||||||
 | 
										<MkFollowButton v-if="userDetailed" :class="$style.followCommandButton" :user="userDetailed" :transparent="false" :full="false"/>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</template>
 | 
								</template>
 | 
				
			||||||
			<span v-else-if="notification.type === 'test'" :class="$style.text">{{ i18n.ts._notification.notificationWillBeDisplayedLikeThis }}</span>
 | 
								<span v-else-if="notification.type === 'test'" :class="$style.text">{{ i18n.ts._notification.notificationWillBeDisplayedLikeThis }}</span>
 | 
				
			||||||
| 
						 | 
					@ -179,8 +183,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { ref } from 'vue';
 | 
					import { Ref, ref, watch } from 'vue';
 | 
				
			||||||
import * as Misskey from 'misskey-js';
 | 
					import * as Misskey from 'misskey-js';
 | 
				
			||||||
 | 
					import { UserDetailed } from 'misskey-js/autogen/models.js';
 | 
				
			||||||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
 | 
					import MkReactionIcon from '@/components/MkReactionIcon.vue';
 | 
				
			||||||
import MkButton from '@/components/MkButton.vue';
 | 
					import MkButton from '@/components/MkButton.vue';
 | 
				
			||||||
import { getNoteSummary } from '@/scripts/get-note-summary.js';
 | 
					import { getNoteSummary } from '@/scripts/get-note-summary.js';
 | 
				
			||||||
| 
						 | 
					@ -190,6 +195,7 @@ import { i18n } from '@/i18n.js';
 | 
				
			||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
 | 
					import { misskeyApi } from '@/scripts/misskey-api.js';
 | 
				
			||||||
import { signinRequired } from '@/account.js';
 | 
					import { signinRequired } from '@/account.js';
 | 
				
			||||||
import { infoImageUrl } from '@/instance.js';
 | 
					import { infoImageUrl } from '@/instance.js';
 | 
				
			||||||
 | 
					import MkFollowButton from '@/components/MkFollowButton.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const $i = signinRequired();
 | 
					const $i = signinRequired();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -202,6 +208,26 @@ const props = withDefaults(defineProps<{
 | 
				
			||||||
	full: false,
 | 
						full: false,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const userDetailed: Ref<UserDetailed | null> = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// watch() is required because computed() doesn't support async.
 | 
				
			||||||
 | 
					watch(props, async () => {
 | 
				
			||||||
 | 
						const type = props.notification.type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// To avoid extra lookups, only do the query when it actually matters.
 | 
				
			||||||
 | 
						if (type === 'follow' || type === 'receiveFollowRequest') {
 | 
				
			||||||
 | 
							const user = await misskeyApi('users/show', {
 | 
				
			||||||
 | 
								userId: props.notification.userId,
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							userDetailed.value = user;
 | 
				
			||||||
 | 
							followRequestDone.value = !user.hasPendingFollowRequestToYou;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							userDetailed.value = null;
 | 
				
			||||||
 | 
							followRequestDone.value = false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}, { immediate: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ExportCompletedNotification = Misskey.entities.Notification & { type: 'exportCompleted' };
 | 
					type ExportCompletedNotification = Misskey.entities.Notification & { type: 'exportCompleted' };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const exportEntityName = {
 | 
					const exportEntityName = {
 | 
				
			||||||
| 
						 | 
					@ -216,7 +242,7 @@ const exportEntityName = {
 | 
				
			||||||
	userList: i18n.ts.lists,
 | 
						userList: i18n.ts.lists,
 | 
				
			||||||
} as const satisfies Record<ExportCompletedNotification['exportedEntity'], string>;
 | 
					} as const satisfies Record<ExportCompletedNotification['exportedEntity'], string>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const followRequestDone = ref(false);
 | 
					const followRequestDone = ref(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const acceptFollowRequest = () => {
 | 
					const acceptFollowRequest = () => {
 | 
				
			||||||
	if (!('user' in props.notification)) return;
 | 
						if (!('user' in props.notification)) return;
 | 
				
			||||||
| 
						 | 
					@ -434,13 +460,24 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
 | 
				
			||||||
.followRequestCommands {
 | 
					.followRequestCommands {
 | 
				
			||||||
	display: flex;
 | 
						display: flex;
 | 
				
			||||||
	gap: 8px;
 | 
						gap: 8px;
 | 
				
			||||||
	max-width: 300px;
 | 
					 | 
				
			||||||
	margin-top: 8px;
 | 
						margin-top: 8px;
 | 
				
			||||||
 | 
						width: 100%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.followRequestCommandButton {
 | 
					.followRequestCommandButton {
 | 
				
			||||||
 | 
						max-width: 175px;
 | 
				
			||||||
 | 
						width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.flexSpacer {
 | 
				
			||||||
	flex: 1;
 | 
						flex: 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.followCommandButton {
 | 
				
			||||||
 | 
						margin-left: auto;
 | 
				
			||||||
 | 
						flex-grow: 0;
 | 
				
			||||||
 | 
						flex-shrink: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.reactionsItem {
 | 
					.reactionsItem {
 | 
				
			||||||
	display: inline-block;
 | 
						display: inline-block;
 | 
				
			||||||
	position: relative;
 | 
						position: relative;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -366,6 +366,29 @@ if (defaultStore.state.keepCw && props.reply && props.reply.cw) {
 | 
				
			||||||
	cw.value = props.reply.cw;
 | 
						cw.value = props.reply.cw;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// apply default CW
 | 
				
			||||||
 | 
					if ($i.defaultCW) {
 | 
				
			||||||
 | 
						useCw.value = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!cw.value || $i.defaultCWPriority === 'default') {
 | 
				
			||||||
 | 
							cw.value = $i.defaultCW;
 | 
				
			||||||
 | 
						} else if ($i.defaultCWPriority !== 'parent') {
 | 
				
			||||||
 | 
							// This is a fancy way of simulating /\bsearch\b/ without a regular expression.
 | 
				
			||||||
 | 
							// We're checking to see whether the default CW appears inside the existing CW, but *only* if there's word boundaries.
 | 
				
			||||||
 | 
							const parts = cw.value.split($i.defaultCW);
 | 
				
			||||||
 | 
							const hasExistingDefaultCW = parts.length === 2 && !/\w$/.test(parts[0]) && !/^\w/.test(parts[1]);
 | 
				
			||||||
 | 
							if (!hasExistingDefaultCW) {
 | 
				
			||||||
 | 
								// We need to merge the CWs
 | 
				
			||||||
 | 
								if ($i.defaultCWPriority === 'defaultParent') {
 | 
				
			||||||
 | 
									cw.value = `${$i.defaultCW}, ${cw.value}`;
 | 
				
			||||||
 | 
								} else if ($i.defaultCWPriority === 'parentDefault') {
 | 
				
			||||||
 | 
									cw.value = `${cw.value}, ${$i.defaultCW}`;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// else { do nothing, because existing CW takes priority. }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function watchForDraft() {
 | 
					function watchForDraft() {
 | 
				
			||||||
	watch(text, () => saveDraft());
 | 
						watch(text, () => saveDraft());
 | 
				
			||||||
	watch(useCw, () => saveDraft());
 | 
						watch(useCw, () => saveDraft());
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -358,6 +358,10 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 | 
				
			||||||
							return h('ruby', {}, [...genEl(token.children.slice(0, token.children.length - 1), scale), h('rt', text.trim())]);
 | 
												return h('ruby', {}, [...genEl(token.children.slice(0, token.children.length - 1), scale), h('rt', text.trim())]);
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
										case 'group': { // this is mostly a hack for the insides of `ruby`
 | 
				
			||||||
 | 
											style = '';
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
					case 'unixtime': {
 | 
										case 'unixtime': {
 | 
				
			||||||
						const child = token.children[0];
 | 
											const child = token.children[0];
 | 
				
			||||||
						const unixtime = parseInt(child.type === 'text' ? child.props.text : '');
 | 
											const unixtime = parseInt(child.type === 'text' ? child.props.text : '');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -155,10 +155,24 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
					<MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch>
 | 
										<MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</MkFolder>
 | 
								</MkFolder>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								<MkSwitch v-model="keepCw" @update:modelValue="save()">{{ i18n.ts.keepCw }}</MkSwitch>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								<MkInput v-model="defaultCW" type="text" manualSave @update:modelValue="save()">
 | 
				
			||||||
 | 
									<template #label>{{ i18n.ts.defaultCW }}</template>
 | 
				
			||||||
 | 
									<template #caption>{{ i18n.ts.defaultCWDescription }}</template>
 | 
				
			||||||
 | 
								</MkInput>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								<MkSelect v-model="defaultCWPriority" :disabled="!defaultCW || !keepCw" @update:modelValue="save()">
 | 
				
			||||||
 | 
									<template #label>{{ i18n.ts.defaultCWPriority }}</template>
 | 
				
			||||||
 | 
									<template #caption>{{ i18n.ts.defaultCWPriorityDescription }}</template>
 | 
				
			||||||
 | 
									<option value="default">{{ i18n.ts._defaultCWPriority.default }}</option>
 | 
				
			||||||
 | 
									<option value="parent">{{ i18n.ts._defaultCWPriority.parent }}</option>
 | 
				
			||||||
 | 
									<option value="parentDefault">{{ i18n.ts._defaultCWPriority.parentDefault }}</option>
 | 
				
			||||||
 | 
									<option value="defaultParent">{{ i18n.ts._defaultCWPriority.defaultParent }}</option>
 | 
				
			||||||
 | 
								</MkSelect>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</FormSection>
 | 
						</FormSection>
 | 
				
			||||||
 | 
					 | 
				
			||||||
	<MkSwitch v-model="keepCw" @update:modelValue="save()">{{ i18n.ts.keepCw }}</MkSwitch>
 | 
					 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -194,6 +208,8 @@ const hideOnlineStatus = ref($i.hideOnlineStatus);
 | 
				
			||||||
const publicReactions = ref($i.publicReactions);
 | 
					const publicReactions = ref($i.publicReactions);
 | 
				
			||||||
const followingVisibility = ref($i.followingVisibility);
 | 
					const followingVisibility = ref($i.followingVisibility);
 | 
				
			||||||
const followersVisibility = ref($i.followersVisibility);
 | 
					const followersVisibility = ref($i.followersVisibility);
 | 
				
			||||||
 | 
					const defaultCW = ref($i.defaultCW);
 | 
				
			||||||
 | 
					const defaultCWPriority = ref($i.defaultCWPriority);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const defaultNoteVisibility = computed(defaultStore.makeGetterSetter('defaultNoteVisibility'));
 | 
					const defaultNoteVisibility = computed(defaultStore.makeGetterSetter('defaultNoteVisibility'));
 | 
				
			||||||
const defaultNoteLocalOnly = computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly'));
 | 
					const defaultNoteLocalOnly = computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly'));
 | 
				
			||||||
| 
						 | 
					@ -252,6 +268,8 @@ function save() {
 | 
				
			||||||
		publicReactions: !!publicReactions.value,
 | 
							publicReactions: !!publicReactions.value,
 | 
				
			||||||
		followingVisibility: followingVisibility.value,
 | 
							followingVisibility: followingVisibility.value,
 | 
				
			||||||
		followersVisibility: followersVisibility.value,
 | 
							followersVisibility: followersVisibility.value,
 | 
				
			||||||
 | 
							defaultCWPriority: defaultCWPriority.value,
 | 
				
			||||||
 | 
							defaultCW: defaultCW.value,
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
				<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/>
 | 
									<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				<div :key="user.id" class="main _panel">
 | 
									<div :key="user.id" class="main _panel">
 | 
				
			||||||
					<div class="banner-container" :style="style">
 | 
										<div class="banner-container" :class="{ [$style.bannerContainerTall]: useTallBanner }" :style="style">
 | 
				
			||||||
						<div ref="bannerEl" class="banner" :style="style"></div>
 | 
											<div ref="bannerEl" class="banner" :style="style"></div>
 | 
				
			||||||
						<div class="fade"></div>
 | 
											<div class="fade"></div>
 | 
				
			||||||
						<div class="title">
 | 
											<div class="title">
 | 
				
			||||||
| 
						 | 
					@ -39,12 +39,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
							<li v-if="user.isBlocking">{{ i18n.ts.blocked }}</li>
 | 
												<li v-if="user.isBlocking">{{ i18n.ts.blocked }}</li>
 | 
				
			||||||
							<li v-if="user.isBlocked && $i.isModerator">{{ i18n.ts.blockingYou }}</li>
 | 
												<li v-if="user.isBlocked && $i.isModerator">{{ i18n.ts.blockingYou }}</li>
 | 
				
			||||||
						</ul>
 | 
											</ul>
 | 
				
			||||||
						<div class="actions">
 | 
											<div :class="$style.actions" class="actions">
 | 
				
			||||||
							<button class="menu _button" @click="menu"><i class="ti ti-dots"></i></button>
 | 
												<button :class="$style.actionsMenu" class="menu _button" @click="menu"><i class="ti ti-dots"></i></button>
 | 
				
			||||||
							<MkFollowButton v-if="$i?.id != user.id" v-model:user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
 | 
												<MkFollowButton v-if="$i?.id != user.id" v-model:user="user" :class="$style.actionsFollow" :disabled="disableFollowControls" :inline="true" :transparent="false" :full="true" class="koudoku" @update:wait="onFollowButtonDisabledChanged"/>
 | 
				
			||||||
 | 
												<div v-if="hasFollowRequest" :class="$style.actionsBanner">{{ i18n.ts.receiveFollowRequest }}</div>
 | 
				
			||||||
 | 
												<MkButton v-if="hasFollowRequest" :class="$style.actionsAccept" :disabled="disableFollowControls" :inline="true" :transparent="false" :full="true" rounded primary @click="acceptFollowRequest"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton>
 | 
				
			||||||
 | 
												<MkButton v-if="hasFollowRequest" :class="$style.actionsReject" :disabled="disableFollowControls" :inline="true" :transparent="false" :full="true" rounded danger @click="rejectFollowRequest"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton>
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
					<MkAvatar class="avatar" :user="user" indicator/>
 | 
										<MkAvatar class="avatar" :class="{ [$style.avatarTall]: useTallBanner }" :user="user" indicator/>
 | 
				
			||||||
					<div class="title">
 | 
										<div class="title">
 | 
				
			||||||
						<MkUserName :user="user" :nowrap="false" class="name"/>
 | 
											<MkUserName :user="user" :nowrap="false" class="name"/>
 | 
				
			||||||
						<div class="bottom">
 | 
											<div class="bottom">
 | 
				
			||||||
| 
						 | 
					@ -220,8 +223,8 @@ import MkSparkle from '@/components/MkSparkle.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MkNote = defineAsyncComponent(() =>
 | 
					const MkNote = defineAsyncComponent(() =>
 | 
				
			||||||
	defaultStore.state.noteDesign === 'sharkey'
 | 
						defaultStore.state.noteDesign === 'sharkey'
 | 
				
			||||||
	? import('@/components/SkNote.vue')
 | 
							? import('@/components/SkNote.vue')
 | 
				
			||||||
	: import('@/components/MkNote.vue'),
 | 
							: import('@/components/MkNote.vue'),
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function calcAge(birthdate: string): number {
 | 
					function calcAge(birthdate: string): number {
 | 
				
			||||||
| 
						 | 
					@ -387,6 +390,42 @@ async function updateMemo() {
 | 
				
			||||||
	isEditingMemo.value = false;
 | 
						isEditingMemo.value = false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Set true to disable the follow / follow request controls
 | 
				
			||||||
 | 
					const disableFollowControls = ref(false);
 | 
				
			||||||
 | 
					const hasFollowRequest = computed(() => user.value.hasPendingFollowRequestToYou);
 | 
				
			||||||
 | 
					const useTallBanner = computed(() => hasFollowRequest.value && narrow.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function onFollowButtonDisabledChanged(disabled: boolean) {
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							// Refresh the UI after MkFollowButton changes the follow relation
 | 
				
			||||||
 | 
							if (!disabled) {
 | 
				
			||||||
 | 
								user.value = await os.apiWithDialog('users/show', { userId: user.value.id });
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} finally {
 | 
				
			||||||
 | 
							disableFollowControls.value = disabled;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function acceptFollowRequest() {
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							disableFollowControls.value = true;
 | 
				
			||||||
 | 
							await os.apiWithDialog('following/requests/accept', { userId: user.value.id });
 | 
				
			||||||
 | 
							user.value = await os.apiWithDialog('users/show', { userId: user.value.id });
 | 
				
			||||||
 | 
						} finally {
 | 
				
			||||||
 | 
							disableFollowControls.value = false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function rejectFollowRequest() {
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							disableFollowControls.value = true;
 | 
				
			||||||
 | 
							await os.apiWithDialog('following/requests/reject', { userId: user.value.id });
 | 
				
			||||||
 | 
							user.value = await os.apiWithDialog('users/show', { userId: user.value.id });
 | 
				
			||||||
 | 
						} finally {
 | 
				
			||||||
 | 
							disableFollowControls.value = false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch([props.user], () => {
 | 
					watch([props.user], () => {
 | 
				
			||||||
	memoDraft.value = props.user.memo;
 | 
						memoDraft.value = props.user.memo;
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -863,4 +902,48 @@ onUnmounted(() => {
 | 
				
			||||||
		margin-left: 8px;
 | 
							margin-left: 8px;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.actions {
 | 
				
			||||||
 | 
						display: grid;
 | 
				
			||||||
 | 
						grid-template-rows: min-content min-content min-content;
 | 
				
			||||||
 | 
						grid-template-columns: min-content auto 1fr;
 | 
				
			||||||
 | 
						grid-template-areas:
 | 
				
			||||||
 | 
							"menu follow follow"
 | 
				
			||||||
 | 
							"banner banner banner"
 | 
				
			||||||
 | 
							"accept accept reject";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.actionsMenu {
 | 
				
			||||||
 | 
						grid-area: menu;
 | 
				
			||||||
 | 
						width: unset;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.actionsFollow {
 | 
				
			||||||
 | 
						grid-area: follow;
 | 
				
			||||||
 | 
						margin-left: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.actionsBanner {
 | 
				
			||||||
 | 
						grid-area: banner;
 | 
				
			||||||
 | 
						justify-self: center;
 | 
				
			||||||
 | 
						margin-top: 8px;
 | 
				
			||||||
 | 
						margin-bottom: 4px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.actionsAccept {
 | 
				
			||||||
 | 
						grid-area: accept;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.actionsReject {
 | 
				
			||||||
 | 
						grid-area: reject;
 | 
				
			||||||
 | 
						margin-left: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.bannerContainerTall {
 | 
				
			||||||
 | 
						height: 200px !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.avatarTall {
 | 
				
			||||||
 | 
						top: 150px !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4244,6 +4244,9 @@ export type components = {
 | 
				
			||||||
          /** Format: date-time */
 | 
					          /** Format: date-time */
 | 
				
			||||||
          lastUsed: string;
 | 
					          lastUsed: string;
 | 
				
			||||||
        }[];
 | 
					        }[];
 | 
				
			||||||
 | 
					      defaultCW: string | null;
 | 
				
			||||||
 | 
					      /** @enum {string} */
 | 
				
			||||||
 | 
					      defaultCWPriority: 'default' | 'parent' | 'defaultParent' | 'parentDefault';
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    UserDetailedNotMe: components['schemas']['UserLite'] & components['schemas']['UserDetailedNotMeOnly'];
 | 
					    UserDetailedNotMe: components['schemas']['UserLite'] & components['schemas']['UserDetailedNotMeOnly'];
 | 
				
			||||||
    MeDetailed: components['schemas']['UserLite'] & components['schemas']['UserDetailedNotMeOnly'] & components['schemas']['MeDetailedOnly'];
 | 
					    MeDetailed: components['schemas']['UserLite'] & components['schemas']['UserDetailedNotMeOnly'] & components['schemas']['MeDetailedOnly'];
 | 
				
			||||||
| 
						 | 
					@ -22777,6 +22780,9 @@ export type operations = {
 | 
				
			||||||
          };
 | 
					          };
 | 
				
			||||||
          emailNotificationTypes?: string[];
 | 
					          emailNotificationTypes?: string[];
 | 
				
			||||||
          alsoKnownAs?: string[];
 | 
					          alsoKnownAs?: string[];
 | 
				
			||||||
 | 
					          defaultCW?: string | null;
 | 
				
			||||||
 | 
					          /** @enum {string} */
 | 
				
			||||||
 | 
					          defaultCWPriority?: 'default' | 'parent' | 'defaultParent' | 'parentDefault';
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -438,4 +438,14 @@ _permissions:
 | 
				
			||||||
robotsTxt: "Custom robots.txt"
 | 
					robotsTxt: "Custom robots.txt"
 | 
				
			||||||
robotsTxtDescription: "Adding entries here will override the default robots.txt packaged with Sharkey."
 | 
					robotsTxtDescription: "Adding entries here will override the default robots.txt packaged with Sharkey."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defaultCW: "Default content warning for new posts"
 | 
				
			||||||
 | 
					defaultCWDescription: "The value here will be auto-filled as the content warning for all new posts and replies."
 | 
				
			||||||
 | 
					defaultCWPriority: "Automatic CW priority"
 | 
				
			||||||
 | 
					defaultCWPriorityDescription: "Select preferred action when default CW and keep CW settings are both enabled at the same time."
 | 
				
			||||||
 | 
					_defaultCWPriority:
 | 
				
			||||||
 | 
					  default: "Use Default (use the default CW, ignoring the inherited CW)"
 | 
				
			||||||
 | 
					  parent: "Use Parent (use the inherited CW, ignoring the default CW)"
 | 
				
			||||||
 | 
					  defaultParent: "Use Default, then Parent (use the default CW, and append the inherited CW)"
 | 
				
			||||||
 | 
					  parentDefault: "Use Parent, then Default (use the inherited CW, and append the default CW)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
id: "ID"
 | 
					id: "ID"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue