Several updates

feat: New website UI
feat: Add few params
feat: Improve performance
This commit is contained in:
roffy3051 2024-10-20 05:11:14 +08:00
parent 81936a9883
commit cc1881cc4f
11 changed files with 2045 additions and 2280 deletions

View File

@ -7,6 +7,8 @@
<details> <details>
<summary>More theme</summary> <summary>More theme</summary>
### *Contribute themes is welcome!*
##### asoul ##### asoul
![asoul](https://count.getloli.com/get/@demo?theme=asoul) ![asoul](https://count.getloli.com/get/@demo?theme=asoul)
@ -17,29 +19,35 @@
![Rule34](https://count.getloli.com/get/@demo?theme=rule34) ![Rule34](https://count.getloli.com/get/@demo?theme=rule34)
##### gelbooru ##### gelbooru
![Gelbooru](https://count.getloli.com/get/@demo?theme=gelbooru)</details> ![Gelbooru](https://count.getloli.com/get/@demo?theme=gelbooru)
</details>
## Demo ## Demo
[https://count.getloli.com](https://count.getloli.com) [https://count.getloli.com](https://count.getloli.com)
## How to use
About the counter usage, please see the [website homepage](https://count.getloli.com).
## Usage ## Usage
### Install ### Install
#### Run on Replit #### Run on Glitch
- Open the url [https://replit.com/@journeyad/Moe-Counter](https://replit.com/@journeyad/Moe-Counter) - Open [Glitch project](https://glitch.com/~moe-counter-api)
- Just hit the **Fork** button - Just hit the **Remix your own** button
- And hit the **Run** button - That's it!
#### Deploying on your own server #### Deploying on your own server
```shell ```shell
$ git clone https://github.com/journey-ad/Moe-Counter.git $ git clone https://github.com/journey-ad/Moe-Counter.git
$ cd Moe-Counter $ cd Moe-Counter
$ yarn install $ pnpm install
$ yarn start $ pnpm run start
``` ```
### Configuration ### Configuration
@ -53,30 +61,27 @@ app:
db: db:
type: sqlite # sqlite or mongodb type: sqlite # sqlite or mongodb
interval: 60 # write to db interval in seconds (0 for realtime)
``` ```
If you use mongodb, you need to specify the environment variable `DB_URL` If using mongodb, you need to specify the environment variable `DB_URL`
```shell ```shell
# eg: # e.g.
export DB_URL=mongodb+srv://account:passwd@***.***.***.mongodb.net/db_count export DB_URL=mongodb+srv://account:passwd@***.***.***.mongodb.net/db_count
``` ```
replit can use Secrets, [documentation](https://docs.replit.com/programming-ide/storing-sensitive-information-environment-variables) Glitch can use `.env` file, [documentation](https://help.glitch.com/hc/en-us/articles/16287550167437-Adding-Private-Data)
```
DB_URL="mongodb+srv://account:passwd@***.***.***.mongodb.net/db_count"
```
## Credits ## Credits
* [replit](https://replit.com/) * [Glitch](https://glitch.com/)
* [A-SOUL_Official](https://space.bilibili.com/703007996) * [A-SOUL_Official](https://space.bilibili.com/703007996)
* [moebooru](https://github.com/moebooru/moebooru) * [moebooru](https://github.com/moebooru/moebooru)
* rule34.xxx NSFW * rule34.xxx NSFW
* gelbooru.com NSFW * gelbooru.com NSFW
* [Icons8](https://icons8.com/icons/set/star) * [Icons8](https://icons8.com/icon/80355/star)
## License ## License
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fjourney-ad%2FMoe-Counter.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fjourney-ad%2FMoe-Counter?ref=badge_large) [MIT License](./LICENSE)

View File

@ -1,3 +1,39 @@
@media screen and (min-width: 800px) {
body {
max-width: min(90%, 800px);
}
}
details>summary {
list-style: none
}
details>summary::-webkit-details-marker,details>summary::marker {
display: none
}
summary:before {
border-bottom: 6px solid transparent;
border-left: 10px solid var(--b-txt);
border-top: 6px solid transparent;
content: "";
display: inline-block;
height: 0;
margin-right: 10px;
position: relative;
transition: .2s;
width: 0
}
details[open] summary:before {
transform: rotate(90deg)
}
h2, h3, h4, h5 {
margin-top: 1.5em;
margin-bottom: .6em;
}
@media screen and (max-width: 900px) { @media screen and (max-width: 900px) {
iframe { iframe {
display: none; display: none;

View File

@ -4,3 +4,4 @@ app:
db: db:
type: sqlite # sqlite or mongodb type: sqlite # sqlite or mongodb
interval: 60 # write to db interval in seconds (0 for realtime)

View File

@ -1,55 +1,60 @@
'use strict' "use strict";
const mongoose = require('mongoose') const mongoose = require("mongoose");
const schema = require('./schema')
const schema = new mongoose.Schema(
{
name: { type: String, required: true },
num: { type: Number, required: true }
},
{ collection: 'tb_count', versionKey: false }
);
// the default mongodb url (local server) // the default mongodb url (local server)
const mongodbURL = process.env.DB_URL || 'mongodb://127.0.0.1:27017' const mongodbURL = process.env.DB_URL || "mongodb://127.0.0.1:27017";
mongoose.connect(mongodbURL, { mongoose.connect(mongodbURL, {
useNewUrlParser: true, useNewUrlParser: true,
useUnifiedTopology: true, useUnifiedTopology: true,
useFindAndModify: false useFindAndModify: false,
}) });
const Count = mongoose.connection.model('Count', schema) const Count = mongoose.connection.model("Count", schema);
function getNum(name) { function getNum(name) {
return Count return Count.findOne({ name }, "-_id -__v").exec();
.findOne({ name }, '-_id -__v')
.exec()
} }
function getAll() { function getAll() {
return Count return Count.find({}, "-_id -__v").exec();
.find({ }, '-_id -__v')
.exec()
} }
function setNum(name, num) { function setNum(name, num) {
return Count return Count.findOneAndUpdate(
.findOneAndUpdate({ name }, { name, num }, { upsert: true }) { name },
.exec() { name, num },
{ upsert: true }
).exec();
} }
function setNumMulti(counters) { function setNumMulti(counters) {
const bulkOps = counters.map(obj => { const bulkOps = counters.map((obj) => {
const { name, num } = obj const { name, num } = obj;
return { return {
updateOne: { updateOne: {
filter: { name }, filter: { name },
update: { name, num }, update: { name, num },
upsert: true upsert: true,
} },
} };
}) });
return Count.bulkWrite(bulkOps, { ordered : false }) return Count.bulkWrite(bulkOps, { ordered: false });
} }
module.exports = { module.exports = {
getNum, getNum,
getAll, getAll,
setNum, setNum,
setNumMulti setNumMulti,
} };

View File

@ -1,8 +0,0 @@
'use strict'
const mongoose = require('mongoose');
module.exports = new mongoose.Schema({
name: { type: String, required: true },
num: { type: Number, required: true }
}, { collection: 'tb_count', versionKey: false });

157
index.js
View File

@ -1,20 +1,17 @@
'use strict' "use strict";
const fs = require('fs') const config = require("config-yml");
const config = require('config-yml') const express = require("express");
const express = require('express') const compression = require("compression");
const compression = require('compression')
const db = require('./db') const db = require("./db");
const themify = require('./utils/themify') const themify = require("./utils/themify");
const PLACES = 7 const app = express();
const app = express() app.use(express.static("assets"));
app.use(compression());
app.use(express.static('assets')) app.set("view engine", "pug");
app.use(compression())
app.set('view engine', 'pug')
app.get('/', (req, res) => { app.get('/', (req, res) => {
const site = config.app.site || `${req.protocol}://${req.get('host')}` const site = config.app.site || `${req.protocol}://${req.get('host')}`
@ -22,104 +19,122 @@ app.get('/', (req, res) => {
}); });
// get the image // get the image
app.get('/get/@:name', async (req, res) => { app.get(["/@:name", "/get/@:name"], async (req, res) => {
const { name } = req.params const { name } = req.params;
const { theme = 'moebooru' } = req.query const { theme = "moebooru", padding = 7, pixelated = '1', darkmode = 'auto' } = req.query;
let length = PLACES const isPixelated = pixelated === '1';
if (name.length > 32) {
res.status(400).send("name too long");
return;
}
if (padding > 32) {
res.status(400).send("padding too long");
return;
}
// This helps with GitHub's image cache // This helps with GitHub's image cache
res.set({ res.set({
'content-type': 'image/svg+xml', "content-type": "image/svg+xml",
'cache-control': 'max-age=0, no-cache, no-store, must-revalidate' "cache-control": "max-age=0, no-cache, no-store, must-revalidate",
}) });
const data = await getCountByName(name) const data = await getCountByName(name);
if (name === 'demo') { if (name === "demo") {
res.set({ res.set("cache-control", "max-age=31536000");
'cache-control': 'max-age=31536000'
})
length = 10
} }
// Send the generated SVG as the result // Send the generated SVG as the result
const renderSvg = themify.getCountImage({ count: data.num, theme, length }) const renderSvg = themify.getCountImage({
res.send(renderSvg) count: data.num,
theme,
padding,
darkmode,
pixelated: isPixelated
});
console.log(data, `theme: ${theme}`, `ref: ${req.get('Referrer') || null}`, `ua: ${req.get('User-Agent') || null}`) res.send(renderSvg);
})
console.log(
data,
`theme: ${theme}`,
`ip: ${req.headers['x-forwarded-for'] || req.connection.remoteAddress}`,
`ref: ${req.get("Referrer") || null}`,
`ua: ${req.get("User-Agent") || null}`
);
});
// JSON record // JSON record
app.get('/record/@:name', async (req, res) => { app.get("/record/@:name", async (req, res) => {
const { name } = req.params const { name } = req.params;
const data = await getCountByName(name) const data = await getCountByName(name);
res.json(data) res.json(data);
}) });
app.get('/heart-beat', (req, res) => { app.get("/heart-beat", (req, res) => {
res.set({ res.set("cache-control", "max-age=0, no-cache, no-store, must-revalidate");
'cache-control': 'max-age=0, no-cache, no-store, must-revalidate' res.send("alive");
}) console.log("heart-beat");
res.send('alive')
console.log('heart-beat')
}); });
const listener = app.listen(config.app.port || 3000, () => { const listener = app.listen(config.app.port || 3000, () => {
console.log('Your app is listening on port ' + listener.address().port) console.log("Your app is listening on port " + listener.address().port);
}) });
let __cache_counter = {}, shouldPush = false let __cache_counter = {};
let enablePushDelay = config.db.interval > 0
let needPush = false;
setInterval(() => { if (enablePushDelay) {
shouldPush = true setInterval(() => {
}, 1000 * 60); needPush = true;
}, 1000 * config.db.interval);
}
async function pushDB() { async function pushDB() {
if (!shouldPush) return if (Object.keys(__cache_counter).length === 0) return;
if (enablePushDelay && !needPush) return;
try { try {
shouldPush = false needPush = false;
if (Object.keys(__cache_counter).length === 0) return console.log("pushDB", __cache_counter);
console.log("pushDB", __cache_counter) const counters = Object.keys(__cache_counter).map((key) => {
const counters = Object.keys(__cache_counter).map(key => {
return { return {
name: key, name: key,
num: __cache_counter[key] num: __cache_counter[key],
} };
}) });
await db.setNumMulti(counters) await db.setNumMulti(counters);
__cache_counter = {} __cache_counter = {};
} catch (error) { } catch (error) {
console.log("pushDB is error: ", error) console.log("pushDB is error: ", error);
} }
} }
async function getCountByName(name) { async function getCountByName(name) {
const defaultCount = { name, num: 0 } const defaultCount = { name, num: 0 };
if (name === 'demo') return { name, num: '0123456789' } if (name === "demo") return { name, num: "0123456789" };
try { try {
if (!(name in __cache_counter)) { if (!(name in __cache_counter)) {
const counter = await db.getNum(name) || defaultCount const counter = (await db.getNum(name)) || defaultCount;
__cache_counter[name] = counter.num + 1 __cache_counter[name] = counter.num + 1;
} else { } else {
__cache_counter[name]++ __cache_counter[name]++;
} }
pushDB() pushDB();
return { name, num: __cache_counter[name] }
return { name, num: __cache_counter[name] };
} catch (error) { } catch (error) {
console.log("get count by name is error: ", error) console.log("get count by name is error: ", error);
return defaultCount return defaultCount;
} }
} }

2091
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,10 +11,10 @@
"author": "journey-ad", "author": "journey-ad",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"better-sqlite3": "^8.1.0", "better-sqlite3": "^8.2.0",
"compression": "^1.7.4", "compression": "^1.7.4",
"config-yml": "^0.10.3", "config-yml": "^0.10.3",
"express": "^4.17.1", "express": "^4.18.2",
"image-size": "^0.8.3", "image-size": "^0.8.3",
"mime-types": "^2.1.27", "mime-types": "^2.1.27",
"mongoose": "^5.9.28", "mongoose": "^5.9.28",

1667
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ const themePath = path.resolve(__dirname, '../assets/theme')
const themeList = {} const themeList = {}
fs.readdirSync(themePath).forEach(theme => { fs.readdirSync(themePath).forEach(theme => {
if(!(theme in themeList)) themeList[theme] = {} if (!(theme in themeList)) themeList[theme] = {}
const imgList = fs.readdirSync(path.resolve(themePath, theme)) const imgList = fs.readdirSync(path.resolve(themePath, theme))
imgList.forEach(img => { imgList.forEach(img => {
const imgPath = path.resolve(themePath, theme, img) const imgPath = path.resolve(themePath, theme, img)
@ -25,39 +25,61 @@ fs.readdirSync(themePath).forEach(theme => {
}) })
}) })
function convertToDatauri(path){ function convertToDatauri(path) {
const mime = mimeType.lookup(path) const mime = mimeType.lookup(path)
const base64 = fs.readFileSync(path).toString('base64') const base64 = fs.readFileSync(path).toString('base64')
return `data:${mime};base64,${base64}` return `data:${mime};base64,${base64}`
} }
function getCountImage({ count, theme='moebooru', length=7 }) { function getCountImage({ count, theme = 'moebooru', padding = 7, pixelated = true, darkmode = 'auto' }) {
if(!(theme in themeList)) theme = 'moebooru' if (!(theme in themeList)) theme = 'moebooru'
// This is not the greatest way for generating an SVG but it'll do for now // This is not the greatest way for generating an SVG but it'll do for now
const countArray = count.toString().padStart(length, '0').split('') const countArray = count.toString().padStart(padding, '0').split('')
const uniqueChar = [...new Set(countArray)]
let x = 0, y = 0 let x = 0, y = 0
const parts = countArray.reduce((acc, next, index) => {
const { width, height, data } = themeList[theme][next]
const image = `${acc} const defs = uniqueChar.reduce((ret, cur) => {
<image x="${x}" y="0" width="${width}" height="${height}" xlink:href="${data}" />` const { width, height, data } = themeList[theme][cur]
if (height > y) y = height
ret = `${ret}
<image id="${cur}" width="${width}" height="${height}" xlink:href="${data}" />`
return ret
}, '')
const parts = countArray.reduce((ret, cur) => {
const { width } = themeList[theme][cur]
const image = `${ret}
<use x="${x}" xlink:href="#${cur}" />`
x += width x += width
if(height > y) y = height
return image return image
}, '') }, '')
const style = `
svg {
${pixelated ? 'image-rendering: pixelated;' : ''}
${darkmode === '1' ? 'filter: brightness(.6);' : ''}
}
${darkmode === 'auto' ? `@media (prefers-color-scheme: dark) { svg { filter: brightness(.6); } }` : ''}
`
return `<?xml version="1.0" encoding="UTF-8"?> return `<?xml version="1.0" encoding="UTF-8"?>
<svg width="${x}" height="${y}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="image-rendering: pixelated;"> <!-- Generated by https://github.com/journey-ad/Moe-Counter -->
<title>Moe Count</title> <svg viewBox="0 0 ${x} ${y}" width="${x}" height="${y}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g> <title>Moe Counter!</title>
${parts} <style>${style}</style>
</g> <defs>${defs}
</defs>
<g>${parts}
</g>
</svg> </svg>
` `
} }

View File

@ -3,7 +3,8 @@ html
title='Moe Counter!' title='Moe Counter!'
meta(name='viewport', content='width=device-width, initial-scale=1') meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='icon', type='image/png', href='favicon.png') link(rel='icon', type='image/png', href='favicon.png')
link(rel='stylesheet', href='https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/light.min.css') link(rel='stylesheet', href='https://unpkg.com/normalize.css')
link(rel='stylesheet', href='https://unpkg.com/bamboo.css')
link(rel='stylesheet', href='style.css') link(rel='stylesheet', href='style.css')
<!-- Global site tag (gtag.js) - Google Analytics --> <!-- Global site tag (gtag.js) - Google Analytics -->
script(async, src='https://www.googletagmanager.com/gtag/js?id=G-2RLWN5JXRL') script(async, src='https://www.googletagmanager.com/gtag/js?id=G-2RLWN5JXRL')
@ -20,47 +21,55 @@ html
'event_label' : label 'event_label' : label
}); });
} }
script(async, src='https://unpkg.com/party-js@2/bundle/party.min.js')
style.
html {
scroll-padding: 50px 0;
}
body body
h3 How to use: h1#main_title(style='margin-top: 0.5em;')
i Moe Counter!
h3 How to use
p Set a unique id for your counter, replace
code :name
| in the url, that's all.
h5 SVG address h5 SVG address
code #{site}/get/@:name code #{site}/@:name
h5 Img tag h5 Img tag
code &lt;img src="#{site}/get/@:name" alt=":name" /> code &lt;img src="#{site}/@:name" alt=":name" />
h5 Markdown h5 Markdown
code ![:name](#{site}/get/@:name) code ![:name](#{site}/@:name)
h3 eg: h5 e.g.
<img src="#{site}/get/@index" alt="Moe Count!" /> <img src="#{site}/@index" alt="Moe Count!" />
i Data can access by anyone, please details#themes
| <span style="color: #ff4500;"> DO NOT</span> summary#more_theme(onclick='_evt_push("click", "normal", "more_theme")')
| enter personal information h3(style='display: inline-block; margin: 0; cursor: pointer;') More theme✨
p(style='margin: 0;') Just use the query parameters <code>theme</code>, like this: <code>#{site}/@:name?theme=moebooru</code>
details
summary(style='display: inline-block;', onclick='_evt_push("click", "normal", "more_theme")')
h3(style='display: inline-block; cursor: pointer;') More theme
p(style='margin: 0;') Just use the query parameters <code>theme</code>, like this: <code>#{site}/get/@:name?theme=moebooru</code>
h5 asoul h5 asoul
img(src='#{site}/get/@demo?theme=asoul', alt='A-SOUL') <img src="#{site}/@demo?theme=asoul" alt="A-SOUL" />
h5 moebooru h5 moebooru
img(src='#{site}/get/@demo?theme=moebooru', alt='Moebooru') <img src="#{site}/@demo?theme=moebooru" alt="Moebooru" />
h5 moebooru-h h5 moebooru-h
img(src='#{site}/get/@demo?theme=moebooru-h', alt='Moebooru-Hentai') <img src="#{site}/@demo?theme=moebooru-h" alt="Moebooru-Hentai" />
h5 rule34 h5 rule34
img(src='#{site}/get/@demo?theme=rule34', alt='Rule34') <img src="#{site}/@demo?theme=rule34" alt="Rule34" />
h5 gelbooru h5 gelbooru
img(src='#{site}/get/@demo?theme=gelbooru', alt='Gelbooru') <img src="#{site}/@demo?theme=gelbooru" alt="Gelbooru" />
h5 gelbooru-h h5 gelbooru-h
img(src='#{site}/get/@demo?theme=gelbooru-h', alt='Gelbooru-Hentai') <img src="#{site}/@demo?theme=gelbooru-h" alt="Gelbooru-Hentai" />
h3 Credits h3 Credits
ul ul
li li
a(href='https://repl.it/', target='_blank', rel='nofollow') repl.it a(href='https://glitch.com/', target='_blank', rel='nofollow') Glitch
li li
a(href='https://space.bilibili.com/703007996', target='_blank', title='A-SOUL_Official') A-SOUL a(href='https://space.bilibili.com/703007996', target='_blank', title='A-SOUL_Official') A-SOUL
li li
@ -72,41 +81,145 @@ html
a(href='javascript:alert("!!! NSFW LINK !!!\\nPlease enter the url manually")') gelbooru.com a(href='javascript:alert("!!! NSFW LINK !!!\\nPlease enter the url manually")') gelbooru.com
| NSFW | NSFW
li li
a(href='https://icons8.com/icons/set/star', target='_blank', rel='nofollow') Icons8 a(href='https://icons8.com/icon/80355/star', target='_blank', rel='nofollow') Icons8
h3 Tool h3 Tool
.tool .tool
code #{site}/get/@ table
input#name(type='text', placeholder=':name', style='display: inline-block; width: 80px; height: 1.4em; line-height: 1.4em; margin: 0 4px; vertical-align: middle;') thead
code ?theme= tr
select#theme(style='display: inline-block; height: 1.6em; line-height: 1.6em; font-size: 14px; margin: 0 4px; padding: 0 4px; vertical-align: middle;') th Param
option(value='asoul') asoul th Value
option(value='moebooru') moebooru tbody
option(value='moebooru-h') moebooru-h tr
option(value='rule34') rule34 td
option(value='gelbooru') gelbooru code name
option(value='gelbooru-h') gelbooru-h td
button#get(style='margin: 10px 0;', onclick='_evt_push("click", "normal", "get_counter")') Get input#name(type='text', placeholder=':name')
img#result(style='display: block;') tr
td
code theme
td
select#theme
option(value='asoul') asoul
option(value='moebooru') moebooru
option(value='moebooru-h') moebooru-h
option(value='rule34') rule34
option(value='gelbooru') gelbooru
option(value='gelbooru-h') gelbooru-h
tr
td
code pixelated
td
input#pixelated(type='checkbox', checked, style='margin: .5rem .75rem;')
tr
td
code padding
td
input#padding(type='number', value='7', min='1', max='32', step='1', oninput='this.value = this.value.replace(/[^0-9]/g, "")')
tr
td
code darkmode
td
select#darkmode(name="darkmode")
option(value="auto", selected) auto
option(value="1") yes
option(value="0") no
button#get(style='margin-bottom: 1em;', onclick='_evt_push("click", "normal", "get_counter")') Generate
div
code#code(style='visibility: hidden; display: inline-block; margin-bottom: 1em;')
img#result(style='display: block;')
script. script.
var btn = document.getElementById('get'), var btn = document.getElementById('get'),
img = document.getElementById('result') img = document.getElementById('result'),
code = document.getElementById('code')
btn.addEventListener('click', function() { btn.addEventListener('click', throttle(function() {
var name = document.getElementById('name'), var $name = document.getElementById('name'),
themeEl = document.getElementById('theme') $theme = document.getElementById('theme'),
var text = name.value ? name.value.trim() : '' $pixelated = document.getElementById('pixelated'),
var theme = themeEl.value || 'moebooru' $padding = document.getElementById('padding'),
if(!text) { $darkmode = document.getElementById('darkmode')
var name = $name.value ? $name.value.trim() : ''
var theme = $theme.value || 'moebooru'
var pixelated = $pixelated.checked ? '1' : '0'
var padding = $padding.value || '7'
var darkmode = $darkmode.value || 'auto'
if(!name) {
alert('Please input counter name.') alert('Please input counter name.')
return return
} }
img.src = '#{site}/get/@' + text + '?theme=' + theme party.confetti(this, { count: party.variation.range(20, 40) });
img.src = `#{site}/@${name}?theme=${theme}&pixelated=${pixelated}&padding=${padding}&darkmode=${darkmode}`
code.textContent = img.src
code.style.visibility = 'visible'
img.onload = function() {
img.scrollIntoView({block:'start', behavior: 'smooth'})
}
}, 500))
code.addEventListener('click', function(e) {
e.preventDefault()
e.stopPropagation()
var target = e.target
var range, selection
if (document.body.createTextRange) {
range = document.body.createTextRange()
range.moveToElementText(target)
range.select()
} else if (window.getSelection) {
selection = window.getSelection()
range = document.createRange()
range.selectNodeContents(target)
selection.removeAllRanges()
selection.addRange(range)
}
}) })
iframe(src="https://chat.getloli.com/room/@Moe-counter?title=%E8%90%8C%E8%90%8C%E8%AE%A1%E6%95%B0%E5%99%A8%E7%9A%84%E7%95%99%E8%A8%80%E6%9D%BF", scrolling="no", frameborder="0", height="70%", width="26%", style="position: fixed;top: 2%;right: 5%;") var $main_title = document.querySelector('#main_title i'),
$themes = document.querySelector('#themes'),
$more_theme = document.querySelector('#more_theme')
$main_title.addEventListener('click', throttle(function() {
party.sparkles(document.documentElement, { count: party.variation.range(40, 100) });
}, 1000))
$more_theme.addEventListener('click', function() {
if (!$themes.hasAttribute('open')) {
party.sparkles($more_theme.querySelector('h3'), { count: party.variation.range(20, 40) });
$themes.scrollIntoView({block:'start', behavior: 'smooth'})
}
})
p.copy function throttle(fn, threshhold, scope) {
threshhold || (threshhold = 250)
var last
var deferTimer
return function () {
var context = scope || this
var now = +new Date
var args = arguments
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer)
deferTimer = setTimeout(function () {
last = now
fn.apply(context, args)
}, threshhold)
} else {
last = now
fn.apply(context, args)
}
}
}
p(style='margin-top: 2em;')
a(href='https://github.com/journey-ad/Moe-Counter', target='_blank', onclick='_evt_push("click", "normal", "go_github")') source code a(href='https://github.com/journey-ad/Moe-Counter', target='_blank', onclick='_evt_push("click", "normal", "go_github")') source code