mirror of
https://github.com/PaiGramTeam/luoxu-api-pub.git
synced 2024-11-29 02:37:19 +00:00
commit
326065950d
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[{package.json,.travis.yml,.eslintrc.json}]
|
||||||
|
indent_style = space
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
|||||||
/public/build/
|
/public/build/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.vscode
|
||||||
|
1300
package-lock.json
generated
1300
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@ -5,17 +5,24 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c",
|
"build": "rollup -c",
|
||||||
"dev": "rollup -c -w",
|
"dev": "rollup -c -w",
|
||||||
"start": "sirv public --no-clear"
|
"start": "sirv public --no-clear",
|
||||||
|
"check": "svelte-check --tsconfig ./tsconfig.json"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^17.0.0",
|
"@rollup/plugin-commonjs": "^17.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^11.0.0",
|
"@rollup/plugin-node-resolve": "^11.0.0",
|
||||||
|
"@rollup/plugin-typescript": "^8.0.0",
|
||||||
|
"@tsconfig/svelte": "^2.0.1",
|
||||||
"rollup": "^2.3.4",
|
"rollup": "^2.3.4",
|
||||||
"rollup-plugin-css-only": "^3.1.0",
|
"rollup-plugin-css-only": "^3.1.0",
|
||||||
"rollup-plugin-livereload": "^2.0.0",
|
"rollup-plugin-livereload": "^2.0.0",
|
||||||
"rollup-plugin-svelte": "^7.0.0",
|
"rollup-plugin-svelte": "^7.0.0",
|
||||||
"rollup-plugin-terser": "^7.0.0",
|
"rollup-plugin-terser": "^7.0.0",
|
||||||
"svelte": "^3.0.0"
|
"svelte": "^3.0.0",
|
||||||
|
"svelte-check": "^2.0.0",
|
||||||
|
"svelte-preprocess": "^4.0.0",
|
||||||
|
"tslib": "^2.0.0",
|
||||||
|
"typescript": "^4.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"sirv-cli": "^1.0.0"
|
"sirv-cli": "^1.0.0"
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
import svelte from 'rollup-plugin-svelte';
|
|
||||||
import commonjs from '@rollup/plugin-commonjs';
|
|
||||||
import resolve from '@rollup/plugin-node-resolve';
|
|
||||||
import livereload from 'rollup-plugin-livereload';
|
|
||||||
import { terser } from 'rollup-plugin-terser';
|
|
||||||
import css from 'rollup-plugin-css-only';
|
|
||||||
|
|
||||||
const production = !process.env.ROLLUP_WATCH;
|
|
||||||
|
|
||||||
function serve() {
|
|
||||||
let server;
|
|
||||||
|
|
||||||
function toExit() {
|
|
||||||
if (server) server.kill(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
writeBundle() {
|
|
||||||
if (server) return;
|
|
||||||
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
|
|
||||||
// stdio: ['ignore', 'inherit', 'inherit'],
|
|
||||||
// shell: true
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('SIGTERM', toExit);
|
|
||||||
process.on('exit', toExit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
input: 'src/main.js',
|
|
||||||
output: {
|
|
||||||
sourcemap: true,
|
|
||||||
format: 'iife',
|
|
||||||
name: 'app',
|
|
||||||
file: 'public/build/bundle.js'
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
svelte({
|
|
||||||
compilerOptions: {
|
|
||||||
// enable run-time checks when not in production
|
|
||||||
dev: !production
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
// we'll extract any component CSS out into
|
|
||||||
// a separate file - better for performance
|
|
||||||
css({ output: 'bundle.css' }),
|
|
||||||
|
|
||||||
// If you have external dependencies installed from
|
|
||||||
// npm, you'll most likely need these plugins. In
|
|
||||||
// some cases you'll need additional configuration -
|
|
||||||
// consult the documentation for details:
|
|
||||||
// https://github.com/rollup/plugins/tree/master/packages/commonjs
|
|
||||||
resolve({
|
|
||||||
browser: true,
|
|
||||||
dedupe: ['svelte']
|
|
||||||
}),
|
|
||||||
commonjs(),
|
|
||||||
|
|
||||||
// In dev mode, call `npm run start` once
|
|
||||||
// the bundle has been generated
|
|
||||||
!production && serve(),
|
|
||||||
|
|
||||||
// Watch the `public` directory and refresh the
|
|
||||||
// browser on changes when not in production
|
|
||||||
!production && livereload('public'),
|
|
||||||
|
|
||||||
// If we're building for production (npm run build
|
|
||||||
// instead of npm run dev), minify
|
|
||||||
production && terser()
|
|
||||||
],
|
|
||||||
watch: {
|
|
||||||
buildDelay: 500,
|
|
||||||
clearScreen: false
|
|
||||||
}
|
|
||||||
};
|
|
89
rollup.config.ts
Normal file
89
rollup.config.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import svelte from "rollup-plugin-svelte";
|
||||||
|
import commonjs from "@rollup/plugin-commonjs";
|
||||||
|
import resolve from "@rollup/plugin-node-resolve";
|
||||||
|
import livereload from "rollup-plugin-livereload";
|
||||||
|
import { terser } from "rollup-plugin-terser";
|
||||||
|
import sveltePreprocess from "svelte-preprocess";
|
||||||
|
import typescript from "@rollup/plugin-typescript";
|
||||||
|
import css from "rollup-plugin-css-only";
|
||||||
|
|
||||||
|
const production = !process.env.ROLLUP_WATCH;
|
||||||
|
|
||||||
|
function serve() {
|
||||||
|
let server;
|
||||||
|
|
||||||
|
function toExit() {
|
||||||
|
if (server) server.kill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
writeBundle() {
|
||||||
|
if (server) return;
|
||||||
|
server = require("child_process").spawn(
|
||||||
|
"npm",
|
||||||
|
["run", "start", "--", "--dev"],
|
||||||
|
{
|
||||||
|
// stdio: ['ignore', 'inherit', 'inherit'],
|
||||||
|
// shell: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
process.on("SIGTERM", toExit);
|
||||||
|
process.on("exit", toExit);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: "src/main.ts",
|
||||||
|
output: {
|
||||||
|
sourcemap: true,
|
||||||
|
format: "iife",
|
||||||
|
name: "app",
|
||||||
|
file: "public/build/bundle.js",
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
svelte({
|
||||||
|
preprocess: sveltePreprocess({ sourceMap: !production }),
|
||||||
|
compilerOptions: {
|
||||||
|
// enable run-time checks when not in production
|
||||||
|
dev: !production,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
// we'll extract any component CSS out into
|
||||||
|
// a separate file - better for performance
|
||||||
|
css({ output: "bundle.css" }),
|
||||||
|
|
||||||
|
// If you have external dependencies installed from
|
||||||
|
// npm, you'll most likely need these plugins. In
|
||||||
|
// some cases you'll need additional configuration -
|
||||||
|
// consult the documentation for details:
|
||||||
|
// https://github.com/rollup/plugins/tree/master/packages/commonjs
|
||||||
|
resolve({
|
||||||
|
browser: true,
|
||||||
|
dedupe: ["svelte"],
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
typescript({
|
||||||
|
sourceMap: true,
|
||||||
|
//sourceMap: !production,
|
||||||
|
inlineSources: !production,
|
||||||
|
}),
|
||||||
|
|
||||||
|
// In dev mode, call `npm run start` once
|
||||||
|
// the bundle has been generated
|
||||||
|
!production && serve(),
|
||||||
|
|
||||||
|
// Watch the `public` directory and refresh the
|
||||||
|
// browser on changes when not in production
|
||||||
|
!production && livereload("public"),
|
||||||
|
|
||||||
|
// If we're building for production (npm run build
|
||||||
|
// instead of npm run dev), minify
|
||||||
|
production && terser(),
|
||||||
|
],
|
||||||
|
watch: {
|
||||||
|
buildDelay: 500,
|
||||||
|
clearScreen: false,
|
||||||
|
},
|
||||||
|
};
|
@ -1,121 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
|
|
||||||
/** This script modifies the project to support TS code in .svelte files like:
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export let name: string;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
As well as validating the code for CI.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** To work on this script:
|
|
||||||
rm -rf test-template template && git clone sveltejs/template test-template && node scripts/setupTypeScript.js test-template
|
|
||||||
*/
|
|
||||||
|
|
||||||
const fs = require("fs")
|
|
||||||
const path = require("path")
|
|
||||||
const { argv } = require("process")
|
|
||||||
|
|
||||||
const projectRoot = argv[2] || path.join(__dirname, "..")
|
|
||||||
|
|
||||||
// Add deps to pkg.json
|
|
||||||
const packageJSON = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf8"))
|
|
||||||
packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, {
|
|
||||||
"svelte-check": "^2.0.0",
|
|
||||||
"svelte-preprocess": "^4.0.0",
|
|
||||||
"@rollup/plugin-typescript": "^8.0.0",
|
|
||||||
"typescript": "^4.0.0",
|
|
||||||
"tslib": "^2.0.0",
|
|
||||||
"@tsconfig/svelte": "^2.0.0"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add script for checking
|
|
||||||
packageJSON.scripts = Object.assign(packageJSON.scripts, {
|
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Write the package JSON
|
|
||||||
fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify(packageJSON, null, " "))
|
|
||||||
|
|
||||||
// mv src/main.js to main.ts - note, we need to edit rollup.config.js for this too
|
|
||||||
const beforeMainJSPath = path.join(projectRoot, "src", "main.js")
|
|
||||||
const afterMainTSPath = path.join(projectRoot, "src", "main.ts")
|
|
||||||
fs.renameSync(beforeMainJSPath, afterMainTSPath)
|
|
||||||
|
|
||||||
// Switch the app.svelte file to use TS
|
|
||||||
const appSveltePath = path.join(projectRoot, "src", "App.svelte")
|
|
||||||
let appFile = fs.readFileSync(appSveltePath, "utf8")
|
|
||||||
appFile = appFile.replace("<script>", '<script lang="ts">')
|
|
||||||
appFile = appFile.replace("export let name;", 'export let name: string;')
|
|
||||||
fs.writeFileSync(appSveltePath, appFile)
|
|
||||||
|
|
||||||
// Edit rollup config
|
|
||||||
const rollupConfigPath = path.join(projectRoot, "rollup.config.js")
|
|
||||||
let rollupConfig = fs.readFileSync(rollupConfigPath, "utf8")
|
|
||||||
|
|
||||||
// Edit imports
|
|
||||||
rollupConfig = rollupConfig.replace(`'rollup-plugin-terser';`, `'rollup-plugin-terser';
|
|
||||||
import sveltePreprocess from 'svelte-preprocess';
|
|
||||||
import typescript from '@rollup/plugin-typescript';`)
|
|
||||||
|
|
||||||
// Replace name of entry point
|
|
||||||
rollupConfig = rollupConfig.replace(`'src/main.js'`, `'src/main.ts'`)
|
|
||||||
|
|
||||||
// Add preprocessor
|
|
||||||
rollupConfig = rollupConfig.replace(
|
|
||||||
'compilerOptions:',
|
|
||||||
'preprocess: sveltePreprocess({ sourceMap: !production }),\n\t\t\tcompilerOptions:'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add TypeScript
|
|
||||||
rollupConfig = rollupConfig.replace(
|
|
||||||
'commonjs(),',
|
|
||||||
'commonjs(),\n\t\ttypescript({\n\t\t\tsourceMap: !production,\n\t\t\tinlineSources: !production\n\t\t}),'
|
|
||||||
);
|
|
||||||
fs.writeFileSync(rollupConfigPath, rollupConfig)
|
|
||||||
|
|
||||||
// Add TSConfig
|
|
||||||
const tsconfig = `{
|
|
||||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
|
||||||
|
|
||||||
"include": ["src/**/*"],
|
|
||||||
"exclude": ["node_modules/*", "__sapper__/*", "public/*"]
|
|
||||||
}`
|
|
||||||
const tsconfigPath = path.join(projectRoot, "tsconfig.json")
|
|
||||||
fs.writeFileSync(tsconfigPath, tsconfig)
|
|
||||||
|
|
||||||
// Add global.d.ts
|
|
||||||
const dtsPath = path.join(projectRoot, "src", "global.d.ts")
|
|
||||||
fs.writeFileSync(dtsPath, `/// <reference types="svelte" />`)
|
|
||||||
|
|
||||||
// Delete this script, but not during testing
|
|
||||||
if (!argv[2]) {
|
|
||||||
// Remove the script
|
|
||||||
fs.unlinkSync(path.join(__filename))
|
|
||||||
|
|
||||||
// Check for Mac's DS_store file, and if it's the only one left remove it
|
|
||||||
const remainingFiles = fs.readdirSync(path.join(__dirname))
|
|
||||||
if (remainingFiles.length === 1 && remainingFiles[0] === '.DS_store') {
|
|
||||||
fs.unlinkSync(path.join(__dirname, '.DS_store'))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the scripts folder is empty
|
|
||||||
if (fs.readdirSync(path.join(__dirname)).length === 0) {
|
|
||||||
// Remove the scripts folder
|
|
||||||
fs.rmdirSync(path.join(__dirname))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds the extension recommendation
|
|
||||||
fs.mkdirSync(path.join(projectRoot, ".vscode"), { recursive: true })
|
|
||||||
fs.writeFileSync(path.join(projectRoot, ".vscode", "extensions.json"), `{
|
|
||||||
"recommendations": ["svelte.svelte-vscode"]
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
console.log("Converted to TypeScript.")
|
|
||||||
|
|
||||||
if (fs.existsSync(path.join(projectRoot, "node_modules"))) {
|
|
||||||
console.log("\nYou will need to re-run your dependency manager to get started.")
|
|
||||||
}
|
|
224
src/App.svelte
224
src/App.svelte
@ -1,169 +1,175 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { onMount, setContext } from 'svelte'
|
import { onMount, setContext } from "svelte";
|
||||||
import Message from './Message.svelte'
|
import Message from "./Message.svelte";
|
||||||
import Name from './Name.svelte'
|
import Name from "./Name.svelte";
|
||||||
import { sleep } from './util.js'
|
import { sleep } from "./util.js";
|
||||||
|
|
||||||
const LUOXU_URL = 'https://lab.lilydjwg.me/luoxu'
|
const LUOXU_URL = "https://lab.lilydjwg.me/luoxu";
|
||||||
const islocal = LUOXU_URL.indexOf('http://localhost') === 0
|
const islocal = LUOXU_URL.startsWith("http://localhost");
|
||||||
let groups = []
|
let groups = [];
|
||||||
let group
|
let group: string;
|
||||||
let query
|
let query: string;
|
||||||
let error
|
let error: string;
|
||||||
let result
|
let result: { messages: string | any[]; has_more: any; groupinfo: any };
|
||||||
let now = new Date()
|
let now = new Date();
|
||||||
let loading = false
|
let loading = false;
|
||||||
let need_update_title = false
|
let need_update_title = false;
|
||||||
let sender
|
let sender: string;
|
||||||
let selected_init
|
let selected_init: string;
|
||||||
let our_hash_change = false
|
let our_hash_change = false;
|
||||||
|
|
||||||
setContext('LUOXU_URL', LUOXU_URL)
|
setContext("LUOXU_URL", LUOXU_URL);
|
||||||
|
|
||||||
function parse_hash() {
|
function parse_hash() {
|
||||||
const hash = location.hash
|
const hash = location.hash;
|
||||||
if (hash) {
|
if (hash) {
|
||||||
return new URLSearchParams(hash.substring(1))
|
return new URLSearchParams(hash.substring(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
do_hash_search()
|
do_hash_search();
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${LUOXU_URL}/groups`)
|
const res = await fetch(`${LUOXU_URL}/groups`);
|
||||||
groups = (await res.json()).groups
|
groups = (await res.json()).groups;
|
||||||
need_update_title = true
|
need_update_title = true;
|
||||||
if (!group) {
|
if (!group) {
|
||||||
group = ''
|
group = "";
|
||||||
}
|
}
|
||||||
break
|
break;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('failed to fetch group info, will retry', e)
|
console.error("failed to fetch group info, will retry", e);
|
||||||
await sleep(1000)
|
await sleep(1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
// only update title on hash change (doing a search)
|
// only update title on hash change (doing a search)
|
||||||
if (need_update_title && groups) {
|
if (need_update_title && groups) {
|
||||||
let group_name
|
let group_name: any;
|
||||||
for (const g of groups) {
|
for (const g of groups) {
|
||||||
if(g.group_id == group) {
|
if (g.group_id === group) {
|
||||||
group_name = g.name
|
group_name = g.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (query && group_name) {
|
if (query && group_name) {
|
||||||
document.title = `搜索:${query} 于 ${group_name} - 落絮`
|
document.title = `搜索:${query} 于 ${group_name} - 落絮`;
|
||||||
} else if (query) {
|
} else if (query) {
|
||||||
document.title = `搜索:${query} - 落絮`
|
document.title = `搜索:${query} - 落絮`;
|
||||||
} else if (group_name) {
|
} else if (group_name) {
|
||||||
document.title = `搜索 ${group_name} - 落絮`
|
document.title = `搜索 ${group_name} - 落絮`;
|
||||||
} else {
|
} else {
|
||||||
document.title = '落絮'
|
document.title = "落絮";
|
||||||
}
|
}
|
||||||
need_update_title = false
|
need_update_title = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function do_hash_search() {
|
function do_hash_search() {
|
||||||
const info = parse_hash()
|
const info = parse_hash();
|
||||||
if (info) {
|
if (info) {
|
||||||
query = ''
|
query = "";
|
||||||
group = ''
|
group = "";
|
||||||
result = null
|
result = null;
|
||||||
if(info.has('g')) {
|
if (info.has("g")) {
|
||||||
group = info.get('g')|0
|
group = (parseFloat(info.get("g")) | 0).toString();
|
||||||
}
|
}
|
||||||
if(info.has('q')) {
|
if (info.has("q")) {
|
||||||
query = info.get('q')
|
query = info.get("q");
|
||||||
}
|
}
|
||||||
if(info.has('sender')) {
|
if (info.has("sender")) {
|
||||||
sender = info.get('sender')
|
sender = info.get("sender");
|
||||||
selected_init = sender
|
selected_init = sender;
|
||||||
}
|
}
|
||||||
if ((group || islocal) && query) {
|
if ((group || islocal) && query) {
|
||||||
result = null
|
result = null;
|
||||||
do_search()
|
do_search();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function do_search(more) {
|
async function do_search(more?: any) {
|
||||||
if (!group && !islocal) {
|
if (!group && !islocal) {
|
||||||
error = '请选择要搜索的群组'
|
error = "请选择要搜索的群组";
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (!query && !islocal) {
|
if (!query && !islocal) {
|
||||||
error = '请输入搜索关键字'
|
error = "请输入搜索关键字";
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
error = ''
|
error = "";
|
||||||
our_hash_change = true
|
our_hash_change = true;
|
||||||
console.log(`searching ${query} for group ${group}, older than ${more}, from ${sender}`)
|
console.log(
|
||||||
const q = new URLSearchParams()
|
`searching ${query} for group ${group}, older than ${more}, from ${sender}`
|
||||||
|
);
|
||||||
|
const q = new URLSearchParams();
|
||||||
if (group) {
|
if (group) {
|
||||||
q.append('g', group)
|
q.append("g", group);
|
||||||
}
|
}
|
||||||
if (query) {
|
if (query) {
|
||||||
q.append('q', query)
|
q.append("q", query);
|
||||||
}
|
}
|
||||||
if (sender) {
|
if (sender) {
|
||||||
q.append('sender', sender)
|
q.append("sender", sender);
|
||||||
}
|
}
|
||||||
let url
|
let url: RequestInfo | URL;
|
||||||
const qstr = q.toString()
|
const qstr = q.toString();
|
||||||
if (!more) {
|
if (!more) {
|
||||||
location.hash = `#${qstr}`
|
location.hash = `#${qstr}`;
|
||||||
need_update_title = true
|
need_update_title = true;
|
||||||
if (result) {
|
if (result) {
|
||||||
result.messages = []
|
result.messages = [];
|
||||||
}
|
}
|
||||||
url = `${LUOXU_URL}/search?${qstr}`
|
url = `${LUOXU_URL}/search?${qstr}`;
|
||||||
} else {
|
} else {
|
||||||
url = `${LUOXU_URL}/search?${q}&end=${more}`
|
url = `${LUOXU_URL}/search?${q}&end=${more}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
now = new Date()
|
now = new Date();
|
||||||
loading = true
|
loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url)
|
const res = await fetch(url);
|
||||||
const r = await res.json()
|
const r = await res.json();
|
||||||
loading = false
|
loading = false;
|
||||||
if (more) {
|
if (more) {
|
||||||
return r
|
return r;
|
||||||
} else {
|
} else {
|
||||||
result = r
|
result = r;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e
|
error = e;
|
||||||
loading = false
|
loading = false;
|
||||||
}
|
}
|
||||||
our_hash_change = false;
|
our_hash_change = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function on_group_change() {
|
async function on_group_change() {
|
||||||
error = ''
|
error = "";
|
||||||
if (query) {
|
if (query) {
|
||||||
await do_search()
|
await do_search();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function do_search_more() {
|
async function do_search_more() {
|
||||||
const more = result.messages[result.messages.length-1].t
|
const more = result.messages[result.messages.length - 1].t;
|
||||||
const old_msgs = result.messages
|
const old_msgs = result.messages;
|
||||||
const new_result = await do_search(more)
|
const new_result = await do_search(more);
|
||||||
result.messages = [...old_msgs, ...new_result.messages]
|
result.messages = [...old_msgs, ...new_result.messages];
|
||||||
result.has_more = new_result.has_more
|
result.has_more = new_result.has_more;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:hashchange={() => {if(!our_hash_change) do_hash_search()}}/>
|
<svelte:window
|
||||||
|
on:hashchange={() => {
|
||||||
|
if (!our_hash_change) do_hash_search();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<div id="searchbox">
|
<div id="searchbox">
|
||||||
{#if groups.length == 0}
|
{#if groups.length === 0}
|
||||||
<select>
|
<select>
|
||||||
<option selected>正在加载群组信息...</option>
|
<option selected>正在加载群组信息...</option>
|
||||||
</select>
|
</select>
|
||||||
@ -177,21 +183,30 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
{/if}
|
{/if}
|
||||||
<input type="search" bind:value={query}
|
<input
|
||||||
on:input={() => error = ''}
|
type="search"
|
||||||
on:keydown={e => {if(e.key == 'Enter'){do_search()}}}
|
bind:value={query}
|
||||||
|
on:input={() => (error = "")}
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
do_search();
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Name group={group} bind:selected={sender} selected_init={selected_init}/>
|
<Name {group} bind:selected={sender} {selected_init} />
|
||||||
<button on:click={() => do_search()}>搜索</button>
|
<button on:click={() => do_search()}>搜索</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if result}
|
{#if result}
|
||||||
{#each result.messages as message}
|
{#each result.messages as message}
|
||||||
<Message msg={message} groupinfo={result.groupinfo} now={now} />
|
<Message msg={message} groupinfo={result.groupinfo} {now} />
|
||||||
{/each}
|
{/each}
|
||||||
{:else if !loading && !error}
|
{:else if !loading && !error}
|
||||||
<div>
|
<div>
|
||||||
<p>搜索消息时,搜索字符串不区分简繁(会使用 OpenCC 自动转换),也不进行分词(请手动将可能不连在一起的词语以空格分开)。</p>
|
<p>
|
||||||
|
搜索消息时,搜索字符串不区分简繁(会使用 OpenCC
|
||||||
|
自动转换),也不进行分词(请手动将可能不连在一起的词语以空格分开)。
|
||||||
|
</p>
|
||||||
<p>搜索字符串支持以下功能:</p>
|
<p>搜索字符串支持以下功能:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>以空格分开的多个搜索词是「与」的关系</li>
|
<li>以空格分开的多个搜索词是「与」的关系</li>
|
||||||
@ -200,7 +215,10 @@
|
|||||||
<li>使用小括号来分组</li>
|
<li>使用小括号来分组</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>人名补全支持上下方向键和 Alt+N/P 进行选择。</p>
|
<p>人名补全支持上下方向键和 Alt+N/P 进行选择。</p>
|
||||||
<p>搜索结果右下角的时间,悬停可查看绝对时间、最后编辑时间(如编辑过),点击可跳转到 Telegram 中展示该消息。</p>
|
<p>
|
||||||
|
搜索结果右下角的时间,悬停可查看绝对时间、最后编辑时间(如编辑过),点击可跳转到
|
||||||
|
Telegram 中展示该消息。
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -209,13 +227,15 @@
|
|||||||
{:else}
|
{:else}
|
||||||
{#if error}
|
{#if error}
|
||||||
<p class="error">{error}</p>
|
<p class="error">{error}</p>
|
||||||
{:else if result && result.messages.length == 0}
|
{:else if result && result.messages.length === 0}
|
||||||
<div class="info"><p>没有匹配的消息。</p></div>
|
<div class="info"><p>没有匹配的消息。</p></div>
|
||||||
{:else if result && !result.has_more}
|
{:else if result && !result.has_more}
|
||||||
<div class="info"><p>到底了。</p></div>
|
<div class="info"><p>到底了。</p></div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if result && result.has_more}
|
{#if result && result.has_more}
|
||||||
<div class="info"><button on:click={do_search_more}>加载更多</button></div>
|
<div class="info">
|
||||||
|
<button on:click={do_search_more}>加载更多</button>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
@ -232,7 +252,7 @@
|
|||||||
#searchbox {
|
#searchbox {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
#searchbox input[type=search] {
|
#searchbox input[type="search"] {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
@ -259,12 +279,16 @@
|
|||||||
border-radius: 2em;
|
border-radius: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(input), :global(button), :global(select) {
|
:global(input),
|
||||||
|
:global(button),
|
||||||
|
:global(select) {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border: 1px solid var(--color-inactive);
|
border: 1px solid var(--color-inactive);
|
||||||
height: 2.3em;
|
height: 2.3em;
|
||||||
}
|
}
|
||||||
:global(input:focus), :global(button:focus), :global(select:focus) {
|
:global(input:focus),
|
||||||
|
:global(button:focus),
|
||||||
|
:global(select:focus) {
|
||||||
border-color: var(--color-active);
|
border-color: var(--color-active);
|
||||||
outline: 1px solid var(--color-active);
|
outline: 1px solid var(--color-active);
|
||||||
}
|
}
|
||||||
|
@ -1,58 +1,66 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { onMount, getContext } from 'svelte'
|
import { onMount, getContext } from "svelte";
|
||||||
|
|
||||||
export let msg
|
export let msg: any;
|
||||||
export let groupinfo
|
export let groupinfo: any;
|
||||||
export let now
|
export let now: any;
|
||||||
|
|
||||||
const formatter = new Intl.DateTimeFormat(undefined, {
|
const formatter = new Intl.DateTimeFormat(undefined, {
|
||||||
timeStyle: "full",
|
timeStyle: "full",
|
||||||
dateStyle: "full",
|
dateStyle: "full",
|
||||||
hour12: false,
|
hour12: false,
|
||||||
})
|
});
|
||||||
|
const format_dt = formatter.format;
|
||||||
|
let dt = new Date(msg.t * 1000);
|
||||||
|
let edited = msg.edited ? new Date(msg.edited * 1000) : null;
|
||||||
|
let title =
|
||||||
|
format_dt(dt) + (edited ? `\n最后编辑于:${format_dt(edited)}` : "");
|
||||||
|
let relative_dt = format_relative_time(dt, now);
|
||||||
|
let iso_date = dt.toISOString();
|
||||||
|
let msgurl = groupinfo[msg.group_id][0]
|
||||||
|
? `tg://resolve?domain=${groupinfo[msg.group_id][0]}&post=${msg.id}`
|
||||||
|
: `tg://privatepost?channel=${msg.group_id}&post=${msg.id}`;
|
||||||
|
|
||||||
let dt = new Date(msg.t * 1000)
|
function format_relative_time(d1: Date, d2: Date) {
|
||||||
let edited = msg.edited ? new Date(msg.edited * 1000) : null
|
|
||||||
let title = format_dt(dt) + (edited ? `\n最后编辑于:${format_dt(edited)}` : '')
|
|
||||||
let relative_dt = format_relative_time(dt, now)
|
|
||||||
let iso_date = dt.toISOString()
|
|
||||||
let msgurl = groupinfo[msg.group_id][0] ? `tg://resolve?domain=${groupinfo[msg.group_id][0]}&post=${msg.id}` : `tg://privatepost?channel=${msg.group_id}&post=${msg.id}`
|
|
||||||
|
|
||||||
function format_relative_time(d1, d2) {
|
|
||||||
// in miliseconds
|
// in miliseconds
|
||||||
const units = {
|
const units = {
|
||||||
year: 24 * 60 * 60 * 1000 * 365,
|
year: 24 * 60 * 60 * 1000 * 365,
|
||||||
month : 24 * 60 * 60 * 1000 * 365 / 12,
|
month: (24 * 60 * 60 * 1000 * 365) / 12,
|
||||||
day: 24 * 60 * 60 * 1000,
|
day: 24 * 60 * 60 * 1000,
|
||||||
hour: 60 * 60 * 1000,
|
hour: 60 * 60 * 1000,
|
||||||
minute: 60 * 1000,
|
minute: 60 * 1000,
|
||||||
second: 1000,
|
second: 1000,
|
||||||
}
|
};
|
||||||
|
|
||||||
const rtf = new Intl.RelativeTimeFormat()
|
const rtf = new Intl.RelativeTimeFormat();
|
||||||
|
//@ts-ignore
|
||||||
|
const elapsed = d1 - d2; //https://stackoverflow.com/a/4944782/13040423
|
||||||
|
|
||||||
const elapsed = d1 - d2
|
for (const [u, period] of Object.entries(units)) {
|
||||||
|
if (Math.abs(elapsed) > period || u === "second") {
|
||||||
for(const u in units) {
|
//@ts-ignore
|
||||||
if(Math.abs(elapsed) > units[u] || u == 'second') {
|
return rtf.format(Math.round(elapsed / period), u);
|
||||||
return rtf.format(Math.round(elapsed/units[u]), u)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function format_dt(t) {
|
|
||||||
return formatter.format(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="message">
|
<div class="message">
|
||||||
<img class="avatar" src="{getContext('LUOXU_URL')}/avatar/{msg.from_id}.jpg" height="64" width="64" alt="{msg.from_name} 的头像"/>
|
<img
|
||||||
|
class="avatar"
|
||||||
|
src="{getContext('LUOXU_URL')}/avatar/{msg.from_id}.jpg"
|
||||||
|
height="64"
|
||||||
|
width="64"
|
||||||
|
alt="{msg.from_name} 的头像"
|
||||||
|
/>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="name">{msg.from_name || ' '}</div>
|
<div class="name">{msg.from_name || " "}</div>
|
||||||
<div class="text">{@html msg.html}</div>
|
<div class="text">{@html msg.html}</div>
|
||||||
<div class="time">{groupinfo[msg.group_id][1]} <a href={msgurl}><time datetime={iso_date} title={title}>{relative_dt}</time></a></div>
|
<div class="time">
|
||||||
|
{groupinfo[msg.group_id][1]}
|
||||||
|
<a href={msgurl}><time datetime={iso_date} {title}>{relative_dt}</time></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -89,7 +97,8 @@
|
|||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
.time, .time > a {
|
.time,
|
||||||
|
.time > a {
|
||||||
color: gray;
|
color: gray;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
180
src/Name.svelte
180
src/Name.svelte
@ -1,140 +1,159 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { onMount, getContext } from 'svelte'
|
import { onMount, getContext } from "svelte";
|
||||||
|
|
||||||
export let group
|
export let group: any;
|
||||||
|
|
||||||
export let selected
|
export let selected: any;
|
||||||
export let selected_init
|
export let selected_init: any;
|
||||||
let selected_name = ''
|
let selected_name = "";
|
||||||
let selected_idx
|
let selected_idx: number;
|
||||||
|
|
||||||
let to
|
let to: string | number | NodeJS.Timeout;
|
||||||
let names = []
|
let names = [];
|
||||||
let url = getContext('LUOXU_URL')
|
let url = getContext("LUOXU_URL");
|
||||||
let input
|
let input: HTMLInputElement;
|
||||||
let ul
|
let ul: HTMLUListElement;
|
||||||
let should_hide = false
|
let should_hide = false;
|
||||||
|
|
||||||
let abort = new AbortController()
|
let abort = new AbortController();
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const rect = input.getBoundingClientRect()
|
const rect = input.getBoundingClientRect();
|
||||||
ul.style.top = `${rect.height - 1}px`
|
ul.style.top = `${rect.height - 1}px`;
|
||||||
ul.style.width = `${rect.width - 2}px`
|
ul.style.width = `${rect.width - 2}px`;
|
||||||
})
|
});
|
||||||
|
|
||||||
function update_list_width() {
|
function update_list_width() {
|
||||||
const rect = input.getBoundingClientRect()
|
const rect = input.getBoundingClientRect();
|
||||||
ul.style.width = `${rect.width - 2}px`
|
ul.style.width = `${rect.width - 2}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function may_complete() {
|
function may_complete() {
|
||||||
if (to) {
|
if (to) {
|
||||||
clearTimeout(to)
|
clearTimeout(to);
|
||||||
}
|
}
|
||||||
to = setTimeout(function () {
|
to = setTimeout(function () {
|
||||||
complete_it()
|
complete_it();
|
||||||
}, 300)
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function complete_it() {
|
async function complete_it() {
|
||||||
if (!input.value) {
|
if (!input.value) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
selected_idx = null
|
selected_idx = null;
|
||||||
abort.abort()
|
abort.abort();
|
||||||
abort = new AbortController()
|
abort = new AbortController();
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${url}/names?g=${group}&q=${input.value}`,
|
const res = await fetch(`${url}/names?g=${group}&q=${input.value}`, {
|
||||||
{signal: abort.signal})
|
signal: abort.signal,
|
||||||
const j = await res.json()
|
});
|
||||||
if(!abort.aborted) {
|
const j = await res.json();
|
||||||
|
if (!abort.signal.aborted) {
|
||||||
// only update if we're current
|
// only update if we're current
|
||||||
names = j.names
|
names = j.names;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if(e instanceof DOMException && e.name == 'AbortError'){
|
if (e instanceof DOMException && e.name === "AbortError") {
|
||||||
} else {
|
} else {
|
||||||
console.error(e)
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function select_by_click(e) {
|
function select_by_click(e: any) {
|
||||||
let el = e.target
|
let el = e.target;
|
||||||
if(el.tagName == 'IMG') {
|
if (el.tagName === "IMG") {
|
||||||
el = el.parentNode
|
el = el.parentNode;
|
||||||
}
|
}
|
||||||
if(el.tagName != 'LI') {
|
if (el.tagName != "LI") {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
selected_idx = el.dataset.idx|0
|
selected_idx = el.dataset.idx | 0;
|
||||||
select_confirmed()
|
select_confirmed();
|
||||||
input.focus()
|
input.focus();
|
||||||
should_hide = true
|
should_hide = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function select_confirmed() {
|
function select_confirmed() {
|
||||||
selected = names[selected_idx][0]
|
selected = names[selected_idx][0];
|
||||||
selected_name = names[selected_idx][1]
|
selected_name = names[selected_idx][1];
|
||||||
input.value = selected_name
|
input.value = selected_name;
|
||||||
selected_init = null
|
selected_init = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_value() {
|
function update_value() {
|
||||||
if (!selected || selected === selected_init) {
|
if (!selected || selected === selected_init) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (input.value) {
|
if (input.value) {
|
||||||
input.value = selected_name
|
input.value = selected_name;
|
||||||
} else {
|
} else {
|
||||||
selected = selected_init
|
selected = selected_init;
|
||||||
selected_name = ''
|
selected_name = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function select_by_key(e) {
|
function select_by_key(e: KeyboardEvent) {
|
||||||
if(e.key == 'ArrowDown' || (e.key == 'n' && e.altKey)) {
|
if (e.key === "ArrowDown" || (e.key === "n" && e.altKey)) {
|
||||||
select_next(1)
|
select_next(1);
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
}else if(e.key == 'ArrowUp' || (e.key == 'p' && e.altKey)) {
|
} else if (e.key === "ArrowUp" || (e.key === "p" && e.altKey)) {
|
||||||
select_next(-1)
|
select_next(-1);
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
}else if(e.key == 'Enter') {
|
} else if (e.key === "Enter") {
|
||||||
select_confirmed()
|
select_confirmed();
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function select_next(dir) {
|
function select_next(dir: number) {
|
||||||
if(typeof selected_idx === 'number') {
|
if (typeof selected_idx === "number") {
|
||||||
if (dir > 0) {
|
if (dir > 0) {
|
||||||
selected_idx = (selected_idx + 1) % names.length
|
selected_idx = (selected_idx + 1) % names.length;
|
||||||
} else {
|
} else {
|
||||||
selected_idx = (selected_idx - 1) % names.length
|
selected_idx = (selected_idx - 1) % names.length;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (dir > 0) {
|
if (dir > 0) {
|
||||||
selected_idx = 0
|
selected_idx = 0;
|
||||||
} else {
|
} else {
|
||||||
selected_idx = names.length - 1
|
selected_idx = names.length - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input bind:this={input} type="text"
|
<input
|
||||||
on:input={() => {should_hide=false;may_complete()}}
|
bind:this={input}
|
||||||
on:focus={() => should_hide=false}
|
type="text"
|
||||||
on:blur={() => {should_hide=true;update_value()}}
|
on:input={() => {
|
||||||
|
should_hide = false;
|
||||||
|
may_complete();
|
||||||
|
}}
|
||||||
|
on:focus={() => (should_hide = false)}
|
||||||
|
on:blur={() => {
|
||||||
|
should_hide = true;
|
||||||
|
update_value();
|
||||||
|
}}
|
||||||
on:keydown={select_by_key}
|
on:keydown={select_by_key}
|
||||||
/>
|
/>
|
||||||
<img class="selected-avatar" alt="" src="{url}/avatar/{selected?selected:'nobody'}.jpg"/>
|
<img
|
||||||
<ul bind:this={ul} on:click={select_by_click} on:mousedown|preventDefault={()=>{}} class:hidden={names.length === 0 || should_hide}>
|
class="selected-avatar"
|
||||||
|
alt=""
|
||||||
|
src="{url}/avatar/{selected ? selected : 'nobody'}.jpg"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
bind:this={ul}
|
||||||
|
on:click={select_by_click}
|
||||||
|
on:mousedown|preventDefault={() => {}}
|
||||||
|
class:hidden={names.length === 0 || should_hide}
|
||||||
|
>
|
||||||
{#each names as name, i (name)}
|
{#each names as name, i (name)}
|
||||||
<li data-idx={i} class:selected={i===selected_idx} title={name[1]}><img src="{url}/avatar/{name[0]}.jpg" alt="avatar"/>{name[1]}</li>
|
<li data-idx={i} class:selected={i === selected_idx} title={name[1]}>
|
||||||
|
<img src="{url}/avatar/{name[0]}.jpg" alt="avatar" />{name[1]}
|
||||||
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -171,18 +190,21 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
ul:not(:hover) > li.selected, li:hover {
|
ul:not(:hover) > li.selected,
|
||||||
|
li:hover {
|
||||||
background-color: #d9f5ff;
|
background-color: #d9f5ff;
|
||||||
}
|
}
|
||||||
input {
|
input {
|
||||||
padding-left: 2.5em;
|
padding-left: 2.5em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
input, ul {
|
input,
|
||||||
|
ul {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border: 1px solid var(--color-inactive);
|
border: 1px solid var(--color-inactive);
|
||||||
}
|
}
|
||||||
input:focus, input:focus ~ ul {
|
input:focus,
|
||||||
|
input:focus ~ ul {
|
||||||
border-color: var(--color-active);
|
border-color: var(--color-active);
|
||||||
box-shadow: 0 0 4px var(--color-active);
|
box-shadow: 0 0 4px var(--color-active);
|
||||||
outline: 1px solid var(--color-active);
|
outline: 1px solid var(--color-active);
|
||||||
|
1
src/global.d.ts
vendored
Normal file
1
src/global.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="svelte" />
|
@ -1,7 +0,0 @@
|
|||||||
import App from './App.svelte';
|
|
||||||
|
|
||||||
const app = new App({
|
|
||||||
target: document.body
|
|
||||||
});
|
|
||||||
|
|
||||||
export default app;
|
|
7
src/main.ts
Normal file
7
src/main.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import App from "./App.svelte";
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
target: document.body,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
@ -1,8 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
export function sleep(ms) {
|
|
||||||
const p = new Promise((resolve, reject) => {
|
|
||||||
setTimeout(resolve, ms)
|
|
||||||
})
|
|
||||||
return p
|
|
||||||
}
|
|
6
src/util.ts
Normal file
6
src/util.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export function sleep(ms: number) {
|
||||||
|
const p = new Promise((resolve, reject) => {
|
||||||
|
setTimeout(resolve, ms);
|
||||||
|
});
|
||||||
|
return p;
|
||||||
|
}
|
6
tsconfig.json
Normal file
6
tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
|
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules/*", "__sapper__/*", "public/*"]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user