Full Code of Honye/weapp-mark for AI

main 881f3651f209 cached
513 files
1.9 MB
535.3k tokens
996 symbols
1 requests
Download .txt
Showing preview only (2,184K chars total). Download the full file or copy to clipboard to get everything.
Repository: Honye/weapp-mark
Branch: main
Commit: 881f3651f209
Files: 513
Total size: 1.9 MB

Directory structure:
gitextract_1qcrx63f/

├── .eslintignore
├── .eslintrc.cjs
├── .github/
│   └── workflows/
│       └── upload.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── .vscode/
│   └── settings.json
├── .yarnrc
├── @types/
│   ├── douban/
│   │   ├── accounts.d.ts
│   │   ├── frodo.d.ts
│   │   └── index.d.ts
│   ├── douban.d.ts
│   ├── index.d.ts
│   ├── miniprogram.d.ts
│   ├── mobx-miniprogram-bindings.d.ts
│   └── wxCloud.d.ts
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── cloudbaserc.json
├── cloudfunctions/
│   ├── app/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── douban/
│   │   ├── config.json
│   │   ├── index.js
│   │   ├── package.json
│   │   ├── request.d.ts
│   │   └── request.js
│   ├── favArticle/
│   │   ├── index.js
│   │   └── package.json
│   ├── favCard/
│   │   ├── index.js
│   │   └── package.json
│   ├── fetch/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── getArticleDetails/
│   │   ├── index.js
│   │   └── package.json
│   ├── getCard/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── getCards/
│   │   ├── index.js
│   │   └── package.json
│   ├── getCategories/
│   │   ├── index.js
│   │   └── package.json
│   ├── getFavArticles/
│   │   ├── index.js
│   │   └── package.json
│   ├── getFavCards/
│   │   ├── index.js
│   │   └── package.json
│   ├── github/
│   │   ├── config.json
│   │   ├── fetch.js
│   │   ├── index.js
│   │   └── package.json
│   ├── initdb/
│   │   ├── index.js
│   │   └── package.json
│   ├── login/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── nowPlaying/
│   │   ├── config.json
│   │   ├── fetch.js
│   │   ├── index.js
│   │   └── package.json
│   ├── showingSoon/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── site/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── subscribeMessage/
│   │   ├── index.js
│   │   └── package.json
│   ├── subscription/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── trending/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── wallpaper/
│   │   ├── index.js
│   │   └── package.json
│   └── wxacode/
│       ├── config.json
│       ├── index.js
│       └── package.json
├── docs/
│   └── 云开发环境初始化.md
├── jest.config.js
├── miniprogram/
│   ├── apis/
│   │   ├── cloud/
│   │   │   └── index.js
│   │   ├── douban/
│   │   │   ├── accounts.js
│   │   │   └── request.js
│   │   ├── douban.js
│   │   ├── github.js
│   │   ├── leancloud/
│   │   │   └── index.js
│   │   ├── server/
│   │   │   └── index.js
│   │   └── vercel.js
│   ├── app.js
│   ├── app.json
│   ├── app.wxss
│   ├── components/
│   │   ├── Tabs/
│   │   │   ├── Tabs.js
│   │   │   ├── Tabs.json
│   │   │   ├── Tabs.wxml
│   │   │   └── Tabs.wxss
│   │   ├── article/
│   │   │   ├── article.js
│   │   │   ├── article.json
│   │   │   ├── article.wxml
│   │   │   └── article.wxss
│   │   ├── btn-fav/
│   │   │   ├── FavButton.js
│   │   │   ├── FavButton.json
│   │   │   ├── FavButton.wxml
│   │   │   └── FavButton.wxss
│   │   ├── cover-page/
│   │   │   ├── cover-page.js
│   │   │   ├── cover-page.json
│   │   │   ├── cover-page.wxml
│   │   │   ├── cover-page.wxs
│   │   │   └── cover-page.wxss
│   │   ├── index-list/
│   │   │   ├── components/
│   │   │   │   └── content/
│   │   │   │       ├── content.js
│   │   │   │       ├── content.json
│   │   │   │       ├── content.wxml
│   │   │   │       └── content.wxss
│   │   │   ├── index-list.js
│   │   │   ├── index-list.json
│   │   │   ├── index-list.wxml
│   │   │   └── index-list.wxss
│   │   ├── load-more/
│   │   │   ├── load-more.js
│   │   │   ├── load-more.json
│   │   │   ├── load-more.wxml
│   │   │   └── load-more.wxss
│   │   ├── painter/
│   │   │   ├── lib/
│   │   │   │   ├── downloader.js
│   │   │   │   ├── gradient.js
│   │   │   │   ├── pen.js
│   │   │   │   ├── qrcode.js
│   │   │   │   └── util.js
│   │   │   ├── painter.js
│   │   │   ├── painter.json
│   │   │   └── painter.wxml
│   │   ├── pre-image/
│   │   │   ├── PreImage.js
│   │   │   ├── PreImage.json
│   │   │   ├── PreImage.wxml
│   │   │   └── PreImage.wxss
│   │   ├── rating/
│   │   │   ├── rating.js
│   │   │   ├── rating.json
│   │   │   ├── rating.wxml
│   │   │   └── rating.wxss
│   │   ├── tab-bar/
│   │   │   ├── index.js
│   │   │   ├── index.json
│   │   │   ├── index.wxml
│   │   │   └── index.wxss
│   │   └── towxml/
│   │       ├── audio-player/
│   │       │   ├── Audio.js
│   │       │   ├── audio-player.js
│   │       │   ├── audio-player.json
│   │       │   ├── audio-player.wxml
│   │       │   └── audio-player.wxss
│   │       ├── config.js
│   │       ├── decode.js
│   │       ├── decode.json
│   │       ├── decode.wxml
│   │       ├── decode.wxss
│   │       ├── img/
│   │       │   ├── img.js
│   │       │   ├── img.json
│   │       │   ├── img.wxml
│   │       │   └── img.wxss
│   │       ├── index.js
│   │       ├── parse/
│   │       │   ├── highlight/
│   │       │   │   ├── highlight.js
│   │       │   │   ├── index.js
│   │       │   │   ├── languages/
│   │       │   │   │   ├── bash.js
│   │       │   │   │   ├── c-like.js
│   │       │   │   │   ├── c.js
│   │       │   │   │   ├── css.js
│   │       │   │   │   ├── dart.js
│   │       │   │   │   ├── go.js
│   │       │   │   │   ├── htmlbars.js
│   │       │   │   │   ├── index.js
│   │       │   │   │   ├── java.js
│   │       │   │   │   ├── javascript.js
│   │       │   │   │   ├── json.js
│   │       │   │   │   ├── less.js
│   │       │   │   │   ├── nginx.js
│   │       │   │   │   ├── php.js
│   │       │   │   │   ├── python-repl.js
│   │       │   │   │   ├── python.js
│   │       │   │   │   ├── scss.js
│   │       │   │   │   ├── shell.js
│   │       │   │   │   ├── typescript.js
│   │       │   │   │   └── xml.js
│   │       │   │   └── style/
│   │       │   │       ├── github.wxss
│   │       │   │       └── monokai.wxss
│   │       │   ├── index.js
│   │       │   ├── markdown/
│   │       │   │   ├── index.js
│   │       │   │   ├── markdown.js
│   │       │   │   └── plugins/
│   │       │   │       ├── emoji.js
│   │       │   │       ├── ins.js
│   │       │   │       ├── mark.js
│   │       │   │       ├── sub.js
│   │       │   │       ├── sup.js
│   │       │   │       └── todo.js
│   │       │   └── parse2/
│   │       │       ├── Parser.js
│   │       │       ├── Tokenizer.js
│   │       │       ├── domhandler/
│   │       │       │   ├── index.js
│   │       │       │   └── node.js
│   │       │       ├── entities/
│   │       │       │   ├── decode.js
│   │       │       │   ├── decode_codepoint.js
│   │       │       │   ├── encode.js
│   │       │       │   ├── index.js
│   │       │       │   └── maps/
│   │       │       │       ├── decode.js
│   │       │       │       ├── entities.js
│   │       │       │       ├── legacy.js
│   │       │       │       └── xml.js
│   │       │       └── index.js
│   │       ├── style/
│   │       │   ├── main.wxss
│   │       │   └── theme/
│   │       │       ├── dark.wxss
│   │       │       └── light.wxss
│   │       ├── table/
│   │       │   ├── table.js
│   │       │   ├── table.json
│   │       │   ├── table.wxml
│   │       │   └── table.wxss
│   │       ├── towxml.js
│   │       ├── towxml.json
│   │       ├── towxml.wxml
│   │       └── towxml.wxss
│   ├── libs/
│   │   ├── av-live-query-core-min.js
│   │   └── leancloud-adapters-weapp.js
│   ├── models/
│   │   ├── Cast.js
│   │   └── Comment.js
│   ├── packages/
│   │   ├── admin/
│   │   │   └── pages/
│   │   │       ├── app/
│   │   │       │   ├── app.js
│   │   │       │   ├── app.json
│   │   │       │   ├── app.wxml
│   │   │       │   └── app.wxss
│   │   │       ├── douban/
│   │   │       │   ├── douban.js
│   │   │       │   ├── douban.json
│   │   │       │   ├── douban.wxml
│   │   │       │   └── douban.wxss
│   │   │       └── index/
│   │   │           ├── index.js
│   │   │           ├── index.json
│   │   │           ├── index.wxml
│   │   │           └── index.wxss
│   │   ├── article/
│   │   │   └── pages/
│   │   │       ├── categories/
│   │   │       │   ├── categories.js
│   │   │       │   ├── categories.json
│   │   │       │   ├── categories.wxml
│   │   │       │   └── categories.wxss
│   │   │       ├── classification/
│   │   │       │   ├── index.wxml
│   │   │       │   └── index.wxss
│   │   │       ├── details/
│   │   │       │   ├── detail.js
│   │   │       │   ├── detail.json
│   │   │       │   ├── detail.wxml
│   │   │       │   └── detail.wxss
│   │   │       └── movie-list-detail/
│   │   │           ├── movie-list-detail.js
│   │   │           ├── movie-list-detail.json
│   │   │           ├── movie-list-detail.wxml
│   │   │           └── movie-list-detail.wxss
│   │   ├── douban/
│   │   │   └── pages/
│   │   │       ├── collection/
│   │   │       │   ├── collection.js
│   │   │       │   ├── collection.json
│   │   │       │   ├── collection.wxml
│   │   │       │   └── collection.wxss
│   │   │       └── login-phone/
│   │   │           ├── login-phone.js
│   │   │           ├── login-phone.json
│   │   │           ├── login-phone.wxml
│   │   │           └── login-phone.wxss
│   │   ├── example/
│   │   │   └── pages/
│   │   │       ├── cover-page/
│   │   │       │   ├── cover-page.js
│   │   │       │   ├── cover-page.json
│   │   │       │   ├── cover-page.wxml
│   │   │       │   └── cover-page.wxss
│   │   │       ├── icons/
│   │   │       │   ├── icons.js
│   │   │       │   ├── icons.json
│   │   │       │   ├── icons.wxml
│   │   │       │   └── icons.wxss
│   │   │       ├── index/
│   │   │       │   ├── index.js
│   │   │       │   ├── index.json
│   │   │       │   ├── index.wxml
│   │   │       │   └── index.wxss
│   │   │       ├── tab-bar/
│   │   │       │   ├── tab-bar.js
│   │   │       │   ├── tab-bar.json
│   │   │       │   ├── tab-bar.wxml
│   │   │       │   └── tab-bar.wxss
│   │   │       └── waterfall/
│   │   │           ├── components/
│   │   │           │   └── unsplash/
│   │   │           │       ├── unsplash.js
│   │   │           │       ├── unsplash.json
│   │   │           │       ├── unsplash.wxml
│   │   │           │       └── unsplash.wxss
│   │   │           ├── waterfall.js
│   │   │           ├── waterfall.json
│   │   │           ├── waterfall.wxml
│   │   │           └── waterfall.wxss
│   │   ├── github/
│   │   │   ├── components/
│   │   │   │   ├── event-item/
│   │   │   │   │   ├── event-item.js
│   │   │   │   │   ├── event-item.json
│   │   │   │   │   ├── event-item.wxml
│   │   │   │   │   ├── event-item.wxs
│   │   │   │   │   └── event-item.wxss
│   │   │   │   ├── lang-item/
│   │   │   │   │   ├── lang-item.js
│   │   │   │   │   ├── lang-item.json
│   │   │   │   │   ├── lang-item.wxml
│   │   │   │   │   └── lang-item.wxss
│   │   │   │   ├── notification-item/
│   │   │   │   │   ├── notification-item.js
│   │   │   │   │   ├── notification-item.json
│   │   │   │   │   ├── notification-item.wxml
│   │   │   │   │   └── notification-item.wxss
│   │   │   │   ├── page-events/
│   │   │   │   │   ├── page-events.js
│   │   │   │   │   ├── page-events.json
│   │   │   │   │   ├── page-events.wxml
│   │   │   │   │   └── page-events.wxss
│   │   │   │   ├── page-trending/
│   │   │   │   │   ├── page-trending.js
│   │   │   │   │   ├── page-trending.json
│   │   │   │   │   ├── page-trending.wxml
│   │   │   │   │   └── page-trending.wxss
│   │   │   │   ├── page-user/
│   │   │   │   │   ├── page-user.js
│   │   │   │   │   ├── page-user.json
│   │   │   │   │   ├── page-user.wxml
│   │   │   │   │   └── page-user.wxss
│   │   │   │   └── repo-item/
│   │   │   │       ├── repo-item.js
│   │   │   │       ├── repo-item.json
│   │   │   │       ├── repo-item.wxml
│   │   │   │       └── repo-item.wxss
│   │   │   └── pages/
│   │   │       ├── events/
│   │   │       │   ├── events.js
│   │   │       │   ├── events.json
│   │   │       │   ├── events.wxml
│   │   │       │   └── events.wxss
│   │   │       ├── home/
│   │   │       │   ├── home.js
│   │   │       │   ├── home.json
│   │   │       │   ├── home.wxml
│   │   │       │   └── home.wxss
│   │   │       ├── languages/
│   │   │       │   ├── languages.js
│   │   │       │   ├── languages.json
│   │   │       │   ├── languages.wxml
│   │   │       │   └── languages.wxss
│   │   │       ├── notifications/
│   │   │       │   ├── notifications.js
│   │   │       │   ├── notifications.json
│   │   │       │   ├── notifications.wxml
│   │   │       │   └── notifications.wxss
│   │   │       ├── repository/
│   │   │       │   ├── repository.js
│   │   │       │   ├── repository.json
│   │   │       │   ├── repository.wxml
│   │   │       │   └── repository.wxss
│   │   │       ├── search/
│   │   │       │   ├── search.js
│   │   │       │   ├── search.json
│   │   │       │   ├── search.wxml
│   │   │       │   └── search.wxss
│   │   │       ├── starred/
│   │   │       │   ├── starred.js
│   │   │       │   ├── starred.json
│   │   │       │   ├── starred.wxml
│   │   │       │   └── starred.wxss
│   │   │       └── trending/
│   │   │           ├── trending.js
│   │   │           ├── trending.json
│   │   │           ├── trending.wxml
│   │   │           └── trending.wxss
│   │   ├── movie/
│   │   │   ├── components/
│   │   │   │   └── comment-item/
│   │   │   │       ├── comment-item.js
│   │   │   │       ├── comment-item.json
│   │   │   │       ├── comment-item.wxml
│   │   │   │       └── comment-item.wxss
│   │   │   └── pages/
│   │   │       ├── cards/
│   │   │       │   ├── card.js
│   │   │       │   ├── card.json
│   │   │       │   ├── card.wxml
│   │   │       │   └── card.wxss
│   │   │       ├── details/
│   │   │       │   ├── details.js
│   │   │       │   ├── details.json
│   │   │       │   ├── details.wxml
│   │   │       │   ├── details.wxs
│   │   │       │   └── details.wxss
│   │   │       ├── intheaters/
│   │   │       │   ├── in_theaters.js
│   │   │       │   ├── in_theaters.json
│   │   │       │   ├── in_theaters.wxml
│   │   │       │   └── in_theaters.wxss
│   │   │       ├── mark/
│   │   │       │   ├── mark.js
│   │   │       │   ├── mark.json
│   │   │       │   ├── mark.wxml
│   │   │       │   └── mark.wxss
│   │   │       ├── photos/
│   │   │       │   ├── photos.js
│   │   │       │   ├── photos.json
│   │   │       │   ├── photos.wxml
│   │   │       │   └── photos.wxss
│   │   │       └── trailers/
│   │   │           ├── trailers.js
│   │   │           ├── trailers.json
│   │   │           ├── trailers.wxml
│   │   │           └── trailers.wxss
│   │   ├── tools/
│   │   │   └── pages/
│   │   │       ├── encode/
│   │   │       │   ├── encode.js
│   │   │       │   ├── encode.json
│   │   │       │   ├── encode.wxml
│   │   │       │   └── encode.wxss
│   │   │       ├── index/
│   │   │       │   ├── index.js
│   │   │       │   ├── index.json
│   │   │       │   ├── index.wxml
│   │   │       │   └── index.wxss
│   │   │       └── random/
│   │   │           ├── random.js
│   │   │           ├── random.json
│   │   │           ├── random.wxml
│   │   │           └── random.wxss
│   │   ├── user/
│   │   │   └── pages/
│   │   │       ├── achievement/
│   │   │       │   ├── achievement.js
│   │   │       │   ├── achievement.json
│   │   │       │   ├── achievement.wxml
│   │   │       │   └── achievement.wxss
│   │   │       ├── evaluate/
│   │   │       │   ├── evaluate.js
│   │   │       │   ├── evaluate.json
│   │   │       │   ├── evaluate.wxml
│   │   │       │   └── evaluate.wxss
│   │   │       ├── favCards/
│   │   │       │   ├── index.js
│   │   │       │   ├── index.json
│   │   │       │   ├── index.wxml
│   │   │       │   └── index.wxss
│   │   │       ├── favMovieList/
│   │   │       │   ├── index.js
│   │   │       │   ├── index.json
│   │   │       │   ├── index.wxml
│   │   │       │   └── index.wxss
│   │   │       └── userinfo/
│   │   │           ├── userinfo.js
│   │   │           ├── userinfo.json
│   │   │           ├── userinfo.wxml
│   │   │           └── userinfo.wxss
│   │   └── wallpaper/
│   │       ├── apis/
│   │       │   └── wallpaper/
│   │       │       └── index.js
│   │       └── pages/
│   │           └── categories/
│   │               ├── categories.js
│   │               ├── categories.json
│   │               ├── categories.wxml
│   │               └── categories.wxss
│   ├── pages/
│   │   ├── about/
│   │   │   ├── about.js
│   │   │   ├── about.json
│   │   │   ├── about.wxml
│   │   │   └── about.wxss
│   │   ├── first/
│   │   │   ├── first.js
│   │   │   ├── first.json
│   │   │   ├── first.wxml
│   │   │   └── first.wxss
│   │   ├── marked/
│   │   │   ├── marked.js
│   │   │   ├── marked.json
│   │   │   ├── marked.wxml
│   │   │   └── marked.wxss
│   │   ├── search/
│   │   │   ├── search.js
│   │   │   ├── search.json
│   │   │   ├── search.wxml
│   │   │   └── search.wxss
│   │   ├── setting/
│   │   │   ├── setting.js
│   │   │   ├── setting.json
│   │   │   ├── setting.wxml
│   │   │   └── setting.wxss
│   │   ├── splash/
│   │   │   ├── splash.js
│   │   │   ├── splash.json
│   │   │   ├── splash.wxml
│   │   │   └── splash.wxss
│   │   ├── tabs/
│   │   │   ├── discovery/
│   │   │   │   ├── discovery.js
│   │   │   │   ├── discovery.json
│   │   │   │   ├── discovery.wxml
│   │   │   │   └── discovery.wxss
│   │   │   ├── index/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.json
│   │   │   │   ├── index.wxml
│   │   │   │   └── index.wxss
│   │   │   └── movies/
│   │   │       ├── movies.js
│   │   │       ├── movies.json
│   │   │       ├── movies.wxml
│   │   │       └── movies.wxss
│   │   ├── test/
│   │   │   ├── test.js
│   │   │   ├── test.json
│   │   │   ├── test.wxml
│   │   │   └── test.wxss
│   │   └── webview/
│   │       ├── index.js
│   │       ├── index.json
│   │       ├── index.wxml
│   │       └── index.wxss
│   ├── sitemap.json
│   ├── store/
│   │   ├── app.js
│   │   ├── douban.js
│   │   ├── index.js
│   │   └── user.js
│   ├── style/
│   │   ├── animate.wxss
│   │   ├── common.wxss
│   │   ├── font-awesome.min.wxss
│   │   ├── iconfont.wxss
│   │   └── weui.wxss
│   ├── templates/
│   │   ├── actionsheet/
│   │   │   ├── actionsheet.js
│   │   │   ├── actionsheet.wxml
│   │   │   └── actionsheet.wxss
│   │   ├── bing/
│   │   │   ├── bing.js
│   │   │   ├── bing.wxml
│   │   │   └── bing.wxss
│   │   ├── casts/
│   │   │   ├── casts.wxml
│   │   │   └── casts.wxss
│   │   ├── cell/
│   │   │   ├── cell.wxml
│   │   │   └── cell.wxss
│   │   ├── circle/
│   │   │   ├── circle.wxml
│   │   │   └── circle.wxss
│   │   ├── component.js
│   │   ├── dropmenu/
│   │   │   ├── dropmenu.js
│   │   │   ├── dropmenu.wxml
│   │   │   └── dropmenu.wxss
│   │   ├── index.js
│   │   ├── loading/
│   │   │   ├── loading.wxml
│   │   │   └── loading.wxss
│   │   ├── login/
│   │   │   ├── login.js
│   │   │   └── login.wxml
│   │   ├── movie/
│   │   │   ├── movieRow.wxml
│   │   │   └── movieRow.wxss
│   │   ├── rating/
│   │   │   ├── rating.wxml
│   │   │   └── rating.wxss
│   │   ├── share/
│   │   │   ├── share.js
│   │   │   ├── share.wxml
│   │   │   └── share.wxss
│   │   └── wxParse/
│   │       ├── html2json.js
│   │       ├── htmlparser.js
│   │       ├── showdown.js
│   │       ├── wxDiscode.js
│   │       ├── wxParse.js
│   │       ├── wxParse.wxml
│   │       └── wxParse.wxss
│   └── utils/
│       ├── Base64.js
│       ├── EventEmitter.js
│       ├── URLSearchParams.js
│       ├── WxUtil.wxs
│       ├── apis.js
│       ├── crypro.js
│       ├── events.js
│       ├── github-colors.js
│       ├── global.js
│       ├── request.js
│       ├── storage.js
│       ├── svg.js
│       ├── util.js
│       ├── utils.wxs
│       └── wxCloud.js
├── mock/
│   └── mock.config.json
├── package.json
├── project.config.json
├── scripts/
│   └── release.cjs
├── test/
│   └── EventEmitter.spec.js
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintignore
================================================
**/node_modules

miniprogram/templates/wxParse/

================================================
FILE: .eslintrc.cjs
================================================
module.exports = {
  "env": {
    "browser": true,
    "es6": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    // "prettier" put last
    "prettier",
  ],
  "globals": {
    "Atomics": "readonly",
    "SharedArrayBuffer": "readonly",
    "wx": "writable",
    "App": "readonly",
    "Page": "readonly",
    "Component": "readonly",
    "getApp": "readonly",
    "getCurrentPages": "readonly"
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module"
  },
  "plugins": [
    "@typescript-eslint"
  ],
  "rules": {
    "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
    "comma-dangle": ["error", "only-multiline"]
  },
  "overrides": [
    {
      "files": ["*.wxs"],
      "globals": {
        "getDate": "readonly",
        "getRegExp": "readonly"
      },
      "env": {
        "es6": false
      },
      "parserOptions": {
        "ecmaVersion": 5,
        "sourceType": "script"
      },
      "rules": {
        "quote-props": ["error", "as-needed", { "numbers": true }],
        "no-useless-escape": "off"
      }
    },
    {
      "files": ["./cloudfunctions/**/*.js"],
      "rules": {
        "@typescript-eslint/no-var-requires": "off"
      }
    },
    {
      "files": ["*.js"],
      "rules": {
        "@typescript-eslint/explicit-module-boundary-types": "off",
        "@typescript-eslint/explicit-function-return-type": "off"
      }
    },
    {
      files: ['./scripts/**/*.cjs'],
      rules: {
        '@typescript-eslint/no-var-requires': 'off',
      },
    },
    {
      files: ['**/*.spec.{js,mjs}'],
      env: { jest: true },
    },
  ]
};


================================================
FILE: .github/workflows/upload.yml
================================================
# 构建并上传到小程序后台

name: Upload to weixin

# Controls when the workflow will run
on:
  # Triggers the workflow on push but only for the main branch
  push:
    branches:
      - main

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-node@v3
        with:
          node-version: 16

      - name: Create file "private.key"
        env:
          WX_PRIVATE_KEY: ${{ secrets.WX_PRIVATE_KEY }}
        run: |
          echo "$WX_PRIVATE_KEY" > private.key

      - name: Install dependencies
        run: |
          corepack enable pnpm
          pnpm install

      - name: Upload to weixin
        run: |
          node scripts/release.cjs


================================================
FILE: .gitignore
================================================
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# 微信开发工具项目配置
# /project.config.json
# project.config.json

# MarkEditor 配置文件
.Archive
.sync/*
.me_configs.data
.z_sync_configs.data
sync_changes.log
sync_hook.script
**/.Archive
.md_configs.data

# Mac file
.DS_Store

**/miniprogram_npm
project.private.config.json
private.key
private.*.key

backup


================================================
FILE: .prettierignore
================================================
coverage


================================================
FILE: .prettierrc.json
================================================
{
  "$schema": "http://json.schemastore.org/prettierrc",
  "semi": true,
  "singleQuote": true
}


================================================
FILE: .vscode/settings.json
================================================
{
  "files.exclude": {
    "**/*.js": {
      "when": "$(basename).ts"
    }
  }
}


================================================
FILE: .yarnrc
================================================
registry "https://registry.npmmirror.com"


================================================
FILE: @types/douban/accounts.d.ts
================================================
declare namespace Douban {
  interface AccountInfo {
    name: string;
    weixin_binded: boolean;
    phone: string;
    avatar: Record<'medium'|'median'|'large'|'raw'|'small'|'icon', string>;
    id: string;
    uid: string;
  }
}


================================================
FILE: @types/douban/frodo.d.ts
================================================
declare namespace Douban {
  interface SubjectCollection {
    subject_type: DouBan.SubjectType;
    updated_at: string;
    total: number;
    header_fg_image: string;
    header_bg_image: string;
    title: string;
    icon_text: string;
    id: string;
    description: string;
    done_count: number;
  }

  /** 榜单合集 */
  interface SubjectCollectionItemsResult {
    count: number;
    subject_collection: SubjectCollection;
    subject_collection_items: DouBan.MovieItem[];
    total: number;
    start: number;
  }

  /** 影院热映 */
  interface SubjectCollectionShowingItemsResult {
    count: number
    subject_collection_items: Array<{
      rating: {
        max: number
        value: number
      }
      cover: {
        url: string
      }
      year: string
      id: string
      title: string
      type: 'movie'
      info: string
      url: string
      release_date: string
      original_title: string
      uri: string
    }>
    total: number
    start: number
  }

  /** 即将上映 */
  interface SubjectCollectionSoonItemsResult {
    subject_collection_items: Array<{
      rating: {
        max: number
        value: number
      }
      cover: {
        url: string
      }
      year: string
      id: string
      title: string
      type: 'movie'|'tv'
      most_recent_release_date: string
      info: string
      url: string
      release_date: string
      original_title: string
      uri: string
    }>
  }

  interface UserInterestsResult {
    count: number;
    start: number;
    total: number;
    interests: Array<{
      rating: DouBan.Rating;
      tags: string[];
      create_time: string;
      status: DouBan.InterestStatus;
      id: string;
      is_private: boolean;
      subject: DouBan.Movie;
    }>
  }
}


================================================
FILE: @types/douban/index.d.ts
================================================
declare namespace Douban {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  interface APIResponseLegacy<T = any> {
    status: 'success'|'failed';
    message: string;
    description: string;
    payload: T;
  }

  interface LoginSuccessResult {
    access_token: string;
    douban_user_id: string;
    account_info: Douban.AccountInfo;
    douban_user_name: string;
    expires_in: number;
    refresh_token: string;
  }
}


================================================
FILE: @types/douban.d.ts
================================================
declare namespace DouBan {
  /** 搜索结果 */
  interface SearchResult {
    count: number;
    items: Array<SearchMovieItem|SearchDoulistItem|SearchBookItem>;
    total: number;
    start: number;
  }

  /** 搜索结果单项 */
  interface SearchItem {
    type_name: string;
    layout: string;
    target: unknown;
    /**
     * - "tv": 电视剧
     * - "movie": 电影
     * - "doulist_cards": 豆列
     * - "music": 音乐
     * - "game": 游戏
     * - "book": 书籍
     */
    target_type: string;
  }
 
  /** 搜索结果-影视单项 */
  interface SearchMovieItem extends SearchItem {
    layout: 'subject';
    target: Movie;
    target_type: 'tv' | 'movie';
  }

  /** 搜索结果-豆列单项 */
  interface SearchDoulistItem extends SearchItem {
    layout: 'doulist_cards';
    target: {
      doulists: Array<Doulists>
    };
    target_type: 'doulist_cards';
  }

  /** 搜索结果-书籍单项 */
  interface SearchBookItem extends SearchItem {
    layout: 'subject';
    target: Book;
    target_type: 'book';
  }

  interface Subject {
    rating: Rating;
    controversy_reason: string;
    title: string;
    abstract: string;
    uri: string;
    cover_url: string;
    card_subtitle: string;
    id: string;
    null_rating_reason: string;
  }

  type SubjectType = 'movie'|'tv'|'book'|'music';

  interface Movie extends Subject {
    has_linewatch: boolean;
    year: string;
  }

  interface Book extends Subject {
    has_ebook: boolean;
  }

  /** 豆列 */
  interface Doulists {
    cover_url: string;
    id: string;
    image_label: string;
    title: string;
  }

  interface Rating {
    count: number;
    max: number;
    star_count: number;
    value: number;
  }

  interface Actor {
    abstract: string;
    author: string | null;
    avatar: { large: string; normal: string };
    cover_url: string;
    id: string;
    name: string;
    roles: string[];
    sharing_url: string;
    title: string;
    type: 'celebrity';
    uri: string;
    url: string;
  }

  interface Trailer {
    sharing_url: string;
    video_url: string;
    title: string;
    uri: string;
    cover_url: string;
    term_num: number;
    n_comments: number;
    create_time: string;
    subject_title: string;
    file_size: number;
    runtime: string;
    type: string;
    id: string;
    desc: string;
  }

  /** 影视列表单元 */
  interface MovieItem {
    rating: Rating;
    cover: { url: string; width: number; height: number; };
    year: string;
    card_subtitle: string;
    id: string;
    title: string;
    type: SubjectType;
    has_linewatch: boolean;
    most_recent_release_date: string;
    info: string;
    url: string;
    release_date: string;
    original_title: string;
    uri: string;
  }

  interface MovieDetail {
    id: string;
    actors: Actor[];
    aka: string[];
    body_bg_color: string;
    card_subtitle: string;
    comment_count: number;
    directors: Actor[];
    has_linewatch: boolean;
    interest?: Interest;
    linewatches: Array<{
      free: boolean;
      source: {
        literal: string;
        name: string;
        pic: string;
      };
      source_uri: string;
      url: string;
    }>;
    title: string;
    trailers: Trailer[];
    type: SubjectType;
  }

  interface User {
    loc: Loc;
    kind: string;
    followed: boolean;
    name: string;
    avatar_side_icon: string;
    url: string;
    gender: string;
    reg_time: string;
    uri: string;
    remark: string;
    in_blacklist: boolean;
    uid: string;
    type: string;
    id: string;
    avatar: string;
  }

  interface Loc {
    uid: string;
    id: string;
    name: string;
  }

  interface Interest {
    comment: string;
    rating?: Rating;
    sharing_url: string;
    tags: string[];
    is_private: boolean;
    is_voted: boolean;
    uri: string;
    platforms: string[];
    vote_count: number;
    create_time: string;
    status: InterestStatus;
    user: User;
    recommend_reason: string;
    user_done_desc: string;
    id: string;
    wechat_timeline_share: 'screenshot';
    subject: Movie;
  }

  interface InterestResult {
    count: number;
    start: number;
    total: number;
    wechat_timeline_share: 'screenshot';
    interests: Interest[]
  }

  type InterestStatus = 'mark'|'doing'|'done';

  interface Image {
    large: ImageMeta;
    raw: string|null;
    small: ImageMeta;
    normal: ImageMeta;
    is_animated: boolean;
  }

  interface ImageMeta {
    url: string;
    width: number;
    size: number;
    height: number;
  }

  interface Photo {
    id: string;
    image: Image;
  }

  interface PhotosResult {
    count: number;
    photos: Photo[];
    total: number;
    start: number;
  }

  interface Trailer {
    reaction_type: number;
    sharing_url: string;
    video_url: string;
    title: string;
    create_time: string;
    uri: string;
    cover_url: string;
    term_num: number;
    n_comments: number;
    reactions_count: number;
    is_collected: boolean;
    subject_title: string;
    collections_count: number;
    file_size: number;
    reshares_count: number;
    runtime: string;
    type: string;
    id: string;
    desc: string;
  }

  interface TrailersResult {
    trailers: Trailer[];
  }

  interface ListParams {
    start?: number;
    count?: number;
  }

  interface ListResult {
    count: number;
    start: number;
    total: number;
  }
}


================================================
FILE: @types/index.d.ts
================================================
import 'miniprogram-api-typings'

declare namespace IMarkr {
}

interface RequestController extends WechatMiniprogram.RequestTask {
  task: WechatMiniprogram.RequestTask;
}

interface RequestOption<T> extends Omit<WechatMiniprogram.RequestOption<T>, 'success' | 'fail' | 'complete'> {
  baseURL?: string;
  controller?: RequestController;
  notAuthorization?: boolean
}

interface RequestSuccessResult<T> extends WechatMiniprogram.RequestSuccessCallbackResult<T> {
  ok: boolean;
}


================================================
FILE: @types/miniprogram.d.ts
================================================
// <reference path="./lib.wx.component.d.ts" />

declare namespace WechatMiniprogram {
  interface FormEvent<Detail extends IAnyObject = IAnyObject> extends CustomEvent {
    detail: {
      value: Detail
      formId: string
    }
  }

  namespace Component {
    interface A {
      a: string
    }
  }
}


================================================
FILE: @types/mobx-miniprogram-bindings.d.ts
================================================
declare module 'mobx-miniprogram-bindings' {
  export const storeBindingsBehavior: WechatMiniprogram.Behavior.BehaviorIdentifier
}

================================================
FILE: @types/wxCloud.d.ts
================================================
interface CloudFunctionBaseEvent {
  userInfo: { appId: string; openId: string };
}

type CloudFunctionEvent<T extends Record<string, any> = {}> = CloudFunctionBaseEvent & T;
interface CloudFunctionContext {
  callbackWaitsForEmptyEventLoop: boolean;
  memory_limit_in_mb: number;
  time_limit_in_ms: number;
  request_id: string;
  environment: string;
  environ: string;
  function_version: string;
  function_name: string;
  namespace: string;
  tencentcloud_region: string;
  tencentcloud_appid: string;
  tencentcloud_uin: string;
}

interface CloudFunction<Event extends Record<string, any> = {}, Result = void> {
  (event: CloudFunctionEvent<Event>, context: CloudFunctionContext): Result;
}

interface CallCloudOptions {
  /** 是否显示加载提示框 */
  loading?: boolean;
  /** 是否显示错误提示框 */
  showError?: boolean;
}

interface CallCloud {
  (name: 'login', data: Record<string, any>, options: CallCloudOptions): Promise<any>;

  (name: 'doouban', data: Record<string, any>, options: CallCloudOptions): Promise<any>;
}


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Code of conduct

### 使用2个空格进行缩进

```javascript
  function hello (name) {
    console.log('Hi, ' + name);
  }
```

### 云存储集合名采用 `_` 连接多个单词

**Bad:**

```javascript
db.createCollection('cardLikeRecord')
db.createCollection('card-like-record')
db.createCollection('cardlikerecord')
```

**Good:**

```javascript
db.createCollection('card_like_record')
```

### 云存储属性名

- 多个单词使用下划线 `_` 连接

- 索引使用小程序提供的 `_id`

- 用户的 openID 统一使用 `openid`

- 记录统一增加 `create_at` 和 `update_at` 两个属性

**Bad:**

```json
{
  "id": 12,
  "openID": "useropenid"
}
```

**Good:**

```json
{
  "_id": "randomstring",
  "openid": "useropenid",
  "create_at": "db.serverDate()",
  "update_at": "db.seerverDate()"
}
```



================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.

"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.

"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.

2. Grant of Copyright License.

Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.

3. Grant of Patent License.

Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.

4. Redistribution.

You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:

You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.

5. Submission of Contributions.

Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.

6. Trademarks.

This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.

7. Disclaimer of Warranty.

Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.

8. Limitation of Liability.

In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability.

While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work

To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "{}" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.

   Copyright 2017 Honye

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

================================================
FILE: README.md
================================================
# Mark

[![Honye](https://img.shields.io/badge/Honye-红叶-red.svg)](https://honye.github.io/)  [![license](https://img.shields.io/github/license/hongye567/weapp-mark.svg)](https://github.com/Honye/weapp-mark/blob/master/LICENSE)

<p align="center">
    <img src="./docs/screenshots/IMG_5435.JPG" height="258px" >
</p>


纯属娱乐学习项目,偶尔记录下开发中遇到的问题和想法,不定期更新,如果你有什么建议也请告诉我([issues](https://github.com/Honye/weapp-mark/issues))。项目中自己有封装一些组件,可在项目结构查看。

~~影视数据全部由[豆瓣](https://developers.douban.com/) API 提供。~~ 目前豆瓣搜索接口已经没有免费的可以使用了,本人提供的接口部署在 Vercel,未备案不可添加到微信后台,项目同时提供了 mock 数据可使用。小程序个人开发功能限制太多,无法完全上线。如若喜欢可以克隆项目自己运行看看。

## 💥 扫码体验

<img src="./docs/screenshots/IMG_5437.JPG" alt="小程序码" title="小程序码" width="300">

## 🔱 分支 Branches

1. [main](https://github.com/Honye/weapp-mark/tree/main) - 采用[微信小程序云开发](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html),无需后台也能开发一款完整的小程序。
2. [master](https://github.com/Honye/weapp-mark/tree/master) - 后台服务由 [LeanCloud 云服务](https://leancloud.cn/)支撑。
3. [cdn-ui](https://github.com/Honye/weapp-mark/tree/cdn-ui) - 没有后台服务支撑,全部采用 HTTP 请求的个人博客的静态 JSON 文件。

[在线思维导图](https://www.processon.com/view/5a5c45d7e4b0abe85d562bda)

**注意事项:**

使用自定义组件 [Component](https://mp.weixin.qq.com/debug/wxadoc/dev/framework/custom-component/),小程序基础版本库要在 1.6.3 以上;

使用 [wxParse](https://github.com/icindy/wxParse),小程序基础版本库要在 1.6.6 及以上。

小程序[简易双向绑定](https://developers.weixin.qq.com/miniprogram/dev/framework/view/two-way-bindings.html),小程序基础版本库 2.9.3 及以上

## 🎨 功能 Features

- 云函数实现微信登录
- 云函数定时任务实现每日卡片
- 云函数聚合查询实现卡片收藏
- Grid 多列表格布局
- Grid 布局实现瀑布流
- 云函数爬取 GitHub Trending
- 关于页背景音频播放
- 分别使用 template 和 Component 实现公用组件
- CSS3 属性动画

## 🐢 规范 Code of conduct

时间久了自己都忘记了以前给自己定的规范是啥,导致代码很不统一,给自己备份个项目规范🥱

[CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md)

## 🛠 运行 Run

克隆本项目,使用微信 Web 开发工具打开项目根目录

#### 安装依赖

```bash
yarn # or npm install
```

菜单栏 **工具 --> 构建 npm**

#### 云开发环境

开通云开发环境后将项目 `/cloudfunctions/` 同步至云(参考 [云开发环境初始化](./docs/云开发环境初始化.md)),修改 `app.js`

```javascript
wx.cloud.init({
  traceUser: true,
  env: 'dev-oucwt' // 此处替换为你自己的云环境 ID
});
```

*注意:真机预览开发环境时需打开调试*

#### Mock API

参考官方文档 [API Mock/规则导入导出](https://developers.weixin.qq.com/miniprogram/dev/devtools/api-mock.html),导入 [mock/mock.config.json](./mock/mock.config.json)。**开发工具提供的 Mock 能力暂不支持手机预览**

**注意:** 如果你没有 AppID 可能看不到数据,手机无法预览。开发工具需要关闭安全域名的校验,**工具栏 --> 详情 --> 项目设置 --> 勾选不校验安全域名...以及 HTTPS 证书**。

## 🪶 笔记 Notes

[Vercel 托管 Next 实现 GitHub Trending API](https://github.com/Honye/notes/blob/vuepress/React/vercel-github-trending.md)

[小程序使用 IconFont](https://github.com/Honye/notes/blob/vuepress/WeChat/miniprogram-iconfont.md)

[云开发关联表(集合)案例](https://github.com/Hongye567/weapp-mark/wiki/小程序关联表学习)

[写项目时的想法](https://github.com/Hongye567/weapp-mark/wiki/thought)

[边写边记](https://github.com/Hongye567/weapp-mark/wiki/小程序笔记)

[小程序使用外部字体](https://github.com/Honye/notes/blob/vuepress/WeChat/use-other-font.md)

[小程序自定义评分组件 - tempalte 实现(精度 0.1)](https://github.com/Hongye567/weapp-mark/wiki/小程序自定义评分组件-template(精度0.1))

[小程序自定义评分组件 - Component 实现(精度0.1)](https://github.com/Hongye567/weapp-mark/wiki/小程序自定义评分组件-Component(精度0.1))

## 📐 结构 Structure

```
├── apis
├── assets
├── components  组件化 Component
│    ├── cover-page 可下拉关闭的半屏组件
│    ├── pre-image 图片预加载
│    ├── rating 评分
│    └── tabs
├── cloudfunctions
├── pages
│    └── common  模板 template
│        ├── actionsheet 操作菜单
│        ├── cell 列表单元
│        ├── dropmenu 下拉菜单
│        ├── loading 加载/加载更多
│        ├── rating  评分
│        ├── share 底部分享菜单
│        ├── wxParse  富文本、HTML 和 MD 解析,小程序基础版本库 1.6.6 及以上
│        └── component.js  wux 针对 template 的组件化,写得挺好,借鉴一下
├── style
│    ├── weui.wxss
│    ├── animate.wxss CSS 动画
│    └── font-awesome.min.wxss Font Awesome 字体图标
├── utils
│    └── wxCloud.js 云函数二次封装
├── app.js
├── app.json
└── app.wxss
```

## 痛点

- 小程序不支持全局组件,需每个页面都引入组件。如自定义全局提示框

## 🔗 参考 Reference

1. 微信官方UI样式 [weui-wxss](https://github.com/Tencent/weui-wxss/)
2. 富文本、HTML 和 Markdown 解析 [wxParse](https://github.com/icindy/wxParse)
3. 针对 template 的自定义组件 [wux](https://github.com/skyvow/wux)
4. [LeanCloud 云服务](https://leancloud.cn/)提供后台支撑
5. [云服务开发环境(官方)](https://cloud.tencent.com/document/product/619/11447)
6. [小程序解决方案(官方)](https://cloud.tencent.com/solution/la)


================================================
FILE: cloudbaserc.json
================================================
{
  "version": "2.0",
  "region": "sh",
  "envId": "release-5g2g137xcedfade7",
  "functionRoot": "./cloudfunctions",
  "functions": [
    {
      "name": "app",
      "runtime": "Nodejs16.13"
    },
    {
      "name": "douban",
      "runtime": "Nodejs16.13",
      "triggers": [
        {
          "name": "CronDouban",
          "type": "timer",
          "config": "0 10 0 * * * *"
        }
      ]
    },
    {
      "name": "favCard",
      "runtime": "Nodejs16.13"
    },
    {
      "name": "fetch",
      "runtime": "Nodejs16.13"
    },
    {
      "name": "getCard",
      "runtime": "Nodejs16.13"
    },
    {
      "name": "getCards",
      "runtime": "Nodejs16.13"
    },
    {
      "name": "getCategories",
      "runtime": "Nodejs16.13"
    },
    {
      "name": "getFavCards",
      "runtime": "Nodejs16.13"
    },
    {
      "name": "initdb",
      "runtime": "Nodejs16.13"
    },
    {
      "name": "login",
      "runtime": "Nodejs16.13"
    },
    {
      "name": "nowPlaying",
      "runtime": "Nodejs16.13"
    },
    {
      "name": "showingSoon",
      "runtime": "Nodejs16.13"
    },
    {
      "name": "site",
      "runtime": "Nodejs16.13",
      "permissions": {
        "openapi": [
          "openapi.search.submitPages"
        ]
      }
    },
    {
      "name": "subscribeMessage",
      "runtime": "Nodejs16.13"
    },
    {
      "name": "subscription",
      "runtime": "Nodejs16.13",
      "triggers": [
        {
          "name": "trending",
          "type": "timer",
          "config": "*/10 * * * * * *"
        }
      ],
      "permissions": {
        "openapi": [
          "subscribeMessage.send"
        ]
      }
    },
    {
      "name": "wallpaper",
      "runtime": "Nodejs16.13"
    },
    {
      "name": "wxacode",
      "runtime": "Nodejs16.13",
      "permissions": {
        "openapi": [
          "wxacode.getUnlimited",
          "wxacode.get"
        ]
      }
    }
  ]
}

================================================
FILE: cloudfunctions/app/config.json
================================================
{
  "permissions": {
    "openapi": [
    ]
  }
}

================================================
FILE: cloudfunctions/app/index.js
================================================
const cloud = require('wx-server-sdk');
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
});

const db = cloud.database();

exports.main = async (event, context) => {
  
  try {
    const { data = [] } = await db.collection('app')
      .orderBy('created_at', 'desc')
      .limit(1)
      .get();
    return data[0];
  } catch (e) {
    // 集合不存在
    if (e.errCode === -502005) {
      return { version: '0.0.0' };
    }
    throw e;
  }
}


================================================
FILE: cloudfunctions/app/package.json
================================================
{
  "name": "app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "~2.3.3"
  }
}


================================================
FILE: cloudfunctions/douban/config.json
================================================
{
  "permissions": {
    "openapi": [
    ]
  },
  "triggers": [
    {
      "name": "CronDouban",
      "type": "timer",
      "config": "0 10 0 * * * *"
    }
  ]
}

================================================
FILE: cloudfunctions/douban/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk');
const fetch = require('node-fetch');

cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
});
const { request } = require('./request.js');
const db = cloud.database();

/**
 * 云函数入口函数
 * @param {object} event
 * @param {'cron'|'api.proxy'|'login'|'logout'|'fetch'} [event.action]
 * @param {object} [event.payload]
 */ 
exports.main = async (event, context) => {
  /** 默认视为定时任务 */
  const { action = 'cron' } = event;
  switch (action) {
    case 'cron':
      /** 每日定时存储每日卡片信息 */
      await storeTodayItem();
      break;
    case 'api.proxy':
      // case 代理接口请求,伪装请求
      return apiProxy(event.payload);
    case 'login':
      return login(event.payload);
    case 'logout':
      return logout();
    case 'fetch': {
      const { url, ...payload } = event.payload;
      return fetch(url, payload).then((resp) => resp.json());
    }
    default:
  }

  const wxContext = cloud.getWXContext()

  return {
    event,
    openid: wxContext.OPENID,
    appid: wxContext.APPID,
    unionid: wxContext.UNIONID,
  }
}

/**
 * 将今日豆瓣日历卡片存入集合
 * 
 * - 用于定时任务
 */
const storeTodayItem = async () => {
  // 云函数默认时区为 UTC+0
  const res = await request({
    headers: {
      'User-Agent': 'api-client/0.1.3 com.douban.frodo/8.0.0'
    },
    path: '/calendar/today',
    data: {
      date: new Intl.DateTimeFormat('zh-CN').format().replace(/\//g, '-'),
      alt: 'json',
      _sig: 'tuOyn+2uZDBFGAFBLklc2GkuQk4=',
      _ts: 1610703479,
      apiKey: '0ab215a8b1977939201640fa14c66bab'
    }
  });
  return db.collection('cards').add({
    data: {
      createTime: db.serverDate(),
      image: res.comment.poster,
      likeCount: 0,
      movieId: res.subject.id,
      quote: res.comment.content,
      shareCount: 0,
      source: `《${res.subject.title}》`
    }
  });
}

/**
 * 存储登录用户信息及 token
 * @param {object} params
 * @param {string} params.uid
 * @param {string} params.user_name
 * @param {string} params.access_token
 * @param {string} params.refresh_token
 * @param {number} params.expires_in token 有效时间,秒为单位
 */
const login = async (params) => {
  const wxContext = cloud.getWXContext();
  const userCollection = db.collection('users');
  const { data: users = [] } = await userCollection
    .where({
      openid: wxContext.OPENID
    })
    .limit(1)
    .get();
  const user = users[0];
  if (user) {
    const { expires_in: expiresIn, ...douban } = params;
    const updateData = {
      douban: {
        ...douban,
        expires_at: db.serverDate({
          offset: (expiresIn - 60) * 1000
        })
      },
      update_at: db.serverDate()
    };
    const { _id, ...profile } = user;
    return await userCollection.doc(_id)
      .set({
        data: { ...profile, ...updateData },
      });
  }
  throw new Error(`user openid=${wxContext.OPENID} not found`);
}

const logout = async () => {
  const wxContext = cloud.getWXContext();
  const usersCollection = db.collection('users');
  const user = await usersCollection
    .where({ openid: wxContext.OPENID })
    .limit(1)
    .get()
    .then(({ data }) => data[0]);
  
  if (user) {
    return usersCollection.doc(user._id)
      .update({
        data: { douban: null },
        update_at: db.serverDate(),
      });
  }

  throw new Error(`user openid=${wxContext.OPENID} not found`);
};

/**
 * 
 * @param {WechatMiniprogram.RequestOption} params
 */
const apiProxy = (params) => {
  const headers = Object.assign({},
    {
      Referer: 'https://servicewechat.com/wx2f9b06c1de1ccfca/81/page-frame.html',
      'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.2(0x18000236) NetType/WIFI Language/en'
    },
    params.header
  );
  return request({
    url: params.url,
    method: params.method,
    data: params.data,
    headers
  });
}

/**
 * @typedef {'movie_showing'|'movie_soon'} RankType
 */

/**
 * 各个排行榜
 * @param {object} params
 * @param {RankType} params.type
 * @param {object} params.params
 */
const getRankList = async (params) => {
  const collectionName = params.type;
  /** @type {Record<RankType, string>} */
  const names = {
    movie_showing: '正在热映',
    movie_soon: '即将上映'
  };
  /**
   * @type {{
   * _id: string;
   * update_time: Date;
   * }}
   */
  let stored;
  try {
    const ranks = await db.collection(collectionName)
    .where({ key: collectionName })
    .limit(1)
    .get();
    stored = ranks[0];
  } catch (e) {
    if (e.errCode === -502005) {
      await db.createCollection(collectionName);
    }
  }
  if (stored && stored.update_time.getTime() + 2 * 60 * 60 * 1000 > Date.now()) {
    // 每两小时一更新
    return stored.data;
  }

  const res = await apiProxy(params.params);
  if (stored) {
    // case 更新
    db.collection(collectionName)
      .doc(stored._id)
      .update({
        data: {
          update_time: db.serverDate(),
          data: res
        }
      });
  } else {
    // case 存储
    db.collection(collectionName)
      .add({
        data: {
          key: collectionName,
          title: names[collectionName],
          create_time: db.serverDate(),
          update_time: db.serverDate(),
          data: res
        }
      });
  }
  return res;
}


================================================
FILE: cloudfunctions/douban/package.json
================================================
{
  "name": "douban",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "node-fetch": "^2.6.1",
    "wx-server-sdk": "~2.3.3"
  }
}


================================================
FILE: cloudfunctions/douban/request.d.ts
================================================
export const request: (params: {
  headers?: Record<string, string>
  url?: string
  path?: string
  method?: 'GET'
  data?: Record<string, unknown>
}) => Promise<unknown>


================================================
FILE: cloudfunctions/douban/request.js
================================================
const cloud = require('wx-server-sdk');
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
});
const fetch = require('node-fetch');
const qs = require('querystring');
const API_BASE = 'https://frodo.douban.com/api/v2';
const API_KEY = '054022eaeae0b00e0fc068c0c0a2102a';

/**
 * 
 * @param {object} params
 * @param {object} [params.headers]
 * @param {string} [params.url]
 * @param {string} [params.path]
 * @param {'GET'} [params.method]
 * @param {object} [params.data]
 */
const request = async (params = {}) => {
  params.url = params.url || `${API_BASE}${params.path}`;
  delete params.path;
  params.data = {
    apikey: API_KEY,
    ...(params.data || {})
  };
  if ((params.method || 'GET') === 'GET' && params.data) {
    params.url += '?' + qs.stringify(params.data);
    delete params.data;
  }
  return fetch(
    params.url,
    {
      headers: params.headers,
      method: params.method || 'GET',
      body: params.data
    }
  )
    .then((resp) => resp.json());
}

module.exports = {
  request
}


================================================
FILE: cloudfunctions/favArticle/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init(
  // {
  //   env: 'dv-963c46'
  // }
)

const db = cloud.database()

/** 与用户关联 */
const relateUser = (event, context) => {
    const { userInfo: { openId }, id: articleID } = event
    return new Promise((resolve, reject) => {
        db.collection('userRelations').where({
            _openid: openId,
        }).get().then(({ data }) => {
            if (data.length) {
                let { favoriteArticles } = data[0]
                const has = favoriteArticles && favoriteArticles.includes(articleID)
                if (typeof favoriteArticles === 'undefined') {
                    favoriteArticles = [articleID]
                } else if (has) {
                    favoriteArticles = favoriteArticles.filter(item => item !== articleID)
                } else {
                    favoriteArticles = Array.from(new Set([articleID, ...favoriteArticles]))
                }
                db.collection('userRelations').doc(data[0]._id).update({
                    data: { favoriteArticles }
                }).then(res => {
                    resolve({ message: has ? '取消喜欢' : '喜欢成功' })
                })
            } else {
                db.collection('userRelations').add({
                    data: {
                        _openid: openId,
                        favoriteArticles: [articleID],
                    }
                }).then(res => {
                    resolve({ message: '喜欢成功' })
                })
            }
        }).catch(err => {
            reject(err)
        })
    })
}

/** 与文章关联 */
const relateArticle = (event, context) => {
    const { userInfo: { openId }, id: articleID } = event
    return new Promise((resolve, reject) => {
        db.collection('articleRelations').where({
            id: articleID,
        }).get().then(({ data }) => {
            if (data.length) {
                let { users } = data[0]
                const has = users && users.includes(openId)
                if (typeof users === 'undefined') {
                    users = [openId]
                } else if (has) {
                    users = users.filter(item => item !== openId)
                } else {
                    users = Array.from(new Set([openId, ...users]))
                }
                db.collection('articleRelations').doc(data[0]._id).update({
                    data: { users }
                }).then( res => {
                    resolve({ message: has ? '取消喜欢' : '喜欢成功' })
                }).catch( err => {
                    reject(err)
                })
            } else {
                db.collection('articleRelations').add({
                    data: {
                        id: articleID,
                        users: [openId],
                    }
                }).then( res => {
                    resolve({ message: '喜欢成功' })
                }).catch( err => {
                    reject(err)
                })
            }
        }).catch( err => {
            reject(err)
        })
    })
}

// 云函数入口函数
exports.main = async (event, context) => {
    return Promise.all([
        relateUser(event, context),
        relateArticle(event, context)
    ]).then( values => values[0] || values)
}

================================================
FILE: cloudfunctions/favArticle/package.json
================================================
{
  "name": "favArticle",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "latest"
  }
}


================================================
FILE: cloudfunctions/favCard/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk');

cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
});

const db = cloud.database();

// 云函数入口函数
exports.main = async (event, context) => {
  const { id: cardID } = event;
  const { OPENID } = cloud.getWXContext();
  const collectionName = 'card_like';
  let record;
  try {
    const records = await db.collection(collectionName)
      .where({
        openid: OPENID,
        card_id: cardID
      })
      .limit(1)
      .get();
    record = records.data[0];
  } catch (e) {
    if (e.errCode === -502005) {
      /** 集合不存在,新创建集合 */
      await db.createCollection(collectionName);
    }
  }

  if (record) {
    return await db.collection(collectionName)
      .doc(record._id)
      .update({
        data: {
          update_at: db.serverDate(),
          state: Number(!record.state)
        }
      });
  }

  /** 不存在集合时肯定是做喜欢操作 */
  return await db.collection(collectionName)
    .add({
      data: {
        openid: OPENID,
        create_at: db.serverDate(),
        update_at: db.serverDate(),
        card_id: cardID,
        state: 1
      }
    });
}


================================================
FILE: cloudfunctions/favCard/package.json
================================================
{
  "name": "favCard",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "latest"
  }
}


================================================
FILE: cloudfunctions/fetch/config.json
================================================
{
  "permissions": {
    "openapi": [
    ]
  }
}

================================================
FILE: cloudfunctions/fetch/index.js
================================================
const cloud = require('wx-server-sdk')
const fetch = require('node-fetch')

cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })

exports.main = async (event, context) => {
  const { url, ...options } = event
  return fetch(url, options)
    .then((resp) => {
      const data = resp.json()
      if (resp.ok) {
        return data
      }
      return Promise.reject(data)
    })
}

================================================
FILE: cloudfunctions/fetch/package.json
================================================
{
  "name": "fetch",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "node-fetch": "~2.6.12",
    "wx-server-sdk": "~2.6.3"
  }
}

================================================
FILE: cloudfunctions/getArticleDetails/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init(
  // {
  //   env: 'dv-963c46'
  // }
)

const db = cloud.database()

/** 获取是否喜欢 */
const getFavStatus = (event, context) => {
    const { userInfo: { openId }, id: articleID } = event
    return db.collection('userRelations').where({
        _openid: openId,
    }).get().then(({ data }) => {
        let result = false
        if (data.length) {
            result = data[0].favoriteArticles && data[0].favoriteArticles.includes(articleID)
        }
        return {
            data: result,
            message: 'success',
        }
    }).catch( err => {
        return Promise.reject({
            data: false,
            message: err.errMsg || JSON.stringify(err),
        })
    })
}

// 云函数入口函数
exports.main = async (event, context) => {
    return getFavStatus(event, context)
}

================================================
FILE: cloudfunctions/getArticleDetails/package.json
================================================
{
  "name": "getArticleDetails",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "latest"
  }
}


================================================
FILE: cloudfunctions/getCard/config.json
================================================
{
  "permissions": {
    "openapi": [
    ]
  }
}

================================================
FILE: cloudfunctions/getCard/index.js
================================================
const cloud = require('wx-server-sdk');

cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV,
});

const db = cloud.database();
const _ = db.command;
const $ = _.aggregate;

exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext();
  const openid = wxContext.OPENID;
  const _id = event._id;

  const promiseCard = db.collection('cards').doc(_id).get().then(({ data }) => data);
  const promiseLiked =
    db.collection('card_like')
      .where({ card_id: _id, state: 1 })
      .count()
      .then(({ total }) => total);
  const promiseState =
    db.collection('card_like')
      .where({ card_id: _id, openid })
      .limit(1)
      .get()
      .then(({ data }) => data[0] && data[0].state);

  const [card, liked, state] = await Promise.all([promiseCard, promiseLiked, promiseState]);

  return {
    ...card,
    like_count: liked,
    like_state: state,
  };
}


================================================
FILE: cloudfunctions/getCard/package.json
================================================
{
  "name": "getCard",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "~2.6.1"
  }
}

================================================
FILE: cloudfunctions/getCards/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk');

cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
});

const db = cloud.database();
const _ = db.command;

// 云函数入口函数
exports.main = async (event, context) => {
  const $ = db.command.aggregate;
  const { OPENID } = cloud.getWXContext();
  const res = await db.collection('cards').aggregate()
    .sort({
      createTime: -1
    })
    .limit(6)
    .lookup({
      from: 'card_like',
      let: {
        card_id: '$_id'
      },
      pipeline: $.pipeline()
        .match(_.expr($.and([
          $.eq(['$card_id', '$$card_id']),
          $.eq(['$state', 1])
        ])))
        .done(),
      as: 'like_list'
    })
    .addFields({
      like_count: $.size('$like_list')
    })
    .addFields({
      like_state: $.let({
        vars: {
          filtered: $.filter({
            input: '$like_list',
            as: 'item',
            cond: $.and($.eq(['$$item.openid', OPENID]), $.eq(['$$item.state', 1]))
          })
        },
        in: $.reduce({
          input: '$$filtered',
          initialValue: 0,
          in: '$$this.state'
        })
      })
    })
    .end();
  return res;
}


================================================
FILE: cloudfunctions/getCards/package.json
================================================
{
  "name": "getCards",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "latest"
  }
}


================================================
FILE: cloudfunctions/getCategories/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })

const db = cloud.database()
const _ = db.command

const getCategories = async (event, context) => {
  let ret = []
  let message = 'success'

  const pidData = await db.collection('categories')
    .aggregate()
    .group({
      _id: '$pid',
    })
    .sort({
      _id: 1
    })
    .end()
  
  const pids = pidData.list || []
  for (let i = 0, len = pids.length; i < len; ++i) {
    const pCateData = await db.collection('categories')
      .where({
        id: pids[i]._id,
      })
      .get()
    if (pCateData.data && pCateData.data.length) {
      const pCate = pCateData.data[0]
      const children = await db.collection('categories')
        .where({
          pid: pCate.id,
          id: _.neq(pCate.id),
        })
        .get()
        .then(({ data = [] }) => data)
      pCate.children = children
      ret.push(pCate)
    } else {
      console.log(`未找到 id 为 \`${pids[i]._id}\` 的分类`)
    }
  }
  return {
    data: ret,
    message,
  }
}

// 云函数入口函数
exports.main = async (event, context) => {
  return getCategories(event, context)
}

================================================
FILE: cloudfunctions/getCategories/package.json
================================================
{
  "name": "getCategories",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "latest"
  }
}

================================================
FILE: cloudfunctions/getFavArticles/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init(
  // {
  //   env: 'dv-963c46'
  // }
)

const db = cloud.database()
const _= db.command

/** 获取喜欢的影单 */
const getFavArticles = (event, context) => {
    const { userInfo: { openId } } = event
    return db.collection('userRelations').where({
        _openid: openId,
    }).get().then(({ data }) => {
        if (data.length) {
            return db.collection('articles').where({
                id: _.in(data[0].favoriteArticles ? data[0].favoriteArticles.map(item => Number(item)) : []),
            }).field({
                id: true,
                image: true,
                likeCount: true,
                title: true,
            }).get().then(({ data }) => {
                return {
                    data,
                    message: 'success',
                }
            }).catch( err => {
                console.error('*** 错误 ***', err)
                return Promise.reject({
                    data: [],
                    message: err.errMsg || JSON.stringify(err)
                })
            })
        } else {
            return {
                data: [],
                message: 'success',
            }
        }
    }).catch( err => {
        console.error('*** 错误 ***', err)
        return Promise.reject({
            data: [],
            message: err.errMsg || JSON.stringify(err)
        })
    })
}

// 云函数入口函数
exports.main = async (event, context) => {
    return getFavArticles(event, context)
}

================================================
FILE: cloudfunctions/getFavArticles/package.json
================================================
{
  "name": "getFavArticles",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "latest"
  }
}


================================================
FILE: cloudfunctions/getFavCards/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init({
    env: cloud.DYNAMIC_CURRENT_ENV,
})

const db = cloud.database()
const _ = db.command
const $ = _.aggregate

/** 获取用户喜欢的卡片 */
const getFavCards = async (event, context) => {
    // const { userInfo: { openId } } = event
    const { OPENID: openId } = cloud.getWXContext();
    const records = await db.collection('card_like')
        .aggregate()
        .match({
            openid: openId,
            state: 1,
        })
        .lookup({
            from: 'cards',
            localField: 'card_id',
            foreignField: '_id',
            as: 'card_detail',
        })
        // 过滤掉卡片已不存在(删除)的数据
        .match({
            card_detail: _.elemMatch({
                _id: _.exists(true),
            }),
        })
        // 只返回卡片信息
        .replaceRoot({
            newRoot: $.arrayElemAt(['$card_detail', 0]),
        })
        .end();
    return records;
}

// 云函数入口函数
exports.main = async (event, context) => {
    return getFavCards(event, context)
}

================================================
FILE: cloudfunctions/getFavCards/package.json
================================================
{
  "name": "getFavCards",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "latest"
  }
}


================================================
FILE: cloudfunctions/github/config.json
================================================
{
  "permissions": {
    "openapi": [
    ]
  }
}

================================================
FILE: cloudfunctions/github/fetch.js
================================================
/**
 * [GitHub REST API](https://docs.github.com/en/rest) 封装
 * 
 * 使用原生 fetch api
 * 
 */
const cloud = require('wx-server-sdk');

cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
});
const fetch = require('node-fetch');
const qs = require('querystring');
const db = cloud.database();
const baseURL = 'https://api.github.com';

/**
 * 
 * @param {object} params
 * @param {string} [params.url]
 * @param {string} params.path
 * @param {'GET'|'POST'} [params.method]
 * @param {Record<string, any>} params.data
 */
const request = async (params = {}) => {
  let token;
  try {
    token = await getToken();
  } catch (e) {
    console.warn(e);
  }

  params.url = params.url || `${baseURL}${params.path}`;
  delete params.path;
  if ((params.method || 'GET') === 'GET') {
    params.url += '?' + qs.stringify(params.data);
    delete params.data;
  }
  return fetch(params.url, {
    headers: {
      Accept: 'application/vnd.github.v3+json',
      Authorization: `token ${token}`,
    },
    method: params.method || 'GET',
    body: params.data
  });
};

/**
 * 
 * @returns {Promise<void|string>}
 */
const getToken = async () => {
  const users = db.collection('users');
  const context =  cloud.getWXContext();
  const openId = context.OPENID;
  const { data: [user] } = await users.where({
    openid: openId
  })
    .limit(1)
    .get();
  if (!user) {
    return;
  }
  return user.githubToken;
};

module.exports = {
  request
};

================================================
FILE: cloudfunctions/github/index.js
================================================
/**
 * GitHub 相关云函数入口
 */

/**
 * @typedef {object} ResponseType 云函数调用成功响应
 * @property {number} code 0:成功;-1:失败
 * @property {any} data 请求结果
 * @property {string} message 请求信息
 */

const cloud = require('wx-server-sdk');

cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
});

const { request } = require('./fetch');
const db = cloud.database();

/**
 * 云函数入口函数
 * @param {object} event
 * @param {string} event.action
 * @param {object} event.data
 */
exports.main = async (event, context) => {
  const { action } = event;
  switch (action) {
    case 'setAccessToken':
      return setAccessToken(event.data);
    case 'getStarredList':
      return getStarredList(event.data);
    case 'removeAccessToken':
      return removeAccessToken(event.data);
    case 'homePage':
      return getHomePage(event.data);
    default:
  }
}

const queryUser = async () => {
  const users = db.collection('users');
  const context = cloud.getWXContext();
  const openId = context.OPENID;
  const { data: [user] } = await users.where({ openid: openId })
    .limit(1)
    .get();
  return user;
};

/**
 * 设置 GitHub AccessToken
 * 
 * [new token](https://github.com/settings/tokens/new)
 * 
 * @param {object} params
 * @param {string} params.token
 */
const setAccessToken = async (params) => {
  const users = db.collection('users');
  const context = cloud.getWXContext();
  const openId = context.OPENID;
  const updateTime = new Date();
  const { data: [user] } = await users.where({
    openid: openId
  })
    .limit(1)
    .get();
  
  if (!user) {
    /** @type {ResponseType} */
    const ret = {
      code: -1,
      message: 'user not found',
    };
    return ret;
  }

  await users.doc(user._id)
    .update({
      data: {
        update_at: updateTime,
        githubToken: params.token
      }
    });
  
  /** @type {ResponseType} */
  const ret = {
    code: 0,
    message: 'set github token success'
  };
  return ret;
};

const removeAccessToken = async () => {
  const user = await queryUser();
  /** @type {ResponseType} */
  const ret = {
    code: 0,
    message: 'remove github token success'
  };
  if (!user) {
    ret.code = -1;
    ret.message = 'user not found';
    return ret;
  }
  const users = db.collection('users');
  await users.doc(user._id)
    .update({
      data: {
        update_at: db.serverDate(),
        githubToken: null
      }
    });
  return ret;
};

/**
 * 获取用户 Star repositories
 * 
 * [List repositories starred by the authenticated user](https://docs.github.com/en/rest/reference/activity#list-repositories-starred-by-the-authenticated-user)
 * 
 * @param {object} params
 * @param {'created'|'updated'} params.sort
 * @param {'asc'|'desc'} params.direction
 * @param {number} params.per_page max 100
 * @param {number} params.page
 */
const getStarredList = async (params) => {
  const resp = await request({
    path: '/user/starred',
    data: params
  })
    .then(resp => resp.json());
  return {
    code: 0,
    data: resp
  };
};

/**
 * GitHub user home page info
 * 
 * @param {object} params
 * @param {string} params.user
 */
const getHomePage = async (params) => {
  return request({
    url: `https://www.imarkr.com/api/github/${params.user}`
  }).then((resp) => resp.json());
};


================================================
FILE: cloudfunctions/github/package.json
================================================
{
  "name": "github",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "node-fetch": "^2.6.1",
    "wx-server-sdk": "~2.3.2"
  }
}


================================================
FILE: cloudfunctions/initdb/index.js
================================================
const cloud = require('wx-server-sdk')

cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })

const db = cloud.database()

/**
 * 初始化表 banners
 */
const initBanners = async () => {
    console.log('*** start initBanners ***')
    const collection = 'banners'
    await db.createCollection(collection)
        .catch((e) => {
            // 集合已存在
        })
    const banners = [{
            image: 'http://static.zhidao.manmankan.com/kimages/201609/26_1474884213331602.jpg',
            type: 'image',
        },
        {
            image: 'http://www.stdaily.com/zhuanti01/2017sc/2017-09/29/581248/images/188f4c63b10547bebf39131bb25c1162.png',
            type: '',
        },
        {
            image: 'https://pbs.twimg.com/media/Cr9fC-dUMAABqEC.jpg',
        },
    ]
    let docLength = 0
    await db.collection(collection)
        .get()
        .then(({ data }) => {
            docLength = data.length
        })
    for (let i = 0, length = banners.length; i < length; ++i) {
        await db.collection(collection).add({
            data: {
                id: docLength + i + 1,
                createTime: db.serverDate(),
                ...banners[i]
            }
        }).then(res => {
            console.log('*** add banner successed ***', res)
        }).catch(err => {
            console.error('*** add banner failed ***', err)
        })
    }
    console.log('*** initBanners finished ***')
}

/**
 * 初始化表 articles
 */
const initArticles = async () => {
    console.log('*** start init articles ***')
    const articles = [
        {
            id: 1125,
            image: "http://7xqnv7.com2.z0.glb.qiniucdn.com/2017-12-21_5a3b66c4e3c92.jpg",
            title: "那些温情电影都告诉了我们什么?!",
            likeCount: 104
        },
        {
            id: 1118,
            image: "http://7xqnv7.com2.z0.glb.qiniucdn.com/2017-12-17_5a362f082c25f.jpg",
            title: "我想知道流星能飞多久",
            likeCount: 99
        },
        {
            id: 1119,
            image: "http://7xqnv7.com2.z0.glb.qiniucdn.com/2017-12-18_5a376fc20e33e.jpg",
            title: "《月升王国》谁说大人的世界没有童话?",
            likeCount: 33
        },
        {
            id: 1102,
            image: "http://7xqnv7.com2.z0.glb.qiniucdn.com/2017-12-03_5a236db32a980.jpg",
            title: "每一个童年都值得被爱:《西葫芦的生活》",
            likeCount: 69
        },
        {
            id: 1074,
            image: "http://7xqnv7.com2.z0.glb.qiniucdn.com/2017-11-04_59fd63d65798d.jpg",
            title: "《蝴蝶梦》:她低到了尘埃里,终将摆脱前任的阴影",
            likeCount: 25
        },
        {
            id: 1062,
            image: "http://7xqnv7.com2.z0.glb.qiniucdn.com/2017-10-27_59f2d62abb85d.jpg",
            title: "《生吃》才不是史上最恐怖的影片,但成长确实是一个思之极恐的过程",
            likeCount: 107
        },
        {
            id: 1059,
            image: "http://7xqnv7.com2.z0.glb.qiniucdn.com/2017-10-27_59f2af5ae403b.jpg",
            title: "谁的人生不迷茫?今敏惊悚悬疑电影《未麻的部屋》观影感悟",
            likeCount: 55
        },
    ]
    for (let i = 0, length = articles.length; i < length; ++i) {
        await db.collection('articles').add({
            data: {
                createTime: db.serverDate(),
                ...articles[i]
            }
        }).then(res => {
            console.log('*** add article successed ***', res)
        }).catch(err => {
            console.error('*** add article failed ***', err)
        })
    }
    console.log('*** init articles finished ***')
}

/**
 * 初始化表 cards
 */
const initCards = async () => {
    console.log('*** start init cards ***')
    const collection = 'cards'
    await db.createCollection(collection)
        .catch(() => {
            // 集合已存在
        })
    const cards = [
        {
            id: 11,
            "image": "https://img3.doubanio.com/view/photo/raw/public/p579724050.jpg",
            quote: "人心其实很脆弱,所以我们要经常哄哄它,经常把手放在心脏旁,对自己说:平安无事,平安无事,平安无事……",
            source: "《三傻大闹宝莱坞》",
            likeCount: 84,
            shareCount: 12,
            movieId: 3793023
        },
        {
            id: 10,
            image: "https://img1.doubanio.com/view/photo/raw/public/p2245932509.jpg",
            quote: "当恋爱了,就算最枯燥的工作也能忍受。",
            source: "《海街日记》",
            likeCount: 51,
            shareCount: 10,
            movieId: 25895901
        },
        {
            id: 9,
            image: "https://img1.doubanio.com/view/photo/l/public/p456514379.jpg",
            quote: "为了你在乎的东西值得冒险。",
            source: "《南极大冒险》",
            likeCount: 52,
            shareCount: 11,
            movieId: 1477448
        },
        {
            id: 8,
            image: "https://img3.doubanio.com/view/photo/l/public/p1716666870.jpg",
            quote: "我一直以为最糟糕的情况是你离开我,其实最令我难过的,是你不快乐。",
            source: "《精灵旅社》",
            likeCount: 121,
            shareCount: 19,
            movieId: 3269068
        },
        {
            id: 7,
            image: "https://img3.doubanio.com/view/photo/l/public/p1106861692.jpg",
            quote: "贬低他人,并不会令自己变得多伟大。",
            source: "《贱女孩》",
            likeCount: 64,
            shareCount: 11,
            movieId: 1309084
        },
        {
            id: 6,
            image: "https://img3.doubanio.com/view/photo/l/public/p2374369280.jpg",
            quote: "尽管已提前知晓人生,以及它的走向,我无所畏惧,并且会珍惜每一分钟。",
            source: "《降临》",
            likeCount: 63,
            shareCount: 10,
            movieId: 21324900
        },
        {
            id: 5,
            image: "https://img3.doubanio.com/view/photo/l/public/p1694419516.jpg",
            quote: "不要让别人告诉你,你不能做什么。只要有梦想,就要去追求。那些做不到的人总要告诉你,你也不行。想要什么就得去努力,去追求。",
            source: "《当幸福来敲门》",
            likeCount: 74,
            shareCount: 15,
            movieId: 1849031
        },
        {
            id: 4,
            image: "https://img3.doubanio.com/view/photo/l/public/p1140053562.jpg",
            quote: "真正的失败者不是那些没有赢的人,而是那些害怕失败而不敢尝试的人。",
            source: "《阳光小美女》",
            likeCount: 98,
            shareCount: 18,
            movieId: 1777612
        },
        {
            id: 3,
            image: "https://img3.doubanio.com/view/photo/l/public/p800791080.jpg",
            quote: "探戈走错了可以重来,人生则不可...",
            source: "《闻香识女人》",
            likeCount: 81,
            shareCount: 11,
            movieId: 1298624
        }
    ]
    for (let i = 0, length = cards.length; i < length; ++i) {
        await db.collection(collection).add({
            data: {
                createTime: db.serverDate(),
                ...cards[i]
            }
        }).then(res => {
            console.log('*** add card successed ***', res)
        }).catch(err => {
            console.error('*** add card failed ***', err)
        })
    }
    console.log('*** init cards finished ***')
}

exports.main = async(event, context) => {
    console.log('*** 开始初始化 ***')
    await initBanners()
    // await initArticles()
    await initCards()
    console.log('*** 初始化结束 ***')
}

================================================
FILE: cloudfunctions/initdb/package.json
================================================
{
  "name": "douban",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "latest"
  }
}


================================================
FILE: cloudfunctions/login/config.json
================================================
{
  "permissions": {
    "openapi": [
    ]
  }
}

================================================
FILE: cloudfunctions/login/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk');

cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
});

const db = cloud.database();

const login = async (event, context) => {
  const wxUserInfo = event.wxUserInfo && event.wxUserInfo.data;
  if (typeof wxUserInfo === 'object') {
    // 😞用户表里字段和微信返回的字段大小写不一致,避免存多字段,替换字段
    wxUserInfo.openid = wxUserInfo.openId || wxUserInfo.openid;
    delete wxUserInfo.openId;
    wxUserInfo.unionid = wxUserInfo.unionId || wxUserInfo.unionid;
    delete wxUserInfo.unionId;
  }

  try {
    await db.createCollection('users');
  } catch (err) {
    // "users" collection already exist
    // do nothing
  }
  const usersCollection = db.collection('users');
  const wxContext = cloud.getWXContext();
  const { data: users } = await usersCollection
    .where({
      openid: wxContext.OPENID
    })
    .limit(1)
    .get();
  
  if (users && users.length) {
    // case 老用户登录,更新用户信息
    const user = users[0];
    const serverDate = db.serverDate();
    const updateData = {
      latest_login: serverDate,
      update_at: serverDate
    };
    if (typeof wxUserInfo === 'object') {
      // 每次登录都更新最新信息
      updateData.rawData = wxUserInfo;
      for (const key in wxUserInfo) {
        if (
          Object.prototype.hasOwnProperty.call(wxUserInfo, key)
          && !isInvalid(wxUserInfo[key])
        ) {
          updateData[key] = wxUserInfo[key];
        }
      }
    }
    await usersCollection.doc(user._id)
      .update({
        data: updateData
      });
    const updatedUser = { ...user, ...updateData };
    if (updatedUser.douban) {
      const { expires_at: expiresAt } = updatedUser.douban;
      if (!expiresAt || new Date(expiresAt).getTime() < Date.now()) {
        // 豆瓣登录已失效,清除 access_token
        updatedUser.douban.access_token = '';
      }
    }
    return {
      data: updatedUser,
      message: 'cloud.login:ok'
    };
  }

  // case 新用户登录
  const serverDate = db.serverDate();
  const user = {
    openid: wxContext.OPENID,
    appid: wxContext.APPID,
    unionid: wxContext.UNIONID,
    create_at: serverDate,
    update_at: serverDate,
    latest_login: serverDate
  };
  if (typeof wxUserInfo === 'object') {
    // 每次登录都更新最新信息
    user.rawData = wxUserInfo;
    for (const key in wxUserInfo) {
      if (
        Object.prototype.hasOwnProperty.call(wxUserInfo, key)
        && !isInvalid(wxUserInfo[key])
      ) {
        user[key] = wxUserInfo[key];
      }
    }
  }
  const { _id } = await usersCollection.add({
    data: user
  });
  return {
    data: {
      _id,
      ...user
    },
    message: 'cloud.login:ok'
  };
};

const isInvalid = (data) => {
  return data === undefined
    || data === null
    || data === ''
    || (typeof data === 'number' && isNaN(data));
}

// 云函数入口函数
exports.main = async (event, context) => {
  return await login(event, context);
}

================================================
FILE: cloudfunctions/login/package.json
================================================
{
  "name": "login",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "~2.2.0"
  }
}


================================================
FILE: cloudfunctions/nowPlaying/config.json
================================================
{
  "permissions": {
    "openapi": [
    ]
  }
}

================================================
FILE: cloudfunctions/nowPlaying/fetch.js
================================================
const cheerio = require('cheerio');
const fetch = require('node-fetch');

const fetchTrendingList = async () => {
  const data = await fetch('https://github.com/trending');
  const $ = cheerio.load(await data.text());
  const allTitles = $('.Box .Box-row')
    .get()
    .map(repo => {
      const $repo = $(repo);
      const title = $repo.find('.h3').text().trim();
      const [owner, name] = title.split('/').map(v => v.trim());
      return `${owner}/${name}`;
    });
  console.log(allTitles);
};

const fetchNowPlaying = async () => {
  const data = await fetch('https://movie.douban.com/cinema/nowplaying/wuhan/');
  const $ = cheerio.load(await data.text());
  const movies = $('ul.lists .list-item')
    .get()
    .map(movie => {
      const $movie = $(movie);
      const id = $movie.attr('id');
      const title = $movie.attr('data-title');
      const realTime = $movie.attr('data-release');
      const duration = $movie.attr('data-duration');
      const rating = $movie.attr('data-score');
      const img = $movie.find('.poster img')
        .attr('src');
      return {
        id,
        title,
        realTime,
        durations: [duration],
        rating,
        img
      };
    });
  console.log('movies===', movies);
  return movies;
};

module.exports = {
  fetchTrendingList,
  fetchNowPlaying
};

================================================
FILE: cloudfunctions/nowPlaying/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk');
const { fetchNowPlaying } = require('./fetch');

cloud.init()

// 云函数入口函数
exports.main = async (event, context) => {
  const list = await fetchNowPlaying();
  return list;
}

================================================
FILE: cloudfunctions/nowPlaying/package.json
================================================
{
  "name": "nowPlaying",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cheerio": "^1.0.0-rc.3",
    "node-fetch": "^2.6.1",
    "wx-server-sdk": "~2.2.0"
  }
}


================================================
FILE: cloudfunctions/showingSoon/config.json
================================================
{
  "permissions": {
    "openapi": [
    ]
  }
}

================================================
FILE: cloudfunctions/showingSoon/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk');
const cheerio = require('cheerio');
const fetch = require('node-fetch');

cloud.init();

const fetchShowingSoon = async () => {
  const data = await fetch('https://movie.douban.com/cinema/later/shanghai/');
  const $ = cheerio.load(await data.text());
  const movies = $('#showing-soon .item')
    .get()
    .map(movie => {
      const $movie = $(movie);
      const title = $movie.find('.intro h3').text().trim();
      const realTime = $movie.find('.intro li.dt:nth-child(1)').text().trim();
      const genres = $movie.find('.intro li.dt:nth-child(2)').text().trim();
      const img = $movie.find('a.thumb img')
        .attr('src');
      return {
        title,
        realTime,
        genres,
        img
      };
    });
  console.log('movies===', movies);
  return movies;
};

// 云函数入口函数
exports.main = async (event, context) => {
  const list = await fetchShowingSoon();
  return list;
}

================================================
FILE: cloudfunctions/showingSoon/package.json
================================================
{
  "name": "showingSoon",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cheerio": "^1.0.0-rc.3",
    "node-fetch": "^2.6.0",
    "wx-server-sdk": "~2.2.0"
  }
}

================================================
FILE: cloudfunctions/site/config.json
================================================
{
  "permissions": {
    "openapi": [
      "openapi.search.submitPages"
    ]
  }
}

================================================
FILE: cloudfunctions/site/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })

/**
 * 
 * @param {object} event
 * @param {'submitPages'} event.action
 * @param {object} [event.payload = {}]
 * @param {Array<{ path: string; query: string; }>} [event.payload.pages]
 */
exports.main = async (event, context) => {
  const { action, payload = {} } = event;

  switch (action) {
    case 'submitPages':
      return await cloud.openapi.search.submitPages(payload);
    default:
  }
}


================================================
FILE: cloudfunctions/site/package.json
================================================
{
  "name": "site",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "~2.4.0"
  }
}


================================================
FILE: cloudfunctions/subscribeMessage/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })

// 云函数入口函数
exports.main = async () => {
  const wxContext = cloud.getWXContext()
  console.log('openid==>', wxContext.OPENID)

  try {
    const result = await cloud.openapi.subscribeMessage.send({
      touser: wxContext.OPENID,
      templateId: 'sJz8Heo9GSqMwhnJFlpEHbm-rmIhUlhOkEOoSvY6BwE',
      data: {
        thing1: {
          value: '《Dr.STONE》',
        },
        time2: {
          value: '2019-12-16 10:00:00'
        },
        name3: {
          value: 'Honye'
        },
      }
    })
    return result
  } catch (err) {
    console.error(err)
    return err
  }
}

================================================
FILE: cloudfunctions/subscribeMessage/package.json
================================================
{
  "name": "subscribeMessage",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "latest"
  }
}


================================================
FILE: cloudfunctions/subscription/config.json
================================================
{
  "triggers": [
    {
      "name": "trending",
      "type": "timer",
      "config": "*/10 * * * * * *"
    }
  ],
  "permissions": {
    "openapi": [
      "subscribeMessage.send"
    ]
  }
}

================================================
FILE: cloudfunctions/subscription/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk');

cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
});
const db = cloud.database();

const send = async (openid, data) => {
  return await cloud.openapi.subscribeMessage.send({
    touser: openid,
    templateId: 'sJz8Heo9GSqMwhnJFlpEHbm-rmIhUlhOkEOoSvY6BwE',
    data: {
      thing1: {
        value: '《Dr.STONE》',
      },
      time2: {
        value: '2019-12-16 10:00:00'
      },
      name3: {
        value: 'Honye'
      },
    }
  })
};

// 云函数入口函数
exports.main = async (event, context) => {
  const usersCollection = db.collection('users');
  const { data: users } = await usersCollection
    .limit(1)
    .get();
  for (const user of users) {
    await send(user.openid);
  }

  return {
    data: null,
    message: 'cloud.subscription:ok'
  }
}

================================================
FILE: cloudfunctions/subscription/package.json
================================================
{
  "name": "subscription",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "~2.2.0"
  }
}


================================================
FILE: cloudfunctions/trending/config.json
================================================
{
  "permissions": {
    "openapi": [
    ]
  }
}

================================================
FILE: cloudfunctions/trending/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk');
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
});
const fetch = require('node-fetch');
const cheerio = require('cheerio');
const qs = require('querystring');

/**
 * 已迁移至 Vercel
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const usedInVercel = async () => {
  const data = await fetch('https://github.com/trending');
  const $ = cheerio.load(await data.text());
  const list = $('.Box .Box-row')
    .get()
    .map(repo => {
      const $repo = $(repo);
      const title = $repo.find('.h3').text().trim();
      const [owner, name] = title.split('/').map(v => v.trim());
      const description = $(($repo.children())[2]).text().trim();
      const language = $repo.find('[itemprop="programmingLanguage"]').text().trim();
      const starCount = $repo.find('[aria-label="star"].octicon.octicon-star').parent().text().trim();
      return {
        owner: {
          login: owner,
          avatar_url: `https://github.com/${owner}.png`
        },
        name,
        description,
        language,
        stargazers_count: starCount
      };
    });
  return list;
};

const fetchTrendingList = async (params) => {
  let url = 'https://www.imarkr.com/api/trending';
  if (params) {
    const query = qs.stringify(params);
    if (query) {
      url += `?${query}`;
    }
  }
  const data = await fetch(url).then((resp) => resp.json());
  return data;
}

/**
 * 云函数入口函数
 * 
 * @param {object} event
 * @param {string} [event.language]
 * @param {'daily'|'weekly'|'monthly'} [event.since]
 * @param {string} [event.spoken_language_code]
 */
exports.main = async (event, context) => {
  return fetchTrendingList(event);
}

================================================
FILE: cloudfunctions/trending/package.json
================================================
{
  "name": "trending",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cheerio": "^1.0.0-rc.5",
    "node-fetch": "^2.6.1",
    "wx-server-sdk": "~2.2.0"
  }
}


================================================
FILE: cloudfunctions/wallpaper/index.js
================================================
const fetch = require('node-fetch')

const fetchCategories = async () => {
  const res = await fetch('http://wallpaper.apc.360.cn/index.php?c=WallPaper&a=getAllCategories')
    .then((resp) => {
      const data = resp.json()
      if (resp.ok) return data
      return Promise.reject(data)
    })
  await Promise.allSettled(
    Object.values(res.data).map((c) =>
      fetchListByCategory({ cid: c.id, count: 1 })
        .then((w) => c.cover = w.data[0]?.url)
    )
  )
  return res
}

const fetchListByCategory = async (data) => {
  const base = 'http://wallpaper.apc.360.cn/index.php?c=WallPaper&a=getAppsByCategory'
  const params = new URLSearchParams(data)
  const url = `${base}&${params}`
  return fetch(url)
    .then((resp) => {
      const data = resp.json()
      if (resp.ok) return data
      return Promise.reject(data)
    })
}

exports.main = async (event, context) => {
  const { action, payload } = event
  return { fetchCategories, fetchListByCategory }[action]?.(payload)
}


================================================
FILE: cloudfunctions/wallpaper/package.json
================================================
{
  "name": "wallpaper",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "node-fetch": "~2.6.12",
    "wx-server-sdk": "~2.6.3"
  }
}

================================================
FILE: cloudfunctions/wxacode/config.json
================================================
{
  "permissions": {
    "openapi": [
      "wxacode.getUnlimited",
      "wxacode.get"
    ]
  }
}

================================================
FILE: cloudfunctions/wxacode/index.js
================================================
// 云函数入口文件
const cloud = require('wx-server-sdk');

cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
});

// 云函数入口函数
exports.main = async (event, context) => {
  const { contentType, buffer } = await cloud.openapi.wxacode.get({
    path: 'pages/tabs/discovery/discovery'
  });
  const base64 = buffer.toString('base64');
  return `data:${contentType};base64,${base64}`;
}

================================================
FILE: cloudfunctions/wxacode/package.json
================================================
{
  "name": "wxacode",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "wx-server-sdk": "~2.2.0"
  }
}


================================================
FILE: docs/云开发环境初始化.md
================================================
# 云开发环境初始化

## 创建环境

登录腾讯云[云开发控制台](https://console.cloud.tencent.com/tcb/platform/env)

在环境管理创建一个环境,如果已有环境可跳过这一步

可以在网页端的控制台创建环境也可在微信开发者工具的云开发面板创建环境

![CloudBase Env](./screenshots/cloudbase_env.png)

## 部署云函数

可以使用微信开发者工具部署,也可以使用 CloudBase CLI 部署

微信开发者工具只能逐个部署,但稳

CloudBase CLI 可以批量部署,但可能会出现异常

### 使用微信开发者工具部署

右键项目根目录下 `/cloudfunctions/` 目录选择当前环境

![](./screenshots/function_env.png)

选择 `/cloudfunctions/` 目录下的一级目录创建并部署云函数

![](./screenshots/function_deploy.png)

### 使用 CloudBase CLI 部署

将项目根目录下的 `/cloudbaserc.json` 中的 `envId` 和 `region` 改为自己的环境 id 和地域

| 地域 | 全称 | 缩写 |
| :--- | :--- | :--- |
| 上海 | `ap-shanghai` | `sh` |
| 广州 | `ap-guangzhou` | `gz` |
| 北京 | `ap-beijing` | `bj` |

```json
{
  "envId": "release-5g2g137xcedfade7",
  "region": "sh"
}
```

全局安装 CloudBase CLI。可选择你喜欢的任意包管理工具,这里使用 pnpm 作为示例

```shell
pnpm add -g @cloudbase/cli
tcb -v
```

执行如下命令通过网页授权登录

```shell
tcb login
```

部署云函数

```shell
tcb fn deploy
```

云函数部署成功后微信开发者工具会显示为绿色

![云函数部署成功示意图](./screenshots/function_deployed.png)

## 初始化集合数据

终端进入 `/cloudfunctions/initdb/` 安装依赖

```shell
pnpm i
```

在微信开发者工具右键目录 `/cloudfunctions/initdb/` 开启云函数本地调试,手动调用一次云函数 `initdb` 即可

================================================
FILE: jest.config.js
================================================
/*
 * For a detailed explanation regarding each configuration property and type check, visit:
 * https://jestjs.io/docs/configuration
 */

export default {
  // All imported modules in your tests should be mocked automatically
  // automock: false,

  // Stop running tests after `n` failures
  // bail: 0,

  // The directory where Jest should store its cached dependency information
  // cacheDirectory: "/private/var/folders/kr/f89s8xwj7vj75cn5rhpj3hq00000gn/T/jest_dx",

  // Automatically clear mock calls, instances, contexts and results before every test
  clearMocks: true,

  // Indicates whether the coverage information should be collected while executing the test
  collectCoverage: true,

  // An array of glob patterns indicating a set of files for which coverage information should be collected
  // collectCoverageFrom: undefined,

  // The directory where Jest should output its coverage files
  coverageDirectory: "coverage",

  // An array of regexp pattern strings used to skip coverage collection
  // coveragePathIgnorePatterns: [
  //   "/node_modules/"
  // ],

  // Indicates which provider should be used to instrument code for coverage
  // coverageProvider: "babel",

  // A list of reporter names that Jest uses when writing coverage reports
  // coverageReporters: [
  //   "json",
  //   "text",
  //   "lcov",
  //   "clover"
  // ],

  // An object that configures minimum threshold enforcement for coverage results
  // coverageThreshold: undefined,

  // A path to a custom dependency extractor
  // dependencyExtractor: undefined,

  // Make calling deprecated APIs throw helpful error messages
  // errorOnDeprecated: false,

  // The default configuration for fake timers
  // fakeTimers: {
  //   "enableGlobally": false
  // },

  // Force coverage collection from ignored files using an array of glob patterns
  // forceCoverageMatch: [],

  // A path to a module which exports an async function that is triggered once before all test suites
  // globalSetup: undefined,

  // A path to a module which exports an async function that is triggered once after all test suites
  // globalTeardown: undefined,

  // A set of global variables that need to be available in all test environments
  // globals: {},

  // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
  // maxWorkers: "50%",

  // An array of directory names to be searched recursively up from the requiring module's location
  // moduleDirectories: [
  //   "node_modules"
  // ],

  // An array of file extensions your modules use
  // moduleFileExtensions: [
  //   "js",
  //   "mjs",
  //   "cjs",
  //   "jsx",
  //   "ts",
  //   "tsx",
  //   "json",
  //   "node"
  // ],

  // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
  // moduleNameMapper: {},

  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
  // modulePathIgnorePatterns: [],

  // Activates notifications for test results
  // notify: false,

  // An enum that specifies notification mode. Requires { notify: true }
  // notifyMode: "failure-change",

  // A preset that is used as a base for Jest's configuration
  // preset: undefined,

  // Run tests from one or more projects
  // projects: undefined,

  // Use this configuration option to add custom reporters to Jest
  // reporters: undefined,

  // Automatically reset mock state before every test
  // resetMocks: false,

  // Reset the module registry before running each individual test
  // resetModules: false,

  // A path to a custom resolver
  // resolver: undefined,

  // Automatically restore mock state and implementation before every test
  // restoreMocks: false,

  // The root directory that Jest should scan for tests and modules within
  // rootDir: undefined,

  // A list of paths to directories that Jest should use to search for files in
  // roots: [
  //   "<rootDir>"
  // ],

  // Allows you to use a custom runner instead of Jest's default test runner
  // runner: "jest-runner",

  // The paths to modules that run some code to configure or set up the testing environment before each test
  // setupFiles: [],

  // A list of paths to modules that run some code to configure or set up the testing framework before each test
  // setupFilesAfterEnv: [],

  // The number of seconds after which a test is considered as slow and reported as such in the results.
  // slowTestThreshold: 5,

  // A list of paths to snapshot serializer modules Jest should use for snapshot testing
  // snapshotSerializers: [],

  // The test environment that will be used for testing
  // testEnvironment: "jest-environment-node",

  // Options that will be passed to the testEnvironment
  // testEnvironmentOptions: {},

  // Adds a location field to test results
  // testLocationInResults: false,

  // The glob patterns Jest uses to detect test files
  testMatch: [
    // "**/__tests__/**/*.[jt]s?(x)",
    // "**/?(*.)+(spec|test).[tj]s?(x)"
    "**/*.spec.js"
  ],

  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
  // testPathIgnorePatterns: [
  //   "/node_modules/"
  // ],

  // The regexp pattern or array of patterns that Jest uses to detect test files
  // testRegex: [],

  // This option allows the use of a custom results processor
  // testResultsProcessor: undefined,

  // This option allows use of a custom test runner
  // testRunner: "jest-circus/runner",

  // A map from regular expressions to paths to transformers
  transform: {},

  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
  // transformIgnorePatterns: [
  //   "/node_modules/",
  //   "\\.pnp\\.[^\\/]+$"
  // ],

  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
  // unmockedModulePathPatterns: undefined,

  // Indicates whether each individual test should be reported during the run
  // verbose: undefined,

  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
  // watchPathIgnorePatterns: [],

  // Whether to use watchman for file crawling
  // watchman: true,
};


================================================
FILE: miniprogram/apis/cloud/index.js
================================================
const db = wx.cloud.database();

export const getBanners = () => {
  return db.collection('banners')
    .orderBy('id', 'desc')
    .limit(4)
    .get()
    .then(({ data }) => data);
};


================================================
FILE: miniprogram/apis/douban/accounts.js
================================================
import { request as baseRequest } from '../../utils/request';
import wxCloud from '../../utils/wxCloud';

/** 豆瓣小程序 AppID */
const AppID = 'wx2f9b06c1de1ccfca';

/**
 * @template T
 * @param {RequestOption<Douban.APIResponseLegacy<T>>} config 
 */
const request = async (config) => {
  const { header, ...rest } = config;
  const _config = {
    baseURL: 'https://accounts.douban.com/j/wxa',
    header: {
      'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
      ...header,
    },
    ...rest,
  };

  /** @type {RequestSuccessResult<Douban.APIResponseLegacy<T>>} */
  const resp = await baseRequest(_config);
  if (resp.ok && resp.data.status === 'success') {
    return resp.data;
  }

  return Promise.reject(resp.data);
};

/**
 * 登录
 * @param {object} params
 * @param {string} params.name 用户名
 * @param {string} params.password 密码
 * @param {string} [params.appid]
 * @param {string} [params.phone]
 * @param {string} [params.captcha_id]
 * @param {string} [params.captcha_solution]
 * @param {string} [params.ticket]
 * @param {string} [params.randstr]
 * @returns 
 */
export const login = async (params) => {
  /** @type {Douban.APIResponseLegacy<Douban.LoginSuccessResult>} */
  const res = await request({
    url: '/login/basic',
    method: 'POST',
    data: { appid: AppID, ...params },
  });
  wxCloud('douban', {
    action: 'login',
    payload: {
      access_token: res.payload.access_token,
      refresh_token: res.payload.refresh_token,
      expires_in: res.payload.expires_in,
      ...res.payload.account_info,
    }
  });

  return res;
}

/**
 * 获取手机验证码
 * @param {object} params
 * @param {string} [params.area_code] 区域号,如:"+86"
 * @param {number} params.number 手机号
 */
export const getCaptcha = async (params) => {
  params = {
    appid: AppID,
    area_code: '+86',
    ...params,
  }
  const res = await request({
    url: '/login/request_phone_code',
    method: 'POST',
    data: params,
  });

  return res;
}


/**
 * 验证验证码
 * @param {object} params
 * @param {string} params.number
 * @param {string} params.code
 * @returns
 */
 export const verifyCaptcha = async (params) => {
  params = {
    area_code: '+86',
    appid: AppID,
    ...params,
  };
  /** @type {Douban.APIResponseLegacy<Douban.LoginSuccessResult>} */
  const res = await request({
    url: '/login/verify_phone_code',
    method: 'POST',
    data: params,
  });

  return res.payload;
};


================================================
FILE: miniprogram/apis/douban/request.js
================================================
import wxCloud from '../../utils/wxCloud';

export class RequestController {
  /** @type {WechatMiniprogram.RequestTask} */
  task;

  constructor() {
    return new Proxy(this, {
      get(target, prop) {
        if (Object.prototype.hasOwnProperty.call(target, prop)) {
          return Reflect.get(target, prop);
        } else {
          return target.task[prop];
        }
      }
    });
  }
}

/**
 * @template T
 * @param {RequestOption<T>} config
 * @returns {Promise<RequestSuccessResult<T>}
 * @example
 *
 * ```
 * const controller = new RequestController();
 * request({
 *   ...config,
 *   controller
 * });
 * // 中断请求任务
 * controller.abort();
 * ```
 */
export const request = ({ baseURL, controller, ...config }) => {
  return new Promise((resolve, reject) => {
    const requestTask = wx.request(
      /** @type {WechatMiniprogram.RequestOption<T>} */
      ({
        ...config,
        url: `${baseURL}${config.url}`,
        success: (res) => {
          res.ok = res.statusCode >= 200 && res.statusCode < 300;
          resolve(res);
        },
        fail: (err) => reject(err),
      })
    );
    if (controller) {
      controller.task = requestTask;
    }
  });
};

/**
 * @template T
 * @param {*} options 
 * @returns {Promise<T>}
 */
export const cloudFetch = (options) => {
  return wxCloud('douban', {
    action: 'fetch',
    payload: options,
  });
};

================================================
FILE: miniprogram/apis/douban.js
================================================
/**
 * @file 豆瓣 API
 */
import { request as baseRequest } from '../utils/request';
import { store } from '../store/index';

/**
 * @type {{ params: RequestOption<any>, resolve: (data: any) => void, reject: (err: any) => void }[]}
 */
const queue = [];

export let isLoginIng = false;
/**
 * @param {boolean} value
 */
export const setLoginIng = (value) => {
  isLoginIng = value;
};

const BASE_URL = 'https://mmovie.imarkr.com/douban/api';

/**
 * @template T
 * @param {RequestOption<T>} params
 * @returns {Promise<T>}
 */
const request = (params) => {
  const accessToken = store.douban.accessToken;
  const { header, notAuthorization, ...rest } = {
    baseURL: BASE_URL,
    ...params,
  };

  return new Promise((resolve, reject) => {
    baseRequest({
      header: {
        ...(!notAuthorization && accessToken && { Authorization: `Bearer ${accessToken}` }),
        ...header,
      },
      ...rest,
    })
      .then((resp) => {
        if (resp.ok) {
          resolve(resp.data);
        } else if (resp.statusCode === 400 && [103, 106].includes(resp.data.code)) {
          queue.push({ params, resolve, reject });
          if (!isLoginIng) {
            isLoginIng = true;
            wx.navigateTo({
              url: '/packages/douban/pages/login-phone/login-phone'
            });
          }
        } else {
          reject(resp.data);
        }
      });
  });
};

/** 登录成功后重放登录失效接口 */
export const replayRequest = () => {
  while (queue.length) {
    const { params, resolve, reject } = queue.shift();
    request(params).then(resolve, reject);
  }
};

/**
 * 搜索
 * @param {object} params
 * @param {string} params.q 关键字
 * @param {number} params.start 起始位置
 * @param {number} params.count 查询数量
 * @returns {Promise<DouBan.SearchResult>}
 */
export const search = (params) => {
  return request({
    url: '/search/weixin',
    data: params
  });
};

/**
 *  影视详情
 * @param {object} params
 * @param {string} params.id
 * @param {'movie'|'tv'} [params.type]
 * @returns {Promise<DouBan.MovieDetail>}
 */
export const getDetail = (params) => {
  const { id, type = 'movie' } = params;
  return request({
    url: `/${type}/${id}`
  });
};

/**
 * 影人列表
 * @param {object} params
 * @param {string} params.id
 * @param {'movie'|'tv'} [params.type]
 * @returns {Promise<{
 *   actors: DouBan.Actor[];
 *   directors: DouBan.Actor[];
 *   total: number;
 * }>}
 */
export const getCelebrities = (params) => {
  const { id, type = 'movie' } = params;
  return request({
    url: `/${type}/${id}/celebrities`
  });
};

/**
 * 短评列表
 * @param {object} params
 * @param {string} params.id
 * @param {number} params.start
 * @param {number} params.count
 * @param {'done'} [params.status]
 * @param {'movie'|'tv'} [params.type = 'movie']
 * @returns {Promise<DouBan.InterestResult>}
 */
export const getInterests = (params) => {
  const { id, type = 'movie', ...data } = params;
  return request({
    url: `/${type}/${id}/interests`,
    data
  });
};

/**
 * 剧照
 * @param {object} params
 * @param {string} params.id
 * @param {number} params.start
 * @param {number} params.count
 * @param {'movie'|'tv'} [params.type = 'movie']
 * @returns {Promise<DouBan.PhotosResult>}
 */
export const getPhotos = (params) => {
  const { id, type = 'movie', ...data } = params;
  return request({
    url: `/${type}/${id}/photos`,
    data
  });
};

/**
 * 预告片列表
 * @param {object} params
 * @param {string} params.id
 * @returns {Promise<DouBan.TrailersResult>}
 */
export const getTrailers = (params) => {
  return request({
    url: `/movie/${params.id}/trailers`
  });
};

/**
 * 豆瓣热门
 * @param {object} params
 * @param {number} [params.start]
 * @param {number} [params.count]
 * @returns {Promise<{
 *   count: number;
 *   subject_collection_items: DouBan.MovieItem[];
 *   total: number;
 *   start: number;
 * }>}
 */
export const getHotMovies = (params) => {
  return request({
    url: '/subject_collection/movie_hot_gaia/items',
    data: params,
    notAuthorization: true
  });
}

/**
 * 榜单合集
 * @param {object} params
 * @param {string} params.type
 * @param {number} [params.start]
 * @param {number} [params.count]
 * @returns {Promise<Douban.SubjectCollectionItemsResult>}
 */
export const getCollectionList = (params) => {
  const { type, ...data } = params;
  return request({
    url: `/subject_collection/${type}/items`,
    data
  });
}

/**
 * 影院热映
 * @param {object} params
 * @param {number} [params.start]
 * @param {number} [params.count]
 * @returns {Promise<Douban.SubjectCollectionShowingItemsResult>}
 */
export const getShowingMovies = (params) => {
  return request({
    url: '/subject_collection/movie_showing/items',
    data: params
  });
}

/**
 * 即将上映
 * @param {object} params
 * @param {number} [params.start]
 * @param {number} [params.count]
 * @returns {Promise<Douban.SubjectCollectionSoonItemsResult>}
 */
export const getSoonMovies = (params) => {
  return request({
    url: '/subject_collection/movie_soon/items',
    data: params
  });
}

/**
 * 获取用户的书影音
 * @param {string} userID
 * @param {object} params
 * @param {'movie'} params.type
 * @param {'doing'} params.status
 * @param {number} [params.start]
 * @param {number} [params.count]
 * @returns {Promise<Douban.UserInterestsResult>}
 */
export const getUserInterests = (userID, params) => {
  return request({
    url: `/user/${userID}/interests`,
    data: params
  });
}

/**
 * 标记影视为想看
 * @param {object} params
 * @param {string} params.movieID
 * @param {DouBan.SubjectType} [params.type = 'movie']
 * @param {number} [params.rating]
 * @param {0|1} [params.sync_douban]
 * @returns {Promise<DouBan.Interest>}
 */
export const markMovie = (params) => {
  const { movieID, type = 'movie', ...rest } = params;
  const data = Object.assign({}, { raing: 0, sync_douban: 0 }, rest);
  return request({
    url: `/${type}/${movieID}/mark`,
    data
  });
}

/**
 * 删除影视标记
 * @param {object} params
 * @param {string} params.movieID
 * @param {DouBan.SubjectType} [params.type = 'movie']
 * @returns {Promise<{
 *   comment: string;
 *   status: DouBan.InterestStatus;
 *   id: string;
 * }>}
 */
export const unmarkMovie = (params) => {
  return request({
    url: `/${params.type || 'movie'}/${params.movieID}/unmark`,
  });
}

/**
 * 标记影视为已看
 * @param {object} params
 * @param {string} params.movieID
 * @param {'movie'|'tv'} [params.type = 'movie']
 * @param {number} [params.rating]
 * @param {string} [params.comment]
 * @param {0|1} [params.sync_douban]
 * @returns {Promise<DouBan.Interest>}
 */
export const doneMovie = (params) => {
  const { movieID, type = 'movie', ...rest } = params;
  const data = Object.assign({},
    {
      rating: 0,
      sync_douban: 0
    },
    rest
  );
  return request({
    url: `/${type}/${movieID}/done`,
    data
  });
}

/**
 * 标记影视为已看
 * @param {object} params
 * @param {string} params.movieID
 * @param {DouBan.SubjectType} [params.type = 'movie']
 * @param {number} [params.rating]
 * @param {string} [params.comment]
 * @param {0|1} [params.sync_douban]
 * @returns {Promise<DouBan.Interest>}
 */
 export const doingMovie = (params) => {
  const { movieID, type = 'movie', ...rest } = params;
  const data = Object.assign({},
    {
      rating: 0,
      sync_douban: 0
    },
    rest
  );
  return request({
    url: `/${type}/${movieID}/doing`,
    data
  });
}


================================================
FILE: miniprogram/apis/github.js
================================================
/**
 * [GitHub API v3](https://docs.github.com/en/rest)
 */
import { store } from '../store/index';

/**
 * 
 * @param {WechatMiniprogram.RequestOption} options 
 * @returns {Promise<WechatMiniprogram.RequestSuccessCallbackResult['data']>} result
 */
const request = (options) => {
  const { header, method, url, data, ...restOpt } = options;
  const token = store.user.info?.githubToken;

  if (Object.prototype.toString.call(data) === '[object Object]') {
    Object.keys(data).forEach((key) => {
      const v = data[key];
      if (!v && v !== 0 && v !== false) {
        delete data[key];
      }
    });
  }

  return new Promise((resolve, reject) => {
    wx.request({
      header: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: token ? `token ${token}` : undefined,
        ...header
      },
      method: method || 'GET',
      url: `https://api.github.com${url}`,
      success: ({ statusCode, data }) => {
        if (statusCode >= 200 && statusCode < 300) {
          resolve(data);
        } else {
          reject({
            ...data,
            message: data.message || '服务器开小差了'
          });
        }
      },
      fail: (err) => {
        reject(err);
      },
      data,
      ...restOpt
    })
  });
};

/**
 * [Get a repository](https://docs.github.com/en/rest/reference/repos#get-a-repository)
 * 
 * @param {Object} params
 * @param {string} params.owner
 * @param {string} params.repo
 */
export const getRepoInfo = (params) => {
  return request({
    method: 'GET',
    url: `/repos/${params.owner}/${params.repo}`
  });
};

/**
 * [Get repository README](https://docs.github.com/en/rest/reference/repos#get-a-repository-readme)
 * 
 * @param {object} params 
 * @param {'raw'|'html'} [params.media] [Custom media types for repository contents](https://docs.github.com/en/rest/reference/repos#custom-media-types-for-repository-contents)
 * @param {string} params.owner
 * @param {string} params.repo
 * @param {string} [params.ref]
 */
export const getRepoReadme = (params) => {
  const { owner, repo, ...query } = params;
  return request({
    header: {
      Accept: params.media ? `application/vnd.github.v3.${params.media}` : `application/vnd.github.v3+json`
    },
    method: 'GET',
    url: `/repos/${owner}/${repo}/readme`,
    data: query
  });
};

/**
 * 
 * @param {object} params 
 * @param {string} params.username
 * @param {number} params.per_page
 * @param {number} params.page 
 */
export const getEvents = (params) => {
  const { username, ...reset } = params;
  return request({
    method: 'GET',
    url: `/users/${username}/received_events`,
    data: reset
  });
};

/**
 * [List notifications for the authenticated user](https://docs.github.com/en/rest/reference/activity#list-notifications-for-the-authenticated-user)
 * 
 * @param {object} params 
 * @param {boolean} [params.all]
 * @param {boolean} [params.participating]
 * @param {string} [params.since] a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ
 * @param {string} [params.before]
 * @param {number} [params.per_page]
 * @param {number} [params.page]
 */
export const getNotifications = (params) => {
  return request({
    method: 'GET',
    url: '/notifications',
    data: params
  });
};

/**
 * https://docs.github.com/en/rest/reference/search#constructing-a-search-query
 * 
 * @typedef {object} Query
 * @property {string} q
 * @property {string} sort
 * @property {number} per_page
 * @property {number} page
 */

/**
 * [Search repositories](https://docs.github.com/en/rest/reference/search#search-repositories)
 * 
 * @param {Query} params
 * @returns {Promise<{
 * total_count: number;
 * incomplete_results: boolean;
 * items: Array<{
 *   id: number;
 *   name: string;
 *   full_name: string;
 * }>;
 * owner: User;
 * private: boolean;
 * created_at: string;
 * updated_at: string;
 * pushed_at: string;
 * homepage: string;
 * stargazers_count: number;
 * watchers_count: number;
 * language: string;
 * forks_count: number;
 * default_branch: string;
 * archived: boolean;
 * disabled: boolean;
 * }>}
 */
export const searchRepositories = (params) => {
  return request({
    header: {
      Accept: 'application/vnd.github.v3.text-match+json'
    },
    method: 'GET',
    url: '/search/repositories',
    data: params
  });
};

/**
 * [Search users](https://docs.github.com/en/rest/reference/search#search-users)
 * 
 * @param {Query} params 
 */
export const searchUser = (params) => {
  return request({
    header: {
      Accept: 'application/vnd.github.v3.text-match+json'
    },
    method: 'GET',
    url: '/search/users',
    data: params
  });
};

/**
 * 获取用户 Star repositories
 * 
 * [List repositories starred by the authenticated user](https://docs.github.com/en/rest/reference/activity#list-repositories-starred-by-the-authenticated-user)
 * 
 * @param {object} params
 * @param {'created'|'updated'} params.sort
 * @param {'asc'|'desc'} params.direction
 * @param {number} params.per_page max 100
 * @param {number} params.page
 */
export const getStarredList = (params) => {
  return request({
    url: '/user/starred',
    method: 'GET',
    data: params
  });
};

/**
 * 获取已授权的用户信息
 * 
 * [Get the authenticated user](https://docs.github.com/en/rest/reference/users#get-the-authenticated-user)
 */
export const getSelfInfo = () => {
  return request({
    url: '/user',
    method: 'GET'
  });
}

/**
 * @typedef {{
 * login: string;
 * id: number;
 * avatar_url: string;
 * }} User
 */

================================================
FILE: miniprogram/apis/leancloud/index.js
================================================
import * as AV from '../../libs/av-live-query-core-min';

/**
 * @returns {Promise<any[]>}
 */
export const getBanners = async () => {
  const query = new AV.Query('Banner');
  const data = await query.find();
  return data.map((item) => {
    const { attributes, ...cols } = item;
    return { ...cols, ... attributes };
  });
};


================================================
FILE: miniprogram/apis/server/index.js
================================================
import { request } from '../../utils/request'

/**
 * @param {RequestOption} config
 */
const fetch = async (config) => {
  config.baseURL = 'https://sanphantom.com/go'
  const resp = await request(config)
  if (resp.ok) {
    return resp
  }
  return Promise.reject(resp.data)
}

export const apiGetBanners = () => {
  return fetch({ url: '/banners' })
}

export const apiGetHotMovies = (data) => {
  return fetch({ url: '/douban/hot/items', data })
}

export const apiGetShowingMovies = (data) => {
  return fetch({ url: '/douban/showing/items', data })
}

export const apiGetSoonMovies = (data) => {
  return fetch({ url: '/douban/soon/items', data })
}

export const apiGetDetail = (params) => {
  const { id, type = 'movie' } = params
  return fetch({ url: `/douban/${type}/${id}` })
}

export const apiGetCelebrities = (params) => {
  const { id, type = 'movie' } = params
  return fetch({ url: `/douban/${type}/${id}/celebrities` })
}

export const apiSearch = (data) => {
  return fetch({ url: '/douban/search', data })
}

export const apiGetPhotos = (params) => {
  const { id, type = 'movie', ...data } = params
  return fetch({ url: `/douban/${type}/${id}/photos`, data })
}

export const apiGetInterests = (params) => {
  const { id, type = 'movie', ...data } = params
  return fetch({ url: `/douban/${type}/${id}/interests`, data })
}

export const apiGetUserInterests = (userID, data) => {
  return fetch({ url: `/douban/user/${userID}/interests`, data })
}

export const apiGetCaptcha = (data) => {
  return fetch({
    url: '/douban/login/request_phone_code',
    method: 'POST',
    data
  })
}

export const apiVerifyCaptcha = (data) => {
  return fetch({
    url: '/douban/login/verify_phone_code',
    method: 'POST',
    data
  })
}


================================================
FILE: miniprogram/apis/vercel.js
================================================
import { request } from '../utils/request'
import { decrypt } from '../utils/crypro';

/**
 * @param {RequestOption} config
 */
const fetch = async (config) => {
  config.baseURL = 'https://mmovie.imarkr.com'
  const resp = await request(config)
  if (resp.ok) {
    return resp.data
  }
  return Promise.reject(resp.data)
}

export const apiAppInfo = async () => {
  const { data } = await fetch({ url: '/wx/app' });
  return JSON.parse(decrypt(data));
};

/**
 * 验证登录验证码(登录)
 * @param {object} data
 * @param {string} data.number 手机号
 * @param {string} data.code 验证码
 * @param {string} data.openid
 * @param {string} data.unionid
 * @param {string} [data.captcha_id]
 * @param {string} [data.captcha_solution]
 */
export const apiVerifyCaptcha = (data) => {
  return fetch({
    url: '/douban/verify_phone_code',
    method: 'POST',
    data
  });
};

export const apiSyncDouban = (data) => {
  return fetch({
    url: '/douban/sync',
    method: 'POST',
    data
  });
};

/**
 * @param {object} data
 * @param {string} data.code
 */
export const apiWxLogin = (data) => {
  return fetch({ url: '/wx/login', data })
};

export const apiGetBanners = () => {
  return fetch({ url: '/banners' })
}

export const apiGetCards = () => {
  return fetch({ url: '/m/cards', method: 'POST' });
};

export const apiSubmitPages = (data) => {
  return fetch({
    url: '/wx/submitpages',
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    data
  })
}


================================================
FILE: miniprogram/app.js
================================================
import { store } from './store/index';
import { compareVersions, isEmpty } from './utils/util';
import { apiAppInfo, apiWxLogin } from './apis/vercel';

wx.cloud.init({
  traceUser: true,
  env: 'dev-oucwt'
});

App({

  globalData: {
    userInfo: null,
    setting: {},
    config: null
  },

  onLaunch () {
    this.getSetting();
    this.getDefaultConfig();
    this.login();
  },

  /** 通过云函数直接登录 */
  async login () {
    const { code } = await new Promise((resolve, reject) => {
      wx.login({ success: resolve, fail: reject });
    });
    const data = await apiWxLogin({ code });
    this.globalData.userInfo = data;
    store['user/updateUserInfo'](data);
    if (data.access_token) {
      const { access_token, refresh_token, ...user } = data;
      store['douban/update']({
        accessToken: access_token,
        refreshToken: refresh_token,
        user
      });
    }
    if (data.douban) {
      const { access_token, refresh_token, ...user } = data.douban;
      store['douban/update']({
        accessToken: access_token,
        refreshToken: refresh_token,
        user
      });
    }
  },

  /**
   * 获取用户信息
   * 支持 callback 和 Promise
   * @param {function} cb (object:userInfo) => void
   */
  getUserInfo (cb) {
    return new Promise((resolve, reject) => {
      if (this.globalData.userInfo) {
        typeof cb === 'function' && cb(this.globalData.userInfo);
        resolve(this.globalData.userInfo);
      } else {
        wx.login({
          success: () => {
            wx.getUserInfo({
              success: res => {
                this.globalData.userInfo = res.userInfo
                typeof cb === 'function' && cb(this.globalData.userInfo)
                resolve(this.globalData.userInfo)
              }
            });
          }
        });
      }
    });
  },

  /** 从服务器获取默认配置 */
  async getDefaultConfig () {
    const appInfo = await apiAppInfo();
    store['app/update']({
      hasPublished: compareVersions(store.app.version, appInfo.version) <= 0,
      ...appInfo
    });
  },

  /** 退出登录 */
  logout (callback) {
    this.globalData.userInfo = null;
    callback && callback(this.globalData);
  },

  /** 获取本地设置 */
  getSetting (callback) {
    const { setting } = this.globalData;
    if (setting && (!isEmpty(setting))) {
      typeof callback == "function" && callback(setting);
    } else {
      wx.getStorage({
        key: 'setting',
        success: res => {
          this.globalData.setting = res.data;
          typeof callback == "function" && callback(res.data);
        }
      });
    }
  }
})


================================================
FILE: miniprogram/app.json
================================================
{
  "pages": [
    "pages/splash/splash",
    "pages/tabs/discovery/discovery",
    "pages/tabs/index/index",
    "pages/tabs/movies/movies",
    "pages/setting/setting",
    "pages/marked/marked",
    "pages/search/search",
    "pages/about/about",
    "pages/first/first",
    "pages/webview/index",
    "pages/test/test",
    "packages/wallpaper/pages/categories/categories"
  ],
  "window": {
    "backgroundTextStyle": "dark",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "iMark",
    "navigationBarTextStyle": "black",
    "backgroundColor": "#f7f7f7"
  },
  "tabBar": {
    "custom": true,
    "color": "#496069",
    "selectedColor": "#000",
    "backgroundColor": "#fff",
    "borderStyle": "black",
    "list": [
      {
        "pagePath": "pages/tabs/discovery/discovery",
        "text": "发现",
        "iconPath": "assets/images/tabbar/tabbar_discovery.png",
        "selectedIconPath": "assets/images/tabbar/tabbar_discovery_selected.png"
      },
      {
        "pagePath": "pages/tabs/movies/movies",
        "text": "清单",
        "iconPath": "assets/images/tabbar/tabbar_list.png",
        "selectedIconPath": "assets/images/tabbar/tabbar_list_selected.png"
      },
      {
        "pagePath": "pages/tabs/index/index",
        "text": "我的",
        "iconPath": "assets/images/tabbar/tabbar_mine.png",
        "selectedIconPath": "assets/images/tabbar/tabbar_mine_selected.png"
      }
    ]
  },
  "subPackages": [
    {
      "root": "packages/article/",
      "pages": [
        "pages/categories/categories",
        "pages/details/detail",
        "pages/movie-list-detail/movie-list-detail"
      ]
    },
    {
      "root": "packages/movie/",
      "pages": [
        "pages/cards/card",
        "pages/details/details",
        "pages/intheaters/in_theaters",
        "pages/trailers/trailers",
        "pages/photos/photos",
        "pages/mark/mark"
      ]
    },
    {
      "root": "packages/user/",
      "pages": [
        "pages/favMovieList/index",
        "pages/evaluate/evaluate",
        "pages/favCards/index",
        "pages/userinfo/userinfo",
        "pages/achievement/achievement"
      ]
    },
    {
      "root": "packages/github/",
      "pages": [
        "pages/home/home",
        "pages/notifications/notifications",
        "pages/starred/starred",
        "pages/trending/trending",
        "pages/languages/languages",
        "pages/repository/repository",
        "pages/search/search"
      ]
    },
    {
      "root": "packages/douban/",
      "pages": [
        "pages/login-phone/login-phone",
        "pages/collection/collection"
      ]
    },
    {
      "root": "packages/example/",
      "pages": [
        "pages/index/index",
        "pages/cover-page/cover-page",
        "pages/tab-bar/tab-bar",
        "pages/waterfall/waterfall",
        "pages/icons/icons"
      ]
    },
    {
      "root": "packages/admin/",
      "pages": [
        "pages/index/index",
        "pages/app/app",
        "pages/douban/douban"
      ]
    },
    {
      "root": "packages/tools",
      "pages": [
        "pages/index/index",
        "pages/encode/encode",
        "pages/random/random"
      ]
    }
  ],
  "preloadRule": {
    "pages/tabs/discovery/discovery": {
      "network": "all",
      "packages": ["packages/article/", "packages/movie/"]
    },
    "pages/tabs/index/index": {
      "network": "all",
      "packages": ["packages/github/"]
    },
    "packages/article/pages/categories/categories": {
      "network": "all",
      "packages": ["packages/douban/"]
    }
  },
  "usingComponents": {},
  "requiredBackgroundModes": ["audio"],
  "sitemapLocation": "sitemap.json"
}

================================================
FILE: miniprogram/app.wxss
================================================
/**app.wxss**/
@import './style/weui.wxss';
@import '/style/animate.wxss';
@import './style/font-awesome.min.wxss';
@import './style/iconfont.wxss';
@import "/templates/wxParse/wxParse.wxss";
@import '/style/common.wxss';

================================================
FILE: miniprogram/components/Tabs/Tabs.js
================================================
// Tabs
Component({
    options: {
        addGlobalClass: true,
    },
    /**
     * 组件的属性列表
     */
    properties: {
        tabs: {
            type: Array,
            value: [],
        },
    },

    /**
     * 组件的初始数据
     */
    data: {
        currentIndex: 0,
    },

    /**
     * 组件的方法列表
     */
    methods: {
        _handleTabTap(e) {
            const { index } = e.currentTarget.dataset
            const { currentIndex } = this.data
            if (index !== currentIndex) {
                this.setData({
                    currentIndex: index,
                })
                this.triggerEvent('change', { value: index })
            }
            this.triggerEvent('itemtap', { value: index })
        },
    }
})


================================================
FILE: miniprogram/components/Tabs/Tabs.json
================================================
{
    "component": true,
    "usingComponents": {}
}

================================================
FILE: miniprogram/components/Tabs/Tabs.wxml
================================================
<!-- Tabs -->
<view class="tabs">
    <view class="tabs-item {{currentIndex === index && 'is-active'}}" wx:for="{{tabs}}" wx:key="index"
        data-index="{{index}}" bind:tap="_handleTabTap"
    >
        <view class="tabs-item__content">{{item}}</view>
    </view>
</view>


================================================
FILE: miniprogram/components/Tabs/Tabs.wxss
================================================
/* Tabs */
.tabs {
    /* position: fixed; */
    z-index: 99;
    top: 0;
    left: 0;
    right: 0;
    display: flex;
    white-space: nowrap;
    padding: 16rpx 0;
    background: #fff;
}

.tabs .tabs-item {
    display: inline-block;
    flex: 1;
    text-align: center;
}

.tabs-item .tabs-item__content {
    display: inline-block;
    font-size: 36rpx;
    line-height: 1;
    color: #999;
    font-weight: 400;
    position: relative;
    z-index: 1;
    transition: all .3s;
}

.tabs-item.is-active .tabs-item__content {
    color: #333;
    font-size: 42rpx;
}

.tabs-item.is-active .tabs-item__content::before {
    content: '';
    display: block;
    position: absolute;
    z-index: -1;
    height: 8rpx;
    bottom: 0;
    left: 0;
    right: 0;
    background: #FFE200;
}

================================================
FILE: miniprogram/components/article/article.js
================================================
// components/article/article.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    aid: {
      type: String,
      value: ''
    },
    image: {
      type: String,
      value: ''
    },
    title: {
      type: String,
      value: ''
    },
    likeCount: {
      type: Number,
      value: 0
    },
    checked: {
      type: Boolean,
      value: false
    }
  },

  /**
   * 组件的初始数据
   */
  data: {

  },

  /**
   * 组件的方法列表
   */
  methods: {
    onFavChange(e){
      this.triggerEvent('change', {
        checked: e.detail.checked,
      })
    }
  }
})


================================================
FILE: miniprogram/components/article/article.json
================================================
{
  "component": true,
  "usingComponents": {
    "comp-btnFav": "/components/btn-fav/FavButton"
  }
}

================================================
FILE: miniprogram/components/article/article.wxml
================================================
<!--components/article/article.wxml-->
<view class='item-bgimg' style='background-image:url({{image}})'>
  <view class='gradient'>
    <text class='item-title'>{{title}}</text>
  </view>
  <view class='item-count'>
    <comp-btnFav width='1.2em' height='1.2em' checked='{{checked}}' iconChecked='/assets/images/like_checked.png' iconNormal='/assets/images/like_unchecked.png' bindchange='onFavChange' />
    <text>{{likeCount}}</text>
  </view>
</view>

================================================
FILE: miniprogram/components/article/article.wxss
================================================
/* components/article/article.wxss */
.item-bgimg {
  margin: 10rpx;
  height: 360rpx;
  border-radius: 10rpx;
  background-repeat: no-repeat;
  background-size: 100% 100%;
  display: flex;
  align-items: flex-end;
  position: relative;
  overflow: hidden;
}
.item-bgimg .gradient {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: flex-end;
  background: -webkit-gradient(linear, left 0, left 165%, color-stop(0,transparent), color-stop(1,#000000));  
  background: -moz-linear-gradient(top, transparent, #000000 165%);  
}
.item-bgimg .item-title {
  color: #fff;
  padding: 15rpx;
  font-weight: bold;
}
.item-bgimg .item-count {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  top: 0;
  right: 20rpx;
  padding: .5em .3em .3em;
  background-color: rgba(0,0,0,.5);
  border-radius: 0 0 10rpx 10rpx;
  z-index: 2;
}
.item-bgimg .item-count>image {
  width: 1.2em;
  height: 1.2em;
}
.item-bgimg .item-count>text {
  color: #fff;
  font-size: .8em;
}

================================================
FILE: miniprogram/components/btn-fav/FavButton.js
================================================
// @ts-check
Component({
  properties: {
    height: {
      type: String,
      value: '2em'
    },
    width: {
      type: String,
      value: '2em'
    },
    checked: {
      type: Boolean,
      value: false
    },
    iconNormal: {
      type: String,
      value: '/assets/images/icon-unlike.png'
    },
    iconChecked: {
      type: String,
      value: '/assets/images/icon-liked.png'
    },
  },

  data: {},

  methods: {
    _onToggle (e) {
      const { checked } = this.data;
      this.triggerEvent('change', { checked })
    }
  }
})


================================================
FILE: miniprogram/components/btn-fav/FavButton.json
================================================
{
  "component": true,
  "usingComponents": {}
}

================================================
FILE: miniprogram/components/btn-fav/FavButton.wxml
================================================
<view class='wrapper' bind:tap='_onToggle' style='height:{{height}};width:{{width}}'>
  <image class='icon-like' 
    hidden='{{checked}}'
    src='{{checked?iconChecked:iconNormal}}'
    mode='widthFix'
  />
  <image class='icon-like' 
    hidden='{{!checked}}'
    src='{{checked?iconChecked:iconNormal}}'
    mode='widthFix'
  />
</view>


================================================
FILE: miniprogram/components/btn-fav/FavButton.wxss
================================================
/* components/btn-fav/FavButton.wxss */

.wrapper {
  display: flex;
  width: 2em;
  height: 2em;
}

.icon-like {
  width: 100%;
  height: 100%;
  animation: zoomIn .5s ease-in;
}

@keyframes zoomIn {
  from {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }

  5% {
    -webkit-transform: scale3d(1.1, 1.1, 1);
    transform: scale3d(1.1, 1.1, 1);
  }

  12.5% {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }

  20% {
    -webkit-transform: scale3d(0.9, 0.9, 1);
    transform: scale3d(0.9, 0.9, 1);
  }

  25% {
    -webkit-transform: scale3d(1.05, 1.05, 1);
    transform: scale3d(1.05, 1.05, 1);
  }

  32.5% {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }

  100% {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }
}


================================================
FILE: miniprogram/components/cover-page/cover-page.js
================================================
Component({
  options: {
    addGlobalClass: true
  },

  properties: {
    visible: {
      type: Boolean,
      value: false
    },
    title: {
      type: String,
      value: ''
    },
    distance: {
      type: Number,
      value: 60
    }
  },

  methods: {
    /**
     * @param {object} params
     * @param {boolean} params.visible
     */
    setVisible ({ visible }) {
      this.setData({ visible });
    },
  
    show () {
      this.setVisible({ visible: true });
    },
  
    hide () {
      this.setVisible({ visible: false });
    },

    prevent () {
      // avoid warning
    }
  }

});


================================================
FILE: miniprogram/components/cover-page/cover-page.json
================================================
{
  "component": true,
  "usingComponents": {}
}

================================================
FILE: miniprogram/components/cover-page/cover-page.wxml
================================================
<wxs module="wxs" src="./cover-page.wxs" />
<view id="coverPage" class="action-sheet-backdrop" catch:tap="hide" catch:touchmove="prevent">
  <view class="action-sheet-wrapper" catch:tap="prevent"
    data-distance="{{distance}}"
    catch:touchmove="{{wxs.handleModalTouchMove}}"
    bind:touchend="{{wxs.handleModalTouchEnd}}"
    visible="{{visible}}"
    change:visible="{{wxs.observeVisible}}"
  >
    <view class="action-sheet__title" wx:if="{{title}}">{{title}}</view>
    <view class="cover-page__content">
      <scroll-view class="cover-page__scroll" scroll-y>
        <slot />
      </scroll-view>
    </view>
  </view>
</view>


================================================
FILE: miniprogram/components/cover-page/cover-page.wxs
================================================
function handleModalTouchMove (event, ownerInstance) {
  var state = ownerInstance.getState();
  var currentY = event.changedTouches[0].pageY;
  if (state.startY === undefined) {
    // 开始滑动的地方。scroll-view 滚动时并不会触发此函数,也就不会有 startY
    state.startY = currentY;
  }
  var diff = currentY - state.startY;
  event.instance.setStyle({
    transition: 'none',
    transform: 'translate3d(0, ' + (diff < 0 ? 0 : diff) + 'px, 0)'
  });
}

function handleModalTouchEnd (event, ownerInstance) {
  var state = ownerInstance.getState();
  var currentY = event.changedTouches[0].pageY;
  var diff = currentY - state.startY;
  var compModal = ownerInstance.selectComponent('#coverPage');
  var dataset = event.currentTarget.dataset;
  if (diff > dataset.distance) {
    compModal.removeClass('is-show');
    ownerInstance.callMethod('setVisible', { visible: false });
  } else {
    ownerInstance.callMethod('setVisible', { visible: true });
  }
  // 还原 startY,便于下次记录
  state.startY = undefined;
}

function observeVisible (newValue, oldValue, ownerInstance, instance) {
  var compModal = ownerInstance.selectComponent('#coverPage');
  if (newValue) {
    instance.setStyle({
      transform: 'translate3d(0, 0, 0)'
    });
    compModal.addClass('is-show');
  } else {
    instance.setStyle({});
    compModal.removeClass('is-show');
  }
}

module.exports = {
  handleModalTouchMove: handleModalTouchMove,
  handleModalTouchEnd: handleModalTouchEnd,
  observeVisible: observeVisible
}


================================================
FILE: miniprogram/components/cover-page/cover-page.wxss
================================================
.action-sheet-backdrop {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 11;
  width: 100%;
  height: 100%;
  background-color: rgba(0,0,0,.4);
  pointer-events: none;
  opacity: 0;
  transition: opacity .3s;
}
.action-sheet-wrapper {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  width: 100%;
  max-height: 100vh;
  border-radius: 40rpx 40rpx 0 0;
  overflow: hidden;
  transform: translate3d(0, 100%, 0);
  transition: transform .3s;
  display: flex;
  flex-direction: column;
  background-color: #fff;
}
.action-sheet__title {
  background-color: #fff;
  color: #333;
  font-size: 28rpx;
  padding: 26rpx 0;
  text-align: center;
}
.action-sheet-backdrop.is-show {
  pointer-events: auto;
  opacity: 1;
}
.action-sheet-backdrop.is-show .action-sheet-wrapper {
  transform: translate3d(0, 0, 0);
}
.cover-page__content {
  flex: 1;
}
.cover-page__scroll {
  max-height: 80vh;
}


================================================
FILE: miniprogram/components/index-list/components/content/content.js
================================================
Component({
  options: {
    addGlobalClass: true
  },

  properties: {
    data: {
      type: Object,
      value: null
    }
  }
});



================================================
FILE: miniprogram/components/index-list/components/content/content.json
================================================
{
  "usingComponents": {}
}

================================================
FILE: miniprogram/components/index-list/components/content/content.wxml
================================================
<view>{{data.name}}</view>

================================================
FILE: miniprogram/components/index-list/components/content/content.wxss
================================================


================================================
FILE: miniprogram/components/index-list/index-list.js
================================================
const throttle = function(func, wait, options) {
  let context; let args; let result
  let timeout = null
  // 上次执行时间点
  let previous = 0
  if (!options) options = {}
  // 延迟执行函数
  const later = function() {
    // 若设定了开始边界不执行选项,上次执行时间始终为0
    previous = options.leading === false ? 0 : Date.now()
    timeout = null
    result = func.apply(context, args)
    if (!timeout) context = args = null
  }
  return function() {
    const now = Date.now()
    // 首次执行时,如果设定了开始边界不执行选项,将上次执行时间设定为当前时间。
    if (!previous && options.leading === false) previous = now
    // 延迟执行时间间隔
    const remaining = wait - (now - previous)
    context = this
    args = arguments
    // 延迟时间间隔remaining小于等于0,表示上次执行至此所间隔时间已经超过一个时间窗口
    // remaining大于时间窗口wait,表示客户端系统时间被调整过
    if (remaining <= 0 || remaining > wait) {
      clearTimeout(timeout)
      timeout = null
      previous = now
      result = func.apply(context, args)
      if (!timeout) context = args = null
    // 如果延迟执行不存在,且没有设定结尾边界不执行选项
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining)
    }
    return result
  }
}

Component({
  options: {
      addGlobalClass: true,
      pureDataPattern: /^_/ // 指定所有 _ 开头的数据字段为纯数据字段
  },
  properties: {
      list: {
          type: Array,
          value: [],
          observer: function(newVal) {
              if (newVal.length === 0) return
              const data = this.data
              const alphabet = data.list.map(item => ({
                alpha: item.alpha,
                anchor: item.anchor
              }))
              this.setData({
                  alphabet,
                  current: alphabet[0].anchor
              }, () => {
                  this.computedSize()
              })
          }

      },
      vibrated: {
          type: Boolean,
          value: true
      }
      
  },
  data: {
      windowHeight: 612,
      current: 'A',
      intoView: '',
      touching: false,
      alphabet: [],
      _tops: [],
      _anchorItemH: 0,
      _anchorItemW: 0,
      _anchorTop: 0,
      _listUpperBound: 0
  },
  lifetimes: {
      attached() {
          this.__scrollTo = throttle(this._scrollTo, 100, {})
          this.__onScroll = throttle(this._onScroll, 100, {})
          const {windowHeight} = wx.getSystemInfoSync()
          this.setData({windowHeight})
      }
  },
  methods: {
      choose (e) {
          const item = e.currentTarget.dataset.item;
          this.triggerEvent('choose', { item });
      },

      scrollTo(e) {
          this.__scrollTo(e)
      },

      _scrollTo(e) {
          const data = this.data
          const clientY = e.changedTouches[0].clientY
          const index = Math.floor((clientY - data._anchorTop) / data._anchorItemH)
          const current = data.alphabet[index]?.anchor

          if (current && current !== data.current) {
              this.setData({current, intoView: current, touching: true})
              // 振动效果
              if (data.vibrated) wx.vibrateShort()
          }
      },

      computedSize() {
          const data = this.data
           // 计算列表每个区块的高度等信息
          const query = this.createSelectorQuery()
          query.selectAll('.index_list_item').boundingClientRect(rects => {
              const result = rects
              data._tops = result.map(item => item.top)
          }).exec()
          // 计算右侧字母栏小区块的高度等信息
          query.select('.anchor-list').boundingClientRect(rect => {
              data._anchorItemH = rect.height / data.alphabet.length
              data._anchorItemW = rect.width
              data._anchorTop = rect.top
          }).exec()
          // 计算滚动区域的上边界
          query.select('.page-select-index').boundingClientRect(rect => {
              data._listUpperBound = rect.top
          })
      },

      // throttle 的延迟
      removeTouching() {
          setTimeout(() => {
              this.setData({touching: false})
          }, 150)
      },

      onScroll(e) {
          this.__onScroll(e)
      },

      _onScroll(e) {
          const data = this.data
          const {_tops, alphabet} = data
          const scrollTop = e.detail.scrollTop
          let current = ''
          if (scrollTop < _tops[0]) {
              current = alphabet[0].anchor
          } else {
              for (let i = 0, len = _tops.length; i < len - 1; i++) {
                  if (scrollTop >= _tops[i] && scrollTop < _tops[i + 1]) {
                      current = alphabet[i].anchor
                  }
              }
          }
         
          if (!current) current = alphabet[alphabet.length - 1].anchor
          this.setData({current})
      }
  }
})


================================================
FILE: miniprogram/components/index-list/index-list.json
================================================
{
  "component": true,
  "usingComponents": {},
  "componentGenerics": {
    "content": {
      "default": "./components/content/content"
    }
  }
}

================================================
FILE: miniprogram/components/index-list/index-list.wxml
================================================
<scroll-view 
  class="page page-select-index" 
  style="height: {{windowHeight}}px;" 
  enable-back-to-top 
  scroll-into-view="{{intoView}}" 
  scroll-y 
  bindscroll="onScroll"
>
  <view>
    <slot></slot>
  </view>
  <view class="index_list_item" wx:for="{{list}}" wx:for-index="groupIndex"
    wx:key="alpha"
    id="{{item.anchor}}"
  >
    <view class="index-group__title font-size-26 tips-color">{{item.alpha}}</view>
    <view class="index-group__content">
      <view class="index-group__list">
        <block wx:for="{{item.subItems}}" wx:for-item="subItem" wx:for-index="contentIndex"
          wx:key="name"
        >
          <view 
            class="index-group__item thin-border-bottom" 
            hover-class="bg-highlight" 
            data-item="{{subItem}}"
            bindtap="choose">
            <content data="{{subItem}}" indexes="{{[groupIndex, contentIndex]}}" />
          </view>
        </block>
      </view>
    </view>
  </view>
</scroll-view>
<view 
  class="anchor-bar__wrp wx-flex" 
  catchtouchstart='scrollTo' 
  catchtouchmove='scrollTo' 
  catchtouchend='removeTouching'
>
  <view class="anchor-bar wx-flex__item">
    <view class="anchor-list">
      <block wx:for="{{alphabet}}" wx:key="anchor">
        <view class="anchor-item {{current == item.anchor ? ( touching ? 'selected tapped' : 'selected' ): ''}}">
          <view class="anchor-item__inner">{{item.alpha}}</view>
          <view class="anchor-item__pop">{{item.alpha}}</view>
        </view>
      </block>
    </view>
  </view>
</view>


================================================
FILE: miniprogram/components/index-list/index-list.wxss
================================================
.wx-flex{
	display: flex;
	align-items: center;
}
.wx-flex__item{
	flex: 1;
}

.thin-border-bottom{
	position: relative;
}
.thin-border-bottom:after{
	content: "";
	position: absolute;
	left: 0;
	bottom: 0;
	right: 0;
	height: 1px;
	border-bottom: 1px solid #EAEAEA;
	color: #e5e5e5;
	-webkit-transform-origin: 0 0;
	transform-origin: 0 100%;
	transform: scaleY(0.5);
}

.index-group__title{
	padding: 12rpx 24rpx;
	background-color: #ddd;
}
.index-group__content{
	font-size: 0;
}

.index-group__item{
	padding: 30rpx 24rpx;
	font-size: 30rpx;
}
.index-group__item.thin-border-bottom:after{
	left: 24rpx;
}
.index-group__item:last-child::after {
	content: none;
}

.anchor-bar__wrp{
	position: fixed;
	top: 0;
	bottom: 0;
	right: 0;
	width: 60rpx;
	z-index: 999;
}
.anchor-item{
	font-size: 0;
	text-align: center;
	position: relative;
}
.anchor-item__inner{
	line-height: 28rpx;
	height: 28rpx;
	width: 28rpx;
	border-radius: 50%;
	display: inline-block;
	font-size: 20rpx;
	margin: 2rpx 0;
	font-weight: 500;
}
.tapped .anchor-item__pop{
	display: block;
}
.anchor-item__pop{
	position: absolute;
	font-size: 64rpx;
	width: 100rpx;
	height: 100rpx;
	line-height: 100rpx;
	color: #fff;
	background-color: #C9C9C9;
	border-radius: 50%;
	right: 80rpx;
	top: 50%;
	transform: translateY(-50%);
	display: none;
}
.anchor-item__pop:after{
	content: "";
	display: block;
	position: absolute;
	width: 0;
	height: 0;
	left: 80rpx;
	border: 40rpx solid;
	border-color: transparent transparent transparent #C9C9C9;
	top: 50%;
	transform: translateY(-50%);
}
.anchor-item.selected .anchor-item__inner{
	color: #fff;
	background-color: #1aad19;
}

================================================
FILE: miniprogram/components/load-more/load-more.js
================================================
Component({
  options: {
    addGlobalClass: true
  },

  properties: {
    loading: Boolean
  },

  attached() {
    this.observe()
  },

  detached() {
    this.observer && this.observer.disconnect()
  },

  methods: {
    observe() {
      this.observer = this.createIntersectionObserver({
        thresholds: [0],
        initialRatio: 1
      })
      this.observer
        .relativeToViewport({
          bottom: 60
        })
        .observe('.load-more', (res) => {
          if (res.intersectionRatio > 0 && !this.loading) {
            this.triggerEvent('loadmore')
          }
        })
    }
  }
})

================================================
FILE: miniprogram/components/load-more/load-more.json
================================================
{
  "usingComponents": {}
}

================================================
FILE: miniprogram/components/load-more/load-more.wxml
================================================
<view class='load-more'>
  <view wx:if='{{loading}}' class='weui-loading' />
  <view>{{loading ? 'Loading...' : 'Load more'}}</view>
</view>

================================================
FILE: miniprogram/components/load-more/load-more.wxss
================================================
.load-more {
  display: flex;
  align-items: center;
  justify-content: center;
}

================================================
FILE: miniprogram/components/painter/lib/downloader.js
================================================
/**
 * LRU 文件存储,使用该 downloader 可以让下载的文件存储在本地,下次进入小程序后可以直接使用
 * 详细设计文档可查看 https://juejin.im/post/5b42d3ede51d4519277b6ce3
 */
const util = require('./util');

const SAVED_FILES_KEY = 'savedFiles';
const KEY_TOTAL_SIZE = 'totalSize';
const KEY_PATH = 'path';
const KEY_TIME = 'time';
const KEY_SIZE = 'size';

// 可存储总共为 6M,目前小程序可允许的最大本地存储为 10M
let MAX_SPACE_IN_B = 6 * 1024 * 1024;
let savedFiles = {};

export default class Dowloader {
  constructor() {
    // app 如果设置了最大存储空间,则使用 app 中的
    if (getApp().PAINTER_MAX_LRU_SPACE) {
      MAX_SPACE_IN_B = getApp().PAINTER_MAX_LRU_SPACE;
    }
    wx.getStorage({
      key: SAVED_FILES_KEY,
      success: function (res) {
        if (res.data) {
          savedFiles = res.data;
        }
      },
    });
  }

  /**
   * 下载文件,会用 lru 方式来缓存文件到本地
   * @param {String} url 文件的 url
   */
  download(url) {
    return new Promise((resolve, reject) => {
      if (!(url && util.isValidUrl(url))) {
        resolve(url);
        return;
      }
      const file = getFile(url);

      if (file) {
        // 检查文件是否正常,不正常需要重新下载
        wx.getSavedFileInfo({
          filePath: file[KEY_PATH],
          success: (res) => {
            resolve(file[KEY_PATH]);
          },
          fail: (error) => {
            console.error(`the file is broken, redownload it, ${JSON.stringify(error)}`);
            downloadFile(url).then((path) => {
              resolve(path);
            }, () => {
              reject();
            });
          },
        });
      } else {
        downloadFile(url).then((path) => {
          resolve(path);
        }, () => {
          reject();
        });
      }
    });
  }
}

function downloadFile(url) {
  return new Promise((resolve, reject) => {
    wx.downloadFile({
      url: url,
      success: function (res) {
        if (res.statusCode !== 200) {
          console.error(`downloadFile ${url} failed res.statusCode is not 200`);
          reject();
          return;
        }
        const { tempFilePath } = res;
        wx.getFileInfo({
          filePath: tempFilePath,
          success: (tmpRes) => {
            const newFileSize = tmpRes.size;
            doLru(newFileSize).then(() => {
              saveFile(url, newFileSize, tempFilePath).then((filePath) => {
                resolve(filePath);
              });
            }, () => {
              resolve(tempFilePath);
            });
          },
          fail: (error) => {
          // 文件大小信息获取失败,则此文件也不要进行存储
            console.error(`getFileInfo ${res.tempFilePath} failed, ${JSON.stringify(error)}`);
            resolve(res.tempFilePath);
          },
        });
      },
      fail: function (error) {
        console.error(`downloadFile failed, ${JSON.stringify(error)} `);
        reject();
      },
    });
  });
}

function saveFile(key, newFileSize, tempFilePath) {
  return new Promise((resolve, reject) => {
    wx.saveFile({
      tempFilePath: tempFilePath,
      success: (fileRes) => {
        const totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0;
        savedFiles[key] = {};
        savedFiles[key][KEY_PATH] = fileRes.savedFilePath;
        savedFiles[key][KEY_TIME] = new Date().getTime();
        savedFiles[key][KEY_SIZE] = newFileSize;
        savedFiles['totalSize'] = newFileSize + totalSize;
        wx.setStorage({
          key: SAVED_FILES_KEY,
          data: savedFiles,
        });
        resolve(fileRes.savedFilePath);
      },
      fail: (error) => {
        console.error(`saveFile ${key} failed, then we delete all files, ${JSON.stringify(error)}`);
        // 由于 saveFile 成功后,res.tempFilePath 处的文件会被移除,所以在存储未成功时,我们还是继续使用临时文件
        resolve(tempFilePath);
        // 如果出现错误,就直接情况本地的所有文件,因为你不知道是不是因为哪次lru的某个文件未删除成功
        reset();
      },
    });
  });
}

/**
 * 清空所有下载相关内容
 */
function reset() {
  wx.removeStorage({
    key: SAVED_FILES_KEY,
    success: () => {
      wx.getSavedFileList({
        success: (listRes) => {
          removeFiles(listRes.fileList);
        },
        fail: (getError) => {
          console.error(`getSavedFileList failed, ${JSON.stringify(getError)}`);
        },
      });
    },
  });
}

function doLru(size) {
  return new Promise((resolve, reject) => {
    let totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0;

    if (size + totalSize <= MAX_SPACE_IN_B) {
      resolve();
      return;
    }
    // 如果加上新文件后大小超过最大限制,则进行 lru
    const pathsShouldDelete = [];
    // 按照最后一次的访问时间,从小到大排序
    const allFiles = JSON.parse(JSON.stringify(savedFiles));
    delete allFiles[KEY_TOTAL_SIZE];
    const sortedKeys = Object.keys(allFiles).sort((a, b) => {
      return allFiles[a][KEY_TIME] - allFiles[b][KEY_TIME];
    });

    for (const sortedKey of sortedKeys) {
      totalSize -= savedFiles[sortedKey].size;
      pathsShouldDelete.push(savedFiles[sortedKey][KEY_PATH]);
      delete savedFiles[sortedKey];
      if (totalSize + size < MAX_SPACE_IN_B) {
        break;
      }
    }

    savedFiles['totalSize'] = totalSize;

    wx.setStorage({
      key: SAVED_FILES_KEY,
      data: savedFiles,
      success: () => {
      // 保证 storage 中不会存在不存在的文件数据
        if (pathsShouldDelete.length > 0) {
          removeFiles(pathsShouldDelete);
        }
        resolve();
      },
      fail: (error) => {
        console.error(`doLru setStorage failed, ${JSON.stringify(error)}`);
        reject();
      },
    });
  });
}

function removeFiles(pathsShouldDelete) {
  for (const pathDel of pathsShouldDelete) {
    let delPath = pathDel;
    if (typeof pathDel === 'object') {
      delPath = pathDel.filePath;
    }
    wx.removeSavedFile({
      filePath: delPath,
      fail: (error) => {
        console.error(`removeSavedFile ${pathDel} failed, ${JSON.stringify(error)}`);
      },
    });
  }
}

function getFile(key) {
  if (!savedFiles[key]) {
    return;
  }
  savedFiles[key]['time'] = new Date().getTime();
  wx.setStorage({
    key: SAVED_FILES_KEY,
    data: savedFiles,
  });
  return savedFiles[key];
}


================================================
FILE: miniprogram/components/painter/lib/gradient.js
================================================
/* eslint-disable */
// 当ctx传入当前文件,const grd = ctx.createCircularGradient() 和 
// const grd = this.ctx.createLinearGradient() 无效,因此只能分开处理
// 先分析,在外部创建grd,再传入使用就可以

!(function () {

  var api = {
    isGradient: function(bg) {
      if (bg && (bg.startsWith('linear') || bg.startsWith('radial'))) {
        return true;
      } 
      return false;
    },

    doGradient: function(bg, width, height, ctx) {
      if (bg.startsWith('linear')) {
        linearEffect(width, height, bg, ctx);
      } else if (bg.startsWith('radial')) {
        radialEffect(width, height, bg, ctx);
      }
    },
  }

  function analizeGrad(string) {
    const colorPercents = string.substring(0, string.length - 1).split("%,");
    const colors = [];
    const percents = [];
    for (let colorPercent of colorPercents) {
      colors.push(colorPercent.substring(0, colorPercent.lastIndexOf(" ")).trim());
      percents.push(colorPercent.substring(colorPercent.lastIndexOf(" "), colorPercent.length) / 100);
    }
    return {colors: colors, percents: percents};
  }

  function radialEffect(width, height, bg, ctx) {
    const colorPer = analizeGrad(bg.match(/radial-gradient\((.+)\)/)[1]);
    const grd = ctx.createCircularGradient(0, 0, width < height ? height / 2 : width / 2);
    for (let i = 0; i < colorPer.colors.length; i++) {
      grd.addColorStop(colorPer.percents[i], colorPer.colors[i]);
    }
    ctx.fillStyle = grd;
    //ctx.fillRect(-(width / 2), -(height / 2), width, height);
  }

  function analizeLinear(bg, width, height) {
    const direction = bg.match(/([-]?\d{1,3})deg/);
    const dir = direction && direction[1] ? parseFloat(direction[1]) : 0;
    let coordinate;
    switch (dir) {
      case 0: coordinate = [0, -height / 2, 0, height / 2]; break;
      case 90: coordinate = [width / 2, 0, -width / 2, 0]; break;
      case -90: coordinate = [-width / 2, 0, width / 2, 0]; break;
      case 180: coordinate = [0, height / 2, 0, -height / 2]; break;
      case -180: coordinate = [0, -height / 2, 0, height / 2]; break;
      default:
        let x1 = 0;
        let y1 = 0;
        let x2 = 0;
        let y2 = 0;
        if (direction[1] > 0 && direction[1] < 90) {
          x1 = (width / 2) - ((width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (90 - direction[1]) * Math.PI * 2 / 360) / 2;
          y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
          x2 = -x1;
          y1 = -y2;
        } else if (direction[1] > -180 && direction[1] < -90) {
          x1 = -(width / 2) + ((width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (90 - direction[1]) * Math.PI * 2 / 360) / 2;
          y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
          x2 = -x1;
          y1 = -y2;
        } else if (direction[1] > 90 && direction[1] < 180) {
          x1 = (width / 2) + (-(width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (90 - direction[1]) * Math.PI * 2 / 360) / 2;
          y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
          x2 = -x1;
          y1 = -y2;
        } else {
          x1 = -(width / 2) - (-(width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (90 - direction[1]) * Math.PI * 2 / 360) / 2;
          y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
          x2 = -x1;
          y1 = -y2;
        }
        coordinate = [x1, y1, x2, y2];
      break;
    }
    return coordinate;
  }

  function linearEffect(width, height, bg, ctx) {
    const param = analizeLinear(bg, width, height);
    const grd = ctx.createLinearGradient(param[0], param[1], param[2], param[3]);
    const content = bg.match(/linear-gradient\((.+)\)/)[1];
    const colorPer = analizeGrad(content.substring(content.indexOf(',') + 1));
    for (let i = 0; i < colorPer.colors.length; i++) {
      grd.addColorStop(colorPer.percents[i], colorPer.colors[i]);
    }
    ctx.fillStyle = grd
    //ctx.fillRect(-(width / 2), -(height / 2), width, height);
  }

  module.exports = { api }

})();


================================================
FILE: miniprogram/components/painter/lib/pen.js
================================================
const QR = require('./qrcode.js');
const GD = require('./gradient.js');

export default class Painter {

  constructor(ctx, data) {
    this.ctx = ctx;
    this.data = data;
    /**
     * 记录元素信息
     * @type {{
     *  [indexOrId: string]: {
     *    width: number
     *    height: number
     *    left: number
     *    top: number
     *  }
     * }}
     */
    this.elements = {};
    this.globalWidth = {};
    this.globalHeight = {};
  }

  paint(callback) {
    this.style = {
      width: this.data.width.toPx(),
      height: this.data.height.toPx(),
    };
    this._background();
    const views = this.data.views || [];
    views.forEach((view) => this._drawAbsolute(view))
    this.ctx.draw(false, () => {
      callback();
    });
  }

  _background() {
    this.ctx.save();
    const {
      width,
      height,
    } = this.style;
    const bg = this.data.background;
    this.ctx.translate(width / 2, height / 2);

    this._doClip(this.data.borderRadius, width, height);
    if (!bg) {
      // 如果未设置背景,则默认使用白色
      this.ctx.fillStyle = '#fff';
      this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
    } else if (bg.startsWith('#') || bg.startsWith('rgba') || bg.toLowerCase() === 'transparent') {
      // 背景填充颜色
      this.ctx.fillStyle = bg;
      this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
    } else if (GD.api.isGradient(bg)) {
      GD.api.doGradient(bg, width, height, this.ctx);
      this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
    } else {
      // 背景填充图片
      this.ctx.drawImage(bg, -(width / 2), -(height / 2), width, height);
    }
    this.ctx.restore();
  }

  _drawAbsolute(view) {
    if (!view) {
      return
    }
    // 证明 css 为数组形式,需要合并
    if (view.css && view.css.length) {
      /* eslint-disable no-param-reassign */
      view.css = Object.assign(...view.css);
    }
    switch (view.type) {
      case 'image':
        this._drawAbsImage(view);
        break;
      case 'text':
        this._fillAbsText(view);
        break;
      case 'rect':
        this._drawAbsRect(view);
        break;
      case 'qrcode':
        this._drawQRCode(view);
        break;
      default:
        break;
    }
  }

  /**
   * 根据 borderRadius 进行裁减
   */
  _doClip(borderRadius, width, height) {
    if (borderRadius && width && height) {
      // radius 改为4个值,分别对应4个角
      let radius = borderRadius.trim().split(' ');
      switch (radius.length) {
        case 1:
          radius.length = 4;
          radius.fill(radius[0], 1, 4);
          break;
        default:
          radius[2] = radius[2] || radius[0];
          radius[3] = radius[3] || radius[1];
      }
      // 转换 px 单位,只保留数字
      radius = radius.map((v) => Math.min(v.toPx(), width / 2, height / 2));

      // 防止在某些机型上周边有黑框现象,此处如果直接设置 fillStyle 为透明,在 Android 机型上会导致被裁减的图片也变为透明, iOS 和 IDE 上不会
      // globalAlpha 在 1.9.90 起支持,低版本下无效,但把 fillStyle 设为了 white,相对默认的 black 要好点
      this.ctx.globalAlpha = 0;
      this.ctx.fillStyle = 'white';
      this.ctx.beginPath();
      this.ctx.arc(-width / 2 + radius[0], -height / 2 + radius[0], radius[0], 1 * Math.PI, 1.5 * Math.PI);
      this.ctx.lineTo(width / 2 - radius[1], -height / 2);
      this.ctx.arc(width / 2 - radius[1], -height / 2 + radius[1], radius[1], 1.5 * Math.PI, 2 * Math.PI);
      this.ctx.lineTo(width / 2, height / 2 - radius[2]);
      this.ctx.arc(width / 2 - radius[2], height / 2 - radius[2], radius[2], 0, 0.5 * Math.PI);
      this.ctx.lineTo(-width / 2 + radius[3], height / 2);
      this.ctx.arc(-width / 2 + radius[3], height / 2 - radius[3], radius[3], 0.5 * Math.PI, 1 * Math.PI);
      this.ctx.closePath();
      this.ctx.fill();
      // 在 ios 的 6.6.6 版本上 clip 有 bug,禁掉此类型上的 clip,也就意味着,在此版本微信的 ios 设备下无法使用 border 属性
      if (!(getApp().systemInfo &&
          getApp().systemInfo.version <= '6.6.6' &&
          getApp().systemInfo.platform === 'ios')) {
        this.ctx.clip();
      }
      this.ctx.globalAlpha = 1;
    }
  }

  /**
   * 画边框
   */
  _doBorder(view, width, height) {
    if (!view.css) {
      return;
    }
    const {
      borderRadius,
      borderWidth,
      borderColor,
    } = view.css;
    if (!borderWidth) {
      return;
    }
    this.ctx.save();
    this._preProcess({ view, notClip: true });
    let r;
    if (borderRadius) {
      r = Math.min(borderRadius.toPx(), width / 2, height / 2);
    } else {
      r = 0;
    }
    const lineWidth = borderWidth.toPx();
    this.ctx.lineWidth = lineWidth;
    this.ctx.strokeStyle = (borderColor || 'black');
    this.ctx.beginPath();
    this.ctx.arc(-width / 2 + r, -height / 2 + r, r + lineWidth / 2, 1 * Math.PI, 1.5 * Math.PI);
    this.ctx.lineTo(width / 2 - r, -height / 2 - lineWidth / 2);
    this.ctx.arc(width / 2 - r, -height / 2 + r, r + lineWidth / 2, 1.5 * Math.PI, 2 * Math.PI);
    this.ctx.lineTo(width / 2 + lineWidth / 2, height / 2 - r);
    this.ctx.arc(width / 2 - r, height / 2 - r, r + lineWidth / 2, 0, 0.5 * Math.PI);
    this.ctx.lineTo(-width / 2 + r, height / 2 + lineWidth / 2);
    this.ctx.arc(-width / 2 + r, height / 2 - r, r + lineWidth / 2, 0.5 * Math.PI, 1 * Math.PI);
    this.ctx.closePath();
    this.ctx.stroke();
    this.ctx.restore();
  }

  /**
   * 
   * @param {Object} params
   * @param {Object} params.view
   * @param {boolean} [params.notClip]
   */
  _preProcess ({ view, notClip }) {
    let width = 0;
    let height;
    let extra;
    switch (view.type) {
      case 'text': {
        const textArray = view.text.split('\n');
        // 处理多个连续的'\n'
        for (let i = 0; i < textArray.length; ++i) {
          if (textArray[i] === '') {
            textArray[i] = ' ';
          }
        }
        const fontWeight = view.css.fontWeight === 'bold' ? 'bold' : 'normal';
        view.css.fontSize = view.css.fontSize ? view.css.fontSize : '20rpx';
        this.ctx.font = `normal ${fontWeight} ${view.css.fontSize.toPx()}px ${view.css.fontFamily ? view.css.fontFamily : 'sans-serif'}`;
        // this.ctx.setFontSize(view.css.fontSize.toPx());
        // 计算行数
        let lines = 0;
        const linesArray = [];
        for (let i = 0; i < textArray.length; ++i) {
          const textLength = this.ctx.measureText(textArray[i]).width;
          const partWidth = view.css.width ? view.css.width.toPx() : textLength;
          const calLines = Math.ceil(textLength / partWidth);
          width = partWidth > width ? partWidth : width;
          lines += calLines;
          linesArray[i] = calLines;
        }
        lines = view.css.maxLines < lines ? view.css.maxLines : lines;
        const lineHeight = view.css.lineHeight ? view.css.lineHeight.toPx() : view.css.fontSize.toPx();
        height = lineHeight * lines;
        extra = {
          lines: lines,
          lineHeight: lineHeight,
          textArray: textArray,
          linesArray: linesArray,
        };
        break;
      }
      case 'image': {
        // image的长宽设置成auto的逻辑处理
        const ratio = getApp().systemInfo.pixelRatio ? getApp().systemInfo.pixelRatio : 2;
        // 有css却未设置width或height,则默认为auto
        if (view.css) {
          if (!view.css.width) {
            view.css.width = 'auto';
          }
          if (!view.css.height) {
            view.css.height = 'auto';
          }
        }
        if (!view.css || (view.css.width === 'auto' && view.css.height === 'auto')) {
          width = Math.round(view.sWidth / ratio);
          height = Math.round(view.sHeight / ratio);
        } else if (view.css.width === 'auto') {
          height = view.css.height.toPx();
          width = view.sWidth / view.sHeight * height;
        } else if (view.css.height === 'auto') {
          width = view.css.width.toPx();
          height = view.sHeight / view.sWidth * width;
        } else {
          width = view.css.width.toPx();
          height = view.css.height.toPx();
        }
        break;
      }
      default:
        if (!(view.css.width && view.css.height)) {
          console.error('You should set width and height');
          return;
        }
        width = view.css.width.toPx();
        height = view.css.height.toPx();
        break;
    }
    let x;
    if (view.css && view.css.right) {
      if (typeof view.css.right === 'string') {
        x = this.style.width - view.css.right.toPx(true);
      } else {
        // 可以用数组方式,把文字长度计算进去
        // [right, 文字id, 乘数(默认 1)]
        const rights = view.css.right;
        // x = relativeElX + relativeWidth * percent + gap - elWidth
        x = this.style.width - rights[0].toPx(true) - this.globalWidth[rights[1]] * (rights[2] || 1);
      }
    } else if (view.css && view.css.left) {
      if (typeof view.css.left === 'string') {
        x = view.css.left.toPx(true);
      } else {
        const lefts = view.css.left;
        // x = relativeElX + relativeWidth * percent + gap
        // x = lefts[0].toPx(true) + this.globalWidth[lefts[1]] * (lefts[2] || 1);
        const relativeEl = this.elements[lefts[1]];
        x = relativeEl.left + relativeEl.width * (lefts[2] === undefined ? 1 : lefts[2]) + lefts[0].toPx(true);
      }
    } else {
      x = 0;
    }
    //const y = view.css && view.css.bottom ? this.style.height - height - view.css.bottom.toPx(true) : (view.css && view.css.top ? view.css.top.toPx(true) : 0);
    let y;
    if (view.css && view.css.bottom) {
      y = this.style.height - height - view.css.bottom.toPx(true);
    } else {
      if (view.css && view.css.top) {
        if (typeof view.css.top === 'string') {
          y = view.css.top.toPx(true);
        } else {
          const tops = view.css.top;
          // y = relativeElY + relativeElHeight * percent + gap
          // y = tops[0].toPx(true) + this.globalHeight[tops[1]] * (tops[2] || 1);
          const relativeEl = this.elements[tops[1]];
          y = relativeEl.top + tops[0].toPx(true) + relativeEl.height * (tops[2] === undefined ? 1 : tops[2]);
        }
      } else {
        y = 0
      }
    }

    const angle = view.css && view.css.rotate ? this._getAngle(view.css.rotate) : 0;
    // 当设置了 right 时,默认 align 用 right,反之用 left
    const align = view.css && view.css.align ? view.css.align : (view.css && view.css.right ? 'right' : 'left');
    switch (align) {
      case 'center':
        this.ctx.translate(x, y + height / 2);
        break;
      case 'right':
        this.ctx.translate(x - width / 2, y + height / 2);
        break;
      default:
        this.ctx.translate(x + width / 2, y + height / 2);
        break;
    }
    this.ctx.rotate(angle);
    if (!notClip && view.css && view.css.borderRadius && view.type !== 'rect') {
      this._doClip(view.css.borderRadius, width, height);
    }
    this._doShadow(view);
    const elInfo = {
      x,
      y,
      width,
      height,
      left: x,
      top: y,
      extra
    };
    if (view.id) {
      this.elements[view.id] = elInfo;
      this.globalWidth[view.id] = width;
      this.globalHeight[view.id] = height;
    }
    return elInfo;
  }

  // 画文字的背景图片
  _doBackground(view) {
    this.ctx.save();
    const {
      width: rawWidth,
      height: rawHeight,
    } = this._preProcess({ view, notClip: true });

    const {
      background,
      padding,
    } = view.css;
    let pd = [0, 0, 0, 0];
    if (padding) {
      const pdg = padding.split(/\s+/);
      if (pdg.length === 1) {
        const x = pdg[0].toPx();
        pd = [x, x, x, x];
      }
      if (pdg.length === 2) {
        const x = pdg[0].toPx();
        const y = pdg[1].toPx();
        pd = [x, y, x, y];
      }
      if (pdg.length === 3) {
        const x = pdg[0].toPx();
        const y = pdg[1].toPx();
        const z = pdg[2].toPx();
        pd = [x, y, z, y];
      }
      if (pdg.length === 4) {
        const x = pdg[0].toPx();
        const y = pdg[1].toPx();
        const z = pdg[2].toPx();
        const a = pdg[3].toPx();
        pd = [x, y, z, a];
      }
    }
    const width = rawWidth + pd[1] + pd[3];
    const height = rawHeight + pd[0] + pd[2];
    this._doClip(view.css.borderRadius, width, height)
    if (GD.api.isGradient(background)) {
      GD.api.doGradient(background, width, height, this.ctx);
    } else {
      this.ctx.fillStyle = background;
    }
    this.ctx.fillRect(-(width / 2), -(height / 2), width, height);

    this.ctx.restore();
  }

  _drawQRCode(view) {
    this.ctx.save();
    const {
      width,
      height,
    } = this._preProcess({ view });
    QR.api.draw(view.content, this.ctx, -width / 2, -height / 2, width, height, view.css.background, view.css.color);
    this.ctx.restore();
    this._doBorder(view, width, height);
  }

  _drawAbsImage(view) {
    if (!view.url) {
      return;
    }
    this.ctx.save();
    const {
      width,
      height,
    } = this._preProcess({ view });
    // 获得缩放到图片大小级别的裁减框
    let rWidth = view.sWidth;
    let rHeight = view.sHeight;
    let startX = 0;
    let startY = 0;
    // 绘画区域比例
    const cp = width / height;
    // 原图比例
    const op = view.sWidth / view.sHeight;
    if (cp >= op) {
      rHeight = rWidth / cp;
      startY = Math.round((view.sHeight - rHeight) / 2);
    } else {
      rWidth = rHeight * cp;
      startX = Math.round((view.sWidth - rWidth) / 2);
    }
    if (view.css && view.css.mode === 'scaleToFill') {
      this.ctx.drawImage(view.url, -(width / 2), -(height / 2), width, height);
    } else {
      this.ctx.drawImage(view.url, startX, startY, rWidth, rHeight, -(width / 2), -(height / 2), width, height);
    }
    this.ctx.restore();
    this._doBorder(view, width, height);
  }

  _fillAbsText(view) {
    if (!view.text) {
      return;
    }
    if (view.css.background) {
      // 生成背景
      this._doBackground(view);
    }
    this.ctx.save();
    const {
      width,
      height,
      extra,
    } = this._preProcess({
      view,
      notClip: view.css.background && view.css.borderRadius
    });

    this.ctx.fillStyle = (view.css.color || 'black');
    const {
      lines,
      lineHeight,
      textArray,
      linesArray,
    } = extra;
    // 如果设置了id,则保留 text 的长度
    if (view.id) {
      let textWidth = 0;
      for (let i = 0; i < textArray.length; ++i) {
        textWidth = this.ctx.measureText(textArray[i]).width > textWidth ? this.ctx.measureText(textArray[i]).width : textWidth;
      }
      this.globalWidth[view.id] = width ? (textWidth < width ? textWidth : width) : textWidth;
    }
    let lineIndex = 0;
    for (let j = 0; j < textArray.length; ++j) {
      const preLineLength = Math.round(textArray[j].length / linesArray[j]);
      let start = 0;
      let alreadyCount = 0;
      for (let i = 0; i < linesArray[j]; ++i) {
        // 绘制行数大于最大行数,则直接跳出循环
        if (lineIndex >= lines) {
          break;
        }
        alreadyCount = preLineLength;
        let text = textArray[j].substr(start, alreadyCount);
        let measuredWith = this.ctx.measureText(text).width;
        // 如果测量大小小于width一个字符的大小,则进行补齐,如果测量大小超出 width,则进行减除
        // 如果已经到文本末尾,也不要进行该循环
        while ((start + alreadyCount <= textArray[j].length) && (width - measuredWith > view.css.fontSize.toPx() || measuredWith > width)) {
          if (measuredWith < width) {
            text = textArray[j].substr(start, ++alreadyCount);
          } else {
            if (text.length <= 1) {
              // 如果只有一个字符时,直接跳出循环
              break;
            }
            text = textArray[j].substr(start, --alreadyCount);
          }
          measuredWith = this.ctx.measureText(text).width;
        }
        start += text.length;
        // 如果是最后一行了,发现还有未绘制完的内容,则加...
        if (lineIndex === lines - 1 && (j < textArray.length - 1 || start < textArray[j].length)) {
          while (this.ctx.measureText(`${text}...`).width > width) {
            if (text.length <= 1) {
              // 如果只有一个字符时,直接跳出循环
              break;
            }
            text = text.substring(0, text.length - 1);
          }
          text += '...';
          measuredWith = this.ctx.measureText(text).width;
        }
        this.ctx.setTextAlign(view.css.textAlign ? view.css.textAlign : 'left');
        let x;
        switch (view.css.textAlign) {
          case 'center':
            x = 0;
            break;
          case 'right':
            x = (width / 2);
            break;
          default:
            x = -(width / 2);
            break;
        }
        const y = -(height / 2) + (lineIndex === 0 ? view.css.fontSize.toPx() : (view.css.fontSize.toPx() + lineIndex * lineHeight));
        lineIndex++;
        if (view.css.textStyle === 'stroke') {
          this.ctx.strokeText(text, x, y, measuredWith);
        } else {
          this.ctx.fillText(text, x, y, measuredWith);
        }
        const fontSize = view.css.fontSize.toPx();
        if (view.css.textDecoration) {
          this.ctx.beginPath();
          if (/\bunderline\b/.test(view.css.textDecoration)) {
            this.ctx.moveTo(x, y);
            this.ctx.lineTo(x + measuredWith, y);
          }
          if (/\boverline\b/.test(view.css.textDecoration)) {
            this.ctx.moveTo(x, y - fontSize);
            this.ctx.lineTo(x + measuredWith, y - fontSize);
          }
          if (/\bline-through\b/.test(view.css.textDecoration)) {
            this.ctx.moveTo(x, y - fontSize / 3);
            this.ctx.lineTo(x + measuredWith, y - fontSize / 3);
          }
          this.ctx.closePath();
          this.ctx.strokeStyle = view.css.color;
          this.ctx.stroke();
        }
      }
    }
    this.ctx.restore();
    this._doBorder(view, width, height);
  }

  _drawAbsRect(view) {
    this.ctx.save();
    const {
      width,
      height,
    } = this._preProcess({ view });
    if (GD.api.isGradient(view.css.color)) {
      GD.api.doGradient(view.css.color, width, height, this.ctx);
    } else {
      this.ctx.fillStyle = view.css.color;
    }
    const borderRadius = view.css.borderRadius
    const r = borderRadius ? Math.min(borderRadius.toPx(), width / 2, height / 2) : 0;
    this.ctx.beginPath();
    this.ctx.arc(-width / 2 + r, -height / 2 + r, r, 1 * Math.PI, 1.5 * Math.PI); //左上角圆弧
    this.ctx.lineTo(width / 2 - r, -height / 2);
    this.ctx.arc(width / 2 - r, -height / 2 + r, r, 1.5 * Math.PI, 2 * Math.PI); // 右上角圆弧
    this.ctx.lineTo(width / 2, height / 2 - r);
    this.ctx.arc(width / 2 - r, height / 2 - r, r, 0, 0.5 * Math.PI); // 右下角圆弧
    this.ctx.lineTo(-width / 2 + r, height / 2);
    this.ctx.arc(-width / 2 + r, height / 2 - r, r, 0.5 * Math.PI, 1 * Math.PI); // 左下角圆弧
    this.ctx.closePath();
    this.ctx.fill();
    this.ctx.restore();
    this._doBorder(view, width, height);
  }

  // shadow 支持 (x, y, blur, color), 不支持 spread
  // shadow:0px 0px 10px rgba(0,0,0,0.1);
  _doShadow(view) {
    if (!view.css || !view.css.shadow) {
      return;
    }
    const box = view.css.shadow.replace(/,\s+/g, ',').split(' ');
    if (box.length > 4) {
      console.error('shadow don\'t spread option');
      return;
    }
    this.ctx.shadowOffsetX = parseInt(box[0], 10);
    this.ctx.shadowOffsetY = parseInt(box[1], 10);
    this.ctx.shadowBlur = parseInt(box[2], 10);
    this.ctx.shadowColor = box[3];
  }

  _getAngle(angle) {
    return Number(angle) * Math.PI / 180;
  }
}

================================================
FILE: miniprogram/components/painter/lib/qrcode.js
================================================
/* eslint-disable */
!(function () {

  // alignment pattern
  var adelta = [
    0, 11, 15, 19, 23, 27, 31,
    16, 18, 20, 22, 24, 26, 28, 20, 22, 24, 24, 26, 28, 28, 22, 24, 24,
    26, 26, 28, 28, 24, 24, 26, 26, 26, 28, 28, 24, 26, 26, 26, 28, 28
  ];

  // version block
  var vpat = [
    0xc94, 0x5bc, 0xa99, 0x4d3, 0xbf6, 0x762, 0x847, 0x60d,
    0x928, 0xb78, 0x45d, 0xa17, 0x532, 0x9a6, 0x683, 0x8c9,
    0x7ec, 0xec4, 0x1e1, 0xfab, 0x08e, 0xc1a, 0x33f, 0xd75,
    0x250, 0x9d5, 0x6f0, 0x8ba, 0x79f, 0xb0b, 0x42e, 0xa64,
    0x541, 0xc69
  ];

  // final format bits with mask: level << 3 | mask
  var fmtword = [
    0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976,    //L
    0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0,    //M
    0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed,    //Q
    0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b    //H
  ];

  // 4 per version: number of blocks 1,2; data width; ecc width
  var eccblocks = [
    1, 0, 19, 7, 1, 0, 16, 10, 1, 0, 13, 13, 1, 0, 9, 17,
    1, 0, 34, 10, 1, 0, 28, 16, 1, 0, 22, 22, 1, 0, 16, 28,
    1, 0, 55, 15, 1, 0, 44, 26, 2, 0, 17, 18, 2, 0, 13, 22,
    1, 0, 80, 20, 2, 0, 32, 18, 2, 0, 24, 26, 4, 0, 9, 16,
    1, 0, 108, 26, 2, 0, 43, 24, 2, 2, 15, 18, 2, 2, 11, 22,
    2, 0, 68, 18, 4, 0, 27, 16, 4, 0, 19, 24, 4, 0, 15, 28,
    2, 0, 78, 20, 4, 0, 31, 18, 2, 4, 14, 18, 4, 1, 13, 26,
    2, 0, 97, 24, 2, 2, 38, 22, 4, 2, 18, 22, 4, 2, 14, 26,
    2, 0, 116, 30, 3, 2, 36, 22, 4, 4, 16, 20, 4, 4, 12, 24,
    2, 2, 68, 18, 4, 1, 43, 26, 6, 2, 19, 24, 6, 2, 15, 28,
    4, 0, 81, 20, 1, 4, 50, 30, 4, 4, 22, 28, 3, 8, 12, 24,
    2, 2, 92, 24, 6, 2, 36, 22, 4, 6, 20, 26, 7, 4, 14, 28,
    4, 0, 107, 26, 8, 1, 37, 22, 8, 4, 20, 24, 12, 4, 11, 22,
    3, 1, 115, 30, 4, 5, 40, 24, 11, 5, 16, 20, 11, 5, 12, 24,
    5, 1, 87, 22, 5, 5, 41, 24, 5, 7, 24, 30, 11, 7, 12, 24,
    5, 1, 98, 24, 7, 3, 45, 28, 15, 2, 19, 24, 3, 13, 15, 30,
    1, 5, 107, 28, 10, 1, 46, 28, 1, 15, 22, 28, 2, 17, 14, 28,
    5, 1, 120, 30, 9, 4, 43, 26, 17, 1, 22, 28, 2, 19, 14, 28,
    3, 4, 113, 28, 3, 11, 44, 26, 17, 4, 21, 26, 9, 16, 13, 26,
    3, 5, 107, 28, 3, 13, 41, 26, 15, 5, 24, 30, 15, 10, 15, 28,
    4, 4, 116, 28, 17, 0, 42, 26, 17, 6, 22, 28, 19, 6, 16, 30,
    2, 7, 111, 28, 17, 0, 46, 28, 7, 16, 24, 30, 34, 0, 13, 24,
    4, 5, 121, 30, 4, 14, 47, 28, 11, 14, 24, 30, 16, 14, 15, 30,
    6, 4, 117, 30, 6, 14, 45, 28, 11, 16, 24, 30, 30, 2, 16, 30,
    8, 4, 106, 26, 8, 13, 47, 28, 7, 22, 24, 30, 22, 13, 15, 30,
    10, 2, 114, 28, 19, 4, 46, 28, 28, 6, 22, 28, 33, 4, 16, 30,
    8, 4, 122, 30, 22, 3, 45, 28, 8, 26, 23, 30, 12, 28, 15, 30,
    3, 10, 117, 30, 3, 23, 45, 28, 4, 31, 24, 30, 11, 31, 15, 30,
    7, 7, 116, 30, 21, 7, 45, 28, 1, 37, 23, 30, 19, 26, 15, 30,
    5, 10, 115, 30, 19, 10, 47, 28, 15, 25, 24, 30, 23, 25, 15, 30,
    13, 3, 115, 30, 2, 29, 46, 28, 42, 1, 24, 30, 23, 28, 15, 30,
    17, 0, 115, 30, 10, 23, 46, 28, 10, 35, 24, 30, 19, 35, 15, 30,
    17, 1, 115, 30, 14, 21, 46, 28, 29, 19, 24, 30, 11, 46, 15, 30,
    13, 6, 115, 30, 14, 23, 46, 28, 44, 7, 24, 30, 59, 1, 16, 30,
    12, 7, 121, 30, 12, 26, 47, 28, 39, 14, 24, 30, 22, 41, 15, 30,
    6, 14, 121, 30, 6, 34, 47, 28, 46, 10, 24, 30, 2, 64, 15, 30,
    17, 4, 122, 30, 29, 14, 46, 28, 49, 10, 24, 30, 24, 46, 15, 30,
    4, 18, 122, 30, 13, 32, 46, 28, 48, 14, 24, 30, 42, 32, 15, 30,
    20, 4, 117, 30, 40, 7, 47, 28, 43, 22, 24, 30, 10, 67, 15, 30,
    19, 6, 118, 30, 18, 31, 47, 28, 34, 34, 24, 30, 20, 61, 15, 30
  ];

  // Galois field log table
  var glog = [
    0xff, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b,
    0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71,
    0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45,
    0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6,
    0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88,
    0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40,
    0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d,
    0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57,
    0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18,
    0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e,
    0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61,
    0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2,
    0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6,
    0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a,
    0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7,
    0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf
  ];

  // Galios field exponent table
  var gexp = [
    0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26,
    0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0,
    0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23,
    0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1,
    0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0,
    0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2,
    0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce,
    0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc,
    0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54,
    0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73,
    0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff,
    0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41,
    0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6,
    0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09,
    0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16,
    0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x00
  ];

  // Working buffers:
  // data input and ecc append, image working buffer, fixed part of image, run lengths for badness
  var strinbuf = [], eccbuf = [], qrframe = [], framask = [], rlens = [];
  // Control values - width is based on version, last 4 are from table.
  var version, width, neccblk1, neccblk2, datablkw, eccblkwid;
  var ecclevel = 2;
  // set bit to indicate cell in qrframe is immutable.  symmetric around diagonal
  function setmask(x, y) {
    var bt;
    if (x > y) {
      bt = x;
      x = y;
      y = bt;
    }
    // y*y = 1+3+5...
    bt = y;
    bt *= y;
    bt += y;
    bt >>= 1;
    bt += x;
    framask[bt] = 1;
  }

  // enter alignment pattern - black to qrframe, white to mask (later black frame merged to mask)
  function putalign(x, y) {
    var j;

    qrframe[x + width * y] = 1;
    for (j = -2; j < 2; j++) {
      qrframe[(x + j) + width * (y - 2)] = 1;
      qrframe[(x - 2) + width * (y + j + 1)] = 1;
      qrframe[(x + 2) + width * (y + j)] = 1;
      qrframe[(x + j + 1) + width * (y + 2)] = 1;
    }
    for (j = 0; j < 2; j++) {
      setmask(x - 1, y + j);
      setmask(x + 1, y - j);
      setmask(x - j, y - 1);
      setmask(x + j, y + 1);
    }
  }

  //========================================================================
  // Reed Solomon error correction
  // exponentiation mod N
  function modnn(x) {
    while (x >= 255) {
      x -= 255;
      x = (x >> 8) + (x & 255);
    }
    return x;
  }

  var genpoly = [];

  // Calculate and append ECC data to data block.  Block is in strinbuf, indexes to buffers given.
  function appendrs(data, dlen, ecbuf, eclen) {
    var i, j, fb;

    for (i = 0; i < eclen; i++)
      strinbuf[ecbuf + i] = 0;
    for (i = 0; i < dlen; i++) {
      fb = glog[strinbuf[data + i] ^ strinbuf[ecbuf]];
      if (fb != 255)     /* fb term is non-zero */
        for (j = 1; j < eclen; j++)
          strinbuf[ecbuf + j - 1] = strinbuf[ecbuf + j] ^ gexp[modnn(fb + genpoly[eclen - j])];
      else
        for (j = ecbuf; j < ecbuf + eclen; j++)
          strinbuf[j] = strinbuf[j + 1];
      strinbuf[ecbuf + eclen - 1] = fb == 255 ? 0 : gexp[modnn(fb + genpoly[0])];
    }
  }

  //========================================================================
  // Frame data insert following the path rules

  // check mask - since symmetrical use half.
  function ismasked(x, y) {
    var bt;
    if (x > y) {
      bt = x;
      x = y;
      y = bt;
    }
    bt = y;
    bt += y * y;
    bt >>= 1;
    bt += x;
    return framask[bt];
  }

  //========================================================================
  //  Apply the selected mask out of the 8.
  function applymask(m) {
    var x, y, r3x, r3y;

    switch (m) {
      case 0:
        for (y = 0; y < width; y++)
          for (x = 0; x < width; x++)
            if (!((x + y) & 1) && !ismasked(x, y))
              qrframe[x + y * width] ^= 1;
        break;
      case 1:
        for (y = 0; y < width; y++)
          for (x = 0; x < width; x++)
            if (!(y & 1) && !ismasked(x, y))
              qrframe[x + y * width] ^= 1;
        break;
      case 2:
        for (y = 0; y < width; y++)
          for (r3x = 0, x = 0; x < width; x++ , r3x++) {
            if (r3x == 3)
              r3x = 0;
            if (!r3x && !ismasked(x, y))
              qrframe[x + y * width] ^= 1;
          }
        break;
      case 3:
        for (r3y = 0, y = 0; y < width; y++ , r3y++) {
          if (r3y == 3)
            r3y = 0;
          for (r3x = r3y, x = 0; x < width; x++ , r3x++) {
            if (r3x == 3)
              r3x = 0;
            if (!r3x && !ismasked(x, y))
              qrframe[x + y * width] ^= 1;
          }
        }
        break;
      case 4:
        for (y = 0; y < width; y++)
          for (r3x = 0, r3y = ((y >> 1) & 1), x = 0; x < width; x++ , r3x++) {
            if (r3x == 3) {
              r3x = 0;
              r3y = !r3y;
            }
            if (!r3y && !ismasked(x, y))
              qrframe[x + y * width] ^= 1;
          }
        break;
      case 5:
        for (r3y = 0, y = 0; y < width; y++ , r3y++) {
          if (r3y == 3)
            r3y = 0;
          for (r3x = 0, x = 0; x < width; x++ , r3x++) {
            if (r3x == 3)
              r3x = 0;
            if (!((x & y & 1) + !(!r3x | !r3y)) && !ismasked(x, y))
              qrframe[x + y * width] ^= 1;
          }
        }
        break;
      case 6:
        for (r3y = 0, y = 0; y < width; y++ , r3y++) {
          if (r3y == 3)
            r3y = 0;
          for (r3x = 0, x = 0; x < width; x++ , r3x++) {
            if (r3x == 3)
              r3x = 0;
            if (!(((x & y & 1) + (r3x && (r3x == r3y))) & 1) && !ismasked(x, y))
              qrframe[x + y * width] ^= 1;
          }
        }
        break;
      case 7:
        for (r3y = 0, y = 0; y < width; y++ , r3y++) {
          if (r3y == 3)
            r3y = 0;
          for (r3x = 0, x = 0; x < width; x++ , r3x++) {
            if (r3x == 3)
              r3x = 0;
            if (!(((r3x && (r3x == r3y)) + ((x + y) & 1)) & 1) && !ismasked(x, y))
              qrframe[x + y * width] ^= 1;
          }
        }
        break;
    }
    return;
  }

  // Badness coefficients.
  var N1 = 3, N2 = 3, N3 = 40, N4 = 10;

  // Using the table of the length of each run, calculate the amount of bad image 
  // - long runs or those that look like finders; called twice, once each for X and Y
  function badruns(length) {
    var i;
    var runsbad = 0;
    for (i = 0; i <= length; i++)
      if (rlens[i] >= 5)
        runsbad += N1 + rlens[i] - 5;
    // BwBBBwB as in finder
    for (i = 3; i < length - 1; i += 2)
      if (rlens[i - 2] == rlens[i + 2]
        && rlens[i + 2] == rlens[i - 1]
        && rlens[i - 1] == rlens[i + 1]
        && rlens[i - 1] * 3 == rlens[i]
        // white around the black pattern? Not part of spec
        && (rlens[i - 3] == 0 // beginning
          || i + 3 > length  // end
          || rlens[i - 3] * 3 >= rlens[i] * 4 || rlens[i + 3] * 3 >= rlens[i] * 4)
      )
        runsbad += N3;
    return runsbad;
  }

  // Calculate how bad the masked image is - blocks, imbalance, runs, or finders.
  function badcheck() {
    var x, y, h, b, b1;
    var thisbad = 0;
    var bw = 0;

    // blocks of same color.
    for (y = 0; y < width - 1; y++)
      for (x = 0; x < width - 1; x++)
        if ((qrframe[x + width * y] && qrframe[(x + 1) + width * y]
          && qrframe[x + width * (y + 1)] && qrframe[(x + 1) + width * (y + 1)]) // all black
          || !(qrframe[x + width * y] || qrframe[(x + 1) + width * y]
            || qrframe[x + width * (y + 1)] || qrframe[(x + 1) + width * (y + 1)])) // all white
          thisbad += N2;

    // X runs
    for (y = 0; y < width; y++) {
      rlens[0] = 0;
      for (h = b = x = 0; x < width; x++) {
        if ((b1 = qrframe[x + width * y]) == b)
          rlens[h]++;
        else
          rlens[++h] = 1;
        b = b1;
        bw += b ? 1 : -1;
      }
      thisbad += badruns(h);
    }

    // black/white imbalance
    if (bw < 0)
      bw = -bw;

    var big = bw;
    var count = 0;
    big += big << 2;
    big <<= 1;
    while (big > width * width)
      big -= width * width, count++;
    thisbad += count * N4;

    // Y runs
    for (x = 0; x < width; x++) {
      rlens[0] = 0;
      for (h = b = y = 0; y < width; y++) {
        if ((b1 = qrframe[x + width * y]) == b)
          rlens[h]++;
        else
          rlens[++h] = 1;
        b = b1;
      }
      thisbad += badruns(h);
    }
    return thisbad;
  }

  function genframe(instring) {
    var x, y, k, t, v, i, j, m;

    // find the smallest version that fits the string
    t = instring.length;
    version = 0;
    do {
      version++;
      k = (ecclevel - 1) * 4 + (version - 1) * 16;
      neccblk1 = eccblocks[k++];
      neccblk2 = eccblocks[k++];
      datablkw = eccblocks[k++];
      eccblkwid = eccblocks[k];
      k = datablkw * (neccblk1 + neccblk2) + neccblk2 - 3 + (version <= 9);
      if (t <= k)
        break;
    } while (version < 40);

    // FIXME - insure that it fits insted of being truncated
    width = 17 + 4 * version;

    // allocate, clear and setup data structures
    v = datablkw + (datablkw + eccblkwid) * (neccblk1 + neccblk2) + neccblk2;
    for (t = 0; t < v; t++)
      eccbuf[t] = 0;
    strinbuf = instring.slice(0);

    for (t = 0; t < width * width; t++)
      qrframe[t] = 0;

    for (t = 0; t < (width * (width + 1) + 1) / 2; t++)
      framask[t] = 0;

    // insert finders - black to frame, white to mask
    for (t = 0; t < 3; t++) {
      k = 0;
      y = 0;
      if (t == 1)
        k = (width - 7);
      if (t == 2)
        y = (width - 7);
      qrframe[(y + 3) + width * (k + 3)] = 1;
      for (x = 0; x < 6; x++) {
        qrframe[(y + x) + width * k] = 1;
        qrframe[y + width * (k + x + 1)] = 1;
        qrframe[(y + 6) + width * (k + x)] = 1;
        qrframe[(y + x + 1) + width * (k + 6)] = 1;
      }
      for (x = 1; x < 5; x++) {
        setmask(y + x, k + 1);
        setmask(y + 1, k + x + 1);
        setmask(y + 5, k + x);
        setmask(y + x + 1, k + 5);
      }
      for (x = 2; x < 4; x++) {
        qrframe[(y + x) + width * (k + 2)] = 1;
        qrframe[(y + 2) + width * (k + x + 1)] = 1;
        qrframe[(y + 4) + width * (k + x)] = 1;
        qrframe[(y + x + 1) + width * (k + 4)] = 1;
      }
    }

    // alignment blocks
    if (version > 1) {
      t = adelta[version];
      y = width - 7;
      for (; ;) {
        x = width - 7;
        while (x > t - 3) {
          putalign(x, y);
          if (x < t)
            break;
          x -= t;
        }
        if (y <= t + 9)
          break;
        y -= t;
        putalign(6, y);
        putalign(y, 6);
      }
    }

    // single black
    qrframe[8 + width * (width - 8)] = 1;

    // timing gap - mask only
    for (y = 0; y < 7; y++) {
      setmask(7, y);
      setmask(width - 8, y);
      setmask(7, y + width - 7);
    }
    for (x = 0; x < 8; x++) {
      setmask(x, 7);
      setmask(x + width - 8, 7);
      setmask(x, width - 8);
    }

    // reserve mask-format area
    for (x = 0; x < 9; x++)
      setmask(x, 8);
    for (x = 0; x < 8; x++) {
      setmask(x + width - 8, 8);
      setmask(8, x);
    }
    for (y = 0; y < 7; y++)
      setmask(8, y + width - 7);

    // timing row/col
    for (x = 0; x < width - 14; x++)
      if (x & 1) {
        setmask(8 + x, 6);
        setmask(6, 8 + x);
      }
      else {
        qrframe[(8 + x) + width * 6] = 1;
        qrframe[6 + width * (8 + x)] = 1;
      }

    // version block
    if (version > 6) {
      t = vpat[version - 7];
      k = 17;
      for (x = 0; x < 6; x++)
        for (y = 0; y < 3; y++ , k--)
          if (1 & (k > 11 ? version >> (k - 12) : t >> k)) {
            qrframe[(5 - x) + width * (2 - y + width - 11)] = 1;
            qrframe[(2 - y + width - 11) + width * (5 - x)] = 1;
          }
          else {
            setmask(5 - x, 2 - y + width - 11);
            setmask(2 - y + width - 11, 5 - x);
          }
    }

    // sync mask bits - only set above for white spaces, so add in black bits
    for (y = 0; y < width; y++)
      for (x = 0; x <= y; x++)
        if (qrframe[x + width * y])
          setmask(x, y);

    // convert string to bitstream
    // 8 bit data to QR-coded 8 bit data (numeric or alphanum, or kanji not supported)
    v = strinbuf.length;

    // string to array
    for (i = 0; i < v; i++)
      eccbuf[i] = strinbuf.charCodeAt(i);
    strinbuf = eccbuf.slice(0);

    // calculate max string length
    x = datablkw * (neccblk1 + neccblk2) + neccblk2;
    if (v >= x - 2) {
      v = x - 2;
      if (version > 9)
        v--;
    }

    // shift and repack to insert length prefix
    i = v;
    if (version > 9) {
      strinbuf[i + 2] = 0;
      strinbuf[i + 3] = 0;
      while (i--) {
        t = strinbuf[i];
        strinbuf[i + 3] |= 255 & (t << 4);
        strinbuf[i + 2] = t >> 4;
      }
      strinbuf[2] |= 255 & (v << 4);
      strinbuf[1] = v >> 4;
      strinbuf[0] = 0x40 | (v >> 12);
    }
    else {
      strinbuf[i + 1] = 0;
      strinbuf[i + 2] = 0;
      while (i--) {
        t = strinbuf[i];
        strinbuf[i + 2] |= 255 & (t << 4);
        strinbuf[i + 1] = t >> 4;
      }
      strinbuf[1] |= 255 & (v << 4);
      strinbuf[0] = 0x40 | (v >> 4);
    }
    // fill to end with pad pattern
    i = v + 3 - (version < 10);
    while (i < x) {
      strinbuf[i++] = 0xec;
      // buffer has room    if (i == x)      break;
      strinbuf[i++] = 0x11;
    }

    // calculate and append ECC

    // calculate generator polynomial
    genpoly[0] = 1;
    for (i = 0; i < eccblkwid; i++) {
      genpoly[i + 1] = 1;
      for (j = i; j > 0; j--)
        genpoly[j] = genpoly[j]
          ? genpoly[j - 1] ^ gexp[modnn(glog[genpoly[j]] + i)] : genpoly[j - 1];
      genpoly[0] = gexp[modnn(glog[genpoly[0]] + i)];
    }
    for (i = 0; i <= eccblkwid; i++)
      genpoly[i] = glog[genpoly[i]]; // use logs for genpoly[] to save calc step

    // append ecc to data buffer
    k = x;
    y = 0;
    for (i = 0; i < neccblk1; i++) {
      appendrs(y, datablkw, k, eccblkwid);
      y += datablkw;
      k += eccblkwid;
    }
    for (i = 0; i < neccblk2; i++) {
      appendrs(y, datablkw + 1, k, eccblkwid);
      y += datablkw + 1;
      k += eccblkwid;
    }
    // interleave blocks
    y = 0;
    for (i = 0; i < datablkw; i++) {
      for (j = 0; j < neccblk1; j++)
        eccbuf[y++] = strinbuf[i + j * datablkw];
      for (j = 0; j < neccblk2; j++)
        eccbuf[y++] = strinbuf[(neccblk1 * datablkw) + i + (j * (datablkw + 1))];
    }
    for (j = 0; j < neccblk2; j++)
      eccbuf[y++] = strinbuf[(neccblk1 * datablkw) + i + (j * (datablkw + 1))];
    for (i = 0; i < eccblkwid; i++)
      for (j = 0; j < neccblk1 + neccblk2; j++)
        eccbuf[y++] = strinbuf[x + i + j * eccblkwid];
    strinbuf = eccbuf;

    // pack bits into frame avoiding masked area.
    x = y = width - 1;
    k = v = 1;         // up, minus
    /* inteleaved data and ecc codes */
    m = (datablkw + eccblkwid) * (neccblk1 + neccblk2) + neccblk2;
    for (i = 0; i < m; i++) {
      t = strinbuf[i];
      for (j = 0; j < 8; j++ , t <<= 1) {
        if (0x80 & t)
          qrframe[x + width * y] = 1;
        do {        // find next fill position
          if (v)
            x--;
          else {
            x++;
            if (k) {
              if (y != 0)
                y--;
              else {
                x -= 2;
                k = !k;
                if (x == 6) {
                  x--;
                  y = 9;
                }
              }
            }
            else {
              if (y != width - 1)
                y++;
              else {
                x -= 2;
                k = !k;
                if (x == 6) {
                  x--;
                  y -= 8;
                }
              }
            }
          }
          v = !v;
        } while (ismasked(x, y));
      }
    }
Download .txt
gitextract_1qcrx63f/

├── .eslintignore
├── .eslintrc.cjs
├── .github/
│   └── workflows/
│       └── upload.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── .vscode/
│   └── settings.json
├── .yarnrc
├── @types/
│   ├── douban/
│   │   ├── accounts.d.ts
│   │   ├── frodo.d.ts
│   │   └── index.d.ts
│   ├── douban.d.ts
│   ├── index.d.ts
│   ├── miniprogram.d.ts
│   ├── mobx-miniprogram-bindings.d.ts
│   └── wxCloud.d.ts
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── cloudbaserc.json
├── cloudfunctions/
│   ├── app/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── douban/
│   │   ├── config.json
│   │   ├── index.js
│   │   ├── package.json
│   │   ├── request.d.ts
│   │   └── request.js
│   ├── favArticle/
│   │   ├── index.js
│   │   └── package.json
│   ├── favCard/
│   │   ├── index.js
│   │   └── package.json
│   ├── fetch/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── getArticleDetails/
│   │   ├── index.js
│   │   └── package.json
│   ├── getCard/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── getCards/
│   │   ├── index.js
│   │   └── package.json
│   ├── getCategories/
│   │   ├── index.js
│   │   └── package.json
│   ├── getFavArticles/
│   │   ├── index.js
│   │   └── package.json
│   ├── getFavCards/
│   │   ├── index.js
│   │   └── package.json
│   ├── github/
│   │   ├── config.json
│   │   ├── fetch.js
│   │   ├── index.js
│   │   └── package.json
│   ├── initdb/
│   │   ├── index.js
│   │   └── package.json
│   ├── login/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── nowPlaying/
│   │   ├── config.json
│   │   ├── fetch.js
│   │   ├── index.js
│   │   └── package.json
│   ├── showingSoon/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── site/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── subscribeMessage/
│   │   ├── index.js
│   │   └── package.json
│   ├── subscription/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── trending/
│   │   ├── config.json
│   │   ├── index.js
│   │   └── package.json
│   ├── wallpaper/
│   │   ├── index.js
│   │   └── package.json
│   └── wxacode/
│       ├── config.json
│       ├── index.js
│       └── package.json
├── docs/
│   └── 云开发环境初始化.md
├── jest.config.js
├── miniprogram/
│   ├── apis/
│   │   ├── cloud/
│   │   │   └── index.js
│   │   ├── douban/
│   │   │   ├── accounts.js
│   │   │   └── request.js
│   │   ├── douban.js
│   │   ├── github.js
│   │   ├── leancloud/
│   │   │   └── index.js
│   │   ├── server/
│   │   │   └── index.js
│   │   └── vercel.js
│   ├── app.js
│   ├── app.json
│   ├── app.wxss
│   ├── components/
│   │   ├── Tabs/
│   │   │   ├── Tabs.js
│   │   │   ├── Tabs.json
│   │   │   ├── Tabs.wxml
│   │   │   └── Tabs.wxss
│   │   ├── article/
│   │   │   ├── article.js
│   │   │   ├── article.json
│   │   │   ├── article.wxml
│   │   │   └── article.wxss
│   │   ├── btn-fav/
│   │   │   ├── FavButton.js
│   │   │   ├── FavButton.json
│   │   │   ├── FavButton.wxml
│   │   │   └── FavButton.wxss
│   │   ├── cover-page/
│   │   │   ├── cover-page.js
│   │   │   ├── cover-page.json
│   │   │   ├── cover-page.wxml
│   │   │   ├── cover-page.wxs
│   │   │   └── cover-page.wxss
│   │   ├── index-list/
│   │   │   ├── components/
│   │   │   │   └── content/
│   │   │   │       ├── content.js
│   │   │   │       ├── content.json
│   │   │   │       ├── content.wxml
│   │   │   │       └── content.wxss
│   │   │   ├── index-list.js
│   │   │   ├── index-list.json
│   │   │   ├── index-list.wxml
│   │   │   └── index-list.wxss
│   │   ├── load-more/
│   │   │   ├── load-more.js
│   │   │   ├── load-more.json
│   │   │   ├── load-more.wxml
│   │   │   └── load-more.wxss
│   │   ├── painter/
│   │   │   ├── lib/
│   │   │   │   ├── downloader.js
│   │   │   │   ├── gradient.js
│   │   │   │   ├── pen.js
│   │   │   │   ├── qrcode.js
│   │   │   │   └── util.js
│   │   │   ├── painter.js
│   │   │   ├── painter.json
│   │   │   └── painter.wxml
│   │   ├── pre-image/
│   │   │   ├── PreImage.js
│   │   │   ├── PreImage.json
│   │   │   ├── PreImage.wxml
│   │   │   └── PreImage.wxss
│   │   ├── rating/
│   │   │   ├── rating.js
│   │   │   ├── rating.json
│   │   │   ├── rating.wxml
│   │   │   └── rating.wxss
│   │   ├── tab-bar/
│   │   │   ├── index.js
│   │   │   ├── index.json
│   │   │   ├── index.wxml
│   │   │   └── index.wxss
│   │   └── towxml/
│   │       ├── audio-player/
│   │       │   ├── Audio.js
│   │       │   ├── audio-player.js
│   │       │   ├── audio-player.json
│   │       │   ├── audio-player.wxml
│   │       │   └── audio-player.wxss
│   │       ├── config.js
│   │       ├── decode.js
│   │       ├── decode.json
│   │       ├── decode.wxml
│   │       ├── decode.wxss
│   │       ├── img/
│   │       │   ├── img.js
│   │       │   ├── img.json
│   │       │   ├── img.wxml
│   │       │   └── img.wxss
│   │       ├── index.js
│   │       ├── parse/
│   │       │   ├── highlight/
│   │       │   │   ├── highlight.js
│   │       │   │   ├── index.js
│   │       │   │   ├── languages/
│   │       │   │   │   ├── bash.js
│   │       │   │   │   ├── c-like.js
│   │       │   │   │   ├── c.js
│   │       │   │   │   ├── css.js
│   │       │   │   │   ├── dart.js
│   │       │   │   │   ├── go.js
│   │       │   │   │   ├── htmlbars.js
│   │       │   │   │   ├── index.js
│   │       │   │   │   ├── java.js
│   │       │   │   │   ├── javascript.js
│   │       │   │   │   ├── json.js
│   │       │   │   │   ├── less.js
│   │       │   │   │   ├── nginx.js
│   │       │   │   │   ├── php.js
│   │       │   │   │   ├── python-repl.js
│   │       │   │   │   ├── python.js
│   │       │   │   │   ├── scss.js
│   │       │   │   │   ├── shell.js
│   │       │   │   │   ├── typescript.js
│   │       │   │   │   └── xml.js
│   │       │   │   └── style/
│   │       │   │       ├── github.wxss
│   │       │   │       └── monokai.wxss
│   │       │   ├── index.js
│   │       │   ├── markdown/
│   │       │   │   ├── index.js
│   │       │   │   ├── markdown.js
│   │       │   │   └── plugins/
│   │       │   │       ├── emoji.js
│   │       │   │       ├── ins.js
│   │       │   │       ├── mark.js
│   │       │   │       ├── sub.js
│   │       │   │       ├── sup.js
│   │       │   │       └── todo.js
│   │       │   └── parse2/
│   │       │       ├── Parser.js
│   │       │       ├── Tokenizer.js
│   │       │       ├── domhandler/
│   │       │       │   ├── index.js
│   │       │       │   └── node.js
│   │       │       ├── entities/
│   │       │       │   ├── decode.js
│   │       │       │   ├── decode_codepoint.js
│   │       │       │   ├── encode.js
│   │       │       │   ├── index.js
│   │       │       │   └── maps/
│   │       │       │       ├── decode.js
│   │       │       │       ├── entities.js
│   │       │       │       ├── legacy.js
│   │       │       │       └── xml.js
│   │       │       └── index.js
│   │       ├── style/
│   │       │   ├── main.wxss
│   │       │   └── theme/
│   │       │       ├── dark.wxss
│   │       │       └── light.wxss
│   │       ├── table/
│   │       │   ├── table.js
│   │       │   ├── table.json
│   │       │   ├── table.wxml
│   │       │   └── table.wxss
│   │       ├── towxml.js
│   │       ├── towxml.json
│   │       ├── towxml.wxml
│   │       └── towxml.wxss
│   ├── libs/
│   │   ├── av-live-query-core-min.js
│   │   └── leancloud-adapters-weapp.js
│   ├── models/
│   │   ├── Cast.js
│   │   └── Comment.js
│   ├── packages/
│   │   ├── admin/
│   │   │   └── pages/
│   │   │       ├── app/
│   │   │       │   ├── app.js
│   │   │       │   ├── app.json
│   │   │       │   ├── app.wxml
│   │   │       │   └── app.wxss
│   │   │       ├── douban/
│   │   │       │   ├── douban.js
│   │   │       │   ├── douban.json
│   │   │       │   ├── douban.wxml
│   │   │       │   └── douban.wxss
│   │   │       └── index/
│   │   │           ├── index.js
│   │   │           ├── index.json
│   │   │           ├── index.wxml
│   │   │           └── index.wxss
│   │   ├── article/
│   │   │   └── pages/
│   │   │       ├── categories/
│   │   │       │   ├── categories.js
│   │   │       │   ├── categories.json
│   │   │       │   ├── categories.wxml
│   │   │       │   └── categories.wxss
│   │   │       ├── classification/
│   │   │       │   ├── index.wxml
│   │   │       │   └── index.wxss
│   │   │       ├── details/
│   │   │       │   ├── detail.js
│   │   │       │   ├── detail.json
│   │   │       │   ├── detail.wxml
│   │   │       │   └── detail.wxss
│   │   │       └── movie-list-detail/
│   │   │           ├── movie-list-detail.js
│   │   │           ├── movie-list-detail.json
│   │   │           ├── movie-list-detail.wxml
│   │   │           └── movie-list-detail.wxss
│   │   ├── douban/
│   │   │   └── pages/
│   │   │       ├── collection/
│   │   │       │   ├── collection.js
│   │   │       │   ├── collection.json
│   │   │       │   ├── collection.wxml
│   │   │       │   └── collection.wxss
│   │   │       └── login-phone/
│   │   │           ├── login-phone.js
│   │   │           ├── login-phone.json
│   │   │           ├── login-phone.wxml
│   │   │           └── login-phone.wxss
│   │   ├── example/
│   │   │   └── pages/
│   │   │       ├── cover-page/
│   │   │       │   ├── cover-page.js
│   │   │       │   ├── cover-page.json
│   │   │       │   ├── cover-page.wxml
│   │   │       │   └── cover-page.wxss
│   │   │       ├── icons/
│   │   │       │   ├── icons.js
│   │   │       │   ├── icons.json
│   │   │       │   ├── icons.wxml
│   │   │       │   └── icons.wxss
│   │   │       ├── index/
│   │   │       │   ├── index.js
│   │   │       │   ├── index.json
│   │   │       │   ├── index.wxml
│   │   │       │   └── index.wxss
│   │   │       ├── tab-bar/
│   │   │       │   ├── tab-bar.js
│   │   │       │   ├── tab-bar.json
│   │   │       │   ├── tab-bar.wxml
│   │   │       │   └── tab-bar.wxss
│   │   │       └── waterfall/
│   │   │           ├── components/
│   │   │           │   └── unsplash/
│   │   │           │       ├── unsplash.js
│   │   │           │       ├── unsplash.json
│   │   │           │       ├── unsplash.wxml
│   │   │           │       └── unsplash.wxss
│   │   │           ├── waterfall.js
│   │   │           ├── waterfall.json
│   │   │           ├── waterfall.wxml
│   │   │           └── waterfall.wxss
│   │   ├── github/
│   │   │   ├── components/
│   │   │   │   ├── event-item/
│   │   │   │   │   ├── event-item.js
│   │   │   │   │   ├── event-item.json
│   │   │   │   │   ├── event-item.wxml
│   │   │   │   │   ├── event-item.wxs
│   │   │   │   │   └── event-item.wxss
│   │   │   │   ├── lang-item/
│   │   │   │   │   ├── lang-item.js
│   │   │   │   │   ├── lang-item.json
│   │   │   │   │   ├── lang-item.wxml
│   │   │   │   │   └── lang-item.wxss
│   │   │   │   ├── notification-item/
│   │   │   │   │   ├── notification-item.js
│   │   │   │   │   ├── notification-item.json
│   │   │   │   │   ├── notification-item.wxml
│   │   │   │   │   └── notification-item.wxss
│   │   │   │   ├── page-events/
│   │   │   │   │   ├── page-events.js
│   │   │   │   │   ├── page-events.json
│   │   │   │   │   ├── page-events.wxml
│   │   │   │   │   └── page-events.wxss
│   │   │   │   ├── page-trending/
│   │   │   │   │   ├── page-trending.js
│   │   │   │   │   ├── page-trending.json
│   │   │   │   │   ├── page-trending.wxml
│   │   │   │   │   └── page-trending.wxss
│   │   │   │   ├── page-user/
│   │   │   │   │   ├── page-user.js
│   │   │   │   │   ├── page-user.json
│   │   │   │   │   ├── page-user.wxml
│   │   │   │   │   └── page-user.wxss
│   │   │   │   └── repo-item/
│   │   │   │       ├── repo-item.js
│   │   │   │       ├── repo-item.json
│   │   │   │       ├── repo-item.wxml
│   │   │   │       └── repo-item.wxss
│   │   │   └── pages/
│   │   │       ├── events/
│   │   │       │   ├── events.js
│   │   │       │   ├── events.json
│   │   │       │   ├── events.wxml
│   │   │       │   └── events.wxss
│   │   │       ├── home/
│   │   │       │   ├── home.js
│   │   │       │   ├── home.json
│   │   │       │   ├── home.wxml
│   │   │       │   └── home.wxss
│   │   │       ├── languages/
│   │   │       │   ├── languages.js
│   │   │       │   ├── languages.json
│   │   │       │   ├── languages.wxml
│   │   │       │   └── languages.wxss
│   │   │       ├── notifications/
│   │   │       │   ├── notifications.js
│   │   │       │   ├── notifications.json
│   │   │       │   ├── notifications.wxml
│   │   │       │   └── notifications.wxss
│   │   │       ├── repository/
│   │   │       │   ├── repository.js
│   │   │       │   ├── repository.json
│   │   │       │   ├── repository.wxml
│   │   │       │   └── repository.wxss
│   │   │       ├── search/
│   │   │       │   ├── search.js
│   │   │       │   ├── search.json
│   │   │       │   ├── search.wxml
│   │   │       │   └── search.wxss
│   │   │       ├── starred/
│   │   │       │   ├── starred.js
│   │   │       │   ├── starred.json
│   │   │       │   ├── starred.wxml
│   │   │       │   └── starred.wxss
│   │   │       └── trending/
│   │   │           ├── trending.js
│   │   │           ├── trending.json
│   │   │           ├── trending.wxml
│   │   │           └── trending.wxss
│   │   ├── movie/
│   │   │   ├── components/
│   │   │   │   └── comment-item/
│   │   │   │       ├── comment-item.js
│   │   │   │       ├── comment-item.json
│   │   │   │       ├── comment-item.wxml
│   │   │   │       └── comment-item.wxss
│   │   │   └── pages/
│   │   │       ├── cards/
│   │   │       │   ├── card.js
│   │   │       │   ├── card.json
│   │   │       │   ├── card.wxml
│   │   │       │   └── card.wxss
│   │   │       ├── details/
│   │   │       │   ├── details.js
│   │   │       │   ├── details.json
│   │   │       │   ├── details.wxml
│   │   │       │   ├── details.wxs
│   │   │       │   └── details.wxss
│   │   │       ├── intheaters/
│   │   │       │   ├── in_theaters.js
│   │   │       │   ├── in_theaters.json
│   │   │       │   ├── in_theaters.wxml
│   │   │       │   └── in_theaters.wxss
│   │   │       ├── mark/
│   │   │       │   ├── mark.js
│   │   │       │   ├── mark.json
│   │   │       │   ├── mark.wxml
│   │   │       │   └── mark.wxss
│   │   │       ├── photos/
│   │   │       │   ├── photos.js
│   │   │       │   ├── photos.json
│   │   │       │   ├── photos.wxml
│   │   │       │   └── photos.wxss
│   │   │       └── trailers/
│   │   │           ├── trailers.js
│   │   │           ├── trailers.json
│   │   │           ├── trailers.wxml
│   │   │           └── trailers.wxss
│   │   ├── tools/
│   │   │   └── pages/
│   │   │       ├── encode/
│   │   │       │   ├── encode.js
│   │   │       │   ├── encode.json
│   │   │       │   ├── encode.wxml
│   │   │       │   └── encode.wxss
│   │   │       ├── index/
│   │   │       │   ├── index.js
│   │   │       │   ├── index.json
│   │   │       │   ├── index.wxml
│   │   │       │   └── index.wxss
│   │   │       └── random/
│   │   │           ├── random.js
│   │   │           ├── random.json
│   │   │           ├── random.wxml
│   │   │           └── random.wxss
│   │   ├── user/
│   │   │   └── pages/
│   │   │       ├── achievement/
│   │   │       │   ├── achievement.js
│   │   │       │   ├── achievement.json
│   │   │       │   ├── achievement.wxml
│   │   │       │   └── achievement.wxss
│   │   │       ├── evaluate/
│   │   │       │   ├── evaluate.js
│   │   │       │   ├── evaluate.json
│   │   │       │   ├── evaluate.wxml
│   │   │       │   └── evaluate.wxss
│   │   │       ├── favCards/
│   │   │       │   ├── index.js
│   │   │       │   ├── index.json
│   │   │       │   ├── index.wxml
│   │   │       │   └── index.wxss
│   │   │       ├── favMovieList/
│   │   │       │   ├── index.js
│   │   │       │   ├── index.json
│   │   │       │   ├── index.wxml
│   │   │       │   └── index.wxss
│   │   │       └── userinfo/
│   │   │           ├── userinfo.js
│   │   │           ├── userinfo.json
│   │   │           ├── userinfo.wxml
│   │   │           └── userinfo.wxss
│   │   └── wallpaper/
│   │       ├── apis/
│   │       │   └── wallpaper/
│   │       │       └── index.js
│   │       └── pages/
│   │           └── categories/
│   │               ├── categories.js
│   │               ├── categories.json
│   │               ├── categories.wxml
│   │               └── categories.wxss
│   ├── pages/
│   │   ├── about/
│   │   │   ├── about.js
│   │   │   ├── about.json
│   │   │   ├── about.wxml
│   │   │   └── about.wxss
│   │   ├── first/
│   │   │   ├── first.js
│   │   │   ├── first.json
│   │   │   ├── first.wxml
│   │   │   └── first.wxss
│   │   ├── marked/
│   │   │   ├── marked.js
│   │   │   ├── marked.json
│   │   │   ├── marked.wxml
│   │   │   └── marked.wxss
│   │   ├── search/
│   │   │   ├── search.js
│   │   │   ├── search.json
│   │   │   ├── search.wxml
│   │   │   └── search.wxss
│   │   ├── setting/
│   │   │   ├── setting.js
│   │   │   ├── setting.json
│   │   │   ├── setting.wxml
│   │   │   └── setting.wxss
│   │   ├── splash/
│   │   │   ├── splash.js
│   │   │   ├── splash.json
│   │   │   ├── splash.wxml
│   │   │   └── splash.wxss
│   │   ├── tabs/
│   │   │   ├── discovery/
│   │   │   │   ├── discovery.js
│   │   │   │   ├── discovery.json
│   │   │   │   ├── discovery.wxml
│   │   │   │   └── discovery.wxss
│   │   │   ├── index/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.json
│   │   │   │   ├── index.wxml
│   │   │   │   └── index.wxss
│   │   │   └── movies/
│   │   │       ├── movies.js
│   │   │       ├── movies.json
│   │   │       ├── movies.wxml
│   │   │       └── movies.wxss
│   │   ├── test/
│   │   │   ├── test.js
│   │   │   ├── test.json
│   │   │   ├── test.wxml
│   │   │   └── test.wxss
│   │   └── webview/
│   │       ├── index.js
│   │       ├── index.json
│   │       ├── index.wxml
│   │       └── index.wxss
│   ├── sitemap.json
│   ├── store/
│   │   ├── app.js
│   │   ├── douban.js
│   │   ├── index.js
│   │   └── user.js
│   ├── style/
│   │   ├── animate.wxss
│   │   ├── common.wxss
│   │   ├── font-awesome.min.wxss
│   │   ├── iconfont.wxss
│   │   └── weui.wxss
│   ├── templates/
│   │   ├── actionsheet/
│   │   │   ├── actionsheet.js
│   │   │   ├── actionsheet.wxml
│   │   │   └── actionsheet.wxss
│   │   ├── bing/
│   │   │   ├── bing.js
│   │   │   ├── bing.wxml
│   │   │   └── bing.wxss
│   │   ├── casts/
│   │   │   ├── casts.wxml
│   │   │   └── casts.wxss
│   │   ├── cell/
│   │   │   ├── cell.wxml
│   │   │   └── cell.wxss
│   │   ├── circle/
│   │   │   ├── circle.wxml
│   │   │   └── circle.wxss
│   │   ├── component.js
│   │   ├── dropmenu/
│   │   │   ├── dropmenu.js
│   │   │   ├── dropmenu.wxml
│   │   │   └── dropmenu.wxss
│   │   ├── index.js
│   │   ├── loading/
│   │   │   ├── loading.wxml
│   │   │   └── loading.wxss
│   │   ├── login/
│   │   │   ├── login.js
│   │   │   └── login.wxml
│   │   ├── movie/
│   │   │   ├── movieRow.wxml
│   │   │   └── movieRow.wxss
│   │   ├── rating/
│   │   │   ├── rating.wxml
│   │   │   └── rating.wxss
│   │   ├── share/
│   │   │   ├── share.js
│   │   │   ├── share.wxml
│   │   │   └── share.wxss
│   │   └── wxParse/
│   │       ├── html2json.js
│   │       ├── htmlparser.js
│   │       ├── showdown.js
│   │       ├── wxDiscode.js
│   │       ├── wxParse.js
│   │       ├── wxParse.wxml
│   │       └── wxParse.wxss
│   └── utils/
│       ├── Base64.js
│       ├── EventEmitter.js
│       ├── URLSearchParams.js
│       ├── WxUtil.wxs
│       ├── apis.js
│       ├── crypro.js
│       ├── events.js
│       ├── github-colors.js
│       ├── global.js
│       ├── request.js
│       ├── storage.js
│       ├── svg.js
│       ├── util.js
│       ├── utils.wxs
│       └── wxCloud.js
├── mock/
│   └── mock.config.json
├── package.json
├── project.config.json
├── scripts/
│   └── release.cjs
├── test/
│   └── EventEmitter.spec.js
└── tsconfig.json
Download .txt
SYMBOL INDEX (996 symbols across 120 files)

FILE: @types/douban.d.ts
  type SearchResult (line 3) | interface SearchResult {
  type SearchItem (line 11) | interface SearchItem {
  type SearchMovieItem (line 27) | interface SearchMovieItem extends SearchItem {
  type SearchDoulistItem (line 34) | interface SearchDoulistItem extends SearchItem {
  type SearchBookItem (line 43) | interface SearchBookItem extends SearchItem {
  type Subject (line 49) | interface Subject {
  type SubjectType (line 61) | type SubjectType = 'movie'|'tv'|'book'|'music';
  type Movie (line 63) | interface Movie extends Subject {
  type Book (line 68) | interface Book extends Subject {
  type Doulists (line 73) | interface Doulists {
  type Rating (line 80) | interface Rating {
  type Actor (line 87) | interface Actor {
  type Trailer (line 102) | interface Trailer {
  type MovieItem (line 120) | interface MovieItem {
  type MovieDetail (line 137) | interface MovieDetail {
  type User (line 162) | interface User {
  type Loc (line 180) | interface Loc {
  type Interest (line 186) | interface Interest {
  type InterestResult (line 206) | interface InterestResult {
  type InterestStatus (line 214) | type InterestStatus = 'mark'|'doing'|'done';
  type Image (line 216) | interface Image {
  type ImageMeta (line 224) | interface ImageMeta {
  type Photo (line 231) | interface Photo {
  type PhotosResult (line 236) | interface PhotosResult {
  type Trailer (line 243) | interface Trailer {
  type TrailersResult (line 265) | interface TrailersResult {
  type ListParams (line 269) | interface ListParams {
  type ListResult (line 274) | interface ListResult {

FILE: @types/douban/accounts.d.ts
  type AccountInfo (line 2) | interface AccountInfo {

FILE: @types/douban/frodo.d.ts
  type SubjectCollection (line 2) | interface SubjectCollection {
  type SubjectCollectionItemsResult (line 16) | interface SubjectCollectionItemsResult {
  type SubjectCollectionShowingItemsResult (line 25) | interface SubjectCollectionShowingItemsResult {
  type SubjectCollectionSoonItemsResult (line 50) | interface SubjectCollectionSoonItemsResult {
  type UserInterestsResult (line 72) | interface UserInterestsResult {

FILE: @types/douban/index.d.ts
  type APIResponseLegacy (line 3) | interface APIResponseLegacy<T = any> {
  type LoginSuccessResult (line 10) | interface LoginSuccessResult {

FILE: @types/index.d.ts
  type RequestController (line 6) | interface RequestController extends WechatMiniprogram.RequestTask {
  type RequestOption (line 10) | interface RequestOption<T> extends Omit<WechatMiniprogram.RequestOption<...
  type RequestSuccessResult (line 16) | interface RequestSuccessResult<T> extends WechatMiniprogram.RequestSucce...

FILE: @types/miniprogram.d.ts
  type FormEvent (line 4) | interface FormEvent<Detail extends IAnyObject = IAnyObject> extends Cust...
  type A (line 12) | interface A {

FILE: @types/wxCloud.d.ts
  type CloudFunctionBaseEvent (line 1) | interface CloudFunctionBaseEvent {
  type CloudFunctionEvent (line 5) | type CloudFunctionEvent<T extends Record<string, any> = {}> = CloudFunct...
  type CloudFunctionContext (line 6) | interface CloudFunctionContext {
  type CloudFunction (line 21) | interface CloudFunction<Event extends Record<string, any> = {}, Result =...
  type CallCloudOptions (line 25) | interface CallCloudOptions {
  type CallCloud (line 32) | interface CallCloud {

FILE: cloudfunctions/douban/request.js
  constant API_BASE (line 7) | const API_BASE = 'https://frodo.douban.com/api/v2';
  constant API_KEY (line 8) | const API_KEY = '054022eaeae0b00e0fc068c0c0a2102a';

FILE: miniprogram/apis/douban.js
  constant BASE_URL (line 20) | const BASE_URL = 'https://mmovie.imarkr.com/douban/api';

FILE: miniprogram/apis/douban/request.js
  class RequestController (line 3) | class RequestController {
    method constructor (line 7) | constructor() {

FILE: miniprogram/app.js
  method onLaunch (line 18) | onLaunch () {
  method login (line 25) | async login () {
  method getUserInfo (line 55) | getUserInfo (cb) {
  method getDefaultConfig (line 77) | async getDefaultConfig () {
  method logout (line 86) | logout (callback) {
  method getSetting (line 92) | getSetting (callback) {

FILE: miniprogram/components/Tabs/Tabs.js
  method _handleTabTap (line 27) | _handleTabTap(e) {

FILE: miniprogram/components/article/article.js
  method onFavChange (line 40) | onFavChange(e){

FILE: miniprogram/components/btn-fav/FavButton.js
  method _onToggle (line 29) | _onToggle (e) {

FILE: miniprogram/components/cover-page/cover-page.js
  method setVisible (line 26) | setVisible ({ visible }) {
  method show (line 30) | show () {
  method hide (line 34) | hide () {
  method prevent (line 38) | prevent () {

FILE: miniprogram/components/index-list/index-list.js
  method attached (line 83) | attached() {
  method choose (line 91) | choose (e) {
  method scrollTo (line 96) | scrollTo(e) {
  method _scrollTo (line 100) | _scrollTo(e) {
  method computedSize (line 113) | computedSize() {
  method removeTouching (line 134) | removeTouching() {
  method onScroll (line 140) | onScroll(e) {
  method _onScroll (line 144) | _onScroll(e) {

FILE: miniprogram/components/load-more/load-more.js
  method attached (line 10) | attached() {
  method detached (line 14) | detached() {
  method observe (line 19) | observe() {

FILE: miniprogram/components/painter/lib/downloader.js
  constant SAVED_FILES_KEY (line 7) | const SAVED_FILES_KEY = 'savedFiles';
  constant KEY_TOTAL_SIZE (line 8) | const KEY_TOTAL_SIZE = 'totalSize';
  constant KEY_PATH (line 9) | const KEY_PATH = 'path';
  constant KEY_TIME (line 10) | const KEY_TIME = 'time';
  constant KEY_SIZE (line 11) | const KEY_SIZE = 'size';
  constant MAX_SPACE_IN_B (line 14) | let MAX_SPACE_IN_B = 6 * 1024 * 1024;
  class Dowloader (line 17) | class Dowloader {
    method constructor (line 18) | constructor() {
    method download (line 37) | download(url) {
  function downloadFile (line 72) | function downloadFile(url) {
  function saveFile (line 110) | function saveFile(key, newFileSize, tempFilePath) {
  function reset (line 141) | function reset() {
  function doLru (line 157) | function doLru(size) {
  function removeFiles (line 203) | function removeFiles(pathsShouldDelete) {
  function getFile (line 218) | function getFile(key) {

FILE: miniprogram/components/painter/lib/gradient.js
  function analizeGrad (line 25) | function analizeGrad(string) {
  function radialEffect (line 36) | function radialEffect(width, height, bg, ctx) {
  function analizeLinear (line 46) | function analizeLinear(bg, width, height) {
  function linearEffect (line 88) | function linearEffect(width, height, bg, ctx) {

FILE: miniprogram/components/painter/lib/pen.js
  class Painter (line 4) | class Painter {
    method constructor (line 6) | constructor(ctx, data) {
    method paint (line 25) | paint(callback) {
    method _background (line 38) | _background() {
    method _drawAbsolute (line 66) | _drawAbsolute(view) {
    method _doClip (line 96) | _doClip(borderRadius, width, height) {
    method _doBorder (line 139) | _doBorder(view, width, height) {
    method _preProcess (line 181) | _preProcess ({ view, notClip }) {
    method _doBackground (line 337) | _doBackground(view) {
    method _drawQRCode (line 387) | _drawQRCode(view) {
    method _drawAbsImage (line 398) | _drawAbsImage(view) {
    method _fillAbsText (line 432) | _fillAbsText(view) {
    method _drawAbsRect (line 550) | _drawAbsRect(view) {
    method _doShadow (line 579) | _doShadow(view) {
    method _getAngle (line 594) | _getAngle(angle) {

FILE: miniprogram/components/painter/lib/qrcode.js
  function setmask (line 119) | function setmask(x, y) {
  function putalign (line 136) | function putalign(x, y) {
  function modnn (line 157) | function modnn(x) {
  function appendrs (line 168) | function appendrs(data, dlen, ecbuf, eclen) {
  function ismasked (line 189) | function ismasked(x, y) {
  function applymask (line 205) | function applymask(m) {
  function badruns (line 298) | function badruns(length) {
  function badcheck (line 320) | function badcheck() {
  function genframe (line 375) | function genframe(instring) {
  method ecclevel (line 703) | get ecclevel() {
  method ecclevel (line 707) | set ecclevel(val) {
  method size (line 711) | get size() {
  method size (line 715) | set size(val) {
  method canvas (line 719) | get canvas() {
  method canvas (line 723) | set canvas(el) {

FILE: miniprogram/components/painter/lib/util.js
  function isValidUrl (line 2) | function isValidUrl(url) {
  function equal (line 14) | function equal(a, b) {

FILE: miniprogram/components/painter/painter.js
  constant MAX_PAINT_COUNT (line 9) | const MAX_PAINT_COUNT = 5;
  method isEmpty (line 52) | isEmpty(object) {
  method isNeedRefresh (line 59) | isNeedRefresh(newVal, oldVal) {
  method startPaint (line 66) | startPaint() {
  method downloadImages (line 115) | downloadImages() {
  method saveImgToLocal (line 176) | saveImgToLocal() {
  method getImageInfo (line 193) | getImageInfo(filePath) {
  function setStringPrototype (line 227) | function setStringPrototype(screenK, scale) {

FILE: miniprogram/components/rating/rating.js
  method _handleTap (line 26) | _handleTap (e) {

FILE: miniprogram/components/tab-bar/index.js
  method handleSwitchTab (line 39) | handleSwitchTab (e) {

FILE: miniprogram/components/towxml/audio-player/Audio.js
  class Audio (line 10) | class Audio{
    method constructor (line 11) | constructor(obj){
    method create (line 22) | create(){
    method play (line 68) | play(){
    method pause (line 75) | pause(){
    method stop (line 82) | stop(){
    method destroy (line 89) | destroy(){
    method eventCanplay (line 94) | eventCanplay(){}
    method eventTimeUpdate (line 95) | eventTimeUpdate(){}
    method eventEnded (line 96) | eventEnded(){}
    method eventError (line 97) | eventError(){}
    method eventPause (line 98) | eventPause(){}
    method eventPlay (line 99) | eventPlay(){}
    method eventSeeked (line 100) | eventSeeked(){}
    method eventSeeking (line 101) | eventSeeking(){}
    method eventStop (line 102) | eventStop(){}
    method eventWaiting (line 103) | eventWaiting(){}

FILE: miniprogram/components/towxml/parse/highlight/highlight.js
  function deepFreeze (line 1) | function deepFreeze(e){Object.freeze(e);var n="function"==typeof e;retur...
  function escapeHTML (line 1) | function escapeHTML(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;...
  function inherit (line 1) | function inherit(e){var n,t={},r=Array.prototype.slice.call(arguments,1)...
  function tag (line 1) | function tag(e){return e.nodeName.toLowerCase()}
  function nodeStream (line 1) | function nodeStream(e){var n=[];return function e(t,r){for(var a=t.first...
  function mergeStreams (line 1) | function mergeStreams(e,n,t){var r=0,a="",i=[];function s(){return e.len...
  class HTMLRenderer (line 1) | class HTMLRenderer{constructor(e,n){this.buffer="",this.classPrefix=n.cl...
    method constructor (line 1) | constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(...
    method addText (line 1) | addText(e){this.buffer+=escapeHTML(e)}
    method openNode (line 1) | openNode(e){if(!emitsWrappingTags(e))return;let n=e.kind;e.sublanguage...
    method closeNode (line 1) | closeNode(e){emitsWrappingTags(e)&&(this.buffer+=SPAN_CLOSE)}
    method span (line 1) | span(e){this.buffer+=`<span class="${e}">`}
    method value (line 1) | value(){return this.buffer}
  class TokenTree (line 1) | class TokenTree{constructor(){this.rootNode={children:[]},this.stack=[th...
    method constructor (line 1) | constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}
    method top (line 1) | get top(){return this.stack[this.stack.length-1]}
    method root (line 1) | get root(){return this.rootNode}
    method add (line 1) | add(e){this.top.children.push(e)}
    method openNode (line 1) | openNode(e){let n={kind:e,children:[]};this.add(n),this.stack.push(n)}
    method closeNode (line 1) | closeNode(){if(this.stack.length>1)return this.stack.pop()}
    method closeAllNodes (line 1) | closeAllNodes(){for(;this.closeNode(););}
    method toJSON (line 1) | toJSON(){return JSON.stringify(this.rootNode,null,4)}
    method walk (line 1) | walk(e){return this.constructor._walk(e,this.rootNode)}
    method _walk (line 1) | static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e...
    method _collapse (line 1) | static _collapse(e){e.children&&(e.children.every(e=>"string"==typeof ...
  class TokenTreeEmitter (line 1) | class TokenTreeEmitter extends TokenTree{constructor(e){super(),this.opt...
    method constructor (line 1) | constructor(e){super(),this.options=e}
    method addKeyword (line 1) | addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNo...
    method addText (line 1) | addText(e){""!==e&&this.add(e)}
    method addSublanguage (line 1) | addSublanguage(e,n){let t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}
    method toHTML (line 1) | toHTML(){return new HTMLRenderer(this,this.options).value()}
    method finalize (line 1) | finalize(){}
  function escape (line 1) | function escape(e){return new RegExp(e.replace(/[-\/\\^$*+?.()|[\]{}]/g,...
  function source (line 1) | function source(e){return e&&e.source||e}
  function countMatchGroups (line 1) | function countMatchGroups(e){return new RegExp(e.toString()+"|").exec(""...
  function startsWith (line 1) | function startsWith(e,n){var t=e&&e.exec(n);return t&&0===t.index}
  function join (line 1) | function join(e,n){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)...
  function compileLanguage (line 1) | function compileLanguage(e){function n(n,t){return new RegExp(source(n),...
  function dependencyOnParent (line 1) | function dependencyOnParent(e){return!!e&&(e.endsWithParent||dependencyO...
  function expand_or_clone_mode (line 1) | function expand_or_clone_mode(e){return e.variants&&!e.cached_variants&&...
  function compileKeywords (line 1) | function compileKeywords(e,n){var t={};return"string"==typeof e?r("keywo...
  function scoreForKeyword (line 1) | function scoreForKeyword(e,n){return n?Number(n):commonKeyword(e)?0:1}
  function commonKeyword (line 1) | function commonKeyword(e){return COMMON_KEYWORDS.includes(e.toLowerCase())}
  function c (line 1) | function c(e){return l.noHighlightRe.test(e)}
  function u (line 1) | function u(e,n,t,r){var a={code:n,language:e};R("before:highlight",a);va...
  function d (line 1) | function d(e,n,r,a){var s=n;function c(e,n){var t=R.case_insensitive?n[0...
  function g (line 1) | function g(e,n){n=n||l.languages||Object.keys(t);var r={relevance:0,emit...
  function h (line 1) | function h(e){return l.tabReplace||l.useBR?e.replace(s,function(e,n){ret...
  function f (line 1) | function f(e){var n,t,a,i,s,d=function(e){var n,t=e.className+" ";if(t+=...
  function E (line 1) | function E(){if(!E.called){E.called=!0;var e=document.querySelectorAll("...
  function p (line 1) | function p(e){return e=(e||"").toLowerCase(),t[e]||t[r[e]]}
  function m (line 1) | function m(e){var n=p(e);return n&&!n.disableAutodetect}
  function R (line 1) | function R(e,n){var t=e;a.forEach(function(e){e[t]&&e[t](n)})}

FILE: miniprogram/components/towxml/parse/highlight/languages/c-like.js
  function optional (line 17) | function optional(s) {

FILE: miniprogram/components/towxml/parse/markdown/markdown.js
  function s (line 1) | function s(i,a){if(!t[i]){if(!r[i]){var c="function"==typeof require&&re...
  function n (line 1) | function n(e){return Object.prototype.toString.call(e)}
  function s (line 1) | function s(e){return"[object String]"===n(e)}
  function o (line 1) | function o(e,r){return y.call(e,r)}
  function i (line 1) | function i(e){return Array.prototype.slice.call(arguments,1).forEach(fun...
  function a (line 1) | function a(e,r,t){return[].concat(e.slice(0,r),t,e.slice(r+1))}
  function c (line 1) | function c(e){return!(e>=55296&&e<=57343)&&(!(e>=64976&&e<=65007)&&(6553...
  function l (line 1) | function l(e){if(e>65535){e-=65536;var r=55296+(e>>10),t=56320+(1023&e);...
  function u (line 1) | function u(e,r){var t=0;return o(w,r)?w[r]:35===r.charCodeAt(0)&&A.test(...
  function p (line 1) | function p(e){return e.indexOf("\\")<0?e:e.replace(x,"$1")}
  function h (line 1) | function h(e){return e.indexOf("\\")<0&&e.indexOf("&")<0?e:e.replace(C,f...
  function f (line 1) | function f(e){return q[e]}
  function d (line 1) | function d(e){return D.test(e)?e.replace(/[&<>"]/g,f):e}
  function m (line 1) | function m(e){return e.replace(/[.?*+^$[\]\\(){}|-]/g,"\\$&")}
  function _ (line 1) | function _(e){switch(e){case 9:case 32:return!0}return!1}
  function g (line 1) | function g(e){if(e>=8192&&e<=8202)return!0;switch(e){case 9:case 10:case...
  function b (line 1) | function b(e){return E.test(e)}
  function k (line 1) | function k(e){switch(e){case 33:case 34:case 35:case 36:case 37:case 38:...
  function v (line 1) | function v(e){return e.trim().replace(/\s+/g," ").toUpperCase()}
  function n (line 1) | function n(e){var r=e.trim().toLowerCase();return!g.test(r)||!!b.test(r)}
  function s (line 1) | function s(e){var r=d.parse(e,!0);if(r.hostname&&(!r.protocol||k.indexOf...
  function o (line 1) | function o(e){var r=d.parse(e,!0);if(r.hostname&&(!r.protocol||k.indexOf...
  function i (line 1) | function i(e,r){if(!(this instanceof i))return new i(e,r);r||a.isString(...
  function n (line 1) | function n(){this.ruler=new s;for(var e=0;e<o.length;e++)this.ruler.push...
  function n (line 1) | function n(){this.ruler=new s;for(var e=0;e<o.length;e++)this.ruler.push...
  function n (line 1) | function n(){var e;for(this.ruler=new s,e=0;e<o.length;e++)this.ruler.pu...
  function n (line 1) | function n(){this.rules=s({},a)}
  function n (line 1) | function n(){this.__rules__=[],this.__cache__=null}
  function n (line 1) | function n(e,r){var t,n,s,o;return n=e.bMarks[r]+e.tShift[r],s=e.eMarks[...
  function s (line 1) | function s(e,r){var t,n=e.bMarks[r]+e.tShift[r],s=n,o=e.eMarks[r];if(s+1...
  function o (line 1) | function o(e,r){var t,n,s=e.level+2;for(t=r+2,n=e.tokens.length-2;t<n;t+...
  function n (line 1) | function n(e,r,t,n){var s,i,a,c,l,u,p,h;for(this.src=e,this.md=r,this.en...
  function n (line 2) | function n(e,r){var t=e.bMarks[r]+e.blkIndent,n=e.eMarks[r];return e.src...
  function s (line 2) | function s(e){var r,t=[],n=0,s=e.length,o=0,i=0,a=!1,c=0;for(r=e.charCod...
  function n (line 2) | function n(e){return/^<a[>\s]/i.test(e)}
  function s (line 2) | function s(e){return/^<\/a\s*>/i.test(e)}
  function n (line 2) | function n(e,r){return c[r.toLowerCase()]}
  function s (line 2) | function s(e){var r,t,s=0;for(r=e.length-1;r>=0;r--)t=e[r],"text"!==t.ty...
  function o (line 2) | function o(e){var r,t,n=0;for(r=e.length-1;r>=0;r--)t=e[r],"text"!==t.ty...
  function n (line 2) | function n(e,r,t){return e.substr(0,r)+t+e.substr(r+1)}
  function s (line 2) | function s(e,r){var t,s,c,u,p,h,f,d,m,_,g,b,k,v,y,x,C,A,w,D,q;for(w=[],t...
  function n (line 2) | function n(e,r,t){this.src=e,this.env=t,this.tokens=[],this.inlineMode=!...
  function n (line 2) | function n(e){var r=32|e;return r>=97&&r<=122}
  function n (line 2) | function n(e,r,t,n){this.src=e,this.env=t,this.md=r,this.tokens=n,this.p...
  function n (line 2) | function n(e){switch(e){case 10:case 33:case 35:case 36:case 37:case 38:...
  function n (line 2) | function n(e,r,t){this.type=e,this.tag=r,this.attrs=null,this.map=null,t...
  function n (line 3) | function n(e){return Array.prototype.slice.call(arguments,1).forEach(fun...
  function s (line 3) | function s(e){return Object.prototype.toString.call(e)}
  function o (line 3) | function o(e){return"[object String]"===s(e)}
  function i (line 3) | function i(e){return"[object Object]"===s(e)}
  function a (line 3) | function a(e){return"[object RegExp]"===s(e)}
  function c (line 3) | function c(e){return"[object Function]"===s(e)}
  function l (line 3) | function l(e){return e.replace(/[.?*+^$[\]\\(){}|-]/g,"\\$&")}
  function u (line 3) | function u(e){return Object.keys(e||{}).reduce(function(e,r){return e||b...
  function p (line 3) | function p(e){e.__index__=-1,e.__text_cache__=""}
  function h (line 3) | function h(e){return function(r,t){var n=r.slice(t);return e.test(n)?n.m...
  function f (line 3) | function f(){return function(e,r){r.normalize(e)}}
  function d (line 3) | function d(r){function t(e){return e.replace("%TLDS%",s.src_tlds)}functi...
  function m (line 3) | function m(e,r){var t=e.__index__,n=e.__last_index__,s=e.__text_cache__....
  function _ (line 3) | function _(e,r){var t=new m(e,r);return e.__compiled__[t.schema].normali...
  function g (line 3) | function g(e,r){if(!(this instanceof g))return new g(e,r);r||u(e)&&(r=e,...
  function n (line 4) | function n(e){var r,t,n=o[e];if(n)return n;for(n=o[e]=[],r=0;r<128;r++)t...
  function s (line 4) | function s(e,r){var t;return"string"!=typeof r&&(r=s.defaultChars),t=n(r...
  function n (line 4) | function n(e){var r,t,n=o[e];if(n)return n;for(n=o[e]=[],r=0;r<128;r++)t...
  function s (line 4) | function s(e,r,t){var o,i,a,c,l,u="";for("string"!=typeof r&&(t=r,r=s.de...
  function n (line 4) | function n(){this.protocol=null,this.slashes=null,this.auth=null,this.po...
  function s (line 4) | function s(e,r){if(e&&e instanceof n)return e;var t=new n;return t.parse...
  function o (line 4) | function o(e){throw new RangeError(w[e])}
  function i (line 4) | function i(e,r){for(var t=e.length,n=[];t--;)n[t]=r(e[t]);return n}
  function a (line 4) | function a(e,r){var t=e.split("@"),n="";return t.length>1&&(n=t[0]+"@",e...
  function c (line 4) | function c(e){for(var r,t,n=[],s=0,o=e.length;s<o;)r=e.charCodeAt(s++),r...
  function l (line 4) | function l(e){return i(e,function(e){var r="";return e>65535&&(e-=65536,...
  function u (line 4) | function u(e){return e-48<10?e-22:e-65<26?e-65:e-97<26?e-97:36}
  function p (line 4) | function p(e,r){return e+22+75*(e<26)-((0!=r)<<5)}
  function h (line 4) | function h(e,r,t){var n=0;for(e=t?D(e/700):e>>1,e+=D(e/r);e>455;n+=36)e=...
  function f (line 4) | function f(e){var r,t,n,s,i,a,c,p,f,d,m=[],_=e.length,g=0,b=128,k=72;for...
  function d (line 4) | function d(e){var r,t,n,s,i,a,l,u,f,d,m,_,g,b,k,v=[];for(e=c(e),_=e.leng...
  function m (line 4) | function m(e){return a(e,function(e){return C.test(e)?f(e.slice(4).toLow...
  function _ (line 4) | function _(e){return a(e,function(e){return A.test(e)?"xn--"+d(e):e})}

FILE: miniprogram/components/towxml/parse/markdown/plugins/emoji.js
  function a (line 2) | function a(e,n,o){function i(_,t){if(!n[_]){if(!e[_]){var s="function"==...
  function e (line 2) | function e(a){return a.replace(/[.?*+^$[\]\\(){}|-]/g,"\\$&")}
  function r (line 2) | function r(a,o,r){var _,t=0,l=[];return a.replace(i,function(o,i,c){var ...

FILE: miniprogram/components/towxml/parse/markdown/plugins/ins.js
  function r (line 1) | function r(s,f){if(!t[s]){if(!n[s]){var u="function"==typeof require&&re...
  function n (line 1) | function n(e,n){var t,o,r,i,s,f=e.pos,u=e.src.charCodeAt(f);if(n)return!...
  function t (line 1) | function t(e){var n,t,o,r,i,s=[],f=e.delimiters,u=e.delimiters.length;fo...

FILE: miniprogram/components/towxml/parse/markdown/plugins/mark.js
  function r (line 1) | function r(s,f){if(!t[s]){if(!n[s]){var u="function"==typeof require&&re...
  function n (line 1) | function n(e,n){var t,o,r,i,s,f=e.pos,u=e.src.charCodeAt(f);if(n)return!...
  function t (line 1) | function t(e){var n,t,o,r,i,s=[],f=e.delimiters,u=e.delimiters.length;fo...

FILE: miniprogram/components/towxml/parse/markdown/plugins/sub.js
  function t (line 1) | function t(i,u){if(!o[i]){if(!r[i]){var f="function"==typeof require&&re...
  function o (line 1) | function o(e,r){var o,t,s,i=e.posMax,u=e.pos;if(126!==e.src.charCodeAt(u...

FILE: miniprogram/components/towxml/parse/markdown/plugins/sup.js
  function t (line 1) | function t(i,p){if(!o[i]){if(!r[i]){var u="function"==typeof require&&re...
  function o (line 1) | function o(e,r){var o,t,s,i=e.posMax,p=e.pos;if(94!==e.src.charCodeAt(p)...

FILE: miniprogram/components/towxml/parse/markdown/plugins/todo.js
  function e (line 1) | function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof requi...
  function attrSet (line 56) | function attrSet(token, name, value) {
  function parentToken (line 67) | function parentToken(tokens, index) {
  function isTodoItem (line 77) | function isTodoItem(tokens, index) {
  function todoify (line 84) | function todoify(token, TokenConstructor) {
  function makeCheckbox (line 104) | function makeCheckbox(token, TokenConstructor) {
  function beginLabel (line 118) | function beginLabel(TokenConstructor) {
  function endLabel (line 124) | function endLabel(TokenConstructor) {
  function afterLabel (line 130) | function afterLabel(content, id, TokenConstructor) {
  function isInline (line 137) | function isInline(token) { return token.type === 'inline'; }
  function isParagraph (line 138) | function isParagraph(token) { return token.type === 'paragraph_open'; }
  function isListItem (line 139) | function isListItem(token) { return token.type === 'list_item_open'; }
  function startsWithTodoMarkdown (line 141) | function startsWithTodoMarkdown(token) {

FILE: miniprogram/components/towxml/parse/parse2/Parser.js
  function Parser (line 2) | function Parser(t,e){var s=this;return s._tagname="",s._attribname="",s....

FILE: miniprogram/components/towxml/parse/parse2/Tokenizer.js
  function whitespace (line 2) | function whitespace(t){return" "===t||"\n"===t||"\t"===t||"\f"===t||"\r"...
  function ifElseState (line 2) | function ifElseState(t,e,s){var i=t.toLowerCase();return t===i?function(...
  function consumeSpecialNameChar (line 2) | function consumeSpecialNameChar(t,e){var s=t.toLowerCase();return functi...
  function t (line 2) | function t(t,e){this._state=1,this._buffer="",this._sectionStart=0,this....

FILE: miniprogram/components/towxml/parse/parse2/domhandler/index.js
  function t (line 2) | function t(t,e,o){this.dom=[],this._done=!1,this._tagStack=[],this._last...

FILE: miniprogram/components/towxml/parse/parse2/domhandler/node.js
  function r (line 2) | function r(){this.constructor=t}
  function e (line 2) | function e(e){this.type=e,this.parent=null,this.prev=null,this.next=null...
  function t (line 2) | function t(t,n){var r=e.call(this,t)||this;return r.data=n,r}
  function t (line 2) | function t(t,n){var r=e.call(this,"directive",n)||this;return r.name=t,r}
  function t (line 2) | function t(t,n){var r=e.call(this,t)||this;return r.children=n,r}
  function t (line 2) | function t(t,n){var r=e.call(this,"script"===t?"script":"style"===t?"sty...

FILE: miniprogram/components/towxml/parse/parse2/entities/decode.js
  function getStrictDecoder (line 2) | function getStrictDecoder(e){var t=Object.keys(e).join("|"),r=getReplace...
  function getReplacer (line 2) | function getReplacer(e){return function(t){return"#"===t.charAt(1)?"X"==...
  function e (line 2) | function e(e){return";"!==e.substr(-1)&&(e+=";"),s(e)}

FILE: miniprogram/components/towxml/parse/parse2/entities/decode_codepoint.js
  function decodeCodePoint (line 2) | function decodeCodePoint(e){if(e>=55296&&e<=57343||e>1114111)return"�";e...

FILE: miniprogram/components/towxml/parse/parse2/entities/encode.js
  function getInverseObj (line 2) | function getInverseObj(e){return Object.keys(e).sort().reduce(function(r...
  function getInverseReplacer (line 2) | function getInverseReplacer(e){var r=[],t=[];return Object.keys(e).forEa...
  function singleCharReplacer (line 2) | function singleCharReplacer(e){return"&#x"+e.charCodeAt(0).toString(16)....
  function astralReplacer (line 2) | function astralReplacer(e,r){return"&#x"+(1024*(e.charCodeAt(0)-55296)+e...
  function getInverse (line 2) | function getInverse(e,r){return function(t){return t.replace(r,function(...
  function escape (line 2) | function escape(e){return e.replace(reXmlChars,singleCharReplacer).repla...

FILE: miniprogram/components/towxml/parse/parse2/entities/index.js
  function decode (line 2) | function decode(e,d){return(!d||d<=0?decode_1.decodeXML:decode_1.decodeH...
  function decodeStrict (line 2) | function decodeStrict(e,d){return(!d||d<=0?decode_1.decodeXML:decode_1.d...
  function encode (line 2) | function encode(e,d){return(!d||d<=0?encode_1.encodeXML:encode_1.encodeH...

FILE: miniprogram/components/towxml/parse/parse2/index.js
  function parseDOM (line 2) | function parseDOM(r,e){var a=new domhandler_1.DomHandler(void 0,e);retur...

FILE: miniprogram/libs/av-live-query-core-min.js
  function e (line 1) | function e(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{...
  function n (line 1) | function n(t){return t&&t.__esModule?t:{default:t}}
  function r (line 1) | function r(t){if(!Object(i.a)(t))return[];if(o.m)return Object(o.m)(t);v...
  function r (line 1) | function r(t){var e="[object "+t+"]";return function(t){return i.t.call(...
  function r (line 1) | function r(t,e,n){return i.a.iteratee!==a.a?i.a.iteratee(t,e):Object(o.a...
  function r (line 1) | function r(t,e){return e=null==e?t.length-1:+e,function(){for(var n=Math...
  function r (line 1) | function r(t){return t instanceof r?t:this instanceof r?void(this._wrapp...
  function r (line 1) | function r(t,e){return null!=t&&i.i.call(t,e)}
  function r (line 1) | function r(t,e){if(this instanceof r?this.constructor:void 0){var n=new ...
  function r (line 1) | function r(t){var e=typeof t;return"function"===e||"object"===e&&!!t}
  function r (line 1) | function r(t,e,n){e=Object(i.a)(e,n);var r,s;if(Object(o.a)(t))for(r=0,s...
  function r (line 1) | function r(t){return(r="function"==typeof Symbol&&"symbol"==typeof Symbo...
  function i (line 1) | function i(){return!("undefined"==typeof window||!window.process||"rende...
  function o (line 1) | function o(e){if(e[0]=(this.useColors?"%c":"")+this.namespace+(this.useC...
  function a (line 1) | function a(){var t;return"object"===("undefined"==typeof console?"undefi...
  function s (line 1) | function s(t){try{t?e.storage.setItem("debug",t):e.storage.removeItem("d...
  function u (line 1) | function u(){var t;try{t=e.storage.getItem("debug")}catch(t){}return!t&&...
  function r (line 1) | function r(t){for(var e=Object(i.a)(t),n=e.length,r=Array(n),o=0;o<n;o++...
  function r (line 1) | function r(t,e,n,u){if(u=u||[],e||0===e){if(e<=0)return u.concat(t)}else...
  function r (line 1) | function r(t,e,n){e=Object(i.a)(e,n);for(var r=!Object(o.a)(t)&&Object(a...
  function r (line 1) | function r(t){if(!Object(i.a)(t))return[];var e=[];for(var n in t)e.push...
  function r (line 1) | function r(t){return i.a.toPath(t)}
  function r (line 1) | function r(t,e,n){if(void 0===e)return t;switch(null==n?3:n){case 1:retu...
  function r (line 1) | function r(t,e,n){var r=[];return e=Object(i.a)(e,n),Object(o.a)(t,funct...
  function r (line 1) | function r(t,e,n,r){return Object(i.a)(t)||(t=Object(o.a)(t)),("number"!...
  function r (line 1) | function r(e){"@babel/helpers - typeof";return t.exports=r="function"==t...
  function r (line 1) | function r(t){return t=Object(i.a)({},t),function(e){return Object(o.a)(...
  function r (line 1) | function r(t,e){return function(n,r,a){var s=e?[[],[]]:{};return r=Objec...
  function r (line 1) | function r(t){return null==t?void 0===t?u:s:c&&c in Object(t)?o(t):a(t)}
  function n (line 1) | function n(t){return null!=t&&"object"==typeof t}
  function r (line 1) | function r(t){return null!=t&&Object(o.a)(t.getInt8)&&Object(a.a)(t.buff...
  function r (line 1) | function r(t){var e=Object(i.a)(t);return function(n){if(null==n)return!...
  function r (line 1) | function r(t,e){return function(n){var r=arguments.length;if(e&&(n=Objec...
  function r (line 1) | function r(t,e){for(var n=e.length,r=0;r<n;r++){if(null==t)return;t=t[e[...
  function r (line 1) | function r(t){return t}
  function r (line 1) | function r(t){return t=Object(o.a)(t),function(e){return Object(i.a)(e,t)}}
  function r (line 1) | function r(t){return function(){return!t.apply(this,arguments)}}
  function r (line 1) | function r(t,e){return Object(i.a)(t,Object(o.a)(e))}
  function t (line 1) | function t(){}
  function r (line 1) | function r(t){return void 0===t}
  function r (line 1) | function r(t){return!0===t||!1===t||"[object Boolean]"===i.t.call(t)}
  function r (line 1) | function r(t){return Object(o.a)(t)&&Object(i.g)(t)}
  function r (line 1) | function r(t){return i.l?Object(i.l)(t)&&!Object(o.a)(t):Object(s.a)(t)&...
  function r (line 1) | function r(t){return function(){return t}}
  function r (line 1) | function r(t){return function(e){var n=t(e);return"number"==typeof n&&n>...
  function r (line 1) | function r(t){return function(e){return null==e?void 0:e[t]}}
  function r (line 1) | function r(t){for(var e={},n=t.length,r=0;r<n;++r)e[t[r]]=!0;return{cont...
  function i (line 1) | function i(t,e){e=r(e);var n=o.n.length,i=t.constructor,u=Object(a.a)(i)...
  function r (line 1) | function r(t,e){var n=Object(i.a)(e),r=n.length;if(null==t)return!r;for(...
  function r (line 1) | function r(t){for(var e={},n=Object(i.a)(t),r=0,o=n.length;r<o;r++)e[t[n...
  function r (line 1) | function r(t){var e=[];for(var n in t)Object(i.a)(t[n])&&e.push(n);retur...
  function r (line 1) | function r(){return function(){}}
  function i (line 1) | function i(t){if(!Object(o.a)(t))return{};if(a.j)return Object(a.j)(t);v...
  function r (line 1) | function r(t){return Object(i.a)(t)?Object(o.a)(t)?t.slice():Object(a.a)...
  function r (line 1) | function r(t,e,n){var r=Object(o.a)(t,Object(i.a)(e));return Object(a.a)...
  function r (line 1) | function r(t){return Object(o.a)(t)?t:[t]}
  function r (line 1) | function r(t,e,n){return null==t?i.a:Object(o.a)(t)?Object(f.a)(t,e,n):O...
  function r (line 1) | function r(t,e){return Object(o.a)(t,e,1/0)}
  function r (line 1) | function r(){}
  function r (line 1) | function r(t,e){return null==e&&(e=t,t=0),t+Math.floor(Math.random()*(e-...
  function r (line 1) | function r(t){var e=function(e){return t[e]},n="(?:"+Object(i.a)(t).join...
  function r (line 1) | function r(t,e,n,r,a){if(!(r instanceof e))return t.apply(n,a);var s=Obj...
  function r (line 1) | function r(t,e){var n;return function(){return--t>0&&(n=e.apply(this,arg...
  function r (line 1) | function r(t,e,n){e=Object(i.a)(e,n);for(var r,a=Object(o.a)(t),s=0,u=a....
  function r (line 1) | function r(t){return function(e,n,r){n=Object(i.a)(n,r);for(var a=Object...
  function r (line 1) | function r(t,e,n,r){n=Object(i.a)(n,r,1);for(var a=n(e),s=0,u=Object(o.a...
  function r (line 1) | function r(t,e,n){return function(r,s,u){var c=0,f=Object(i.a)(r);if("nu...
  function r (line 1) | function r(t,e,n){var r=Object(i.a)(t)?o.a:a.a,s=r(t,e,n);if(void 0!==s&...
  function r (line 1) | function r(t){var e=function(e,n,r,a){var s=!Object(i.a)(e)&&Object(o.a)...
  function r (line 1) | function r(t,e,n){var r,u,c=-1/0,f=-1/0;if(null==e||"number"==typeof e&&...
  function r (line 1) | function r(t,e,n){if(null==e||n)return Object(i.a)(t)||(t=Object(a.a)(t)...
  function r (line 1) | function r(t,e,n){return i.q.call(t,0,Math.max(0,t.length-(null==e||n?1:...
  function r (line 1) | function r(t,e,n){return i.q.call(t,null==e||n?1:e)}
  function r (line 1) | function r(t,e,n,r){Object(i.a)(e)||(r=n,n=e,e=!1),null!=n&&(n=Object(o....
  function r (line 1) | function r(t){for(var e=t&&Object(i.a)(t,o.a).length||0,n=Array(e),r=0;r...
  function r (line 1) | function r(t,e){return t._chain?Object(i.a)(e).chain():e}
  function r (line 1) | function r(t,e,n){var r=e&&n||0;"string"==typeof t&&(e="binary"===t?new ...
  function r (line 1) | function r(){}
  function i (line 1) | function i(t,e,n){this.fn=t,this.context=e,this.once=n||!1}
  function o (line 1) | function o(){this._events=new r,this._eventsCount=0}
  method async (line 1) | get async(){return a("storage").async}
  function n (line 1) | function n(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Ar...
  function n (line 1) | function n(t){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(t...
  function r (line 1) | function r(t,e){if(t){if("string"==typeof t)return i(t,e);var n=Object.p...
  function r (line 1) | function r(t,e){var n=-1,r=t.length,o=r-1;for(e=void 0===e?r:e;++n<e;){v...
  function r (line 1) | function r(t){return null==t?[]:i(t,o(t))}
  function n (line 1) | function n(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=r}
  function n (line 1) | function n(t,e){return function(n){return t(e(n))}}
  function r (line 1) | function r(t){return null===t}
  function r (line 1) | function r(t){return!(!t||1!==t.nodeType)}
  function r (line 1) | function r(t){return!Object(o.a)(t)&&Object(i.f)(t)&&!isNaN(parseFloat(t))}
  function r (line 1) | function r(t){if(null==t)return!0;var e=Object(i.a)(t);return"number"==t...
  function r (line 1) | function r(t,e,n,r){if(t===e)return 0!==t||1/t==1/e;if(null==t||null==e)...
  function i (line 1) | function i(t,e,n,o){t instanceof a.a&&(t=t._wrapped),e instanceof a.a&&(...
  function o (line 1) | function o(t,e){return r(t,e)}
  function r (line 1) | function r(t){return new Uint8Array(t.buffer||t,t.byteOffset||0,Object(i...
  function r (line 1) | function r(t){for(var e=Object(i.a)(t),n=e.length,r=Array(n),o=0;o<n;o++...
  function r (line 1) | function r(t,e){var n=Object(i.a)(t);return e&&Object(o.a)(n,e),n}
  function r (line 1) | function r(t,e){return e(t),t}
  function r (line 1) | function r(t,e){e=Object(o.a)(e);for(var n=e.length,r=0;r<n;r++){var a=e...
  function r (line 1) | function r(t,e,n){e=Object(i.a)(e,n);for(var r=Object(o.a)(t),a=r.length...
  function r (line 1) | function r(t){return null==t?i.a:function(e){return Object(o.a)(t,e)}}
  function r (line 1) | function r(t,e,n){var r=Array(Math.max(0,t));e=Object(i.a)(e,n,1);for(va...
  function r (line 1) | function r(t){return"\\"+u[t]}
  function i (line 1) | function i(t,e,n){!e&&n&&(e=n),e=Object(o.a)({},e,a.a.templateSettings);...
  function r (line 1) | function r(t,e,n){e=Object(o.a)(e);var r=e.length;if(!r)return Object(i....
  function r (line 1) | function r(t){var e=++i+"";return t?t+e:e}
  function r (line 1) | function r(t){var e=Object(i.a)(t);return e._chain=!0,e}
  function r (line 1) | function r(t,e){var n=function(r){var o=n.cache,a=""+(e?e.apply(this,arg...
  function r (line 1) | function r(t,e,n){var r,o,a,s,u=0;n||(n={});var c=function(){u=!1===n.le...
  function r (line 1) | function r(t,e,n){var r,a,s,u,c,f=function(){var i=Object(o.a)()-a;e>i?r...
  function r (line 1) | function r(t,e){return Object(i.a)(e,t)}
  function r (line 1) | function r(){var t=arguments,e=t.length-1;return function(){for(var n=e,...
  function r (line 1) | function r(t,e){return function(){if(--t<1)return e.apply(this,arguments)}}
  function r (line 1) | function r(t,e){return Object(i.a)(t,Object(o.a)(e))}
  function r (line 1) | function r(t,e,n){return Object(i.a)(t,Object(o.a)(Object(a.a)(e)),n)}
  function r (line 1) | function r(t,e,n){e=Object(i.a)(e,n);for(var r=!Object(o.a)(t)&&Object(a...
  function r (line 1) | function r(t,e,n){e=Object(i.a)(e,n);for(var r=!Object(o.a)(t)&&Object(a...
  function r (line 1) | function r(t,e){return Object(i.a)(t,Object(o.a)(e))}
  function r (line 1) | function r(t,e,n){var r,u,c=1/0,f=1/0;if(null==e||"number"==typeof e&&"o...
  function r (line 1) | function r(t){return Object(i.a)(t,1/0)}
  function r (line 1) | function r(t,e,n){var r=0;return e=Object(i.a)(e,n),Object(o.a)(Object(a...
  function r (line 1) | function r(t){return t?Object(i.a)(t)?o.q.call(t):Object(a.a)(t)?t.match...
  function r (line 1) | function r(t){return null==t?0:Object(i.a)(t)?t.length:Object(o.a)(t).le...
  function r (line 1) | function r(t,e,n){return e in n}
  function r (line 1) | function r(t,e,n){return null==t||t.length<1?null==e||n?void 0:[]:null==...
  function r (line 1) | function r(t,e,n){return null==t||t.length<1?null==e||n?void 0:[]:null==...
  function r (line 1) | function r(t){return Object(i.a)(t,Boolean)}
  function r (line 1) | function r(t,e){return Object(i.a)(t,e,!1)}
  function r (line 1) | function r(t){for(var e=[],n=arguments.length,r=0,a=Object(i.a)(t);r<a;r...
  function r (line 1) | function r(t,e){for(var n={},r=0,o=Object(i.a)(t);r<o;r++)e?n[t[r]]=e[r]...
  function r (line 1) | function r(t,e,n){null==e&&(e=t||0,t=0),n||(n=e<t?-1:1);for(var r=Math.m...
  function r (line 1) | function r(t,e){if(null==e||e<1)return[];for(var n=[],r=0,o=t.length;r<o...
  function r (line 1) | function r(t){return Object(o.a)(Object(a.a)(t),function(e){var n=i.a[e]...
  function n (line 1) | function n(t,e){var n=e||0,i=r;return[i[t[n++]],i[t[n++]],i[t[n++]],i[t[...
  function r (line 1) | function r(t){function e(t){for(var e=0,n=0;n<t.length;n++)e=(e<<5)-e+t....
  function n (line 1) | function n(t){if(t=String(t),!(t.length>100)){var e=/^(-?(?:\d+)?\.?\d+)...
  function r (line 1) | function r(t){var e=Math.abs(t);return e>=c?Math.round(t/c)+"d":e>=u?Mat...
  function i (line 1) | function i(t){var e=Math.abs(t);return e>=c?o(t,e,c,"day"):e>=u?o(t,e,u,...
  function o (line 1) | function o(t,e,n,r){var i=e>=1.5*n;return Math.round(t/n)+" "+r+(i?"s":"")}
  function r (line 1) | function r(t){var e,n,r,i,s;if(g(t))return{};var u=(0,o.default)(t).call...
  function r (line 1) | function r(t){var e=this;this.AV=t,this.lockedUntil=0,o.getAsync("server...
  function r (line 1) | function r(t){var e=i();return function(){var n,r=(0,b.default)(t);if(e)...
  function i (line 1) | function i(){if("undefined"==typeof Reflect||!v)return!1;if(v.sham)retur...
  function o (line 1) | function o(t,e){var n=void 0!==d&&p(t)||t["@@iterator"];if(!n){if(Array....
  function a (line 1) | function a(t,e){var n;if(t){if("string"==typeof t)return s(t,e);var r=l(...
  function s (line 1) | function s(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Ar...
  function u (line 1) | function u(t,e,n){var r=arguments.length>3&&void 0!==arguments[3]?argume...
  function c (line 1) | function c(t){var e,n=C(unescape(encodeURIComponent(t))),r="",i=o(n);try...
  function f (line 1) | function f(t){return"undefined"!=typeof Blob&&t instanceof Blob}
  function t (line 1) | function t(e,n,r,i){var o,a,s=this;(0,_.default)(this,t),this.uploadInfo...
  function e (line 1) | function e(t,r,i,o){var a;return(0,_.default)(this,e),a=n.call(this,t,r,...
  function t (line 1) | function t(){}
  function r (line 1) | function r(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function r (line 1) | function r(e,n){var a;return t.exports=r=i?o(a=i).call(a):function(t,e){...
  function r (line 1) | function r(t,e){if(e&&("object"===i(e)||"function"==typeof e))return e;i...
  function n (line 1) | function n(t){if(void 0===t)throw new ReferenceError("this hasn't been i...
  function r (line 1) | function r(e){var n;return t.exports=r=i?o(n=a).call(n):function(t){retu...
  function n (line 1) | function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function r (line 1) | function r(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function i (line 1) | function i(t,e,n){return e&&r(t.prototype,e),n&&r(t,n),o(t,"prototype",{...
  function n (line 1) | function n(t){return!!t.constructor&&"function"==typeof t.constructor.is...
  function r (line 1) | function r(t){return"function"==typeof t.readFloatLE&&"function"==typeof...
  function r (line 7) | function r(t,e){return t&&t[e]?v.isFunction(t[e])?t[e]():t[e]:null}
  function r (line 7) | function r(t,e){return i(t)||o(t,e)||a(t,e)||s()}
  function r (line 7) | function r(t){if(i(t))return t}
  function r (line 7) | function r(t,e){var n=null==t?null:void 0!==i&&o(t)||t["@@iterator"];if(...
  function r (line 7) | function r(t,e){var n;if(t){if("string"==typeof t)return a(t,e);var r=i(...
  function n (line 7) | function n(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Ar...
  function n (line 7) | function n(){throw new TypeError("Invalid attempt to destructure non-ite...
  function r (line 7) | function r(t,e,n){return e in t?i(t,e,{value:n,enumerable:!0,configurabl...
  function r (line 7) | function r(t){var e=t.name,n=t.value,r=t.version;this.name=e,this.value=...
  function r (line 7) | function r(t){return t&&"object"===(0,d.default)(t)&&"default"in t?t.def...
  function i (line 7) | function i(t,e){var n=(0,b.default)(t);if(_.default){var r=(0,_.default)...
  function o (line 7) | function o(t){for(var e=1;e<arguments.length;e++){var n=null!=arguments[...
  function a (line 7) | function a(t,e){var n=(0,b.default)(t);if(_.default){var r=(0,_.default)...
  function s (line 7) | function s(t){for(var e=1;e<arguments.length;e++){var n=null!=arguments[...
  function u (line 7) | function u(t,e){var n=(0,b.default)(t);if(_.default){var r=(0,_.default)...
  function c (line 7) | function c(t){for(var e=1;e<arguments.length;e++){var n=null!=arguments[...
  function t (line 7) | function t(t,e){this.originalValue=t,"number"==typeof e&&(this.expiredAt...
  function t (line 7) | function t(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0...
  function e (line 7) | function e(e,n){var r;return r=t.call(this)||this,r.init(),r._protocol=n...
  function e (line 7) | function e(e,n){var r,i,o=n.format,a=n.version;re("initializing Connecti...
  function e (line 7) | function e(t){return n.apply(this,arguments)}
  function e (line 7) | function e(n){var r,i,o=n.plugins,a=B(n,["plugins"]);he("initializing Re...
  function t (line 7) | function t(t){return e.apply(this,arguments)}
  function t (line 7) | function t(t){return e.apply(this,arguments)}
  function t (line 7) | function t(t){return e.apply(this,arguments)}
  function n (line 12) | function n(n){if("string"==typeof n&&(n=e.TYPES[n]),void 0===n.defaultVa...
  function r (line 12) | function r(t,n){if(t&&"number"==typeof t.low&&"number"==typeof t.high&&"...
  function i (line 12) | function i(t,n){var r=n.readVarint32(),o=7&r,a=r>>>3;switch(o){case e.WI...
  function i (line 12) | function i(t,e){throw Error("Illegal value for "+o.toString(!0)+" of typ...
  function r (line 12) | function r(n,i,o,a){if(null===n||"object"!=typeof n){if(a&&a instanceof ...
  function r (line 12) | function r(t,e){throw Error("Illegal value for "+i.toString(!0)+" of typ...
  function i (line 12) | function i(t){t.messages&&t.messages.forEach(function(e){e.syntax=t.synt...
  function n (line 12) | function n(t){var e=0;return{next:function(){return e<t.length?{done:!1,...
  function e (line 18) | function e(t){var e=0;return function(){return e<t.length?t.charCodeAt(e...
  function n (line 18) | function n(){var t=[],e=[];return function(){if(0===arguments.length)ret...
  function r (line 18) | function r(t,e,n,r,i){var o,a,s=8*i-r-1,u=(1<<s)-1,c=u>>1,f=-7,l=n?i-1:0...
  function i (line 18) | function i(t,e,n,r,i,o){var a,s,u,c=8*o-i-1,f=(1<<c)-1,l=f>>1,h=23===i?M...
  function r (line 18) | function r(t){throw Error("Illegal character code: "+t)}
  function t (line 23) | function t(t,e,n){this.low=0|t,this.high=0|e,this.unsigned=!!n}
  function e (line 23) | function e(t){return!0===(t&&t.__isLong__)}
  function n (line 23) | function n(t,e){var n,r,o;return e?(t>>>=0,(o=0<=t&&t<256)&&(r=u[t])?r:(...
  function r (line 23) | function r(t,e){if(isNaN(t)||!isFinite(t))return e?v:p;if(e){if(t<0)retu...
  function i (line 23) | function i(e,n,r){return new t(e,n,r)}
  function o (line 23) | function o(t,e,n){if(0===t.length)throw Error("empty string");if("NaN"==...
  function a (line 23) | function a(e){return e instanceof t?e:"number"==typeof e?r(e):"string"==...
  function r (line 23) | function r(){}
  function i (line 23) | function i(t,e,n){this.fn=t,this.context=e,this.once=n||!1}
  function o (line 23) | function o(t,e,n,r,o){if("function"!=typeof n)throw new TypeError("The l...
  function a (line 23) | function a(t,e){0==--t._eventsCount?t._events=new r:delete t._events[e]}
  function s (line 23) | function s(){this._events=new r,this._eventsCount=0}
  function e (line 23) | function e(t,e,n){return Object.defineProperty(t,e,{value:n,enumerable:!...
  function n (line 23) | function n(t,e,n,r){var o=e&&e.prototype instanceof i?e:i,a=Object.creat...
  function r (line 23) | function r(t,e,n){try{return{type:"normal",arg:t.call(e,n)}}catch(t){ret...
  function i (line 23) | function i(){}
  function o (line 23) | function o(){}
  function a (line 23) | function a(){}
  function s (line 23) | function s(t){["next","throw","return"].forEach(function(n){e(t,n,functi...
  function u (line 23) | function u(t,e){function n(i,o,a,s){var u=r(t[i],t,o);if("throw"!==u.typ...
  function c (line 23) | function c(t,e,n){var i=O;return function(o,a){if(i===S)throw new Error(...
  function f (line 23) | function f(t,e){var n=t.iterator[e.method];if(n===g){if(e.delegate=null,...
  function l (line 23) | function l(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.f...
  function h (line 23) | function h(t){var e=t.completion||{};e.type="normal",delete e.arg,t.comp...
  function d (line 23) | function d(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(l,this),this.r...
  function p (line 23) | function p(t){if(t){var e=t[_];if(e)return e.call(t);if("function"==type...
  function v (line 23) | function v(){return{value:g,done:!0}}
  function e (line 23) | function e(e,r){return o.type="throw",o.arg=t,n.next=e,r&&(n.method="nex...
  function n (line 23) | function n(t,e,n,r,i,o,a){try{var s=t[o](a),u=s.value}catch(t){return vo...
  function r (line 23) | function r(t){return function(){var e=this,r=arguments;return new Promis...
  function r (line 23) | function r(t){return i(t)||o(t)||a(t)||s()}
  function r (line 23) | function r(t){if(Array.isArray(t))return i(t)}
  function n (line 23) | function n(){throw new TypeError("Invalid attempt to spread non-iterable...
  function n (line 23) | function n(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enume...
  function r (line 23) | function r(t,e){if(null==t)return{};var n,r,o=i(t,e);if(Object.getOwnPro...
  function n (line 23) | function n(t,e){if(null==t)return{};var n,r,i={},o=Object.keys(t);for(r=...
  function n (line 23) | function n(t){if(void 0===t)throw new ReferenceError("this hasn't been i...
  function n (line 23) | function n(t,e){t.prototype=Object.create(e.prototype),t.prototype.const...
  function r (line 23) | function r(t){return(a(t)?i:o)(t)}
  function r (line 23) | function r(t){return o(i(t))}
  function n (line 23) | function n(t,e){var n=-1,r=t.length;for(e||(e=Array(r));++n<r;)e[n]=t[n]...
  function n (line 23) | function n(t,e){return t+r(i()*(e-t+1))}
  function r (line 23) | function r(t){return i(o(t))}
  function r (line 23) | function r(t,e){return i(e,function(e){return t[e]})}
  function n (line 23) | function n(t,e){for(var n=-1,r=null==t?0:t.length,i=Array(r);++n<r;)i[n]...
  function r (line 23) | function r(t){return a(t)?i(t):o(t)}
  function r (line 23) | function r(t,e){var n=a(t),r=!n&&o(t),f=!n&&!r&&s(t),h=!n&&!r&&!f&&c(t),...
  function n (line 23) | function n(t,e){for(var n=-1,r=Array(t);++n<t;)r[n]=e(n);return r}
  function r (line 23) | function r(t){return o(t)&&i(t)==a}
  function r (line 23) | function r(t){var e=a.call(t,u),n=t[u];try{t[u]=void 0;var r=!0}catch(t)...
  function n (line 23) | function n(t){return i.call(t)}
  function n (line 23) | function n(){return!1}
  function n (line 23) | function n(t,e){var n=typeof t;return!!(e=null==e?r:e)&&("number"==n||"s...
  function r (line 23) | function r(t){return a(t)&&o(t.length)&&!!s[i(t)]}
  function n (line 23) | function n(t){return function(e){return t(e)}}
  function r (line 23) | function r(t){if(!i(t))return o(t);var e=[];for(var n in Object(t))s.cal...
  function n (line 23) | function n(t){var e=t&&t.constructor;return t===("function"==typeof e&&e...
  function r (line 23) | function r(t){return null!=t&&o(t.length)&&!i(t)}
  function r (line 23) | function r(t){if(!o(t))return!1;var e=i(t);return e==s||e==u||e==a||e==c}
  function n (line 23) | function n(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}
  function r (line 23) | function r(t){return i(t)||o(t)||a(t)||s()}
  function n (line 23) | function n(t){if(Array.isArray(t))return t}
  function n (line 23) | function n(){throw new TypeError("Invalid attempt to destructure non-ite...
  function n (line 23) | function n(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function r (line 23) | function r(t,e,r){return e&&n(t.prototype,e),r&&n(t,r),t}
  function n (line 23) | function n(t,e,n,r,i){var o={};return Object.keys(r).forEach(function(t)...
  function n (line 23) | function n(e){"@babel/helpers - typeof";return"function"==typeof Symbol&...
  function r (line 23) | function r(t){if(!a(t)||i(t)!=s)return!1;var e=o(t);if(null===e)return!0...
  function n (line 23) | function n(t,e){t.prototype=Object.create(e.prototype),t.prototype.const...
  function r (line 23) | function r(t){return i(t)||o(t)||a()}
  function i (line 23) | function i(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e<t...
  function o (line 23) | function o(t){if((0,s.default)(Object(t))||"[object Arguments]"===Object...
  function a (line 23) | function a(){throw new TypeError("Invalid attempt to spread non-iterable...
  function r (line 23) | function r(n,r,i){var o;return o=t.call(this)||this,o._appId=n,o.id=r,o....

FILE: miniprogram/libs/leancloud-adapters-weapp.js
  function __extends$1 (line 28) | function __extends$1(d, b) {
  function __rest (line 47) | function __rest(s, e) {
  function __awaiter (line 59) | function __awaiter(thisArg, _arguments, P, generator) {
  function __generator (line 69) | function __generator(thisArg, body) {
  function getLoginCode (line 99) | function getLoginCode() {
  function __extends (line 174) | function __extends(d, b) {
  function AbortError (line 184) | function AbortError() {
  function pd (line 308) | function pd(event) {
  function setCancelFlag (line 322) | function setCancelFlag(data) {
  function Event (line 355) | function Event(eventTarget, event) {
  method type (line 387) | get type() {
  method target (line 395) | get target() {
  method currentTarget (line 403) | get currentTarget() {
  method composedPath (line 410) | composedPath() {
  method NONE (line 422) | get NONE() {
  method CAPTURING_PHASE (line 430) | get CAPTURING_PHASE() {
  method AT_TARGET (line 438) | get AT_TARGET() {
  method BUBBLING_PHASE (line 446) | get BUBBLING_PHASE() {
  method eventPhase (line 454) | get eventPhase() {
  method stopPropagation (line 462) | stopPropagation() {
  method stopImmediatePropagation (line 475) | stopImmediatePropagation() {
  method bubbles (line 489) | get bubbles() {
  method cancelable (line 497) | get cancelable() {
  method preventDefault (line 505) | preventDefault() {
  method defaultPrevented (line 513) | get defaultPrevented() {
  method composed (line 521) | get composed() {
  method timeStamp (line 529) | get timeStamp() {
  method srcElement (line 538) | get srcElement() {
  method cancelBubble (line 547) | get cancelBubble() {
  method cancelBubble (line 550) | set cancelBubble(value) {
  method returnValue (line 567) | get returnValue() {
  method returnValue (line 570) | set returnValue(value) {
  method initEvent (line 583) | initEvent() {
  function defineRedirectDescriptor (line 609) | function defineRedirectDescriptor(key) {
  function defineCallDescriptor (line 628) | function defineCallDescriptor(key) {
  function defineWrapper (line 646) | function defineWrapper(BaseEvent, proto) {
  function getWrapper (line 686) | function getWrapper(proto) {
  function wrapEvent (line 706) | function wrapEvent(eventTarget, event) {
  function isStopped (line 717) | function isStopped(event) {
  function setEventPhase (line 728) | function setEventPhase(event, eventPhase) {
  function setCurrentTarget (line 739) | function setCurrentTarget(event, currentTarget) {
  function setPassiveListener (line 750) | function setPassiveListener(event, passiveListener) {
  constant CAPTURE (line 771) | const CAPTURE = 1;
  constant BUBBLE (line 772) | const BUBBLE = 2;
  constant ATTRIBUTE (line 773) | const ATTRIBUTE = 3;
  function isObject (line 780) | function isObject(x) {
  function getListeners (line 790) | function getListeners(eventTarget) {
  function defineEventAttributeDescriptor (line 806) | function defineEventAttributeDescriptor(eventName) {
  function defineEventAttribute (line 873) | function defineEventAttribute(eventTargetPrototype, eventName) {
  function defineCustomEventTarget (line 887) | function defineCustomEventTarget(eventNames) {
  function EventTarget (line 921) | function EventTarget() {
  method addEventListener (line 950) | addEventListener(eventName, listener, options) {
  method removeEventListener (line 1004) | removeEventListener(eventName, listener, options) {
  method dispatchEvent (line 1042) | dispatchEvent(event) {
  function WS (line 1130) | function WS(url, protocol) {
  function WechatWS (line 1169) | function WechatWS(url, protocol) {

FILE: miniprogram/models/Cast.js
  class Cast (line 4) | class Cast {
    method constructor (line 21) | constructor(props) {
    method fromMtime (line 33) | static fromMtime(json) {
    method fromDouban (line 48) | static fromDouban(json) {

FILE: miniprogram/models/Comment.js
  class Comment (line 6) | class Comment {
    method constructor (line 14) | constructor(props) {
    method fromMtime (line 23) | static fromMtime(obj) {
    method fromDouban (line 34) | static fromDouban(obj) {

FILE: miniprogram/packages/admin/pages/app/app.js
  method onLoad (line 10) | async onLoad () {
  method submit (line 24) | async submit (e) {

FILE: miniprogram/packages/admin/pages/douban/douban.js
  method handleTap (line 4) | async handleTap (e) {

FILE: miniprogram/packages/article/pages/categories/categories.js
  method onLoad (line 10) | onLoad(options) {
  method getData (line 17) | async getData () {
  method modifyData (line 77) | modifyData(categories) {
  method toList (line 95) | toList(e) {

FILE: miniprogram/packages/article/pages/details/detail.js
  method onLoad (line 15) | onLoad(options) {
  method getDetail (line 27) | getDetail(id) {
  method handleFavChange (line 46) | handleFavChange(e) {
  method handleComment (line 61) | handleComment(e) {
  method handleShare (line 68) | handleShare(e) {

FILE: miniprogram/packages/article/pages/movie-list-detail/movie-list-detail.js
  method onLoad (line 8) | onLoad() {
  method getMovielistInfo (line 13) | getMovielistInfo() {
  method getMovieList (line 28) | async getMovieList() {
  method toMovieDetail (line 36) | toMovieDetail(e) {
  method handleLikeTap (line 43) | handleLikeTap(e) {
  method onShareAppMessage (line 49) | onShareAppMessage() {

FILE: miniprogram/packages/douban/pages/collection/collection.js
  method onLoad (line 20) | onLoad (options) {
  method getList (line 25) | async getList () {
  method onReachBottom (line 50) | onReachBottom () {
  method handleMovieTap (line 57) | handleMovieTap (e) {

FILE: miniprogram/packages/douban/pages/login-phone/login-phone.js
  method onUnload (line 25) | onUnload() {
  method handlePhoneConfirm (line 29) | async handlePhoneConfirm() {
  method submit (line 55) | async submit (e) {

FILE: miniprogram/packages/example/pages/cover-page/cover-page.js
  method showMovieListPopup (line 7) | showMovieListPopup () {
  method getMovielistList (line 13) | async getMovielistList () {

FILE: miniprogram/packages/example/pages/icons/icons.js
  method handleIconTap (line 13) | handleIconTap(e) {

FILE: miniprogram/packages/example/pages/waterfall/components/unsplash/unsplash.js
  method attached (line 14) | attached() {
  method handleImgLoad (line 22) | handleImgLoad(e) {

FILE: miniprogram/packages/example/pages/waterfall/waterfall.js
  method onLoad (line 6) | onLoad () {
  method setupList (line 10) | async setupList () {
  method handleImgLoad (line 42) | handleImgLoad (e) {
  method handlePreview (line 51) | handlePreview (e) {

FILE: miniprogram/packages/github/components/lang-item/lang-item.js
  method data (line 22) | data (data) {
  method handleStar (line 30) | handleStar () {

FILE: miniprogram/packages/github/components/page-events/page-events.js
  method attached (line 16) | attached () {
  method getEventList (line 22) | async getEventList () {

FILE: miniprogram/packages/github/components/page-trending/page-trending.js
  method attached (line 36) | attached () {
  method onPageScroll (line 42) | onPageScroll (e) {
  method getTrendingList (line 56) | async getTrendingList () {
  method handlePickerChange (line 83) | handlePickerChange (e) {
  method toChooseLanguage (line 109) | toChooseLanguage (e) {

FILE: miniprogram/packages/github/components/page-user/page-user.js
  method attached (line 16) | async attached () {
  method getUserInfo (line 22) | async getUserInfo () {

FILE: miniprogram/packages/github/pages/events/events.js
  method onLoad (line 10) | onLoad (options) {
  method getEventList (line 14) | async getEventList () {

FILE: miniprogram/packages/github/pages/home/home.js
  method onLoad (line 30) | onLoad (options) {
  method lazyLoadComponent (line 34) | lazyLoadComponent () {
  method handleSwiperChange (line 44) | handleSwiperChange (e) {
  method switchTab (line 56) | switchTab (e) {
  method handleSwipeFinish (line 72) | handleSwipeFinish (e) {

FILE: miniprogram/packages/github/pages/languages/languages.js
  method onLoad (line 19) | onLoad () {
  method onUnload (line 23) | onUnload () {
  method setLanguages (line 36) | setLanguages () {
  method handleChoose (line 86) | handleChoose (e) {
  method onLangStar (line 92) | onLangStar (e) {

FILE: miniprogram/packages/github/pages/notifications/notifications.js
  method onLoad (line 8) | onLoad () {
  method getAllNotifications (line 23) | getAllNotifications () {
  method getStarredList (line 38) | async getStarredList () {

FILE: miniprogram/packages/github/pages/repository/repository.js
  method onLoad (line 22) | onLoad (options) {
  method getReadme (line 33) | async getReadme () {

FILE: miniprogram/packages/github/pages/search/search.js
  method onReachBottom (line 13) | onReachBottom () {
  method search (line 20) | async search () {
  method handleSearch (line 44) | handleSearch (e) {

FILE: miniprogram/packages/github/pages/starred/starred.js
  method onLoad (line 15) | onLoad () {
  method onReachBottom (line 19) | onReachBottom () {
  method getStarredList (line 23) | async getStarredList () {

FILE: miniprogram/packages/github/pages/trending/trending.js
  method onLoad (line 29) | onLoad () {
  method onPageScroll (line 33) | onPageScroll (e) {
  method getTrendingList (line 47) | async getTrendingList () {
  method handlePickerChange (line 74) | handlePickerChange (e) {
  method toChooseLanguage (line 100) | toChooseLanguage (e) {

FILE: miniprogram/packages/movie/components/comment-item/comment-item.js
  method expand (line 13) | expand() {

FILE: miniprogram/packages/movie/pages/cards/card.js
  method onLoad (line 31) | onLoad(options) {
  method handleShareTap (line 39) | handleShareTap (e) {
  method setShareMenu (line 43) | setShareMenu () {
  method getCards (line 49) | async getCards () {
  method getCard (line 54) | async getCard(_id) {
  method onShareAppMessage (line 60) | onShareAppMessage () {
  method onChange (line 70) | onChange (event) {
  method setCanvasData (line 76) | setCanvasData () {
  method showShareMenu (line 166) | showShareMenu (e) {
  method favOrCancel (line 223) | async favOrCancel (e) {
  method onPaintSuccess (line 233) | onPaintSuccess (e) {
  method saveCardImg (line 239) | saveCardImg () {

FILE: miniprogram/packages/movie/pages/details/details.js
  method onLoad (line 53) | onLoad (options) {
  method getDetails (line 75) | async getDetails (id, type = 'movie') {
  method getCelebrities (line 106) | async getCelebrities (id, type = 'movie') {
  method getComments (line 120) | async getComments (id, type = 'movie') {
  method handleAction (line 134) | async handleAction (e) {
  method getPhotos (line 212) | async getPhotos (id, type = 'movie') {
  method foldToggle (line 227) | foldToggle() {
  method onImagePre (line 239) | onImagePre(e) {
  method handlePreviewImage (line 249) | handlePreviewImage (e) {
  method handleCopyPath (line 259) | handleCopyPath () {
  method showMovieListPopup (line 275) | showMovieListPopup() {
  method createList (line 280) | createList() {
  method addToMovieList (line 288) | addToMovieList() {
  method onShareAppMessage (line 296) | onShareAppMessage() {
  method getMovielistList (line 304) | async getMovielistList () {
  method handleOnlineTap (line 316) | handleOnlineTap () {
  method handleSourceCopy (line 321) | handleSourceCopy (e) {
  method submitSearchPage (line 338) | submitSearchPage () {

FILE: miniprogram/packages/movie/pages/intheaters/in_theaters.js
  method onLoad (line 19) | onLoad (options) {
  method onUnload (line 23) | onUnload () {
  method getInTheater (line 29) | async getInTheater () {
  method onSwiperChange (line 46) | onSwiperChange (e) {
  method changeSwiper (line 57) | changeSwiper (e) {
  method toDetail (line 68) | toDetail (event) {
  method getComming (line 76) | async getComming () {
  method onScrolTolLower (line 95) | onScrolTolLower (e) {
  method onShareAppMessage (line 108) | onShareAppMessage () {

FILE: miniprogram/packages/movie/pages/mark/mark.js
  method onLoad (line 20) | onLoad (options) {
  method getDetail (line 25) | async getDetail (movieID, type = 'movie') {
  method submit (line 40) | async submit (e) {
  method deleteMark (line 53) | async deleteMark (e) {
  method change (line 75) | change (interest) {

FILE: miniprogram/packages/movie/pages/photos/photos.js
  method onLoad (line 20) | onLoad (options) {
  method onReachBottom (line 33) | onReachBottom () {
  method getPhotoList (line 39) | async getPhotoList () {
  method handlePreview (line 57) | handlePreview (e) {

FILE: miniprogram/packages/movie/pages/trailers/trailers.js
  method onLoad (line 23) | async onLoad (options) {
  method onShareAppMessage (line 43) | onShareAppMessage () {
  method changeTrailer (line 48) | changeTrailer (e) {
  method videoEnded (line 64) | videoEnded () {

FILE: miniprogram/packages/tools/pages/encode/encode.js
  method encode (line 14) | encode() {
  method decode (line 27) | decode() {

FILE: miniprogram/packages/tools/pages/index/index.js
  method scan (line 3) | async scan() {

FILE: miniprogram/packages/tools/pages/random/random.js
  method generate (line 8) | generate() {

FILE: miniprogram/packages/user/pages/achievement/achievement.js
  method onLoad (line 3) | onLoad() {

FILE: miniprogram/packages/user/pages/evaluate/evaluate.js
  method handleChange (line 11) | handleChange(e) {

FILE: miniprogram/packages/user/pages/favCards/index.js
  method onLoad (line 10) | onLoad(options) {
  method getFavCards (line 15) | async getFavCards() {

FILE: miniprogram/packages/user/pages/favMovieList/index.js
  method onLoad (line 11) | onLoad(options) {
  method getFavArticles (line 16) | getFavArticles() {
  method onFavChange (line 25) | onFavChange(e) {
  method handleTabChange (line 40) | handleTabChange(e) {
  method handleTabItemTap (line 45) | handleTabItemTap(e) {
  method changeTab (line 51) | changeTab(e) {
  method showMoreAction (line 61) | showMoreAction() {

FILE: miniprogram/packages/user/pages/userinfo/userinfo.js
  method onChooseAvatar (line 32) | async onChooseAvatar(e) {
  method onNicknameConfirm (line 41) | async onNicknameConfirm(e) {
  method handleThirdSwitch (line 51) | handleThirdSwitch (e) {
  method handleDoubanSwitch (line 61) | async handleDoubanSwitch(checked) {

FILE: miniprogram/packages/wallpaper/pages/categories/categories.js
  method attached (line 13) | attached() {
  method onLoad (line 19) | onLoad() {
  method getCategories (line 22) | async getCategories() {
  method getWallpapers (line 28) | async getWallpapers() {
  method onCategoryTap (line 44) | onCategoryTap(e) {
  method onImgTap (line 52) | onImgTap(e) {
  method onImgLongPress (line 58) | onImgLongPress(e) {
  method onLoadMore (line 70) | onLoadMore() {

FILE: miniprogram/pages/about/about.js
  method onLoad (line 34) | onLoad (options) {
  method onShow (line 42) | onShow (options) {
  method initAudio (line 49) | initAudio () {
  method audioToggle (line 61) | audioToggle () {
  method initAudioListener (line 72) | initAudioListener () {
  method toWebview (line 95) | toWebview (e) {

FILE: miniprogram/pages/first/first.js
  method saveCanvas (line 44) | saveCanvas() {
  method onClick (line 68) | onClick(event) {
  method toMini (line 77) | toMini() {

FILE: miniprogram/pages/search/search.js
  method onLoad (line 19) | onLoad(options) {
  method clearInput (line 26) | clearInput() {
  method inputTyping (line 33) | inputTyping(e) {
  method inputConfirm (line 40) | inputConfirm() {
  method searchMovie (line 56) | async searchMovie (e) {
  method quickSearch (line 80) | quickSearch(e) {
  method goBack (line 89) | goBack() {
  method loadMore (line 94) | loadMore(e) {
  method hiddenCommand (line 102) | hiddenCommand() {
  method setHistory (line 135) | setHistory() {
  method clearHistory (line 149) | clearHistory() {

FILE: miniprogram/pages/setting/setting.js
  method onLoad (line 14) | onLoad (options) {
  method switchNotice (line 31) | async switchNotice (event) {
  method clearCache (line 53) | clearCache () {

FILE: miniprogram/pages/tabs/discovery/discovery.js
  method onLoad (line 31) | onLoad (options) {
  method onShow (line 46) | onShow () {
  method handleSwitchTab (line 52) | handleSwitchTab (e) {
  method getData (line 62) | getData () {
  method getBanners (line 68) | async getBanners () {
  method getHotMovies (line 74) | async getHotMovies () {
  method loadMoreHot (line 89) | loadMoreHot () {
  method onBannerTap (line 96) | onBannerTap(event) {
  method onShareAppMessage (line 115) | onShareAppMessage (opt) {
  method cardSwiper (line 123) | cardSwiper(e) {
  method hideSwiperBg (line 129) | hideSwiperBg() {
  method showSwiperBg (line 135) | showSwiperBg() {

FILE: miniprogram/pages/tabs/index/index.js
  method onShow (line 17) | onShow () {
  method handleSwitchTab (line 21) | handleSwitchTab (e) {
  method handleUserTap (line 29) | handleUserTap (e) {
  method toGitMark (line 41) | toGitMark () {
  method toSetting (line 48) | toSetting() {
  method toAbout (line 55) | toAbout() {
  method onShareAppMessage (line 62) | onShareAppMessage(opt) {
  method toFavMovieList (line 71) | toFavMovieList() {
  method toFavCards (line 78) | toFavCards() {
  method toEvalute (line 85) | toEvalute () {
  method navigate (line 94) | navigate (e) {

FILE: miniprogram/pages/tabs/movies/movies.js
  method onLoad (line 42) | onLoad(options) {
  method onShow (line 53) | onShow() {
  method onHide (line 58) | onHide(options) {
  method onUnload (line 62) | onUnload() {
  method onPullDownRefresh (line 68) | onPullDownRefresh() {
  method onReachBottom (line 74) | onReachBottom() {
  method showDoubanTip (line 84) | showDoubanTip () {
  method handleSwitchTab (line 103) | handleSwitchTab (e) {
  method getMovies (line 110) | async getMovies() {
  method changeTab (line 136) | changeTab(e) {
  method bindViewTap (line 152) | bindViewTap(event) {
  method changeLayout (line 160) | changeLayout() {
  method changeSort (line 177) | changeSort() {
  method onPageScroll (line 206) | onPageScroll(e) {

FILE: miniprogram/pages/test/test.js
  method handleSubscribeTap (line 20) | handleSubscribeTap() {
  method handleTempTap (line 29) | handleTempTap() {

FILE: miniprogram/pages/webview/index.js
  method onLoad (line 9) | onLoad(options) {
  method onPullDownRefresh (line 16) | onPullDownRefresh() {
  method onShareAppMessage (line 21) | onShareAppMessage(options) {

FILE: miniprogram/store/app.js
  method update (line 5) | update (data = {}) {

FILE: miniprogram/store/douban.js
  method update (line 7) | update (data = {}) {
  method logout (line 16) | logout() {

FILE: miniprogram/store/user.js
  method updateUserInfo (line 5) | updateUserInfo (userInfo) {

FILE: miniprogram/templates/actionsheet/actionsheet.js
  method setDefaults (line 7) | setDefaults() {
  method show (line 46) | show(opts = {}) {

FILE: miniprogram/templates/bing/bing.js
  class Bing (line 5) | class Bing {
    method constructor (line 7) | constructor() {
    method _init (line 14) | _init() {
    method _initMethod (line 29) | _initMethod() {

FILE: miniprogram/templates/component.js
  class Component (line 4) | class Component {
    method constructor (line 11) | constructor(options = {}) {
    method __init (line 19) | __init() {
    method __initState (line 61) | __initState() {
    method __initData (line 69) | __initData() {
    method __initMethods (line 97) | __initMethods() {
    method getComponentData (line 122) | getComponentData() {
    method isEmptyObject (line 136) | isEmptyObject(e) {
    method setVisible (line 145) | setVisible(className = `weui-animate-fade-in`) {
    method setHidden (line 155) | setHidden(className = `weui-animate-fade-out`, timer = 300) {

FILE: miniprogram/templates/dropmenu/dropmenu.js
  method setDefaults (line 5) | setDefaults() {
  method show (line 15) | show(opts={}) {

FILE: miniprogram/templates/login/login.js
  function LoginPannel (line 51) | function LoginPannel() {

FILE: miniprogram/templates/share/share.js
  method setDefaults (line 5) | setDefaults() {
  method show (line 16) | show(opts={}) {

FILE: miniprogram/templates/wxParse/html2json.js
  function makeMap (line 38) | function makeMap(str) {
  function q (line 45) | function q(v) {
  function removeDOCTYPE (line 49) | function removeDOCTYPE(html) {
  function trimHtml (line 56) | function trimHtml(html) {
  function html2json (line 65) | function html2json(html, bindName) {
  function transEmojiStr (line 258) | function transEmojiStr(str){
  function emojisInit (line 293) | function emojisInit(reg='',baseSrc="/wxParse/emojis/",emojis){

FILE: miniprogram/templates/wxParse/htmlparser.js
  function HTMLParser (line 38) | function HTMLParser(html, handler) {
  function makeMap (line 185) | function makeMap(str) {

FILE: miniprogram/templates/wxParse/showdown.js
  function getDefaultOpts (line 15) | function getDefaultOpts(simple) {
  function validate (line 310) | function validate(extension, name) {
  function escapeCharactersCallback (line 509) | function escapeCharactersCallback(wholeMatch, m1) {
  function _constructor (line 749) | function _constructor() {
  function _parseExtension (line 781) | function _parseExtension(ext, name) {
  function legacyExtensionLoading (line 845) | function legacyExtensionLoading(ext, name) {
  function listen (line 877) | function listen(name, callback) {
  function rTrimInputText (line 892) | function rTrimInputText(text) {
  function replaceLink (line 1262) | function replaceLink(wm, link) {
  function replaceMail (line 1270) | function replaceMail(wholeMatch, m1) {
  function headerId (line 1877) | function headerId(m) {
  function writeImageTag (line 1913) | function writeImageTag (wholeMatch, altText, linkId, url, width, height,...
  function processListItems (line 2017) | function processListItems (listStr, trimTrailing) {
  function parseConsecutiveLists (line 2105) | function parseConsecutiveLists(list, listType, trimTrailing) {
  function parseStyles (line 2399) | function parseStyles(sLine) {
  function parseHeaders (line 2411) | function parseHeaders(header, style) {
  function parseCells (line 2422) | function parseCells(cell, style) {
  function buildTable (line 2427) | function buildTable(headers, cells) {

FILE: miniprogram/templates/wxParse/wxDiscode.js
  function strNumDiscode (line 2) | function strNumDiscode(str){
  function strGreeceDiscode (line 44) | function strGreeceDiscode(str){
  function strcharacterDiscode (line 104) | function strcharacterDiscode(str){
  function strOtherDiscode (line 120) | function strOtherDiscode(str){
  function strMoreDiscode (line 178) | function strMoreDiscode(str){
  function strDiscode (line 186) | function strDiscode(str){
  function urlToHttpUrl (line 194) | function urlToHttpUrl(url,rep){

FILE: miniprogram/templates/wxParse/wxParse.js
  function wxParse (line 31) | function wxParse(bindName = 'wxParseData', type='html', data='<div class...
  function wxParseImgTap (line 55) | function wxParseImgTap(e) {
  function wxParseImgLoad (line 70) | function wxParseImgLoad(e) {
  function calMoreImageInfo (line 79) | function calMoreImageInfo(e, idx, that, bindName) {
  function wxAutoImageCal (line 105) | function wxAutoImageCal(originalWidth, originalHeight,that,bindName) {
  function wxParseTemArray (line 129) | function wxParseTemArray(temArrayName,bindNameReg,total,that){
  function emojisInit (line 149) | function emojisInit(reg='',baseSrc="/wxParse/emojis/",emojis){

FILE: miniprogram/utils/EventEmitter.js
  class EventEmitter (line 1) | class EventEmitter {
    method constructor (line 5) | constructor() {
    method on (line 13) | on(eventName, listener) {
    method emit (line 25) | emit(eventName, ...args) {
    method off (line 37) | off(eventName, listener) {

FILE: miniprogram/utils/URLSearchParams.js
  class URLSearchParams (line 1) | class URLSearchParams {
    method constructor (line 4) | constructor(params) {
    method toString (line 11) | toString() {

FILE: miniprogram/utils/apis.js
  constant HY_HOST (line 3) | const HY_HOST = 'https://hongye567.github.io';
  constant DB_HOST (line 4) | const DB_HOST = 'https://api.douban.com';
  method complete (line 114) | complete(res) {
  constant MTIME_HOST (line 121) | const MTIME_HOST = 'https://api-m.mtime.cn'
  method getCities (line 134) | getCities() {
  method getLocationMovies (line 141) | getLocationMovies(cityId = 290) {
  method getComingMovies (line 150) | getComingMovies(cityId = 290) {
  method searchMovie (line 163) | searchMovie(params = {}, keywords, pageIndex = 1, type = 3, cityId = 290) {
  method getMovieDetail (line 179) | getMovieDetail(params) {
  method getMovieCredits (line 187) | getMovieCredits(params) {
  method getMovieComments (line 196) | getMovieComments(params) {

FILE: miniprogram/utils/crypro.js
  function encrypt (line 12) | function encrypt(message) {
  function decrypt (line 27) | function decrypt(message) {

FILE: miniprogram/utils/request.js
  class RequestController (line 1) | class RequestController {
    method constructor (line 5) | constructor() {

FILE: miniprogram/utils/storage.js
  function clear (line 37) | function clear(callback) {
  function getSetting (line 42) | function getSetting(callback) {

FILE: miniprogram/utils/svg.js
  function getSVGUri (line 4) | function getSVGUri(xml) {

FILE: miniprogram/utils/util.js
  function formatTime (line 1) | function formatTime (date) {
  function formatNumber (line 14) | function formatNumber(n) {
  function isEmpty (line 41) | function isEmpty (object) {

FILE: scripts/release.cjs
  function packNpm (line 34) | function packNpm () {
  function preview (line 41) | function preview () {
  function upload (line 50) | function upload () {
  function outputZip (line 60) | function outputZip () {

FILE: test/EventEmitter.spec.js
  method fn (line 22) | fn() {
Condensed preview — 513 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,266K chars).
[
  {
    "path": ".eslintignore",
    "chars": 47,
    "preview": "**/node_modules\n\nminiprogram/templates/wxParse/"
  },
  {
    "path": ".eslintrc.cjs",
    "chars": 1722,
    "preview": "module.exports = {\n  \"env\": {\n    \"browser\": true,\n    \"es6\": true,\n    \"node\": true\n  },\n  \"extends\": [\n    \"eslint:rec"
  },
  {
    "path": ".github/workflows/upload.yml",
    "chars": 793,
    "preview": "# 构建并上传到小程序后台\n\nname: Upload to weixin\n\n# Controls when the workflow will run\non:\n  # Triggers the workflow on push but o"
  },
  {
    "path": ".gitignore",
    "chars": 1179,
    "preview": "# Logs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory fo"
  },
  {
    "path": ".prettierignore",
    "chars": 9,
    "preview": "coverage\n"
  },
  {
    "path": ".prettierrc.json",
    "chars": 97,
    "preview": "{\n  \"$schema\": \"http://json.schemastore.org/prettierrc\",\n  \"semi\": true,\n  \"singleQuote\": true\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 83,
    "preview": "{\n  \"files.exclude\": {\n    \"**/*.js\": {\n      \"when\": \"$(basename).ts\"\n    }\n  }\n}\n"
  },
  {
    "path": ".yarnrc",
    "chars": 42,
    "preview": "registry \"https://registry.npmmirror.com\"\n"
  },
  {
    "path": "@types/douban/accounts.d.ts",
    "chars": 233,
    "preview": "declare namespace Douban {\n  interface AccountInfo {\n    name: string;\n    weixin_binded: boolean;\n    phone: string;\n  "
  },
  {
    "path": "@types/douban/frodo.d.ts",
    "chars": 1753,
    "preview": "declare namespace Douban {\n  interface SubjectCollection {\n    subject_type: DouBan.SubjectType;\n    updated_at: string;"
  },
  {
    "path": "@types/douban/index.d.ts",
    "chars": 444,
    "preview": "declare namespace Douban {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  interface APIResponseLegac"
  },
  {
    "path": "@types/douban.d.ts",
    "chars": 5287,
    "preview": "declare namespace DouBan {\n  /** 搜索结果 */\n  interface SearchResult {\n    count: number;\n    items: Array<SearchMovieItem|"
  },
  {
    "path": "@types/index.d.ts",
    "chars": 482,
    "preview": "import 'miniprogram-api-typings'\n\ndeclare namespace IMarkr {\n}\n\ninterface RequestController extends WechatMiniprogram.Re"
  },
  {
    "path": "@types/miniprogram.d.ts",
    "chars": 307,
    "preview": "// <reference path=\"./lib.wx.component.d.ts\" />\n\ndeclare namespace WechatMiniprogram {\n  interface FormEvent<Detail exte"
  },
  {
    "path": "@types/mobx-miniprogram-bindings.d.ts",
    "chars": 130,
    "preview": "declare module 'mobx-miniprogram-bindings' {\n  export const storeBindingsBehavior: WechatMiniprogram.Behavior.BehaviorId"
  },
  {
    "path": "@types/wxCloud.d.ts",
    "chars": 1015,
    "preview": "interface CloudFunctionBaseEvent {\n  userInfo: { appId: string; openId: string };\n}\n\ntype CloudFunctionEvent<T extends R"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 688,
    "preview": "# Code of conduct\n\n### 使用2个空格进行缩进\n\n```javascript\n  function hello (name) {\n    console.log('Hi, ' + name);\n  }\n```\n\n### "
  },
  {
    "path": "LICENSE",
    "chars": 10250,
    "preview": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AN"
  },
  {
    "path": "README.md",
    "chars": 4238,
    "preview": "# Mark\n\n[![Honye](https://img.shields.io/badge/Honye-红叶-red.svg)](https://honye.github.io/)  [![license](https://img.shi"
  },
  {
    "path": "cloudbaserc.json",
    "chars": 1942,
    "preview": "{\n  \"version\": \"2.0\",\n  \"region\": \"sh\",\n  \"envId\": \"release-5g2g137xcedfade7\",\n  \"functionRoot\": \"./cloudfunctions\",\n  \""
  },
  {
    "path": "cloudfunctions/app/config.json",
    "chars": 49,
    "preview": "{\n  \"permissions\": {\n    \"openapi\": [\n    ]\n  }\n}"
  },
  {
    "path": "cloudfunctions/app/index.js",
    "chars": 437,
    "preview": "const cloud = require('wx-server-sdk');\ncloud.init({\n  env: cloud.DYNAMIC_CURRENT_ENV\n});\n\nconst db = cloud.database();\n"
  },
  {
    "path": "cloudfunctions/app/package.json",
    "chars": 254,
    "preview": "{\n  \"name\": \"app\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"E"
  },
  {
    "path": "cloudfunctions/douban/config.json",
    "chars": 166,
    "preview": "{\n  \"permissions\": {\n    \"openapi\": [\n    ]\n  },\n  \"triggers\": [\n    {\n      \"name\": \"CronDouban\",\n      \"type\": \"timer\""
  },
  {
    "path": "cloudfunctions/douban/index.js",
    "chars": 5247,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk');\nconst fetch = require('node-fetch');\n\ncloud.init({\n  env: cloud.DYNAM"
  },
  {
    "path": "cloudfunctions/douban/package.json",
    "chars": 285,
    "preview": "{\n  \"name\": \"douban\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo "
  },
  {
    "path": "cloudfunctions/douban/request.d.ts",
    "chars": 172,
    "preview": "export const request: (params: {\n  headers?: Record<string, string>\n  url?: string\n  path?: string\n  method?: 'GET'\n  da"
  },
  {
    "path": "cloudfunctions/douban/request.js",
    "chars": 1012,
    "preview": "const cloud = require('wx-server-sdk');\ncloud.init({\n  env: cloud.DYNAMIC_CURRENT_ENV\n});\nconst fetch = require('node-fe"
  },
  {
    "path": "cloudfunctions/favArticle/index.js",
    "chars": 3252,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk')\n\ncloud.init(\n  // {\n  //   env: 'dv-963c46'\n  // }\n)\n\nconst db = cloud"
  },
  {
    "path": "cloudfunctions/favArticle/package.json",
    "chars": 261,
    "preview": "{\n  \"name\": \"favArticle\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"e"
  },
  {
    "path": "cloudfunctions/favCard/index.js",
    "chars": 1114,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk');\n\ncloud.init({\n  env: cloud.DYNAMIC_CURRENT_ENV\n});\n\nconst db = cloud."
  },
  {
    "path": "cloudfunctions/favCard/package.json",
    "chars": 258,
    "preview": "{\n  \"name\": \"favCard\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo"
  },
  {
    "path": "cloudfunctions/fetch/config.json",
    "chars": 49,
    "preview": "{\n  \"permissions\": {\n    \"openapi\": [\n    ]\n  }\n}"
  },
  {
    "path": "cloudfunctions/fetch/index.js",
    "chars": 376,
    "preview": "const cloud = require('wx-server-sdk')\nconst fetch = require('node-fetch')\n\ncloud.init({ env: cloud.DYNAMIC_CURRENT_ENV "
  },
  {
    "path": "cloudfunctions/fetch/package.json",
    "chars": 284,
    "preview": "{\n  \"name\": \"fetch\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\"
  },
  {
    "path": "cloudfunctions/getArticleDetails/index.js",
    "chars": 852,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk')\n\ncloud.init(\n  // {\n  //   env: 'dv-963c46'\n  // }\n)\n\nconst db = cloud"
  },
  {
    "path": "cloudfunctions/getArticleDetails/package.json",
    "chars": 268,
    "preview": "{\n  \"name\": \"getArticleDetails\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"te"
  },
  {
    "path": "cloudfunctions/getCard/config.json",
    "chars": 49,
    "preview": "{\n  \"permissions\": {\n    \"openapi\": [\n    ]\n  }\n}"
  },
  {
    "path": "cloudfunctions/getCard/index.js",
    "chars": 892,
    "preview": "const cloud = require('wx-server-sdk');\n\ncloud.init({\n  env: cloud.DYNAMIC_CURRENT_ENV,\n});\n\nconst db = cloud.database()"
  },
  {
    "path": "cloudfunctions/getCard/package.json",
    "chars": 257,
    "preview": "{\n  \"name\": \"getCard\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo"
  },
  {
    "path": "cloudfunctions/getCards/index.js",
    "chars": 1154,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk');\n\ncloud.init({\n  env: cloud.DYNAMIC_CURRENT_ENV\n});\n\nconst db = cloud."
  },
  {
    "path": "cloudfunctions/getCards/package.json",
    "chars": 259,
    "preview": "{\n  \"name\": \"getCards\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"ech"
  },
  {
    "path": "cloudfunctions/getCategories/index.js",
    "chars": 1154,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk')\n\ncloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })\n\nconst db = cloud.data"
  },
  {
    "path": "cloudfunctions/getCategories/package.json",
    "chars": 263,
    "preview": "{\n  \"name\": \"getCategories\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\":"
  },
  {
    "path": "cloudfunctions/getFavArticles/index.js",
    "chars": 1508,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk')\n\ncloud.init(\n  // {\n  //   env: 'dv-963c46'\n  // }\n)\n\nconst db = cloud"
  },
  {
    "path": "cloudfunctions/getFavArticles/package.json",
    "chars": 265,
    "preview": "{\n  \"name\": \"getFavArticles\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\""
  },
  {
    "path": "cloudfunctions/getFavCards/index.js",
    "chars": 1038,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk')\n\ncloud.init({\n    env: cloud.DYNAMIC_CURRENT_ENV,\n})\n\nconst db = cloud"
  },
  {
    "path": "cloudfunctions/getFavCards/package.json",
    "chars": 262,
    "preview": "{\n  \"name\": \"getFavCards\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \""
  },
  {
    "path": "cloudfunctions/github/config.json",
    "chars": 49,
    "preview": "{\n  \"permissions\": {\n    \"openapi\": [\n    ]\n  }\n}"
  },
  {
    "path": "cloudfunctions/github/fetch.js",
    "chars": 1436,
    "preview": "/**\n * [GitHub REST API](https://docs.github.com/en/rest) 封装\n * \n * 使用原生 fetch api\n * \n */\nconst cloud = require('wx-ser"
  },
  {
    "path": "cloudfunctions/github/index.js",
    "chars": 3242,
    "preview": "/**\n * GitHub 相关云函数入口\n */\n\n/**\n * @typedef {object} ResponseType 云函数调用成功响应\n * @property {number} code 0:成功;-1:失败\n * @pro"
  },
  {
    "path": "cloudfunctions/github/package.json",
    "chars": 285,
    "preview": "{\n  \"name\": \"github\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo "
  },
  {
    "path": "cloudfunctions/initdb/index.js",
    "chars": 6960,
    "preview": "const cloud = require('wx-server-sdk')\n\ncloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })\n\nconst db = cloud.database()\n\n/**"
  },
  {
    "path": "cloudfunctions/initdb/package.json",
    "chars": 257,
    "preview": "{\n  \"name\": \"douban\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo "
  },
  {
    "path": "cloudfunctions/login/config.json",
    "chars": 49,
    "preview": "{\n  \"permissions\": {\n    \"openapi\": [\n    ]\n  }\n}"
  },
  {
    "path": "cloudfunctions/login/index.js",
    "chars": 2856,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk');\n\ncloud.init({\n  env: cloud.DYNAMIC_CURRENT_ENV\n});\n\nconst db = cloud."
  },
  {
    "path": "cloudfunctions/login/package.json",
    "chars": 256,
    "preview": "{\n  \"name\": \"login\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\"
  },
  {
    "path": "cloudfunctions/nowPlaying/config.json",
    "chars": 49,
    "preview": "{\n  \"permissions\": {\n    \"openapi\": [\n    ]\n  }\n}"
  },
  {
    "path": "cloudfunctions/nowPlaying/fetch.js",
    "chars": 1329,
    "preview": "const cheerio = require('cheerio');\nconst fetch = require('node-fetch');\n\nconst fetchTrendingList = async () => {\n  cons"
  },
  {
    "path": "cloudfunctions/nowPlaying/index.js",
    "chars": 224,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk');\nconst { fetchNowPlaying } = require('./fetch');\n\ncloud.init()\n\n// 云函数"
  },
  {
    "path": "cloudfunctions/nowPlaying/package.json",
    "chars": 319,
    "preview": "{\n  \"name\": \"nowPlaying\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"e"
  },
  {
    "path": "cloudfunctions/showingSoon/config.json",
    "chars": 49,
    "preview": "{\n  \"permissions\": {\n    \"openapi\": [\n    ]\n  }\n}"
  },
  {
    "path": "cloudfunctions/showingSoon/index.js",
    "chars": 951,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk');\nconst cheerio = require('cheerio');\nconst fetch = require('node-fetch"
  },
  {
    "path": "cloudfunctions/showingSoon/package.json",
    "chars": 319,
    "preview": "{\n  \"name\": \"showingSoon\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \""
  },
  {
    "path": "cloudfunctions/site/config.json",
    "chars": 84,
    "preview": "{\n  \"permissions\": {\n    \"openapi\": [\n      \"openapi.search.submitPages\"\n    ]\n  }\n}"
  },
  {
    "path": "cloudfunctions/site/index.js",
    "chars": 500,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk')\n\ncloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })\n\n/**\n * \n * @param {ob"
  },
  {
    "path": "cloudfunctions/site/package.json",
    "chars": 255,
    "preview": "{\n  \"name\": \"site\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\""
  },
  {
    "path": "cloudfunctions/subscribeMessage/index.js",
    "chars": 682,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk')\n\ncloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })\n\n// 云函数入口函数\nexports.ma"
  },
  {
    "path": "cloudfunctions/subscribeMessage/package.json",
    "chars": 267,
    "preview": "{\n  \"name\": \"subscribeMessage\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"tes"
  },
  {
    "path": "cloudfunctions/subscription/config.json",
    "chars": 196,
    "preview": "{\n  \"triggers\": [\n    {\n      \"name\": \"trending\",\n      \"type\": \"timer\",\n      \"config\": \"*/10 * * * * * *\"\n    }\n  ],\n "
  },
  {
    "path": "cloudfunctions/subscription/index.js",
    "chars": 807,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk');\n\ncloud.init({\n  env: cloud.DYNAMIC_CURRENT_ENV\n});\nconst db = cloud.d"
  },
  {
    "path": "cloudfunctions/subscription/package.json",
    "chars": 263,
    "preview": "{\n  \"name\": \"subscription\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": "
  },
  {
    "path": "cloudfunctions/trending/config.json",
    "chars": 49,
    "preview": "{\n  \"permissions\": {\n    \"openapi\": [\n    ]\n  }\n}"
  },
  {
    "path": "cloudfunctions/trending/index.js",
    "chars": 1696,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk');\ncloud.init({\n  env: cloud.DYNAMIC_CURRENT_ENV\n});\nconst fetch = requi"
  },
  {
    "path": "cloudfunctions/trending/package.json",
    "chars": 317,
    "preview": "{\n  \"name\": \"trending\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"ech"
  },
  {
    "path": "cloudfunctions/wallpaper/index.js",
    "chars": 997,
    "preview": "const fetch = require('node-fetch')\n\nconst fetchCategories = async () => {\n  const res = await fetch('http://wallpaper.a"
  },
  {
    "path": "cloudfunctions/wallpaper/package.json",
    "chars": 288,
    "preview": "{\n  \"name\": \"wallpaper\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"ec"
  },
  {
    "path": "cloudfunctions/wxacode/config.json",
    "chars": 99,
    "preview": "{\n  \"permissions\": {\n    \"openapi\": [\n      \"wxacode.getUnlimited\",\n      \"wxacode.get\"\n    ]\n  }\n}"
  },
  {
    "path": "cloudfunctions/wxacode/index.js",
    "chars": 368,
    "preview": "// 云函数入口文件\nconst cloud = require('wx-server-sdk');\n\ncloud.init({\n  env: cloud.DYNAMIC_CURRENT_ENV\n});\n\n// 云函数入口函数\nexport"
  },
  {
    "path": "cloudfunctions/wxacode/package.json",
    "chars": 258,
    "preview": "{\n  \"name\": \"wxacode\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo"
  },
  {
    "path": "docs/云开发环境初始化.md",
    "chars": 1154,
    "preview": "# 云开发环境初始化\n\n## 创建环境\n\n登录腾讯云[云开发控制台](https://console.cloud.tencent.com/tcb/platform/env)\n\n在环境管理创建一个环境,如果已有环境可跳过这一步\n\n可以在网页端"
  },
  {
    "path": "jest.config.js",
    "chars": 6523,
    "preview": "/*\n * For a detailed explanation regarding each configuration property and type check, visit:\n * https://jestjs.io/docs/"
  },
  {
    "path": "miniprogram/apis/cloud/index.js",
    "chars": 187,
    "preview": "const db = wx.cloud.database();\n\nexport const getBanners = () => {\n  return db.collection('banners')\n    .orderBy('id', "
  },
  {
    "path": "miniprogram/apis/douban/accounts.js",
    "chars": 2418,
    "preview": "import { request as baseRequest } from '../../utils/request';\nimport wxCloud from '../../utils/wxCloud';\n\n/** 豆瓣小程序 AppI"
  },
  {
    "path": "miniprogram/apis/douban/request.js",
    "chars": 1388,
    "preview": "import wxCloud from '../../utils/wxCloud';\n\nexport class RequestController {\n  /** @type {WechatMiniprogram.RequestTask}"
  },
  {
    "path": "miniprogram/apis/douban.js",
    "chars": 7335,
    "preview": "/**\n * @file 豆瓣 API\n */\nimport { request as baseRequest } from '../utils/request';\nimport { store } from '../store/index"
  },
  {
    "path": "miniprogram/apis/github.js",
    "chars": 5478,
    "preview": "/**\n * [GitHub API v3](https://docs.github.com/en/rest)\n */\nimport { store } from '../store/index';\n\n/**\n * \n * @param {"
  },
  {
    "path": "miniprogram/apis/leancloud/index.js",
    "chars": 331,
    "preview": "import * as AV from '../../libs/av-live-query-core-min';\n\n/**\n * @returns {Promise<any[]>}\n */\nexport const getBanners ="
  },
  {
    "path": "miniprogram/apis/server/index.js",
    "chars": 1754,
    "preview": "import { request } from '../../utils/request'\n\n/**\n * @param {RequestOption} config\n */\nconst fetch = async (config) => "
  },
  {
    "path": "miniprogram/apis/vercel.js",
    "chars": 1476,
    "preview": "import { request } from '../utils/request'\nimport { decrypt } from '../utils/crypro';\n\n/**\n * @param {RequestOption} con"
  },
  {
    "path": "miniprogram/app.js",
    "chars": 2573,
    "preview": "import { store } from './store/index';\nimport { compareVersions, isEmpty } from './utils/util';\nimport { apiAppInfo, api"
  },
  {
    "path": "miniprogram/app.json",
    "chars": 3680,
    "preview": "{\n  \"pages\": [\n    \"pages/splash/splash\",\n    \"pages/tabs/discovery/discovery\",\n    \"pages/tabs/index/index\",\n    \"pages"
  },
  {
    "path": "miniprogram/app.wxss",
    "chars": 221,
    "preview": "/**app.wxss**/\n@import './style/weui.wxss';\n@import '/style/animate.wxss';\n@import './style/font-awesome.min.wxss';\n@imp"
  },
  {
    "path": "miniprogram/components/Tabs/Tabs.js",
    "chars": 742,
    "preview": "// Tabs\nComponent({\n    options: {\n        addGlobalClass: true,\n    },\n    /**\n     * 组件的属性列表\n     */\n    properties: {"
  },
  {
    "path": "miniprogram/components/Tabs/Tabs.json",
    "chars": 52,
    "preview": "{\n    \"component\": true,\n    \"usingComponents\": {}\n}"
  },
  {
    "path": "miniprogram/components/Tabs/Tabs.wxml",
    "chars": 276,
    "preview": "<!-- Tabs -->\n<view class=\"tabs\">\n    <view class=\"tabs-item {{currentIndex === index && 'is-active'}}\" wx:for=\"{{tabs}}"
  },
  {
    "path": "miniprogram/components/Tabs/Tabs.wxss",
    "chars": 788,
    "preview": "/* Tabs */\n.tabs {\n    /* position: fixed; */\n    z-index: 99;\n    top: 0;\n    left: 0;\n    right: 0;\n    display: flex;"
  },
  {
    "path": "miniprogram/components/article/article.js",
    "chars": 571,
    "preview": "// components/article/article.js\nComponent({\n  /**\n   * 组件的属性列表\n   */\n  properties: {\n    aid: {\n      type: String,\n   "
  },
  {
    "path": "miniprogram/components/article/article.json",
    "chars": 102,
    "preview": "{\n  \"component\": true,\n  \"usingComponents\": {\n    \"comp-btnFav\": \"/components/btn-fav/FavButton\"\n  }\n}"
  },
  {
    "path": "miniprogram/components/article/article.wxml",
    "chars": 452,
    "preview": "<!--components/article/article.wxml-->\n<view class='item-bgimg' style='background-image:url({{image}})'>\n  <view class='"
  },
  {
    "path": "miniprogram/components/article/article.wxss",
    "chars": 1053,
    "preview": "/* components/article/article.wxss */\n.item-bgimg {\n  margin: 10rpx;\n  height: 360rpx;\n  border-radius: 10rpx;\n  backgro"
  },
  {
    "path": "miniprogram/components/btn-fav/FavButton.js",
    "chars": 553,
    "preview": "// @ts-check\nComponent({\n  properties: {\n    height: {\n      type: String,\n      value: '2em'\n    },\n    width: {\n      "
  },
  {
    "path": "miniprogram/components/btn-fav/FavButton.json",
    "chars": 48,
    "preview": "{\n  \"component\": true,\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "miniprogram/components/btn-fav/FavButton.wxml",
    "chars": 341,
    "preview": "<view class='wrapper' bind:tap='_onToggle' style='height:{{height}};width:{{width}}'>\n  <image class='icon-like' \n    hi"
  },
  {
    "path": "miniprogram/components/btn-fav/FavButton.wxss",
    "chars": 844,
    "preview": "/* components/btn-fav/FavButton.wxss */\n\n.wrapper {\n  display: flex;\n  width: 2em;\n  height: 2em;\n}\n\n.icon-like {\n  widt"
  },
  {
    "path": "miniprogram/components/cover-page/cover-page.js",
    "chars": 612,
    "preview": "Component({\n  options: {\n    addGlobalClass: true\n  },\n\n  properties: {\n    visible: {\n      type: Boolean,\n      value:"
  },
  {
    "path": "miniprogram/components/cover-page/cover-page.json",
    "chars": 48,
    "preview": "{\n  \"component\": true,\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "miniprogram/components/cover-page/cover-page.wxml",
    "chars": 638,
    "preview": "<wxs module=\"wxs\" src=\"./cover-page.wxs\" />\n<view id=\"coverPage\" class=\"action-sheet-backdrop\" catch:tap=\"hide\" catch:to"
  },
  {
    "path": "miniprogram/components/cover-page/cover-page.wxs",
    "chars": 1472,
    "preview": "function handleModalTouchMove (event, ownerInstance) {\n  var state = ownerInstance.getState();\n  var currentY = event.ch"
  },
  {
    "path": "miniprogram/components/cover-page/cover-page.wxss",
    "chars": 897,
    "preview": ".action-sheet-backdrop {\n  position: fixed;\n  top: 0;\n  left: 0;\n  z-index: 11;\n  width: 100%;\n  height: 100%;\n  backgro"
  },
  {
    "path": "miniprogram/components/index-list/components/content/content.js",
    "chars": 137,
    "preview": "Component({\n  options: {\n    addGlobalClass: true\n  },\n\n  properties: {\n    data: {\n      type: Object,\n      value: nul"
  },
  {
    "path": "miniprogram/components/index-list/components/content/content.json",
    "chars": 27,
    "preview": "{\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "miniprogram/components/index-list/components/content/content.wxml",
    "chars": 26,
    "preview": "<view>{{data.name}}</view>"
  },
  {
    "path": "miniprogram/components/index-list/components/content/content.wxss",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "miniprogram/components/index-list/index-list.js",
    "chars": 4641,
    "preview": "const throttle = function(func, wait, options) {\n  let context; let args; let result\n  let timeout = null\n  // 上次执行时间点\n "
  },
  {
    "path": "miniprogram/components/index-list/index-list.json",
    "chars": 149,
    "preview": "{\n  \"component\": true,\n  \"usingComponents\": {},\n  \"componentGenerics\": {\n    \"content\": {\n      \"default\": \"./components"
  },
  {
    "path": "miniprogram/components/index-list/index-list.wxml",
    "chars": 1546,
    "preview": "<scroll-view \n  class=\"page page-select-index\" \n  style=\"height: {{windowHeight}}px;\" \n  enable-back-to-top \n  scroll-in"
  },
  {
    "path": "miniprogram/components/index-list/index-list.wxss",
    "chars": 1636,
    "preview": ".wx-flex{\n\tdisplay: flex;\n\talign-items: center;\n}\n.wx-flex__item{\n\tflex: 1;\n}\n\n.thin-border-bottom{\n\tposition: relative;"
  },
  {
    "path": "miniprogram/components/load-more/load-more.js",
    "chars": 612,
    "preview": "Component({\n  options: {\n    addGlobalClass: true\n  },\n\n  properties: {\n    loading: Boolean\n  },\n\n  attached() {\n    th"
  },
  {
    "path": "miniprogram/components/load-more/load-more.json",
    "chars": 27,
    "preview": "{\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "miniprogram/components/load-more/load-more.wxml",
    "chars": 140,
    "preview": "<view class='load-more'>\n  <view wx:if='{{loading}}' class='weui-loading' />\n  <view>{{loading ? 'Loading...' : 'Load mo"
  },
  {
    "path": "miniprogram/components/load-more/load-more.wxss",
    "chars": 81,
    "preview": ".load-more {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}"
  },
  {
    "path": "miniprogram/components/painter/lib/downloader.js",
    "chars": 6002,
    "preview": "/**\n * LRU 文件存储,使用该 downloader 可以让下载的文件存储在本地,下次进入小程序后可以直接使用\n * 详细设计文档可查看 https://juejin.im/post/5b42d3ede51d4519277b6ce3"
  },
  {
    "path": "miniprogram/components/painter/lib/gradient.js",
    "chars": 4126,
    "preview": "/* eslint-disable */\n// 当ctx传入当前文件,const grd = ctx.createCircularGradient() 和 \n// const grd = this.ctx.createLinearGradi"
  },
  {
    "path": "miniprogram/components/painter/lib/pen.js",
    "chars": 19184,
    "preview": "const QR = require('./qrcode.js');\nconst GD = require('./gradient.js');\n\nexport default class Painter {\n\n  constructor(c"
  },
  {
    "path": "miniprogram/components/painter/lib/qrcode.js",
    "chars": 25304,
    "preview": "/* eslint-disable */\n!(function () {\n\n  // alignment pattern\n  var adelta = [\n    0, 11, 15, 19, 23, 27, 31,\n    16, 18,"
  },
  {
    "path": "miniprogram/components/painter/lib/util.js",
    "chars": 1530,
    "preview": "\nfunction isValidUrl(url) {\n  return /(ht|f)tp(s?):\\/\\/([^ \\\\/]*\\.)+[^ \\\\/]*(:[0-9]+)?\\/?/.test(url);\n}\n\n/**\n * 深度对比两个对象"
  },
  {
    "path": "miniprogram/components/painter/painter.js",
    "chars": 6984,
    "preview": "import Pen from './lib/pen';\nimport Downloader from './lib/downloader';\n\nconst util = require('./lib/util');\n\nconst down"
  },
  {
    "path": "miniprogram/components/painter/painter.json",
    "chars": 48,
    "preview": "{\n  \"component\": true,\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "miniprogram/components/painter/painter.wxml",
    "chars": 71,
    "preview": "<canvas canvas-id=\"k-canvas\" style=\"{{painterStyle}}{{customStyle}}\" />"
  },
  {
    "path": "miniprogram/components/pre-image/PreImage.js",
    "chars": 1023,
    "preview": "// components/pre-image/PreImage.js\nComponent({\n  externalClasses: 'extra-class',\n  /**\n   * 组件的属性列表\n   */\n  properties:"
  },
  {
    "path": "miniprogram/components/pre-image/PreImage.json",
    "chars": 48,
    "preview": "{\n  \"component\": true,\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "miniprogram/components/pre-image/PreImage.wxml",
    "chars": 596,
    "preview": "<!--components/pre-image/PreImage.wxml-->\n<view style='width:{{width}};height:{{height}};'>\n  <image wx:if='{{!loaded &&"
  },
  {
    "path": "miniprogram/components/pre-image/PreImage.wxss",
    "chars": 445,
    "preview": "/* components/pre-image/PreImage.wxss */\n.comp-preImg {\n  background-color: #eee;\n  width: 100%;\n  height: 100%;\n}\n.comp"
  },
  {
    "path": "miniprogram/components/rating/rating.js",
    "chars": 574,
    "preview": "// 组件-评分\n\nComponent({\n  behaviors: ['wx://form-field'],\n\n  options: {\n    addGlobalClass: true\n  },\n\n  properties: {\n   "
  },
  {
    "path": "miniprogram/components/rating/rating.json",
    "chars": 23,
    "preview": "{\n  \"component\": true\n}"
  },
  {
    "path": "miniprogram/components/rating/rating.wxml",
    "chars": 508,
    "preview": "<!--components/rating/rating.wxml 评分组件-->\n\n<view class='com-rating'>\n  <view class='rating-icon' wx:for='{{[1,2,3,4,5]}}"
  },
  {
    "path": "miniprogram/components/rating/rating.wxss",
    "chars": 482,
    "preview": "/* components/rating/rating.wxss */\n\n.com-rating {\n  display: inline-block;\n  letter-spacing: .3em;\n  position: relative"
  },
  {
    "path": "miniprogram/components/tab-bar/index.js",
    "chars": 1226,
    "preview": "Component({\n  properties: {\n    selected: {\n      type: Number,\n      value: 0\n    },\n    placeholder: {\n      type: Boo"
  },
  {
    "path": "miniprogram/components/tab-bar/index.json",
    "chars": 48,
    "preview": "{\n  \"component\": true,\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "miniprogram/components/tab-bar/index.wxml",
    "chars": 594,
    "preview": "<view class=\"tab-bar-container\">\n  <view class=\"tab-bar-placeholder\" wx:if=\"{{placeholder}}\"></view>\n  <view class=\"tab-"
  },
  {
    "path": "miniprogram/components/tab-bar/index.wxss",
    "chars": 1291,
    "preview": ".tab-bar-placeholder,\n.tab-bar {\n  display: flex;\n  height: 100rpx;\n  border-radius: 60rpx 60rpx 0 0;\n  padding-bottom: "
  },
  {
    "path": "miniprogram/components/towxml/audio-player/Audio.js",
    "chars": 2468,
    "preview": "const fillIn = val => `${val < 10 ? '0' : ''}${val}`,\n    formatTime = _time => {\n        let time = Math.round(_time);\n"
  },
  {
    "path": "miniprogram/components/towxml/audio-player/audio-player.js",
    "chars": 2295,
    "preview": "const Audio = require('./Audio');\nComponent({\n\toptions: {\n\t\tstyleIsolation: 'shared'\n\t},\n\tproperties: {\n\t\tdata: {\n\t\t\ttyp"
  },
  {
    "path": "miniprogram/components/towxml/audio-player/audio-player.json",
    "chars": 51,
    "preview": "{\n  \"component\": true,\n  \"usingComponents\": {\n  }\n}"
  },
  {
    "path": "miniprogram/components/towxml/audio-player/audio-player.wxml",
    "chars": 772,
    "preview": "<view class=\"h2w__audio {{tips.state || 'h2w__audio--loading'}}\" bind:tap=\"playAndPause\">\n    <view class=\"h2w__audioIco"
  },
  {
    "path": "miniprogram/components/towxml/audio-player/audio-player.wxss",
    "chars": 3125,
    "preview": "/*音频播放器样式*/\n.h2w__audio {\n    height: 136rpx;\n    margin:16rpx 0;\n    background: #f1f1f1;\n    position: relative;\n}\n\n.h"
  },
  {
    "path": "miniprogram/components/towxml/config.js",
    "chars": 6227,
    "preview": "module.exports = {\n    // LaTex公式、yuml解析服务架设参见 https://github.com/sbfkcel/markdown-server\n\n    // 数学公式解析API\n    // latex"
  },
  {
    "path": "miniprogram/components/towxml/decode.js",
    "chars": 440,
    "preview": "const config = require('./config');\n\nComponent({\n\toptions: {\n\t\tstyleIsolation: 'apply-shared'\n\t},\n\tproperties: {\n\t\tnodes"
  },
  {
    "path": "miniprogram/components/towxml/decode.json",
    "chars": 181,
    "preview": "{\n  \"component\": true,\n  \"usingComponents\": {\n    \"decode\": \"./decode\",\n    \"audio-player\": \"./audio-player/audio-player"
  },
  {
    "path": "miniprogram/components/towxml/decode.wxml",
    "chars": 4715,
    "preview": "<block wx:for=\"{{nodes.child}}\" wx:for-index=\"i\" wx:for-item=\"item\" wx:key=\"i\"><block wx:if=\"{{item.tag===undefined}}\">{"
  },
  {
    "path": "miniprogram/components/towxml/decode.wxss",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "miniprogram/components/towxml/img/img.js",
    "chars": 1782,
    "preview": "const config = require('../config');\nComponent({\n\toptions: {\n\t\tstyleIsolation: 'shared'\n\t},\n\tproperties: {\n\t\tdata: {\n\t\t\t"
  },
  {
    "path": "miniprogram/components/towxml/img/img.json",
    "chars": 23,
    "preview": "{\n  \"component\": true\n}"
  },
  {
    "path": "miniprogram/components/towxml/img/img.wxml",
    "chars": 188,
    "preview": "<image class=\"{{attr.class}}\" lazy-load=\"true\" mode=\"{{attr.mode || 'widthFix'}}\" src=\"{{attr.src}}\" style=\"{{attr.style"
  },
  {
    "path": "miniprogram/components/towxml/img/img.wxss",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "miniprogram/components/towxml/index.js",
    "chars": 489,
    "preview": "const md = require('./parse/markdown/index'),\n    parse = require('./parse/index')\n\nmodule.exports = (str,type,option)=>"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/highlight.js",
    "chars": 16494,
    "preview": "\"use strict\";function deepFreeze(e){Object.freeze(e);var n=\"function\"==typeof e;return Object.getOwnPropertyNames(e).for"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/index.js",
    "chars": 967,
    "preview": "import config from '../../config';\nimport hljs from './highlight';\n\nconst languages ={\n  'c-like': require('./languages/"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/bash.js",
    "chars": 3014,
    "preview": "/*\nLanguage: Bash\nAuthor: vah <vahtenberg@gmail.com>\nContributrors: Benjamin Pannell <contact@sierrasoftworks.com>\nWebsi"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/c-like.js",
    "chars": 8132,
    "preview": "/*\nLanguage: C-like foundation grammar for C/C++ grammars\nAuthor: Ivan Sagalaev <maniac@softwaremaniacs.org>\nContributor"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/c.js",
    "chars": 549,
    "preview": "/*\nLanguage: C\nCategory: common, system\nWebsite: https://en.wikipedia.org/wiki/C_(programming_language)\nRequires: c-like"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/css.js",
    "chars": 3482,
    "preview": "/*\nLanguage: CSS\nCategory: common, css\nWebsite: https://developer.mozilla.org/en-US/docs/Web/CSS\n*/\n\nexport default func"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/dart.js",
    "chars": 3242,
    "preview": "/*\nLanguage: Dart\nRequires: markdown.js\nAuthor: Maxim Dikun <dikmax@gmail.com>\nDescription: Dart a modern, object-orient"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/go.js",
    "chars": 1713,
    "preview": "/*\nLanguage: Go\nAuthor: Stephan Kountso aka StepLg <steplg@gmail.com>\nContributors: Evgeny Stepanischev <imbolk@gmail.co"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/htmlbars.js",
    "chars": 1860,
    "preview": "/*\nLanguage: HTMLBars\nRequires: xml.js\nAuthor: Michael Johnston <lastobelus@gmail.com>\nDescription: Matcher for HTMLBars"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/index.js",
    "chars": 646,
    "preview": "export * as cLike from './c-like';\nexport * as c from './c';\nexport * as bash from './bash';\nexport * as css from './css"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/java.js",
    "chars": 3670,
    "preview": "/*\nLanguage: Java\nAuthor: Vsevolod Solovyov <vsevolod.solovyov@gmail.com>\nCategory: common, enterprise\nWebsite: https://"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/javascript.js",
    "chars": 7221,
    "preview": "/*\nLanguage: JavaScript\nDescription: JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/json.js",
    "chars": 1339,
    "preview": "/*\nLanguage: JSON\nDescription: JSON (JavaScript Object Notation) is a lightweight data-interchange format.\nAuthor: Ivan "
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/less.js",
    "chars": 5047,
    "preview": "/*\nLanguage: Less\nDescription: It's CSS, with just a little more.\nAuthor:   Max Mikhailov <seven.phases.max@gmail.com>\nW"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/nginx.js",
    "chars": 2528,
    "preview": "/*\nLanguage: Nginx config\nAuthor: Peter Leonov <gojpeg@yandex.ru>\nContributors: Ivan Sagalaev <maniac@softwaremaniacs.or"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/php.js",
    "chars": 6387,
    "preview": "/*\nLanguage: PHP\nAuthor: Victor Karamzin <Victor.Karamzin@enterra-inc.com>\nContributors: Evgeny Stepanischev <imbolk@gma"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/python-repl.js",
    "chars": 603,
    "preview": "/*\nLanguage: Python REPL\nRequires: python.js\nAuthor: Josh Goebel <hello@joshgoebel.com>\nCategory: common\n*/\n\nexport defa"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/python.js",
    "chars": 3367,
    "preview": "/*\nLanguage: Python\nDescription: Python is an interpreted, object-oriented, high-level programming language with dynamic"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/scss.js",
    "chars": 7896,
    "preview": "/*\nLanguage: SCSS\nDescription: Scss is an extension of the syntax of CSS.\nAuthor: Kurt Emch <kurt@kurtemch.com>\nWebsite:"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/shell.js",
    "chars": 406,
    "preview": "/*\nLanguage: Shell Session\nRequires: bash.js\nAuthor: TSUYUSATO Kitsune <make.just.on@gmail.com>\nCategory: common\n*/\n\nexp"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/typescript.js",
    "chars": 5712,
    "preview": "/*\nLanguage: TypeScript\nAuthor: Panu Horsmalahti <panu.horsmalahti@iki.fi>\nContributors: Ike Ku <dempfi@yahoo.com>\nDescr"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/languages/xml.js",
    "chars": 3526,
    "preview": "/*\nLanguage: HTML, XML\nWebsite: https://www.w3.org/XML/\nCategory: common\n*/\n\nexport default function(hljs) {\n  var XML_I"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/style/github.wxss",
    "chars": 1511,
    "preview": "/*\n\ngithub.com style (c) Vasily Polovnyov <vast@whiteants.net>\n\n*/\n\n.h2w-light .hljs {\n  display: block;\n  overflow-x: a"
  },
  {
    "path": "miniprogram/components/towxml/parse/highlight/style/monokai.wxss",
    "chars": 1338,
    "preview": "/*\nMonokai style - ported by Luigi Maselli - http://grigio.org\n*/\n\n.h2w-dark .hljs {\n  display: block;\n  overflow-x: aut"
  },
  {
    "path": "miniprogram/components/towxml/parse/index.js",
    "chars": 3331,
    "preview": "const parse2 = require('./parse2/index'),\n    // parse5 = require('./parse5/index').parse,\n    config = require('../conf"
  },
  {
    "path": "miniprogram/components/towxml/parse/markdown/index.js",
    "chars": 1730,
    "preview": "let hljs;\nhljs = require('../highlight/index');\n\nconst config = require('../../config'),\n    mdOption = (()=>{\n        l"
  },
  {
    "path": "miniprogram/components/towxml/parse/markdown/markdown.js",
    "chars": 106595,
    "preview": "!function(e){if(\"object\"==typeof exports&&\"undefined\"!=typeof module)module.exports=e();else if(\"function\"==typeof defin"
  },
  {
    "path": "miniprogram/components/towxml/parse/markdown/plugins/emoji.js",
    "chars": 28104,
    "preview": "/*! Project:无, Create:FWS 2020.01.15 14:04, Update:FWS 2020.01.15 14:04 */ \r\n!function(a){if(\"object\"==typeof exports&&\""
  },
  {
    "path": "miniprogram/components/towxml/parse/markdown/plugins/ins.js",
    "chars": 1868,
    "preview": "!function(e){if(\"object\"==typeof exports&&\"undefined\"!=typeof module)module.exports=e();else if(\"function\"==typeof defin"
  },
  {
    "path": "miniprogram/components/towxml/parse/markdown/plugins/mark.js",
    "chars": 1876,
    "preview": "!function(e){if(\"object\"==typeof exports&&\"undefined\"!=typeof module)module.exports=e();else if(\"function\"==typeof defin"
  },
  {
    "path": "miniprogram/components/towxml/parse/markdown/plugins/sub.js",
    "chars": 1415,
    "preview": "!function(e){if(\"object\"==typeof exports&&\"undefined\"!=typeof module)module.exports=e();else if(\"function\"==typeof defin"
  },
  {
    "path": "miniprogram/components/towxml/parse/markdown/plugins/sup.js",
    "chars": 1413,
    "preview": "!function(e){if(\"object\"==typeof exports&&\"undefined\"!=typeof module)module.exports=e();else if(\"function\"==typeof defin"
  },
  {
    "path": "miniprogram/components/towxml/parse/markdown/plugins/todo.js",
    "chars": 6591,
    "preview": "(function(f){if(typeof exports===\"object\"&&typeof module!==\"undefined\"){module.exports=f()}else if(typeof define===\"func"
  },
  {
    "path": "miniprogram/components/towxml/parse/parse2/Parser.js",
    "chars": 6669,
    "preview": "/*! Project:无, Create:FWS 2020.01.08 21:48, Update:FWS 2020.01.08 21:48 */ \r\n\"use strict\";function Parser(t,e){var s=thi"
  },
  {
    "path": "miniprogram/components/towxml/parse/parse2/Tokenizer.js",
    "chars": 14641,
    "preview": "/*! Project:无, Create:FWS 2020.01.08 21:48, Update:FWS 2020.01.08 21:48 */ \r\n\"use strict\";function whitespace(t){return\""
  },
  {
    "path": "miniprogram/components/towxml/parse/parse2/domhandler/index.js",
    "chars": 2795,
    "preview": "/*! Project:无, Create:FWS 2020.01.08 21:48, Update:FWS 2020.01.08 21:48 */ \r\n\"use strict\";Object.defineProperty(exports,"
  },
  {
    "path": "miniprogram/components/towxml/parse/parse2/domhandler/node.js",
    "chars": 2804,
    "preview": "/*! Project:无, Create:FWS 2020.01.08 21:48, Update:FWS 2020.01.08 21:48 */ \r\n\"use strict\";var __extends=this&&this.__ext"
  },
  {
    "path": "miniprogram/components/towxml/parse/parse2/entities/decode.js",
    "chars": 1521,
    "preview": "/*! Project:无, Create:FWS 2020.01.08 21:48, Update:FWS 2020.01.08 21:48 */ \r\n\"use strict\";function getStrictDecoder(e){v"
  },
  {
    "path": "miniprogram/components/towxml/parse/parse2/entities/decode_codepoint.js",
    "chars": 591,
    "preview": "/*! Project:无, Create:FWS 2020.01.08 21:48, Update:FWS 2020.01.08 21:48 */ \r\n\"use strict\";function decodeCodePoint(e){if"
  },
  {
    "path": "miniprogram/components/towxml/parse/parse2/entities/encode.js",
    "chars": 1649,
    "preview": "/*! Project:无, Create:FWS 2020.01.08 21:48, Update:FWS 2020.01.08 21:48 */ \r\n\"use strict\";function getInverseObj(e){retu"
  },
  {
    "path": "miniprogram/components/towxml/parse/parse2/entities/index.js",
    "chars": 1144,
    "preview": "/*! Project:无, Create:FWS 2020.01.08 21:48, Update:FWS 2020.01.08 21:48 */ \r\n\"use strict\";function decode(e,d){return(!d"
  },
  {
    "path": "miniprogram/components/towxml/parse/parse2/entities/maps/decode.js",
    "chars": 335,
    "preview": "/*! Project:无, Create:FWS 2020.01.08 21:48, Update:FWS 2020.01.08 21:48 */ \r\nmodule.exports={0:65533,128:8364,130:8218,1"
  },
  {
    "path": "miniprogram/components/towxml/parse/parse2/entities/maps/entities.js",
    "chars": 24819,
    "preview": "/*! Project:无, Create:FWS 2020.01.08 21:48, Update:FWS 2020.01.08 21:48 */ \r\nmodule.exports={Aacute:\"Á\",aacute:\"á\",Abrev"
  }
]

// ... and 313 more files (download for full content)

About this extraction

This page contains the full source code of the Honye/weapp-mark GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 513 files (1.9 MB), approximately 535.3k tokens, and a symbol index with 996 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!