Skip to content

Commit d7fbb2b

Browse files
committed
docs: sponsor
1 parent 97a5bbb commit d7fbb2b

File tree

15 files changed

+599
-1
lines changed

15 files changed

+599
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ pnpm-debug.log*
3636
*.sln
3737
*.sw?
3838

39+
sponsor.iife.js

docs/.vitepress/config/shared.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,14 @@ export const shared = defineConfig({
8181
['meta', { property: 'og:title', content: 'VueHooks plus | High-performance Hooks Library for Vue' }],
8282
['meta', { property: 'og:site_name', content: 'VueHooks plus' }],
8383
['meta', { property: 'og:url', content: 'https://inhiblabcore.github.io/vue-hooks-plus/' }],
84-
['script', { src: 'https://cdn.usefathom.com/script.js', 'data-site': 'AZBRSFGG', 'data-spa': 'auto', defer: '' }]
84+
['script', { src: 'https://cdn.usefathom.com/script.js', 'data-site': 'AZBRSFGG', 'data-spa': 'auto', defer: '' }],
85+
[
86+
'script',
87+
{
88+
src: '/sponsor/sponsor.iife.js',
89+
defer: ''
90+
}
91+
]
8592
],
8693

8794
themeConfig: {

packages/sponsor/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 YongGit
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/sponsor/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# useImmer
2+
3+
A hook to use [immer](https://github.com/mweststrate/immer) as a Vue hook to manipulate state.
4+
5+
## Install
6+
7+
```bash
8+
9+
npm i @vue-hooks-plus/use-immer
10+
11+
```
12+
13+
> The `hook` is based on `immer` management status, `immer` will be installed to ensure normal work in the project
14+
15+
> Independent installation `@vue-hooks-plus/use-immer`

packages/sponsor/package.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "@vue-hooks-plus/sponsor",
3+
"version": "1.0.0",
4+
"description": "Vue hooks plus sponsor",
5+
"files": [
6+
"dist",
7+
"LICENSE",
8+
"package.json",
9+
"README.md"
10+
],
11+
"scripts": {
12+
"build": "npm run clean && vite build",
13+
"clean": "rimraf dist"
14+
},
15+
"keywords": [],
16+
"repository": "https://github.com/InhiblabCore/vue-hooks-plus",
17+
"homepage": "https://github.com/InhiblabCore/vue-hooks-plus",
18+
"author": "NelsonYong",
19+
"license": "MIT"
20+
}

packages/sponsor/src/banner.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// @ts-ignore
2+
import styleInline from './styles/app.css?inline';
3+
import { updateLastCloseTime } from './updateLastCloseTime';
4+
5+
6+
export class Banner extends HTMLElement {
7+
#banner: HTMLTemplateElement | null = null;
8+
#_observer: MutationObserver | null = null;
9+
lang: string = 'en';
10+
constructor() {
11+
super();
12+
this.attachShadow({ mode: 'open' });
13+
this.addSponsorListener(); // 添加这行
14+
}
15+
16+
addSponsorCloseListener() {
17+
18+
}
19+
addSponsorListener() {
20+
this.shadowRoot?.addEventListener('click', (e) => {
21+
const target = e.target as HTMLElement;
22+
if (target.matches('.sponsor-btn')) {
23+
const modal = document.querySelector('sponsor-modal') as HTMLElement;
24+
modal.setAttribute('visible', 'true');
25+
}
26+
if (target.matches('.close-icon')) {
27+
updateLastCloseTime();
28+
// 销毁整个 web component
29+
this.remove();
30+
document.querySelector('sponsor-banner')?.remove();
31+
}
32+
});
33+
}
34+
35+
getMainContent(lang: string = 'en') {
36+
if (lang === 'zh-Hans') {
37+
return `
38+
<div class="sponsor-message">
39+
如果这个项目对你 <span>有帮助</span>,您可以 <button class="sponsor-btn">赞助</button> 我们来帮助项目更好地发展!
40+
</div>
41+
`
42+
}
43+
return `
44+
<div class="sponsor-message">
45+
If this project helps you <span>in any way</span>, you can <button class="sponsor-btn">sponsor</button> us to help the project grow better!
46+
</div>
47+
`
48+
}
49+
50+
connectedCallback() {
51+
const style = document.createElement('style');
52+
style.innerHTML = styleInline + styleInline;
53+
54+
const rootTemplate = document.createElement('template');
55+
rootTemplate.innerHTML = this.template();
56+
this.#banner = rootTemplate
57+
this.shadowRoot!.appendChild(style);
58+
this.shadowRoot!.appendChild(this.#banner.content.cloneNode(true));
59+
this.renderContent();
60+
this.observeLanguage();
61+
}
62+
template() {
63+
return `
64+
<div id="vhp-widget-sponsor-banner-root" class="vhp-widget-sponsor-banner-root">
65+
<div class="vhp-widget-sponsor-thumbnail">
66+
<strong class="main">-</strong>
67+
</div>
68+
<div style="cursor: pointer;" class="close-icon">
69+
<svg width="1em" height="1em" viewBox="0 0 24 24" class="bi bi-x close-icon" fill="white" xmlns="http://www.w3.org/2000/svg">
70+
<path d="M6.225 4.811a1 1 0 00-1.414 1.414L10.586 12 4.81 17.775a1 1 0 101.414 1.414L12 13.414l5.775 5.775a1 1 0 001.414-1.414L13.414 12l5.775-5.775a1 1 0 00-1.414-1.414L12 10.586 6.225 4.81z"/>
71+
</svg>
72+
</div>
73+
</div>
74+
`
75+
}
76+
77+
renderContent() {
78+
this.lang = document.documentElement.getAttribute('lang') ?? 'en-US';
79+
// 修改 main 里面的内容
80+
const main = this.shadowRoot!.querySelector('.main') as HTMLElement;
81+
main.innerHTML = this.getMainContent(this.lang);
82+
}
83+
84+
85+
// observe html language
86+
observeLanguage() {
87+
const observer = new MutationObserver((mutationsList) => {
88+
for (const mutation of mutationsList) {
89+
if (mutation.type === 'attributes' && mutation.attributeName === 'lang') {
90+
const lang = document.documentElement.getAttribute('lang');
91+
if (lang) {
92+
this.renderContent()
93+
}
94+
}
95+
}
96+
});
97+
98+
// 开始观察 HTML 元素
99+
observer.observe(document.documentElement, {
100+
attributes: true,
101+
attributeFilter: ['lang']
102+
});
103+
104+
// 存储 observer 以便后续清理
105+
this.#_observer = observer;
106+
}
107+
108+
109+
disconnectedCallback() {
110+
// 组件销毁时清理 observer
111+
if (this.#_observer) {
112+
this.#_observer.disconnect();
113+
}
114+
}
115+
}

packages/sponsor/src/constant.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// banner 相关
2+
export const ROOT_ID = 'vhp-widget-sponsor-banner-root';
3+
export const BANNER_ID = 'vhp-widget-sponsor-banner';

packages/sponsor/src/index.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Banner } from "./banner";
2+
import { Modal } from "./modal";
3+
4+
(function () {
5+
const shouldShowBanner = () => {
6+
const lastCloseTime = localStorage.getItem('vhp-sponsor-last-close');
7+
if (!lastCloseTime) return true;
8+
9+
const oneMonth = 30 * 24 * 60 * 60 * 1000; // 一个月的毫秒数
10+
const now = new Date().getTime();
11+
return now - parseInt(lastCloseTime) > oneMonth;
12+
}
13+
if (shouldShowBanner()) {
14+
const shadowEle = document.createElement('sponsor-banner');
15+
const shadowModal = document.createElement('sponsor-modal');
16+
shadowEle.setAttribute('display', 'block');
17+
shadowEle.setAttribute(
18+
'style',
19+
'z-index: 2147483647; position: fixed; display: unset; bottom:0;width:100%'
20+
);
21+
// document.body.appendChild(shadowEle);
22+
document.body.insertBefore(shadowEle, document.body.firstChild);
23+
document.body.insertBefore(shadowModal, document.body.firstChild);
24+
}
25+
26+
customElements.define('sponsor-banner', Banner);
27+
customElements.define('sponsor-modal', Modal);
28+
})();
29+

packages/sponsor/src/modal.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// @ts-ignore
2+
import modalInline from './styles/modal.css?inline'
3+
export class Modal extends HTMLElement {
4+
#modal: HTMLElement | null = null;
5+
constructor() {
6+
super();
7+
this.attachShadow({ mode: 'open' });
8+
const modalStyle = document.createElement('style');
9+
modalStyle.innerHTML = modalInline;
10+
this.shadowRoot!.appendChild(modalStyle);
11+
this.addCloseListener();
12+
this.addPaymentListener(); // 添加支付按钮监听
13+
}
14+
15+
addPaymentListener() {
16+
this.shadowRoot?.addEventListener('click', (e) => {
17+
const target = e.target as HTMLElement;
18+
if (target.matches('.wechat-pay')) {
19+
window.open('https://raw.githubusercontent.com/InhiblabCore/vue-hooks-plus/master/docs/public/wx.JPG', '_blank');
20+
} else if (target.matches('.alipay')) {
21+
window.open('https://raw.githubusercontent.com/InhiblabCore/vue-hooks-plus/master/docs/public/zfb.JPG', '_blank');
22+
}
23+
});
24+
}
25+
26+
addCloseListener() {
27+
this.shadowRoot?.addEventListener('click', (e) => {
28+
const target = e.target as HTMLElement;
29+
// 检查点击的是否是遮罩层本身
30+
if (target.classList.contains('modal-overlay')) {
31+
this.closeModal();
32+
return;
33+
}
34+
// 检查是否点击了关闭按钮或其子元素
35+
const closeButton = target.closest('.modal-close');
36+
if (closeButton) {
37+
this.closeModal();
38+
}
39+
});
40+
}
41+
42+
openModal() {
43+
const template = `
44+
<div class="modal-overlay">
45+
<div class="modal-container">
46+
<div class="modal-content">
47+
<div class="modal-header">
48+
<h4> Sponsor </h4>
49+
50+
<div style="cursor: pointer;" class="modal-close">
51+
<svg width="1em" height="1em" viewBox="0 0 24 24" class="bi bi-x close-icon" fill="black" xmlns="http://www.w3.org/2000/svg">
52+
<path d="M6.225 4.811a1 1 0 00-1.414 1.414L10.586 12 4.81 17.775a1 1 0 101.414 1.414L12 13.414l5.775 5.775a1 1 0 001.414-1.414L13.414 12l5.775-5.775a1 1 0 00-1.414-1.414L12 10.586 6.225 4.81z"/>
53+
</svg>
54+
</div>
55+
56+
</div>
57+
<div class="modal-body">
58+
<p>如果您已经赞助了我们,请通过邮箱 <span class="email">1013588891@qq.com</span> 联系我们,感谢您的支持!</p>
59+
<p>If you have sponsored us, please contact us via email <span class="email">1013588891@qq.com</span>. Thank you for your support!</p>
60+
</div>
61+
<div class="modal-footer">
62+
<button class="wechat-pay">微信支付/Wechat</button>
63+
<button class="alipay">支付宝支付/AliPay</button>
64+
</div>
65+
</div>
66+
</div>
67+
</div>
68+
`
69+
this.#modal = document.createElement('div');
70+
this.#modal.className = 'modal';
71+
this.#modal.innerHTML = template;
72+
this.shadowRoot!.appendChild(this.#modal);
73+
}
74+
75+
closeModal() {
76+
77+
if (this.#modal) {
78+
this.#modal.remove();
79+
this.#modal = null;
80+
}
81+
}
82+
static get observedAttributes() {
83+
return ['visible'];
84+
}
85+
86+
// 监听属性变化
87+
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
88+
if (name === 'visible') {
89+
if (newValue === 'true') {
90+
this.openModal();
91+
} else {
92+
this.closeModal();
93+
}
94+
}
95+
}
96+
97+
98+
}

0 commit comments

Comments
 (0)