mirror of https://github.com/andrigamerita/simpkey
ひととおり動いた
This commit is contained in:
commit
b958350d0f
|
@ -0,0 +1,36 @@
|
|||
module.exports = {
|
||||
'env': {
|
||||
'browser': true,
|
||||
'es2020': true
|
||||
},
|
||||
'extends': [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended'
|
||||
],
|
||||
'parser': '@typescript-eslint/parser',
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 11,
|
||||
'sourceType': 'module'
|
||||
},
|
||||
'plugins': [
|
||||
'@typescript-eslint'
|
||||
],
|
||||
'rules': {
|
||||
'indent': [
|
||||
'error',
|
||||
'tab'
|
||||
],
|
||||
'linebreak-style': [
|
||||
'error',
|
||||
'unix'
|
||||
],
|
||||
'quotes': [
|
||||
'error',
|
||||
'single'
|
||||
],
|
||||
'semi': [
|
||||
'error',
|
||||
'always'
|
||||
]
|
||||
}
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
built
|
||||
yarn-error.log
|
|
@ -0,0 +1,15 @@
|
|||
simpkey
|
||||
Copyright (C) 2020 Xeltica
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,19 @@
|
|||
# Simpkey
|
||||
|
||||
**Use misskey without JavaScript 🥴**
|
||||
|
||||
Simpkey is a HTML-Form based server-side-processing misskey client.
|
||||
|
||||
It is suitable if you are using a legacy computer, or you are not prefer to enable JavaScript.
|
||||
|
||||
## build
|
||||
|
||||
```
|
||||
yarn install
|
||||
yarn build
|
||||
yarn start
|
||||
```
|
||||
|
||||
## LICENSE
|
||||
|
||||
[AGPL 3.0](LICENSE)
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"watch": ["src"],
|
||||
"ext": "ts,pug",
|
||||
"exec": "npm-run-all -p tsc copy -s start"
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"name": "simpkey",
|
||||
"version": "1.0.0",
|
||||
"description": "server-side misskey client",
|
||||
"main": "built/app.js",
|
||||
"author": "Xeltica",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"tsc": "tsc",
|
||||
"start": "node built/app.js",
|
||||
"lint": "eslint src/index.ts",
|
||||
"lint:fix": "eslint --fix src/index.ts",
|
||||
"copy": "copyfiles -u 1 src/views/*.pug ./built/",
|
||||
"clean": "rimraf built",
|
||||
"build": "run-p tsc copy",
|
||||
"watch": "nodemon",
|
||||
"pope": "echo ぽぺ",
|
||||
"bebeyo": "echo ベベヨ",
|
||||
"chasmo": "echo チャスモハァーワ」。"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/koa-bodyparser": "^4.3.0",
|
||||
"axios": "^0.19.2",
|
||||
"koa": "^2.13.0",
|
||||
"koa-bodyparser": "^4.3.0",
|
||||
"koa-router": "^9.1.0",
|
||||
"koa-session": "^6.0.0",
|
||||
"koa-views": "^6.3.0",
|
||||
"pug": "^3.0.0",
|
||||
"typescript": "^3.9.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/koa": "^2.11.3",
|
||||
"@types/koa-router": "^7.4.1",
|
||||
"@types/koa-session": "^5.10.2",
|
||||
"@types/koa-views": "^2.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "^3.7.0",
|
||||
"@typescript-eslint/parser": "^3.7.0",
|
||||
"copyfiles": "^2.3.0",
|
||||
"eslint": "^7.5.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"nodemon": "^2.0.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.0.5",
|
||||
"rimraf": "^3.0.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import Koa from 'koa';
|
||||
import { router, render } from '.';
|
||||
import config from './config';
|
||||
import session from 'koa-session';
|
||||
import bodyParser from 'koa-bodyparser';
|
||||
|
||||
|
||||
const app = new Koa();
|
||||
|
||||
console.log('Simpkey v' + config.version);
|
||||
|
||||
app.use(bodyParser());
|
||||
app.use(render);
|
||||
app.use(router.routes());
|
||||
app.use(router.allowedMethods());
|
||||
|
||||
console.log('App launched!');
|
||||
|
||||
app.listen(3000);
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
version: '1.0.0',
|
||||
changelog: [
|
||||
'initial release'
|
||||
],
|
||||
};
|
|
@ -0,0 +1,100 @@
|
|||
import { Context, DefaultState } from 'koa';
|
||||
import views from 'koa-views';
|
||||
import Router from 'koa-router';
|
||||
import config from './config';
|
||||
import { signIn, api, i } from './misskey';
|
||||
import { Note } from './models/Note';
|
||||
import { User } from './models/User';
|
||||
|
||||
export const die = (ctx: Context, error: string): Promise<void> => {
|
||||
ctx.status = 400;
|
||||
return ctx.render('error', { error });
|
||||
};
|
||||
|
||||
export const render = views(__dirname + '/views', {
|
||||
extension: 'pug', options: {
|
||||
...config,
|
||||
getAcct: (user: User) => user.host ? `@${user.username}@${user.host}` : `@${user.username}`,
|
||||
getUserName: (user: User) => user.name || user.username,
|
||||
}
|
||||
});
|
||||
|
||||
export const router = new Router<DefaultState, Context>();
|
||||
|
||||
const staticRouting = [
|
||||
[ 'about', 'Simpkey について' ],
|
||||
[ 'terms', '利用規約' ],
|
||||
[ 'privacy-policy', 'プライバシーポリシー' ],
|
||||
];
|
||||
|
||||
for (const [ name, title ] of staticRouting) {
|
||||
router.get('/' + name, async (ctx, next) => {
|
||||
await ctx.render(name, { title });
|
||||
await next();
|
||||
});
|
||||
}
|
||||
|
||||
async function timeline(ctx: Context, host: string, endpoint: string, timelineName: string, token: string) {
|
||||
const user = await i(host, token);
|
||||
|
||||
const timeline = await api<Note[]>(host, endpoint, { i: token });
|
||||
await ctx.render('timeline', {
|
||||
title: timelineName + ' - Simpkey',
|
||||
user, timeline, timelineName
|
||||
});
|
||||
}
|
||||
|
||||
router.get('/ltl', async (ctx, next) => {
|
||||
const token = ctx.cookies.get('i');
|
||||
const host = ctx.cookies.get('host');
|
||||
if (!token || !host) {
|
||||
await die(ctx, 'ログインしてください');
|
||||
} else {
|
||||
const meta = await api<any>(host, 'meta', { i: token });
|
||||
if (meta.disableLocalTimeline) {
|
||||
await die(ctx, 'ローカルタイムラインは無効化されています');
|
||||
} else {
|
||||
await timeline(ctx, host, 'notes/local-timeline', 'ローカルタイムライン', token);
|
||||
}
|
||||
}
|
||||
await next();
|
||||
});
|
||||
|
||||
router.get('/', async (ctx, next) => {
|
||||
const token = ctx.cookies.get('i');
|
||||
const host = ctx.cookies.get('host');
|
||||
if (!token || !host) {
|
||||
console.log('no session so show top page');
|
||||
await ctx.render('index', {
|
||||
title: 'Simpkey'
|
||||
});
|
||||
} else {
|
||||
console.log('show timeline with the session');
|
||||
await timeline(ctx, host, 'notes/timeline', 'ホームタイムライン', token);
|
||||
}
|
||||
await next();
|
||||
});
|
||||
|
||||
router.post('/', async (ctx) => {
|
||||
const {
|
||||
host,
|
||||
username,
|
||||
password,
|
||||
token
|
||||
} = ctx.request.body;
|
||||
if (!host || !username || !password) {
|
||||
await die(ctx, 'パラメータが足りません');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { id, i } = await signIn(host, username, password, token);
|
||||
ctx.cookies.set('id', id);
|
||||
ctx.cookies.set('host', host);
|
||||
ctx.cookies.set('i', i);
|
||||
console.log('login as ' + username);
|
||||
ctx.redirect('/');
|
||||
} catch (err) {
|
||||
await die(ctx, err.message);
|
||||
console.error(err);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
import axios from 'axios';
|
||||
import { User } from './models/User';
|
||||
import { Note } from './models/Note';
|
||||
|
||||
export async function api<T>(host: string, endpoint: string, opts: { [_: string]: string }): Promise<T> {
|
||||
const res = await axios.post<T>(`https://${host}/api/${endpoint}`, opts);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
type SignInResult = { id: string, i: string };
|
||||
|
||||
export function signIn(host: string, username: string, password: string, token?: string): Promise<SignInResult> {
|
||||
return api<SignInResult>(host, 'signin', {
|
||||
username, password, ...( token ? { token } : { } )
|
||||
});
|
||||
}
|
||||
|
||||
export function i(host: string, i: string): Promise<User> {
|
||||
return api<User>(host, 'i', { i });
|
||||
}
|
||||
|
||||
export function usersShow(host: string, userId: string): Promise<User> {
|
||||
return api<User>(host, 'users/show', { userId });
|
||||
}
|
||||
|
||||
export function notesShow(host: string, noteId: string): Promise<Note> {
|
||||
return api<Note>(host, 'notes/show', { noteId });
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { User } from './User';
|
||||
|
||||
export type NoteVisibility = 'public' | 'home' | 'followers' | 'specified';
|
||||
|
||||
export interface Note {
|
||||
createdAt: string;
|
||||
cw: string | null;
|
||||
// emojis: Emoji[];
|
||||
fileIds: string[];
|
||||
// files: DriveFile[];
|
||||
id: string;
|
||||
reactions: Record<string, number>;
|
||||
renoteCount: 0;
|
||||
renoteId: string | null;
|
||||
renote?: Note;
|
||||
repliesCount: 0;
|
||||
replyId: string | null;
|
||||
reply?: Note;
|
||||
text: string | null;
|
||||
uri: string | null;
|
||||
url: string | null;
|
||||
user: User,
|
||||
userId: string;
|
||||
visibility: NoteVisibility;
|
||||
localOnly: boolean;
|
||||
viaMobile: boolean;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import { Note } from './Note';
|
||||
|
||||
export interface User {
|
||||
alwaysMarkNsfw: boolean;
|
||||
autoAcceptFollowed: boolean;
|
||||
avatarUrl: string;
|
||||
bannerUrl: string;
|
||||
birthday: string;
|
||||
carefulBot: boolean;
|
||||
createdAt: string;
|
||||
description: string | null;
|
||||
fields: { name: string, value: string }[];
|
||||
followersCount: number;
|
||||
followingCount: number;
|
||||
hasPendingReceivedFollowRequest: boolean;
|
||||
hasUnreadAnnouncement: boolean;
|
||||
hasUnreadAntenna: boolean;
|
||||
hasUnreadMentions: boolean;
|
||||
hasUnreadMessagingMessage: boolean;
|
||||
hasUnreadNotification: boolean;
|
||||
hasUnreadSpecifiedNotes: boolean;
|
||||
host: string | null;
|
||||
id: string;
|
||||
injectFeaturedNote: boolean;
|
||||
isAdmin: boolean;
|
||||
isBot: boolean;
|
||||
isCat: boolean;
|
||||
isLocked: boolean;
|
||||
isModerator: boolean;
|
||||
isSilenced: boolean;
|
||||
isSuspended: boolean;
|
||||
location: string | null;
|
||||
name: string | null;
|
||||
notesCount: number;
|
||||
pinnedNotes: Note[];
|
||||
// pinnedPage: Page;
|
||||
token?: string;
|
||||
twoFactorEnabled: boolean;
|
||||
updatedAt: string;
|
||||
url: string | null;
|
||||
usePasswordLessLogin: boolean;
|
||||
username: string;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
mixin avatar(user)
|
||||
img.avatar(src=user.avatarUrl, alt="avatar for " + user.username style="width: 64px; height: 64px; border-radius: 50%")
|
||||
|
||||
mixin note(note)
|
||||
.note(id=note.id)
|
||||
+avatar(note.user)
|
||||
p: b=getUserName(note.user)
|
||||
|
|
||||
span(style="color: gray")= getAcct(note.user)
|
||||
p= note.text
|
||||
aside= new Date(note.createdAt).toLocaleString()
|
||||
aside= note.visibility
|
||||
|
||||
html
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport", content="width=device-width, initial-scale=1.0")
|
||||
block meta
|
||||
title= title
|
||||
body
|
||||
header
|
||||
h1: a(href="/") Simpkey
|
||||
block header
|
||||
main
|
||||
block content
|
||||
|
||||
footer
|
||||
hr
|
||||
div
|
||||
a(href="/privacy-policy") プライバシーポリシー
|
||||
| ・
|
||||
a(href="/terms") 利用規約
|
||||
p (C)2020 Xeltica -
|
||||
a(href="/about") version !{version}
|
||||
block footer
|
|
@ -0,0 +1,8 @@
|
|||
extends _base
|
||||
|
||||
block content
|
||||
p Simpkey は、JavaScript のいらない Misskey クライアントです。
|
||||
h2 バージョン !{version}
|
||||
ul
|
||||
each val in changelog
|
||||
li= val
|
|
@ -0,0 +1,6 @@
|
|||
extends _base
|
||||
|
||||
block content
|
||||
h2 エラー
|
||||
p= error || '不明なエラーです'
|
||||
p: a(href="/") トップページに戻る
|
|
@ -0,0 +1,15 @@
|
|||
extends _base
|
||||
|
||||
block content
|
||||
p Simpkey へようこそ
|
||||
p 使用するインスタンス、ユーザー名、パスワードを入力して、今すぐ始めましょう。
|
||||
form(action="/", method="post")
|
||||
div: label インスタンス名:
|
||||
input(type="text", name="host", placeholder="例: misskey.io")
|
||||
div: label ユーザー名:
|
||||
input(type="text", name="username")
|
||||
div: label パスワード:
|
||||
input(type="password", name="password")
|
||||
div: label 2段階認証コード (必要なら):
|
||||
input(type="text", name="token")
|
||||
button(type="submit") ログイン
|
|
@ -0,0 +1,20 @@
|
|||
extends _base
|
||||
|
||||
block content
|
||||
h2 プライバシーポリシー
|
||||
p 本サイトのプライバシーポリシーを以下に示します。本サイトのサービスをご利用いただいた時点で、自動的にポリシーに同意したものとみなされます。
|
||||
|
||||
h3 個人情報の利用目的
|
||||
p 本サイトでは、対象とする Misskey インスタンスにログインする際にユーザー名およびパスワードを入力する必要があります。
|
||||
p これらの情報は対象の Misskey インスタンスにログインするためだけに使用されます。本サイト自体は、ユーザーの入力したユーザー名・パスワードを一切収集致しません。
|
||||
p また、本サイトではユーザーの IP アドレスを収集しています。収集された IP アドレスは、利用規約への違反を行ったユーザーの特定および処分の為に使用されます。
|
||||
|
||||
h3 個人情報の第三者への開示
|
||||
p 悪質な違反行為を行ったユーザーに対する処罰の一環としてプロバイダーへの通報を行う場合や、法令に基づく要請がある場合を除き、収集した個人情報を外部に開示することはありません。また、個人情報の取扱を第三者に委託することもありません。
|
||||
|
||||
h3 免責事項
|
||||
p 本サイトを通じてアクセスする Misskey インスタンスにつきましては、本プライバシーポリシーは適用されません。別途、当該インスタンスのプライバシーポリシーをご確認頂く必要があります。本サイトでは、アクセス先のインスタンスにて取り扱われる個人情報については一切の責任を負いません。
|
||||
|
||||
h3 変更について
|
||||
p 当サイトは、個人情報に関して適用される日本の法令を遵守するとともに、本ポリシーの内容を適宜見直しその改善に努めます。
|
||||
p 修正された最新のプライバシーポリシーは常に本ページにて開示されます。
|
|
@ -0,0 +1,8 @@
|
|||
extends _base
|
||||
|
||||
block content
|
||||
h2 利用規約
|
||||
p 本サイトの利用規約を以下に示します。本サイトのサービスをご利用いただいた時点で、自動的に本規約に同意したものとみなされます。
|
||||
|
||||
h3 禁止行為
|
||||
p 以下に定める行為は固く禁じます。
|
|
@ -0,0 +1,27 @@
|
|||
extends _base
|
||||
|
||||
block content
|
||||
+avatar(user)
|
||||
p: b=getUserName(user)
|
||||
|
|
||||
span(style="color: gray")= getAcct(user)
|
||||
form(action="/note", method="post")
|
||||
textarea(name="text", placeholder="今何してる?" style="max-width: 100%; min-width: 100%; height: 6em; margin-bottom: 8px")
|
||||
button(type="submit") ノート
|
||||
hr
|
||||
div
|
||||
a(href="/") ホーム
|
||||
| ・
|
||||
a(href="/ltl") ローカル
|
||||
| ・
|
||||
a(href="/stl") ソーシャル
|
||||
| ・
|
||||
a(href="/gtl") グローバル
|
||||
h2= timelineName
|
||||
each note in timeline
|
||||
if (note.renote)
|
||||
p: b 🔁 !{getUserName(note.user)} がRenote
|
||||
+note(note.renote)
|
||||
else
|
||||
+note(note)
|
||||
hr
|
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./built/", /* Redirect output structure to the directory. */
|
||||
"rootDir": "./src/", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
"typeRoots": [
|
||||
"node_modules/@types",
|
||||
"src/@types"
|
||||
], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue