CatPtain commited on
Commit
765bc42
·
verified ·
1 Parent(s): 35d2472

Upload 83 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
.eslintrc.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "parserOptions": {
3
+ "ecmaVersion": 11
4
+ },
5
+ "extends": [
6
+ "airbnb-base",
7
+ "prettier"
8
+ ],
9
+ "rules": {
10
+ "camelcase": "off",
11
+ "linebreak-style": "off"
12
+ },
13
+ "globals": {
14
+ "axios": "readonly",
15
+ "window": "readonly",
16
+ "browser": "readonly",
17
+ "chrome": "readonly"
18
+ },
19
+ "env": {
20
+ "browser": true,
21
+ "node": false
22
+ },
23
+ "ignorePatterns": [
24
+ "**/vendor/*.js"
25
+ ]
26
+ }
.github/workflows/eslint.yml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: ESLint
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ - uses: actions/setup-node@v2
16
+ with:
17
+ node-version: 16
18
+
19
+ - name: Install Dependencies
20
+ run: npm ci
21
+
22
+ - name: Run ESLint
23
+ run: npx eslint .
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Folder view configuration files
2
+ .DS_Store
3
+ .vscode
4
+ .eslintcache
5
+
6
+ node_modules
7
+ dist
.prettierrc ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "singleQuote": true,
3
+ "trailingComma": "es5"
4
+ }
LICENSE ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Listen 1
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
README.md CHANGED
@@ -1,10 +1,510 @@
1
- ---
2
- title: ListenOne
3
- emoji: 👀
4
- colorFrom: blue
5
- colorTo: blue
6
- sdk: static
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Listen 1 (Chrome Extension) V2.21.7
2
+
3
+ (最后更新于 2022 年 01 月 23 日)
4
+
5
+ [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
6
+
7
+ [English Version](https://github.com/listen1/listen1_chrome_extension/blob/master/README_EN.md)
8
+
9
+ ## 缘起
10
+
11
+ 当我发现找个想听的歌因为版权听不了,需要打开好几个网站开始搜索,来回切换让我抓狂的时候,我知道是时候该做点什么了。
12
+
13
+ 妈妈再也不用担心我找不到我想听的歌了。
14
+
15
+ 支持音乐平台
16
+
17
+ - 网易云音乐
18
+ - QQ 音乐
19
+ - 酷狗音乐
20
+ - 酷我音乐
21
+ - bilibili
22
+ - 咪咕音乐
23
+ - 千千音乐
24
+
25
+ 搜歌,听歌,就用 `Listen1`。
26
+
27
+ [![imgur](https://i.imgur.com/dIVFtor.gif)]()
28
+
29
+ V2.9.0 新特性:自动切换播放源(Beta)
30
+
31
+ 当一首歌的播放源不可用时,会自动搜索其他平台,获得可用的播放源。避免了用户手动搜索的麻烦。
32
+
33
+ 还有精选歌单哦。
34
+
35
+ ## 官方商店安装(推荐)
36
+
37
+ 按你的浏览器类型点击下面的链接安装
38
+
39
+ - [Chrome Web Store 安装](https://chrome.google.com/webstore/detail/listen-1/indecfegkejajpaipjipfkkbedgaodbp)
40
+ - [FireFox 安装](https://addons.mozilla.org/zh-CN/firefox/addon/listen1/)
41
+ - [Microsoft Edge 安装](https://microsoftedge.microsoft.com/addons/detail/hneiglcmpeedblkmbndhfbeahcpjojjg)
42
+
43
+ 感谢 [@TNT-c](https://github.com/TNT-c) 维护 Firefox 的发布渠道
44
+
45
+ 感谢 [@dhxh](https://github.com/dhxh) 维护 Microsoft Edge 的发布渠道
46
+
47
+ ## Chrome 下载安装
48
+
49
+ 1. 下载项目的 zip 文件,在右上方有个 `Download ZIP`, 解压到本地
50
+
51
+ 2. chrome 右上角的设置按钮下找到更多工具,打开`扩展程序`
52
+
53
+ 3. 选择 `加载已解压的扩展程序`(如果没有显示先选中`开发者模式`),选中解压后的文件夹,完成!
54
+
55
+ ## Firefox 打包安装
56
+
57
+ 1. 将根目录下 manifest_firefox.json 替换 manifest.json
58
+
59
+ 2. `cd listen1_chrome_extension`
60
+
61
+ 3. `zip -r ../listen1.xpi *`, 完成打包 xpi 文件
62
+
63
+ 4. 打开 Firefox,加载 xpi 文件,完成安装
64
+
65
+ ## QQ 音乐举报 Listen1 导致代码库临时关闭事件 (2017 年 11 月)
66
+
67
+ Listen1 的用户,有个坏消息希望和大家分享。Listen1 最近收到了[QQ 音乐的 DMCA Takedown Notice](https://github.com/github/dmca/blob/master/2017/2017-11-17-Listen1.md), 主要代码库已经因为此原因而临时关闭。悲观一点看,Listen1 项目可能会在今年内彻底消失。
68
+
69
+ Listen1 诞生的初衷从不是和大公司的争夺版权利益,而是为了给予热爱音乐的人更好的收听体验,所以,Listen1 是开源,免费的,并且不接受任何形式的捐助。正是因为有热爱音乐的 Listen1 的你们,Listen1 才发展到今天这一步。不管结果如何,Listen1 团队感谢所有支持过这个项目的人们。
70
+
71
+ 在这个关系项目生死存亡的时刻,我寻求项目因为 DMCA 被 github 关闭的援助。如果有对这个比较了解如何解决的人,或者你想对这个事情发表看法和建议,可以在[issue](https://github.com/listen1/listen1_chrome_extension/issues/113)留言,或者发送邮件到 [email protected]。我们会尽最大努力,来守护 Listen1,即使可能它即将成为历史。
72
+
73
+ ## 更新日志
74
+
75
+ `2021-08 ~ 2022-01`
76
+
77
+ 修复:
78
+
79
+ - 修复音乐分类按钮显示没有间距的问题 (感谢 @yinzhenyu-su 的提交)
80
+ - 修复在 firefox 无法打开 bilibili 音乐的问题 (感谢 @ktmzcpl 的提交)
81
+ - 修复在 electron 环境启动时的 UI 崩溃问题
82
+
83
+ 优化:
84
+
85
+ - 更平滑的当前播放切换效果 (感谢 @mikelxk 的提交)
86
+
87
+ `2021-07`
88
+
89
+ 修复:
90
+
91
+ - 禁止图片拖动
92
+ - 增加快捷键中放大缩小功能的描述
93
+ - 修改 windows 用户的窗口控制按钮位置到右上角 (感谢 @mikelxk 的提交)
94
+ - 升级 howler 库 (感谢 @mikelxk 的提交)
95
+ - 修复 QQ 音乐无法搜索的问题
96
+ - 修复 chrome 浏览器媒体控制中进度条拖动的问题 (感谢 @mikelxk 的提交)
97
+ - 增加本地音乐的本地 lrc 歌词文件支持 (感谢 @mikelxk 的提交)
98
+
99
+ `2021-04`
100
+
101
+ 功能改进:
102
+
103
+ - 增加 QQ 音乐的登录支持
104
+ - 增加拖拽支持,支持歌单内歌曲调整顺序,歌单调整顺序,正在播放歌曲调整顺序,以及拖动歌曲加入歌单的操作
105
+ - 支持歌单内搜索
106
+ - 桌面版支持代理设置
107
+ - 支持配置自动切换源的搜索平台
108
+ - 增加显示当前最新版本
109
+ - 增加对网易云平台的默认高码率音源支持
110
+
111
+ 重构和优化:
112
+
113
+ - 将音乐平台接口做 class 改造 #553
114
+ - github 模块去除 angular 依赖 #532 (感谢 @Dumeng 的提交)
115
+ - lastfm 模块去除 angular 依赖 #532 (感谢 @Dumeng 的提交)
116
+ - 优化 UI 细节,提升用户体验 #537
117
+
118
+ 修复:
119
+
120
+ - 修复需要登录才能获取咪咕播放链接,并增加码率数据 #536 (感谢 @RecluseWind 的提交)
121
+ - 修复音乐榜和影视榜在 Firefox 上的不能正确获取的 bug #536 (感谢 @RecluseWind 的提交)
122
+ - 修复某些情况下歌曲在播放前总是等待 15 秒的 bug
123
+ - 修复 QQ 音乐短链接歌单分享地址不被识别的问题
124
+ - 修复开启关闭静音功能失效的问题
125
+ - 修复 GitHub 账户无法退出的问题
126
+ - 修复 kugou 部分音乐因专辑缺失导致的播放错误
127
+ - 修复多首歌曲重复播放的问题
128
+
129
+ `2021-03`
130
+
131
+ 功能改进:
132
+
133
+ - 新增千千音乐平台 (感谢 @Dumeng 的提交)
134
+ - 支持咪咕音乐的分类歌单和排行榜歌单功能 (感谢 @RecluseWind 的提交)
135
+ - 桌面版支持放大功能 (感谢 @mikelxk 的提交)
136
+ - 支持网易登录功能,支持打开我的歌单和推荐歌单
137
+ - 支持咪咕登录功能
138
+ - 支持在正在播放页面显示当前播放歌曲的码率和平台
139
+ - 移除虾米平台
140
+
141
+ 重构和优化:
142
+
143
+ - 替换了对 translate,i18n, hotkeys 的 angular 模块依赖,替换为纯 js 库 (感谢 @Dumeng 的提交)
144
+ - 优化载入 feather 图标库的效率 (感谢 @Dumeng 的提交)
145
+ - 改善了多个平台默认码率,默认播放高码率音乐文件
146
+ - 将 app.js 按多个 controller 模块分为多个文件
147
+ - 优化显示了因为版权问题无法播放的通知
148
+ - 将大部分链接改成 https 协议
149
+
150
+ 修复:
151
+
152
+ - 修复新语法导致媒体控制在某些系统中不可用的问题 (感谢 @mikelxk 的提交)
153
+ - 修复音量控制快捷键失效的问题 (感谢 @mikelxk 的提交)
154
+ - 修复了在 firefox 上的滚动条样式 (感谢 @RecluseWind 的提交)
155
+ - 修复酷狗音乐封面的错误
156
+ - 修复酷狗某些歌曲不能播放的问题
157
+ - 修复通知无法显示的问题
158
+ - 修复了删除当前播放列表歌曲后导致的各种异常
159
+
160
+ `2021-02`
161
+
162
+ 功能改进:
163
+
164
+ - 支持分类歌单和排行榜(感谢 https://github.com/lyswhut/lx-music-desktop 提供 QQ 音乐排行实现)
165
+ - 增加繁体中文翻译 (感谢 @yujiangqaq 提供翻译)
166
+ - 增加 chrome 媒体控制上一曲,下一曲和快进快退 (感谢 @mikelxk 的提交)
167
+ - 改进桌面版桌面歌词,增加字体大小颜色设置和背景透明度调整
168
+
169
+ 重构:
170
+
171
+ - 将媒体资源服务重构成 MediaService 模块,除去对 angularjs 的依赖 (特别感谢 @Dumeng 的提交)
172
+ - 增加 prettier 配置文件和 commit 前检查 (感谢 @mikelxk 的提交)
173
+ - 修正一些过往代码的格式错误 (感谢 @mikelxk 的提交)
174
+
175
+ 修复:
176
+
177
+ - 修复 Github API (感谢 @NoDocCat 和 @Dumeng 的提交)
178
+ - 修复因 svg 动画导致的性能问题 (感谢 @Dumeng 的提交)
179
+ - 修复虾米部分失效 API(感谢 @RecluseWind 的提交)
180
+ - 修复 Mac 桌面版无法导入本地音乐的问题 (感谢 @virgil1996 的提交)
181
+ - 修复酷我搜索出错的问题
182
+
183
+ `2021-01`
184
+
185
+ 功能改进:
186
+
187
+ - 支持插件版后台播放功能 (特别感谢 @Dumeng 的提交)
188
+ - 优化酷我代码 (感谢 @RecluseWind 的提交)
189
+ - 优化咪咕音乐代码 (感谢 @RecluseWind 的提交)
190
+ - 本地音乐支持 flac 格式 (感谢 @mikelxk 的提交)
191
+ - 在软件中增加反馈链接 (感谢 @mikelxk 的提交)
192
+ - 增加虾米歌单搜索,统一端口代码 (感谢 @RecluseWind 的提交)
193
+ - 优化了歌单访问,增加本地缓存
194
+
195
+ 重构:
196
+
197
+ - 更换所有加解密库到 forge (感谢 @Dumeng 的提交)
198
+ - 去除对 jquery 库的依赖 (感谢 @Dumeng 的提交)
199
+ - 更换音频播放库到 howler.js (感谢 @Dumeng 的提交)
200
+ - 更换 http 请求库到 axios (感谢 @Dumeng 的提交)
201
+ - 支持 eslint 的 github action 语法检查 (感谢 @Dumeng 的提交)
202
+
203
+ bug 修复:
204
+
205
+ - 修复 MediaSession 不支持时的报错问题 (感谢 @Jyuaan 的提交)
206
+ - 修复咪咕歌单的 404 错误
207
+ - 修复正在播放窗口点击空白处弹回的功能 (感谢 @Demeng 的提交)
208
+
209
+ `2020-12-28`
210
+
211
+ - 修复最大,最小,关闭按钮在桌面版失效的问题
212
+
213
+ `2020-12-27`
214
+
215
+ - 修复无法显示收藏歌单的 bug
216
+ - 支持一次输入搜索所有平台(Beta)
217
+ - 修复咪咕音乐歌单只显示前 20 首歌的 bug
218
+ - 修复网易和酷狗音乐搜索错误未处理的 bug
219
+ - 修复虾米音乐歌词解析错误导致无法显示的 bug
220
+ - 根据 chrome web store 上架要求修改部分权限
221
+
222
+ `2020-12-22`
223
+
224
+ - 修复酷我音乐无法播放的问题
225
+ - 修复我创建的歌单升级后无法播放的问题
226
+
227
+ `2020-12-20`
228
+
229
+ - 修复版权问题造成的播放中断和循环弹出提示通知的 bug
230
+ - 修改歌曲封面为背景时歌词看不清的问题
231
+ - 修复 qq 搜索的一个错误,优化接口返回时处理(感谢@RecluseWind 的提交)
232
+
233
+ `2020-12-12`
234
+
235
+ - 支持 QQ 音乐歌单搜索 (感谢@RecluseWind 的提交)
236
+ - 修复网易云音乐无法打开手机分享的歌单链接的 bug (感谢@RecluseWind 的提交)
237
+ - 修复咪咕音乐无法搜索的 bug
238
+
239
+ `2020-10-28`
240
+
241
+ - 增加本地音乐(仅限桌面版)
242
+
243
+ `2020-10-27`
244
+
245
+ - 增加歌单搜索功能(暂时只支持网易云)
246
+ - 优化歌词显示
247
+ - 修复 blili 歌手 API 错误��修复歌词时间轴格式不统一产生的错误 (感谢@RecluseWind 的提交)
248
+ - 优化 UI,正在播放页增加翻译按钮
249
+
250
+ `2020-10-26`
251
+
252
+ - 增加歌词翻译功能 QQ 音乐和虾米音乐的支持(感谢@RecluseWind 的提交)
253
+ - 更新了虾米音乐获取歌曲播放地址,获取歌单,搜索 API 的获取方式,增加可靠性 (感谢@RecluseWind 的提交)
254
+ - 修复安装插件后 qq 音乐网页部分歌单无法打开的 bug
255
+
256
+ `2020-10-18`
257
+
258
+ - 增加歌词翻译功能,暂时只支持网易云音乐 (感谢@reserveword 的提交)
259
+ - 修复 bilibili 音乐无法播放的 bug
260
+ - 修复虾米播放页歌曲封面无法显示的 bug
261
+ - 修复酷我音乐歌单无法打开的 bug
262
+
263
+ `2020-09-12`
264
+
265
+ - 修复网易歌单超过 1000 首时导入失败的 bug (感谢@YueShangGuan 的提交)
266
+ - 支持显示歌曲封面作为正在播放背景 (感谢@YueShangGuan 的提交)
267
+
268
+ `2020-08-24`
269
+
270
+ - 修复虾米歌单歌曲只显示部分歌曲的 bug (感谢@RecluseWind 的提交)
271
+ - 修复歌单图片和标题显示问题 (感谢@RecluseWind 的提交)
272
+ - 支持桌面版点击链接打开系统默认浏览器
273
+
274
+ `2020-08-04`
275
+
276
+ - 增加正在播放窗口和播放列表弹窗的动画效果
277
+ - 修复虾米艺人封面图片无法显示的问题 (感谢@RecluseWind 的提交)
278
+ - 优化打开歌单功能,支持网易云排行榜单,艺人页面,专辑页面网址(感谢@whtiehack 的提交)
279
+ - 优化专辑图片显示,避免图片被压缩 (感谢@RecluseWind 的提交)
280
+
281
+ `2020-07-10`
282
+
283
+ - 修复咪咕音乐无法播放的问题
284
+ - 支持顶部搜索栏回车触发 (感谢@kangbb 的提交)
285
+ - 支持歌单歌曲数显示,支持播放/暂停全局快捷键(桌面版)(感谢@x2009again 的提交)
286
+ - 支持返回时回到滚动条历史位置(感谢@x2009again 参与完成)
287
+ - 优化 firefox 滑动条,修改 qq 音乐图标网址,解决 firefox 上架 jquery 代码问题 (感谢@RecluseWind 的提交)
288
+
289
+ `2020-06-29`
290
+
291
+ - 支持播放失败时自动切换播放源(Beta)
292
+
293
+ `2020-06-28`
294
+
295
+ - 修复网易歌单仅显示 10 首歌曲的问题
296
+
297
+ `2020-04-30`
298
+
299
+ - 修复咪咕音质较差的问题
300
+
301
+ `2020-04-27`
302
+
303
+ - 增加收藏歌单功能,特别感谢 @zhenyiLiang
304
+ - 修复咪咕音乐无法播放的 bug
305
+ - 一些细节优化
306
+
307
+ `2019-11-27`
308
+
309
+ - 加入法语支持, 特别感谢 @Leoche
310
+
311
+ `2019-09-07`
312
+
313
+ - 修复 migu 无法播放的 bug
314
+
315
+ `2019-08-09`
316
+
317
+ - 增加深色主题
318
+
319
+ `2019-07-03`
320
+
321
+ - 修复咪咕音乐无法播放的 bug
322
+
323
+ `2019-06-24`
324
+
325
+ - 增加咪咕音乐
326
+ - 修复网易音乐无法播放的 bug
327
+ - 修复酷狗音乐无法播放的 bug
328
+
329
+ `2019-06-23`
330
+
331
+ - 修复无法连接到 github 的 bug
332
+
333
+ `2019-05-26`
334
+
335
+ - 修复酷狗音乐无法播放的 bug
336
+
337
+ `2019-04-26`
338
+
339
+ - 修复虾米音乐无法播放的 bug
340
+ - 修复播放器未在页面底端显示的 bug
341
+
342
+ `2019-03-03`
343
+
344
+ - 修复删除单个歌曲导致歌单所有歌曲消失的 bug
345
+ - 修复删除单个歌单导致所有歌单消失的 bug
346
+
347
+ `2019-02-26`
348
+
349
+ - 修复 qq 音乐歌单无法显示的 bug
350
+
351
+ `2018-12-30`
352
+
353
+ - 修复酷我音乐歌单缺失歌曲的问题
354
+ - 自动检测客户端语言
355
+
356
+ `2018-12-29`
357
+
358
+ - 修复虾米音乐搜索失败的问题
359
+ - 修复部分 QQ 音乐歌曲无法播放的问题
360
+ - 修复使用插件时 QQ 官方网站无法使用的问题
361
+
362
+ `2018-12-24`
363
+
364
+ - 多语言支持,支持英文
365
+ - 新添加到歌单的歌曲将出现在歌单头部
366
+ - 修复版权通知占满屏幕的 bug
367
+
368
+ `2018-12-22`
369
+
370
+ - 全新版本 2.0 发布,更新界面(特别感谢@iparanoid 提供主题设计)
371
+ - 升级 jquery 和 angular 版本
372
+
373
+ `2018-12-21`
374
+
375
+ - 修复虾米音乐歌单访问的问题
376
+ - 修复网易云音乐歌单只有一首歌的问题
377
+ - 修复 bilibili 滚动时加载重复歌单的问题
378
+ - 修复酷狗部分音乐无法播放的问题
379
+ - 修复 Github Gist 备份无法导入的问题
380
+ - 升级 soundmanager2 库到最新版本
381
+
382
+ `2018-12-05`
383
+
384
+ - 完全修复虾米音乐歌单访问的问题
385
+
386
+ `2018-08-25`
387
+
388
+ - 修复虾米音乐无法播放的 bug
389
+
390
+ `2018-06-15`
391
+
392
+ - 增加酷我音乐的支持(特别感谢@WinterXMQ 的提交)
393
+
394
+ `2018-06-10`
395
+
396
+ - 修复酷狗音乐收藏歌单后可能显示空歌单的 bug
397
+
398
+ `2018-06-10`
399
+
400
+ - 修复虾米音乐无法显示歌词的 bug
401
+
402
+ `2018-06-05`
403
+
404
+ - 增加酷狗音乐的支持(感谢@WinterXMQ )
405
+
406
+ `2018-05-30`
407
+
408
+ - 修复 QQ 音乐无法播放的问题(感谢@noschoollee 提供修复方案)
409
+
410
+ `2018-04-23`
411
+
412
+ - 修复虾米音乐无法播放的问题
413
+
414
+ `2018-02-18`
415
+
416
+ - 修复无法创建歌单的 bug
417
+ - 修复点击关闭歌单按钮后无法再打开歌单的 bug
418
+ - 增加歌曲主页,点击封面可进入(特别感谢@iparanoid 提供歌曲页面 UI 设计)
419
+
420
+ `2018-02-15`
421
+
422
+ - 修复随机播放在播放列表播放结束后自动停止的问题,开启无限洗脑循环(感谢@sunjie21 的提交)
423
+ - 增加将当前播放列表全部添加到歌单的功能 (感谢@sunjie21 的提交)
424
+ - 修复标题播放状态不实时更新的 bug (感谢@sibojia 的提交)
425
+
426
+ `2018-02-14`
427
+
428
+ - 修复主页在加载更多数据时出现双重滚动条的 bug,并修改了滚动条样式(感谢@zhuzhuyule 的提交)
429
+ - 修复打开歌单时,网易云音乐个人歌单地址无法解析的 bug(感谢@zhuzhuyule 的提交)
430
+
431
+ `2017-12-26`
432
+
433
+ - 增加同步歌单到 Github Gist 功能。(特别感谢@ConstLhq 提供创意和部分代码实现)
434
+
435
+ `2017-12-20`
436
+
437
+ - 增加搜索翻页功能,你可以看到更多的搜索结果了。(感谢@ConstLhq 的提交)
438
+ - 增加合并歌单功能。可以快速的把其它你创建的歌曲合并到当前的歌单中了。(感谢@Dumeng 的提交)
439
+
440
+ `2017-11-27`
441
+
442
+ - 修复网易云音乐歌单只显示第一首歌的 Bug(感谢[@Binaryify/NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)提供接口实现)
443
+
444
+ `2017-11-18`
445
+
446
+ - 修复版权原因无法播放歌曲时自动暂停的问题
447
+
448
+ `2017-11-17`
449
+
450
+ - 在我的歌单页面增加“打开歌单”功能,可打开支持网页的歌单链接地址。这样就可以导入你喜欢的歌单了。
451
+ - HTTP 请求头部的 Origin 字段设置为正常网址
452
+
453
+ `2017-10-16`
454
+
455
+ - 修复 QQ 音乐歌单翻页显示重复的问题(感谢@Moobusy 的提交)
456
+
457
+ `2017-10-03`
458
+
459
+ - 修复网易云音乐歌单无法显示的问题(感谢@Moobusy 的提交)
460
+
461
+ `2017-09-14`
462
+
463
+ - 修复 QQ 音乐无法播放的 bug
464
+
465
+ `2016-05-27`
466
+
467
+ - 增加快捷键功能(输入?查看快捷键设置)
468
+ - 支持同步播放记录到 last.fm
469
+ - 增加搜索 loading 时的图标(感谢@richdho 的提交)
470
+ - 页面标题增加显示当前播放信息
471
+ - 修复了在收藏对话框点击取消出现新建歌单的 bug
472
+ - 重新组织代码文件夹结构
473
+
474
+ `2016-05-21`
475
+
476
+ - 增加歌单分页加载功能(感谢@wild-flame 的提交)
477
+ - 修复关闭按钮随网页滚动的 bug
478
+ - 修复点击暂停按钮会重置进度条和歌词的 bug
479
+ - 修复点击歌单名称不跳转的 bug
480
+ - 调整歌单水平位置居中
481
+
482
+ `2016-05-14`
483
+
484
+ - 增加 firefox 插件支持(感谢 fulesdle 的提交)
485
+
486
+ `2016-05-13`
487
+
488
+ - 增加我的歌单功能,可以收藏现有歌单,并创建自己的歌单
489
+ - 点击 Listen 1 和图标可以回到首页
490
+ - 标记了部分因版权无法播放的歌曲,增加版权提示
491
+ - 重构了音乐平台代码,使用统一的接口规范
492
+ - 重构了歌单接口,合并歌手,专辑和歌单接口
493
+ - 修复了阿里云歌手链接点击错误的 bug
494
+
495
+ `2016-05-08`
496
+
497
+ - 增加歌词显示
498
+ - 精选歌单:添加歌单到当前播放列表,可点击跳转到原始链接
499
+ - 修复了搜索 qq 音乐时的乱码问题
500
+ - 修复了循环播放网易歌曲一段时间后暂停的 bug
501
+ - 修复了可能导致微信公众号无法登录的 bug
502
+ - 优化性能,删除了不必要的事件消息触发
503
+
504
+ `2016-05-02`
505
+
506
+ - 增加音量控制
507
+
508
+ ## License
509
+
510
+ MIT
README_EN.md ADDED
@@ -0,0 +1,358 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Listen 1 (Chrome Extension) V2.21.7
2
+
3
+ (Last Update Jan 23rd, 2022)
4
+
5
+ [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
6
+
7
+ ## One for all free music in China
8
+
9
+ When I found many songs are unavailable because copyright issue, I realized there's something I should do.
10
+ Mom never need to worry about I can't listen my favorite songs.
11
+
12
+ Supported music platform:
13
+
14
+ - Netease
15
+ - QQ
16
+ - Kugou
17
+ - Kuwo
18
+ - Bilibili
19
+ - Migu
20
+ - Qianqian (taihe)
21
+
22
+ Search songs, listen songs from multiple platforms, that's `Listen 1`.
23
+
24
+ V2.9.0 New Feature: Auto choose source
25
+
26
+ when music play source url is not available, auto choose source from other sources.
27
+
28
+ Making your own playlist is also supported.
29
+
30
+ ## How to change language ?
31
+
32
+ 1. Click Settings icon in right top of application
33
+ 2. Click `English` under `Language` or `语言`
34
+
35
+ ## Install (Chrome)
36
+
37
+ 1. download zip file from github and uncompress to local.
38
+
39
+ 2. open Extensions from chrome.
40
+
41
+ 3. Choose `Load unpacked`(Open Develop Mode first),Click folder you just uncompressed, finish!
42
+
43
+ ## Install (Firefox)
44
+
45
+ 1. Visit Listen1 Firefox Page https://addons.mozilla.org/zh-CN/firefox/addon/listen-1/
46
+ 2. Click Add to Firefox button
47
+
48
+ ## Changelog
49
+
50
+ `2021-08 ~ 2022-01`
51
+
52
+ Fix bugs:
53
+
54
+ - fix music category line height (thanks @yinzhenyu-su)
55
+ - fix bilibili play issue in firefox (thanks @ktmzcpl)
56
+ - fix UI crash in electron environment
57
+
58
+ Optimaze:
59
+
60
+ - More fluent effect for current playing switching (thanks @mikelxk)
61
+
62
+ `2021-07`
63
+
64
+ Fix Bugs:
65
+
66
+ - disable image drag
67
+ - add shortcuts description for zoom in/out
68
+ - move window control panel to top right for windows users (thanks @mikelxk)
69
+ - upgrade howler lib (thanks @mikelxk)
70
+ - fix QQ search problem
71
+ - fix media center progress bar control for chrome users
72
+ - add local lrc file support when import local music (thanks @mikelxk)
73
+
74
+ `2021-04`
75
+
76
+ Features:
77
+
78
+ - QQ Login
79
+ - Drag and drop to reorder songs in playlist, reorder playlist and quick add song to playlist
80
+ - Search in playlist
81
+ - Proxy setting (desktop version only)
82
+ - Configure auto detect playable source list
83
+ - Display latest version in setting page
84
+ - Highest bitrate for netease music
85
+
86
+ Refactor:
87
+
88
+ - Change music platform resource API to class #553
89
+ - remove angular dependency for github module #532 (thanks @Dumeng)
90
+ - emove angular dependency for lastfm module #532 (thanks @Dumeng)
91
+ - UX optimaze #537
92
+
93
+ Fix Bugs:
94
+
95
+ - Fix migu resource api to use without login, add bitrate info #536 (thanks @RecluseWind)
96
+ - Fix display error in firefox for migu hot rank #536 (thanks @RecluseWind)
97
+ - Fix sometimes song keep waiting for 15 seconds before playing bug
98
+ - Fix qq short link parse error
99
+ - Fix toggle mute error
100
+ - Fix GitHub logout error
101
+ - Fix some kugou music without album play error
102
+ - Fix two songs play in same time
103
+
104
+ `2021-03`
105
+
106
+ Features:
107
+
108
+ - Add qianqian music platform (thanks @Dumeng)
109
+ - Support playlist filters and top list in migu (thanks @RecluseWind)
110
+ - Zoom in/out function for desktop version (thanks @mikelxk)
111
+ - Support netease login, show my playlist and recommend playlist
112
+ - Support migu login
113
+ - Show bitrate and music platform in now playing page
114
+ - deprecated xiami
115
+
116
+ Refactor:
117
+
118
+ - Replace angular module dependencies: translate,i18n, hotkeys,replace with js library (thanks @Dumeng)
119
+ - Optimaze feather load performance (thanks @Dumeng)
120
+ - Optimaze bitrate for qq and kugou platform, default high bitrate
121
+ - Split app.js into files by controller
122
+ - Optimaze copyright notice show
123
+ - Change http to https for several links
124
+
125
+ Fix bugs:
126
+
127
+ - Fix media control invalid because new es6 optional chain (thanks @mikelxk)
128
+ - Fix volume control not working (thanks @mikelxk)
129
+ - Fix scorll bar style in firefox (thanks @RecluseWind)
130
+ - Fix kugou music cover url
131
+ - Fix kugou music play url
132
+ - Fix notification not shown bug
133
+ - Fix delete songs in current playlist mess up playing bug
134
+
135
+ `2021-02`
136
+
137
+ Features:
138
+
139
+ - Support playlist filters and top playlist (special thanks [lyswhut/lx-music-desktop](https://github.com/lyswhut/lx-music-desktop) )
140
+ - Add Traditional Chinese language (thanks @yujiangqaq)
141
+ - Add chrome media panel functino: prev/next track, back/forward (thanks @mikelxk)
142
+ - New lyric floating window, support config font size, color and background transparency
143
+
144
+ Refactor:
145
+
146
+ - Build MediaService module,remove dependency on angularjs(special thanks @Dumeng)
147
+ - Add prettier config file, add pre-commit style check(thanks @mikelxk)
148
+ - Fix history code style problems(thanks @mikelxk)
149
+
150
+ Fix bugs:
151
+
152
+ - Fix Github API (thanks @NoDocCat 和 @Dumeng)
153
+ - Fix svg animation performance issue (thanks @Dumeng)
154
+ - Fix xiami API(thanks @RecluseWind)
155
+ - Fix import local music error for mac desktop version(thanks @virgil1996)
156
+ - Fix kuwo search error
157
+
158
+ `2021-01`
159
+
160
+ Features:
161
+
162
+ - support play music background (thanks @Dumeng)
163
+ - optimaze kugo related code (thanks @RecluseWind)
164
+ - optimaze migu related code (thanks @RecluseWind)
165
+ - support flac for local music (thanks @mikelxk)
166
+ - add feedback link (thanks @mikelxk)
167
+ - optimaze xiami music, add playlist search (thanks @RecluseWind)
168
+ - optimaze cache for playlist
169
+
170
+ Refactor:
171
+
172
+ - replace encrypt lib to forge (thanks @Dumeng)
173
+ - remove jquery (thanks @Dumeng)
174
+ - replace ngsoundmanager2 to howler.js (thanks @Dumeng)
175
+ - replace angular http to axios (thanks @Dumeng)
176
+ - support eslint check in github action (thanks @Dumeng)
177
+
178
+ Fix bugs:
179
+
180
+ - fix MediaSession error when not supported (thanks @Jyuaan)
181
+ - fix migu playlist 404 link
182
+ - fix current playling music list modal (thanks @Demeng)
183
+
184
+ `2020-12-28`
185
+
186
+ - fix bug for desktop: max,min,close button not available
187
+
188
+ `2020-12-27`
189
+
190
+ - fix bug: can't play favorite playlist
191
+ - feature: search all music (beta)
192
+ - fix bug: migu playlist shows first 20 tracks
193
+ - fix bug: netease/kugou search error not handle
194
+ - fix bug: xiami lyric parse error
195
+ - change manitest permession config to pass chrome web store review
196
+
197
+ `2020-12-22`
198
+
199
+ - fix bug: kuwo music can't be played
200
+ - fix bug: after upgrade v2.17.2, my playlist can't be played
201
+
202
+ `2020-12-20`
203
+
204
+ - fix play interrupted by copyright notice bug, infinite notice popup bug
205
+ - change style for now playing page when using album cover as background
206
+ - fix minor bug for qq search and optimaze api handler(thanks @RecluseWind)
207
+
208
+ `2020-12-12`
209
+
210
+ - support search songlist for qq music (thanks @RecluseWind)
211
+ - fix bug: netease songlist shared by mobile open error (thanks @RecluseWind)
212
+ - fix bug: migu search song error
213
+
214
+ `2020-10-28`
215
+
216
+ - add local music (desktop version only)
217
+
218
+ `2020-10-27`
219
+
220
+ - support search playlist (only for netease by now)
221
+ - optimaze lyric display
222
+ - fix bilibili artist api, fix lyric time tag format parse error (thanks @RecluseWind)
223
+ - optimaze UI, add translate button in now playing page
224
+
225
+ `2020-10-26`
226
+
227
+ - add lyric translation support for qq music, xiami music (thanks @RecluseWind)
228
+ - update xiami api including get playlist, search, play music (thanks @RecluseWind)
229
+ - fix bug some playlist not response in qq music website after installed extension
230
+
231
+ `2020-10-18`
232
+
233
+ - add lyric translation, now for netease music only (thanks @reserveword)
234
+ - fix bilibili play fail bug
235
+ - fix xiami now playing page music cover missing bug
236
+ - fix kuwo music can't open bug
237
+
238
+ `2020-09-12`
239
+
240
+ - fix netease songlist contains more than 1k tracks import error (thanks @YueShangGuan)
241
+ - support album cover as nowplaying background (thanks @YueShangGuan)
242
+
243
+ `2020-08-24`
244
+
245
+ - fix xiami songlist only shows part of songs bug (thanks @RecluseWind)
246
+ - fix songlist cover and title display bug (thanks @RecluseWind)
247
+ - support open url using system default browser for desktop version
248
+
249
+ `2020-08-04`
250
+
251
+ - add animation for now playing and current playlist window
252
+ - fix xiami cover image not loaded bug (thanks @RecluseWind)
253
+ - optimaze open songlist url, support netease toplist, artist, album (thanks @whtiehack)
254
+ - optimaze cover image display, avoid resize (thanks @RecluseWind)
255
+
256
+ `2020-07-10`
257
+
258
+ - fix migu play fail bug
259
+ - support press enter key to search in search bar thanks @kangbb)
260
+ - support playlist song count show, support play/pause shortcut, desktop only(thanks @x2009again)
261
+ - support restore scrollbar offset when go back(thanks @x2009again for discuss solution)
262
+ - optimaze firefox scorlling bar, modify source image url for qq music, fix firefox jquery lib md5 error(thanks @RecluseWind)
263
+
264
+ `2020-06-29`
265
+
266
+ - support auto choose source when play fail
267
+
268
+ `2020-06-28`
269
+
270
+ - fix netease music only show 10 tracks bug
271
+
272
+ `2020-04-30`
273
+
274
+ - fix migu poor music quality bug
275
+
276
+ `2020-04-27`
277
+
278
+ - support adding playlist to favorite, special thanks to @zhenyiLiang
279
+ - fix migu music
280
+ - some minor optimaze
281
+
282
+ `2019-11-27`
283
+
284
+ - add frech language, special thanks to @Leoche
285
+
286
+ `2019-09-07`
287
+
288
+ - fix migu
289
+
290
+ `2019-08-09`
291
+
292
+ - add dark theme
293
+
294
+ `2019-07-03`
295
+
296
+ - fix migu play error
297
+
298
+ `2019-06-24`
299
+
300
+ - add migu music
301
+ - fix kugou play bug
302
+ - fix netease play bug
303
+
304
+ `2019-06-23`
305
+
306
+ - fix connect to github.com error
307
+
308
+ `2019-05-26`
309
+
310
+ - fix kugou music can't play bug
311
+
312
+ `2019-04-26`
313
+
314
+ - fix xiami music can't play bug
315
+ - fix footer player out of page bug
316
+
317
+ `2019-03-03`
318
+
319
+ - fix delete single playlist destroy all playlists bug
320
+
321
+ `2019-02-26`
322
+
323
+ - fix qq music songlist not shown bug
324
+
325
+ `2018-12-30`
326
+
327
+ - fix songs missing in kuwo playlist
328
+ - auto detect language
329
+
330
+ `2018-12-29`
331
+
332
+ - fix fail on xiami search
333
+ - fix some qq songs fail to play
334
+ - fix qq music web visit problem after extension installed
335
+
336
+ `2018-12-24`
337
+
338
+ - i18n support, support English language.
339
+ - new song will now add to top of playlist
340
+ - copyright notification will not mess up the screen
341
+
342
+ `2018-12-22`
343
+
344
+ - Version 2.0 released. New UI(Special Thanks to @iparanoid)
345
+ - Upgrade jquery, Angular
346
+
347
+ `2018-12-21`
348
+
349
+ - Fix xiami playlist bug
350
+ - Fix netease playlist only shows one song bug
351
+ - Fix bilibili first load duplicate playlists
352
+ - Fix can't play some kugou songs
353
+ - Fix github gist backup recover bug
354
+ - Upgrade soundmanager2
355
+
356
+ ## License
357
+
358
+ MIT
css/common.css ADDED
@@ -0,0 +1,1802 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html,
2
+ body {
3
+ margin: 0;
4
+ padding: 0;
5
+ font-size: var(--text-default-size);
6
+ color: var(--text-default-color);
7
+ font-family: system-ui, 'PingFang SC', STHeiti, sans-serif;
8
+ }
9
+
10
+ a {
11
+ cursor: pointer;
12
+ }
13
+
14
+ .wrap {
15
+ /* https://stackoverflow.com/questions/28897089/z-index-on-borders */
16
+ outline: solid 1px var(--windows-border-color);
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ /* remove focus highlight */
21
+ input:focus,
22
+ select:focus,
23
+ textarea:focus,
24
+ button:focus {
25
+ outline: none;
26
+ }
27
+
28
+ ul {
29
+ list-style: none;
30
+ margin: 0;
31
+ padding: 0;
32
+ }
33
+
34
+ input,
35
+ svg,
36
+ .icon {
37
+ -webkit-app-region: no-drag;
38
+ }
39
+
40
+ button {
41
+ background-color: var(--button-background-color);
42
+ color: var(--text-default-color);
43
+ cursor: pointer;
44
+ border: solid 1px var(--button-background-color);
45
+ border-radius: var(--default-border-radius);
46
+ padding: 5px;
47
+ min-width: 80px;
48
+ min-height: 32px;
49
+ }
50
+ button:hover {
51
+ background-color: var(--button-hover-background-color);
52
+ }
53
+ img {
54
+ -webkit-user-drag: none;
55
+ }
56
+ .l1-button {
57
+ background-color: var(--button-background-color);
58
+ color: var(--text-default-color);
59
+ border-radius: var(--default-border-radius);
60
+ padding: 5px;
61
+ margin-right: 4px;
62
+ color: var(--text-default-color);
63
+ cursor: pointer;
64
+ display: inline-block;
65
+ }
66
+ .l1-button:hover {
67
+ background: var(--button-hover-background-color);
68
+ }
69
+ svg {
70
+ width: 24px;
71
+ height: 24px;
72
+ stroke: currentColor;
73
+ stroke-width: 1;
74
+ stroke-linecap: round;
75
+ stroke-linejoin: round;
76
+ fill: none;
77
+ cursor: pointer;
78
+ /* stroke: var(--icon-default-color);*/
79
+ }
80
+
81
+ /* svg:hover {
82
+ fill: var(--icon-highlight-color);
83
+ stroke: var(--icon-highlight-color);
84
+ } */
85
+
86
+ .icon {
87
+ /* default icon settings */
88
+ font-size: 16px;
89
+ cursor: pointer;
90
+ }
91
+
92
+ /* tools utils */
93
+ .flex-scroll-wrapper {
94
+ flex: 1;
95
+ height: 100px;
96
+ overflow-y: scroll;
97
+ scrollbar-width: thin;
98
+ scrollbar-color: var(--scroll-color) var(--content-background-color);
99
+ }
100
+
101
+ /* scroll bar style */
102
+ ::-webkit-scrollbar {
103
+ width: 14px;
104
+ height: 18px;
105
+ background: transparent;
106
+ }
107
+
108
+ ::-webkit-scrollbar-thumb {
109
+ height: 49px;
110
+ border: 5px solid rgba(0, 0, 0, 0);
111
+ background-clip: padding-box;
112
+ border-radius: 7px;
113
+ -webkit-border-radius: 7px;
114
+ background-color: var(--scroll-color);
115
+ /*rgba(151, 151, 151, 0.4);*/
116
+
117
+ /* -webkit-box-shadow: inset -1px -1px 0px rgba(0, 0, 0, 0.05), inset 1px 1px 0px rgba(0, 0, 0, 0.05);*/
118
+ }
119
+
120
+ ::-webkit-scrollbar-button {
121
+ width: 0;
122
+ height: 0;
123
+ display: none;
124
+ }
125
+
126
+ ::-webkit-scrollbar-corner {
127
+ background-color: transparent;
128
+ }
129
+
130
+ /* main framework start */
131
+ .wrap {
132
+ display: flex;
133
+ height: 100vh;
134
+ flex-direction: column;
135
+ margin: auto;
136
+ }
137
+
138
+ /* split screen to up/down 2 parts */
139
+ .main {
140
+ flex: 1;
141
+ display: flex;
142
+ overflow: hidden;
143
+ }
144
+
145
+ .footer {
146
+ background: var(--foot-background-color);
147
+ height: 60px;
148
+ border-top: solid 1px var(--line-default-color);
149
+ display: flex;
150
+ position: relative;
151
+ z-index: 99;
152
+ }
153
+
154
+ /* split main to left/right 2 parts */
155
+ .main .sidebar {
156
+ flex: 0 0 200px;
157
+ display: flex;
158
+ flex-direction: column;
159
+ background: var(--sidebar-background-color);
160
+ }
161
+
162
+ .main .content {
163
+ background: var(--content-background-color);
164
+ flex: 1;
165
+ display: flex;
166
+ flex-direction: column;
167
+ }
168
+
169
+ /* split content to up/down 2 parts */
170
+ .main .content .navigation {
171
+ height: 46px;
172
+ flex: 0 0 46px;
173
+ border-bottom: solid 1px var(--line-default-color);
174
+ display: flex;
175
+ align-items: center;
176
+ -webkit-app-region: drag;
177
+ }
178
+
179
+ .main .content .browser {
180
+ flex: 1;
181
+ }
182
+
183
+ /* main framework end */
184
+
185
+ /*****************************************************************/
186
+
187
+ /* main sidebar start */
188
+ .sidebar .menu-control {
189
+ height: 43px;
190
+ width: 125px;
191
+ -webkit-app-region: drag;
192
+ }
193
+ .sidebar .menu-title {
194
+ height: 28px;
195
+ line-height: 28px;
196
+ margin: 0 12px 4px 12px;
197
+ color: var(--link-default-color);
198
+ padding-left: 10px;
199
+ display: flex;
200
+ align-items: center;
201
+ font-size: 12px;
202
+ }
203
+ .sidebar .menu-title .title {
204
+ flex: 1;
205
+ }
206
+ .sidebar .menu-title svg {
207
+ flex: 0 0 18px;
208
+ }
209
+
210
+ .sidebar ul li {
211
+ cursor: pointer;
212
+ padding-left: 10px;
213
+ border-top: solid 2px transparent;
214
+ border-bottom: solid 2px transparent;
215
+ margin-bottom: -2px;
216
+ }
217
+ .sidebar ul li .sidebar-block {
218
+ display: flex;
219
+ align-items: center;
220
+ line-height: 28px;
221
+ padding-left: 12px;
222
+ margin: 3px 0;
223
+ color: var(--text-default-color);
224
+
225
+ border-radius: var(--default-border-radius);
226
+ }
227
+
228
+ .sidebar svg {
229
+ width: 18px;
230
+ height: 18px;
231
+ }
232
+
233
+ .sidebar ul li a {
234
+ margin-left: 10px;
235
+ width: 125px;
236
+ overflow: hidden;
237
+ white-space: nowrap;
238
+ text-overflow: ellipsis;
239
+ }
240
+
241
+ .sidebar ul li:hover .sidebar-block {
242
+ background: var(--sidebar-hover-background-color);
243
+ color: var(--sidebar-hover-text-color);
244
+ }
245
+
246
+ .sidebar ul li.active .sidebar-block,
247
+ .sidebar ul li.active:hover .sidebar-block {
248
+ background: var(--sidebar-highlight-background-color);
249
+ color: var(--sidebar-highlight-text-color);
250
+ }
251
+ .sidebar ul li.dragover .sidebar-block {
252
+ background: var(--sidebar-highlight-background-color);
253
+ color: var(--sidebar-highlight-text-color);
254
+ }
255
+ /*
256
+ avoid hover effect trigger dragleave event
257
+ https://stackoverflow.com/questions/19889615/can-an-angular-directive-pass-arguments-to-functions-in-expressions-specified-in
258
+ */
259
+ .sidebar ul li * {
260
+ pointer-events: none;
261
+ }
262
+ /* main sidebar end */
263
+
264
+ /* widget navigation start */
265
+ .navigation svg {
266
+ width: 18px;
267
+ height: 18px;
268
+ color: var(--icon-default-color);
269
+ }
270
+ .navigation .icon svg {
271
+ color: var(--text-default-color);
272
+ }
273
+ .navigation .backfront {
274
+ flex: 0 0 45px;
275
+ line-height: 46px;
276
+ vertical-align: middle;
277
+ padding: 0 13px;
278
+ }
279
+
280
+ .navigation .search {
281
+ flex: 1;
282
+ }
283
+
284
+ .navigation .settings {
285
+ flex: 0 0 32px;
286
+ }
287
+
288
+ .navigation .icon {
289
+ color: var(--text-default-color);
290
+ opacity: 0.5;
291
+ }
292
+
293
+ .navigation .icon:hover {
294
+ opacity: 1;
295
+ }
296
+
297
+ .navigation .backfront .icon {
298
+ display: inline-block;
299
+ vertical-align: middle;
300
+ margin-bottom: 4px;
301
+ }
302
+
303
+ .navigation .backfront .icon:nth-of-type(1) {
304
+ margin-right: 8px;
305
+ }
306
+
307
+ .navigation .search-input {
308
+ width: 270px;
309
+ height: 23px;
310
+ background: var(--search-input-background-color);
311
+ border-style: none;
312
+ border-radius: var(--default-border-radius);
313
+ padding-left: 10px;
314
+ font-size: 12px;
315
+ color: var(--text-default-color);
316
+ }
317
+
318
+ .navigation .window-control {
319
+ flex: 0 0 105px;
320
+ border-left: solid 1px var(--window-control-border-color);
321
+ margin-left: 15px;
322
+ }
323
+
324
+ .navigation .window-control svg {
325
+ margin-left: 8px;
326
+ }
327
+
328
+ .navigation .window-control svg:first-of-type {
329
+ margin-left: 15px;
330
+ }
331
+
332
+ /* navigation end */
333
+
334
+ /* page hot-playlist start */
335
+ .page-hot-playlist {
336
+ max-width: 850px;
337
+ margin: 0 auto;
338
+ }
339
+
340
+ .playlist-covers {
341
+ margin: 0;
342
+ padding: 0 13px;
343
+ display: flex;
344
+ flex-flow: row wrap;
345
+ position: relative;
346
+ }
347
+
348
+ .playlist-covers li {
349
+ flex: 0 1 calc(20% - 26px);
350
+ min-height: 156px;
351
+ color: var(--text-default-color);
352
+ margin: 0 13px;
353
+ }
354
+
355
+ .playlist-covers .u-cover {
356
+ display: flex;
357
+ position: relative;
358
+ }
359
+
360
+ .playlist-covers .u-cover img {
361
+ height: 136px;
362
+ min-width: 136px;
363
+ max-width: 100%;
364
+ object-fit: cover;
365
+ margin: auto;
366
+ border: solid 1px var(--line-default-color);
367
+ margin-bottom: 2px;
368
+ cursor: pointer;
369
+ }
370
+
371
+ .playlist-covers .u-cover .bottom {
372
+ position: absolute;
373
+ right: 5px;
374
+ bottom: 10px;
375
+ height: 30px;
376
+ width: 30px;
377
+ cursor: pointer;
378
+ opacity: 0;
379
+ transition: opacity 0.2s linear;
380
+ }
381
+
382
+ .playlist-covers .u-cover:hover .bottom {
383
+ opacity: 1;
384
+ }
385
+
386
+ .playlist-covers .u-cover .bottom svg {
387
+ height: 30px;
388
+ width: 30px;
389
+ fill: rgba(200, 200, 200, 0.5);
390
+ stroke-width: 1;
391
+ stroke: #ffffff;
392
+ }
393
+
394
+ .playlist-covers .u-cover .bottom svg:hover {
395
+ fill: rgba(100, 100, 100, 0.5);
396
+ }
397
+
398
+ .playlist-covers .desc {
399
+ cursor: pointer;
400
+ }
401
+
402
+ .playlist-covers .desc .title {
403
+ display: flex;
404
+ min-height: 32px;
405
+ margin: 0 0 5px;
406
+ }
407
+ /* page hot-playlist end */
408
+
409
+ /* page playlist-detail start */
410
+ .page .playlist-detail {
411
+ padding-bottom: 37px;
412
+ }
413
+
414
+ .page .playlist-detail .detail-head {
415
+ display: flex;
416
+ }
417
+
418
+ .page .playlist-detail .detail-head img {
419
+ height: 150px;
420
+ }
421
+
422
+ .page .playlist-detail .detail-head .detail-head-cover {
423
+ flex: 0 0 150px;
424
+ padding: 26px 26px 8px 26px;
425
+ }
426
+
427
+ .page .playlist-detail .detail-head .detail-head-title {
428
+ flex: 1;
429
+ }
430
+
431
+ .playlist-button-list {
432
+ display: flex;
433
+ flex-flow: row wrap;
434
+ }
435
+
436
+ .playlist-button-list .playlist-button {
437
+ height: 26px;
438
+ border: solid 1px var(--button-border-color);
439
+ cursor: pointer;
440
+ border-radius: 2px;
441
+ display: flex;
442
+ margin: 0 20px 20px 0;
443
+ }
444
+
445
+ .playlist-button-list .playlist-button.playadd-button {
446
+ flex: 0 0 136px;
447
+ }
448
+
449
+ .playlist-button-list .playlist-button .play-list {
450
+ flex: 1;
451
+ padding: 0 18px;
452
+ display: flex;
453
+ align-items: center;
454
+ }
455
+ .playlist-button-list .playlist-button .play-list svg {
456
+ margin-right: 4px;
457
+ }
458
+
459
+ .playlist-button-list .playlist-button.playadd-button .play-list svg {
460
+ width: 14px;
461
+ height: 14px;
462
+ flex: 0 0 14px;
463
+ margin-right: 4px;
464
+ stroke: var(--important-color);
465
+ fill: var(--important-color);
466
+ }
467
+ .playlist-button-list .playlist-button .play-list .icon {
468
+ margin-right: 8px;
469
+ }
470
+ .playlist-button-list .playlist-button.playadd-button .play-list .icon {
471
+ flex: 0 0 14px;
472
+ margin-right: 4px;
473
+ color: var(--important-color);
474
+ }
475
+
476
+ .playlist-button-list .playlist-button.playadd-button .add-list {
477
+ flex: 0 0 26px;
478
+ height: 26px;
479
+ width: 26px;
480
+ border-left: solid 1px var(--button-border-color);
481
+ cursor: pointer;
482
+ display: flex;
483
+ align-items: center;
484
+ justify-content: center;
485
+ }
486
+
487
+ .playlist-button-list .playlist-button.edit-button .play-list.favorited {
488
+ color: var(--text-default-color);
489
+ }
490
+ .playlist-button-list .playlist-button.edit-button .play-list.notfavorite {
491
+ color: var(--text-default-color);
492
+ }
493
+
494
+ .playlist-button-list .playlist-button .play-list:hover,
495
+ .playlist-button-list .playlist-button.playadd-button .add-list:hover {
496
+ background: var(--button-hover-background-color);
497
+ }
498
+ .playlist-button-list .playlist-button.playadd-button .add-list svg {
499
+ width: 14px;
500
+ height: 14px;
501
+ }
502
+
503
+ .playlist-button-list .playlist-button.clone-button,
504
+ .playlist-button-list .playlist-button.edit-button,
505
+ .playlist-button-list .playlist-button.fav-button {
506
+ flex: 0 0 auto;
507
+ }
508
+
509
+ .playlist-button-list .playlist-button.clone-button .play-list svg,
510
+ .playlist-button-list .playlist-button.edit-button .play-list svg,
511
+ .playlist-button-list .playlist-button.fav-button .play-list svg {
512
+ width: 16px;
513
+ height: 16px;
514
+ flex: 0 0 16px;
515
+ margin-right: 8px;
516
+ stroke: rgb(102, 102, 102);
517
+ }
518
+
519
+ .playlist-button-list .playlist-button.fav-button .play-list.favorited svg {
520
+ fill: rgb(102, 102, 102);
521
+ }
522
+
523
+ .page .playlist-detail .detail-head .detail-head-title h2 {
524
+ font-size: var(--h2-title-font-size);
525
+ }
526
+ /* page playlist detail end */
527
+
528
+ /* page song detail start */
529
+ .page .songdetail-wrapper {
530
+ position: absolute;
531
+ top: 0;
532
+ left: 0;
533
+ right: 0;
534
+ bottom: 60px;
535
+ background: var(--now-playing-page-background-color);
536
+ overflow: hidden;
537
+ border: solid 1px var(--windows-border-color);
538
+ -webkit-app-region: no-drag;
539
+ transition: all 0.3s;
540
+ }
541
+
542
+ .page .songdetail-wrapper .draggable-zone {
543
+ position: absolute;
544
+ left: 0;
545
+ top: 0;
546
+ right: 0;
547
+ -webkit-app-region: drag;
548
+ height: 80px;
549
+ }
550
+
551
+ .page .songdetail-wrapper.slidedown .draggable-zone {
552
+ display: none;
553
+ -webkit-app-region: no-drag;
554
+ }
555
+
556
+ .page .songdetail-wrapper .translate-switch {
557
+ border: solid 1px;
558
+ width: 20px;
559
+ height: 20px;
560
+ display: flex;
561
+ align-items: center;
562
+ justify-content: center;
563
+ position: absolute;
564
+ bottom: 30px;
565
+ right: 30px;
566
+ color: #888888;
567
+ cursor: pointer;
568
+ -webkit-app-region: no-drag;
569
+ }
570
+ .page .songdetail-wrapper .translate-switch:hover {
571
+ color: var(--text-default-color);
572
+ }
573
+ .page .songdetail-wrapper .translate-switch.selected {
574
+ color: var(--text-default-color);
575
+ }
576
+ .page .songdetail-wrapper.slidedown {
577
+ top: calc(100% - 60px);
578
+ }
579
+
580
+ .page .songdetail-wrapper .close {
581
+ position: absolute;
582
+ top: 24px;
583
+ left: 24px;
584
+ height: 24px;
585
+ width: 24px;
586
+ cursor: pointer;
587
+ -webkit-app-region: no-drag;
588
+ }
589
+ .page .songdetail-wrapper .close.mac {
590
+ top: 44px;
591
+ }
592
+
593
+ .page .songdetail-wrapper .window-control {
594
+ position: absolute;
595
+ top: 24px;
596
+ right: 24px;
597
+ height: 24px;
598
+ cursor: pointer;
599
+ -webkit-app-region: no-drag;
600
+ z-index: 99;
601
+ }
602
+
603
+ .page .songdetail-wrapper .window-control svg {
604
+ margin-left: 8px;
605
+ stroke: var(--now-playing-close-icon-color);
606
+ }
607
+
608
+ .page .songdetail-wrapper .close svg {
609
+ stroke: var(--now-playing-close-icon-color);
610
+ }
611
+
612
+ .page .bg {
613
+ opacity: 0.5;
614
+ height: 100%;
615
+ text-align: center;
616
+ line-height: 100%;
617
+ float: left;
618
+ width: 100%;
619
+ background-repeat: no-repeat;
620
+ background-position: center;
621
+ background-size: cover;
622
+ filter: blur(10px) brightness(0.6);
623
+ transition: background ease-in-out 1.5s;
624
+ }
625
+
626
+ .page .playsong-detail {
627
+ position: absolute;
628
+ left: 10px;
629
+ right: 10px;
630
+ max-width: 770px;
631
+ margin: 0 auto;
632
+ display: flex;
633
+ height: 100%;
634
+ }
635
+
636
+ .page .playsong-detail .detail-head {
637
+ flex: 0 0 350px;
638
+ overflow: hidden;
639
+ }
640
+
641
+ .page .playsong-detail .detail-head .detail-head-cover {
642
+ width: 250px;
643
+ height: 250px;
644
+ margin-top: 110px;
645
+ }
646
+
647
+ .page .playsong-detail .detail-head img {
648
+ width: 250px;
649
+ height: 250px;
650
+ object-fit: cover;
651
+ }
652
+
653
+ .page .playsong-detail .detail-songinfo {
654
+ flex: 1;
655
+ margin-top: 80px;
656
+ display: flex;
657
+ flex-direction: column;
658
+ overflow: hidden;
659
+ -webkit-app-region: no-drag;
660
+ }
661
+ .page .playsong-detail .detail-songinfo .title {
662
+ display: flex;
663
+ align-items: center;
664
+ }
665
+ .page .playsong-detail .detail-songinfo .title h2 {
666
+ font-size: var(--h2-title-font-size);
667
+ font-weight: 400;
668
+ }
669
+ .page .playsong-detail .detail-songinfo .title .badge {
670
+ font-size: var(--badge-font-size);
671
+ color: var(--badge-font-color);
672
+ border: solid 1px var(--badge-border-color);
673
+ border-radius: 3px;
674
+ margin-left: 5px;
675
+ padding-left: 4px;
676
+ padding-right: 4px;
677
+ margin-top: 4px;
678
+ box-sizing: border-box;
679
+ height: 20px;
680
+ display: flex;
681
+ align-items: center;
682
+ justify-content: center;
683
+ white-space: nowrap;
684
+ }
685
+ .page .playsong-detail .detail-songinfo .title .badge.platform {
686
+ padding-top: 1px;
687
+ }
688
+ .page .playsong-detail .detail-songinfo .title .badge:first-of-type {
689
+ margin-left: 15px;
690
+ }
691
+ .page .playsong-detail .detail-songinfo .info {
692
+ border-bottom: solid 1px var(--line-default-color);
693
+ padding-bottom: 6px;
694
+ flex: 0 0 20px;
695
+ display: flex;
696
+ }
697
+ .page .playsong-detail .detail-songinfo .info a {
698
+ cursor: pointer;
699
+ }
700
+ .page .playsong-detail .detail-songinfo .info .singer {
701
+ flex: 1;
702
+ overflow: hidden;
703
+ white-space: nowrap;
704
+ text-overflow: ellipsis;
705
+ }
706
+
707
+ .page .playsong-detail .detail-songinfo .info .album {
708
+ flex: 2;
709
+ overflow: hidden;
710
+ white-space: nowrap;
711
+ text-overflow: ellipsis;
712
+ }
713
+
714
+ .page .playsong-detail .detail-songinfo .info span {
715
+ color: var(--lyric-default-color);
716
+ }
717
+ .page .coverbg .playsong-detail .detail-songinfo .info span {
718
+ color: var(--lyric-on-cover-color);
719
+ }
720
+ .page .playsong-detail .detail-songinfo .lyric {
721
+ position: relative;
722
+ flex: 0 0 380px;
723
+ overflow-y: scroll;
724
+ color: var(--lyric-default-color);
725
+ scrollbar-width: thin;
726
+ scrollbar-color: var(--scroll-color) transparent;
727
+ font-size: var(--lyric-font-size);
728
+ }
729
+ .page .coverbg .playsong-detail .detail-songinfo .lyric {
730
+ color: var(--lyric-on-cover-color);
731
+ }
732
+ .page .playsong-detail .detail-songinfo .lyric p {
733
+ margin: var(--lyric-line-margin) 0 0 0;
734
+ }
735
+ .page .playsong-detail .detail-songinfo .lyric p.translate {
736
+ margin: 5px 0 0 0;
737
+ }
738
+ .page .playsong-detail .detail-songinfo .lyric p.hide {
739
+ display: none;
740
+ }
741
+ .page .playsong-detail .detail-songinfo .lyric p.highlight {
742
+ color: var(--lyric-important-color);
743
+ }
744
+ .page .coverbg .playsong-detail .detail-songinfo .lyric p.highlight {
745
+ color: var(--lyric-important-on-cover-color);
746
+ }
747
+
748
+ ul.detail-songlist {
749
+ padding: 0 25px;
750
+ position: relative;
751
+ }
752
+
753
+ ul.detail-songlist .playlist-search {
754
+ position: absolute;
755
+ right: 0;
756
+ top: -30px;
757
+ }
758
+ ul.detail-songlist .playlist-search .playlist-search-icon {
759
+ width: 14px;
760
+ position: absolute;
761
+ left: 7px;
762
+ top: 1px;
763
+ }
764
+ ul.detail-songlist .playlist-search .playlist-clear-icon {
765
+ width: 14px;
766
+ position: absolute;
767
+ left: 158px;
768
+ }
769
+ ul.detail-songlist .playlist-search .playlist-search-input {
770
+ margin-right: 28px;
771
+ margin-bottom: 10px;
772
+ border: none;
773
+ height: 24px;
774
+ border-radius: 12px;
775
+ padding: 0 30px;
776
+ background: var(--content-background-color);
777
+ color: #bbbbbb;
778
+ width: 120px;
779
+ }
780
+ ul.detail-songlist .playlist-search .playlist-search-input:hover {
781
+ background-color: var(--songlist-odd-background-color);
782
+ }
783
+ ul.detail-songlist .playlist-search .playlist-search-input::placeholder {
784
+ color: #bbbbbb;
785
+ }
786
+
787
+ ul.detail-songlist li {
788
+ /* https://stackoverflow.com/questions/4157005/css-positioning-z-index-negative-margins */
789
+ position: relative;
790
+ display: flex;
791
+ border-top: solid 2px var(--songlist-border-color);
792
+ border-bottom: solid 2px var(--songlist-border-color);
793
+ height: 37px;
794
+ align-items: center;
795
+ padding: 0 20px;
796
+ font-size: 14px;
797
+ margin-bottom: -2px;
798
+ }
799
+
800
+ ul.detail-songlist li.playlist-result {
801
+ height: 80px;
802
+ padding: 0 10px;
803
+ }
804
+ ul.detail-songlist li.odd {
805
+ background-color: var(--songlist-odd-background-color);
806
+ }
807
+
808
+ ul.detail-songlist li:hover,
809
+ ul.detail-songlist li.odd:hover {
810
+ background-color: var(--songlist-hover-background-color);
811
+ }
812
+
813
+ ul.detail-songlist li a {
814
+ cursor: pointer;
815
+ }
816
+ ul.detail-songlist li a.disabled {
817
+ color: var(--disable-song-title-color);
818
+ }
819
+ ul.detail-songlist li a span.source {
820
+ border: solid 1px #ccc;
821
+ border-radius: 4px;
822
+ margin-right: 10px;
823
+ display: inline-block;
824
+ padding: 0 4px;
825
+ color: #ccc;
826
+ font-size: 12px;
827
+ width: 24px;
828
+ text-align: center;
829
+ }
830
+ ul.detail-songlist li a span.source.playlist {
831
+ margin-left: 10px;
832
+ margin-right: 0;
833
+ }
834
+ ul.detail-songlist li.head {
835
+ height: 28px;
836
+ color: var(--text-disable-color);
837
+ border-top: none;
838
+ padding-bottom: 2px;
839
+ }
840
+ ul.detail-songlist li.head:hover {
841
+ background-color: transparent;
842
+ }
843
+
844
+ ul.detail-songlist li .title {
845
+ flex: 2;
846
+ overflow: hidden;
847
+ text-overflow: ellipsis;
848
+ display: -webkit-box;
849
+ line-height: 17px;
850
+ max-height: 38px;
851
+ -webkit-line-clamp: 2;
852
+ -webkit-box-orient: vertical;
853
+ }
854
+ ul.detail-songlist li.playlist-result .title {
855
+ max-height: 80px;
856
+ }
857
+
858
+ ul.detail-songlist li.playlist-result .title a {
859
+ display: flex;
860
+ align-items: center;
861
+ }
862
+
863
+ ul.detail-songlist li.playlist-result .title img {
864
+ height: 60px;
865
+ width: 60px;
866
+ display: block;
867
+ margin-right: 10px;
868
+ }
869
+
870
+ ul.detail-songlist li .artist {
871
+ flex: 1;
872
+ overflow: hidden;
873
+ text-overflow: ellipsis;
874
+ display: -webkit-box;
875
+ line-height: 17px;
876
+ max-height: 38px;
877
+ -webkit-line-clamp: 2;
878
+ -webkit-box-orient: vertical;
879
+ }
880
+
881
+ ul.detail-songlist li .album {
882
+ flex: 1;
883
+ overflow: hidden;
884
+ text-overflow: ellipsis;
885
+ display: -webkit-box;
886
+ line-height: 17px;
887
+ max-height: 38px;
888
+ -webkit-line-clamp: 2;
889
+ -webkit-box-orient: vertical;
890
+ }
891
+
892
+ ul.detail-songlist li .tools {
893
+ flex: 0 0 110px;
894
+ display: flex;
895
+ align-items: center;
896
+ }
897
+ ul.detail-songlist li .tools .icon {
898
+ height: 16px;
899
+ width: 16px;
900
+ color: #9d9d9d;
901
+ margin-top: 2px;
902
+ margin-right: 10px;
903
+ }
904
+ /* page song detail end */
905
+
906
+ /* page login start */
907
+ .page .login {
908
+ display: flex;
909
+ flex-direction: column;
910
+ align-items: center;
911
+ justify-content: center;
912
+ min-height: calc(100vh - 192px);
913
+ }
914
+ .page .login .login-logo {
915
+ margin-bottom: 16px;
916
+ display: flex;
917
+ align-items: center;
918
+ }
919
+ .page .login .login-logo img {
920
+ height: 64px;
921
+ margin: 20px;
922
+ }
923
+ .page .login .login-title {
924
+ font-size: 18px;
925
+ margin-bottom: 10px;
926
+ }
927
+ .page .login .login-form .login-form_field {
928
+ display: flex;
929
+ align-items: center;
930
+ height: 40px;
931
+ margin: 24px;
932
+ width: 270px;
933
+ border: solid 1px var(--button-background-color);
934
+ }
935
+ .page .login .login-form .login-form_field input {
936
+ background: var(--content-background-color);
937
+ color: var(--text-default-color);
938
+ }
939
+ .page .login .login-form .login-form_field input.login-form_field_countrycode {
940
+ flex: 0 0 40px;
941
+ width: 40px;
942
+ }
943
+ .page .login .login-form .login-form_field svg {
944
+ margin-left: 12px;
945
+ margin-right: 12px;
946
+ color: var(--icon-default-color);
947
+ width: 18px;
948
+ height: 18px;
949
+ }
950
+ .page .login .login-form .login-form_field input {
951
+ border: none;
952
+ flex: 1;
953
+ font-size: 16px;
954
+ }
955
+ .page .login .login-submit_button {
956
+ display: flex;
957
+ align-items: center;
958
+ justify-content: center;
959
+ font-size: 14px;
960
+ margin-top: 24px;
961
+ padding: 8px;
962
+ width: 270px;
963
+ cursor: pointer;
964
+ border: solid 1px var(--button-border-color);
965
+ }
966
+ .page .login .login-switcher {
967
+ margin-top: 24px;
968
+ cursor: pointer;
969
+ }
970
+ .page .login .login-notice {
971
+ width: 270px;
972
+ border-top: 1px solid var(--button-border-color);
973
+ margin-top: 30px;
974
+ padding-top: 12px;
975
+ font-size: 12px;
976
+ color: var(--text-subtitle-color);
977
+ }
978
+ .page .login .usercard {
979
+ display: flex;
980
+ align-items: center;
981
+ width: 400px;
982
+ border: solid 1px var(--button-border-color);
983
+ margin-bottom: 20px;
984
+ }
985
+ .page .login .usercard img {
986
+ width: 60px;
987
+ height: 60px;
988
+ margin: 10px;
989
+ }
990
+ .page .login .usercard .usercard-title {
991
+ flex: 1;
992
+ height: 50px;
993
+ font-size: 16px;
994
+ font-weight: 700;
995
+ }
996
+ .page .login .usercard .usercard-title .usercard-info {
997
+ color: var(--link-inactive-color);
998
+ font-size: 12px;
999
+ }
1000
+ .page .login .usercard button {
1001
+ margin: 10px;
1002
+ }
1003
+ /* page login end */
1004
+
1005
+ /* page setting start */
1006
+ .page .settings-title {
1007
+ border-bottom: solid 1px var(--line-default-color);
1008
+ padding-bottom: 10px;
1009
+ max-width: 800px;
1010
+ margin: 0 25px;
1011
+ font-size: 17px;
1012
+ margin-bottom: 10px;
1013
+ }
1014
+ .page .settings-title:first-of-type {
1015
+ margin-top: 20px;
1016
+ }
1017
+ .page .settings-content {
1018
+ margin: 0 25px 25px 25px;
1019
+ }
1020
+ .page .settings-content label.upload-button,
1021
+ .page .settings-content .language-button {
1022
+ padding: 5px;
1023
+ background: var(--button-background-color);
1024
+ margin-right: 4px;
1025
+ color: var(--text-default-color);
1026
+ cursor: pointer;
1027
+ }
1028
+
1029
+ .page .settings-content label.upload-button:hover,
1030
+ .page .settings-content .language-button:hover {
1031
+ background: var(--button-hover-background-color);
1032
+ }
1033
+ .page .settings-content .shortcut {
1034
+ display: flex;
1035
+ margin-top: 10px;
1036
+ }
1037
+ .page .settings-content .shortcut svg {
1038
+ width: 18px;
1039
+ height: 18px;
1040
+ margin-right: 10px;
1041
+ }
1042
+ .page .searchbox .search-pagination {
1043
+ text-align: center;
1044
+ padding: 15px;
1045
+ }
1046
+ .page .settings-content .shortcut_table .shortcut_table-header,
1047
+ .page .settings-content .shortcut_table .shortcut_table-line {
1048
+ display: flex;
1049
+ color: var(--text-default-color);
1050
+ box-sizing: border-box;
1051
+ align-items: center;
1052
+ height: 40px;
1053
+ }
1054
+ .page .settings-content .shortcut_table .shortcut_table-header {
1055
+ color: var(--link-default-color);
1056
+ height: 30px;
1057
+ }
1058
+ .page .settings-content .shortcut_table .shortcut_table-function {
1059
+ flex: 0 140px;
1060
+ padding: 0 10px;
1061
+ box-sizing: border-box;
1062
+ }
1063
+ .page .settings-content .shortcut_table .shortcut_table-key {
1064
+ flex: 0 200px;
1065
+ margin-right: 20px;
1066
+ box-sizing: border-box;
1067
+ }
1068
+ .page .settings-content .shortcut_table .shortcut_table-globalkey {
1069
+ flex: 0 240px;
1070
+ box-sizing: border-box;
1071
+ }
1072
+ .page
1073
+ .settings-content
1074
+ .shortcut_table
1075
+ .shortcut_table-line
1076
+ .shortcut_table-key {
1077
+ border: solid 1px var(--button-border-color);
1078
+ border-radius: 5px;
1079
+ padding: 0 10px;
1080
+ height: 30px;
1081
+ display: flex;
1082
+ align-items: center;
1083
+ }
1084
+ .page
1085
+ .settings-content
1086
+ .shortcut_table
1087
+ .shortcut_table-line
1088
+ .shortcut_table-globalkey {
1089
+ border: solid 1px var(--button-border-color);
1090
+ border-radius: 5px;
1091
+ height: 30px;
1092
+ padding: 0 10px;
1093
+ display: flex;
1094
+ align-items: center;
1095
+ box-sizing: border-box;
1096
+ }
1097
+
1098
+ .page .settings-content .custom-proxy {
1099
+ margin-top: 10px;
1100
+ }
1101
+ .page .settings-content .custom-proxy .rule-input {
1102
+ margin-top: 8px;
1103
+ }
1104
+ .page .settings-content .custom-proxy input {
1105
+ margin-right: 15px;
1106
+ height: 24px;
1107
+ width: 200px;
1108
+ }
1109
+ .page .settings-content .search-description {
1110
+ margin: 10px 0 5px 0;
1111
+ }
1112
+ .page .settings-content .search-source-list {
1113
+ display: flex;
1114
+ align-items: center;
1115
+ flex-wrap: wrap;
1116
+ line-height: 30px;
1117
+ }
1118
+ .page .settings-content .search-source-list .search-source {
1119
+ display: flex;
1120
+ align-items: center;
1121
+ width: 130px;
1122
+ }
1123
+ .page .settings-content .search-source-list .search-source svg {
1124
+ width: 18px;
1125
+ height: 18px;
1126
+ margin-right: 4px;
1127
+ }
1128
+ /* page setting end */
1129
+
1130
+ .loading_bottom {
1131
+ display: block;
1132
+ width: 40px;
1133
+ margin: 0 auto;
1134
+ }
1135
+
1136
+ svg.searchspinner {
1137
+ width: 20px;
1138
+ height: 20px;
1139
+ vertical-align: top;
1140
+ margin-left: 15px;
1141
+ }
1142
+ /* footer start */
1143
+
1144
+ .footer {
1145
+ background: var(--foot-background-color);
1146
+ height: 60px;
1147
+ border-top: solid 1px var(--foot-border-color);
1148
+ display: flex;
1149
+ position: relative;
1150
+ }
1151
+
1152
+ .footer .left-control {
1153
+ flex: 0 0 300px;
1154
+ display: flex;
1155
+ align-items: center;
1156
+ }
1157
+
1158
+ .footer .left-control .icon {
1159
+ font-size: 22px;
1160
+ color: var(--player-left-icon-color);
1161
+ margin: 0 13px;
1162
+ }
1163
+
1164
+ .footer .left-control .icon.play {
1165
+ margin-right: 10px;
1166
+ }
1167
+
1168
+ .footer .left-control .icon:first-of-type {
1169
+ margin-left: 42px;
1170
+ }
1171
+
1172
+ .footer .left-control .icon.play {
1173
+ color: var(--player-icon-color);
1174
+ }
1175
+ .footer .left-control .icon.play:hover {
1176
+ color: var(--player-icon-hover-color);
1177
+ }
1178
+
1179
+ .footer .main-info {
1180
+ flex: 1;
1181
+ background: var(--footer-main-background-color);
1182
+ display: flex;
1183
+ overflow: hidden;
1184
+ z-index: 1;
1185
+ }
1186
+
1187
+ .footer .main-info .logo-banner {
1188
+ text-align: center;
1189
+ flex: 1;
1190
+ display: flex;
1191
+ align-items: center;
1192
+ }
1193
+
1194
+ .footer .main-info .logo-banner svg.logo {
1195
+ height: 48px;
1196
+ width: 48px;
1197
+ fill: #666666;
1198
+ stroke: #666666;
1199
+ margin: 0 auto;
1200
+ }
1201
+
1202
+ .footer .main-info .cover {
1203
+ height: 60px;
1204
+ width: 60px;
1205
+ object-fit: cover;
1206
+ flex: 0 0 60px;
1207
+ cursor: pointer;
1208
+ position: relative;
1209
+ color: #ffffff;
1210
+ }
1211
+ .footer .main-info .cover img {
1212
+ height: 60px;
1213
+ width: 60px;
1214
+ object-fit: cover;
1215
+ }
1216
+ .footer .main-info .cover .mask {
1217
+ display: none;
1218
+ }
1219
+ .footer .main-info .cover:hover .mask {
1220
+ display: flex;
1221
+ align-items: center;
1222
+ justify-content: center;
1223
+ background: rgba(0, 0, 0, 0.6);
1224
+ position: absolute;
1225
+ top: 0;
1226
+ right: 0;
1227
+ left: 0;
1228
+ bottom: 0;
1229
+ }
1230
+
1231
+ .footer .main-info .detail {
1232
+ flex: 1;
1233
+ position: relative;
1234
+ overflow: hidden;
1235
+ }
1236
+ .footer .main-info .detail .ctrl {
1237
+ position: absolute;
1238
+ right: 0px;
1239
+ top: 4px;
1240
+ padding-right: 6px;
1241
+ /* background: #eeeeee; */
1242
+ }
1243
+ .footer .main-info .detail .ctrl:first-of-type .icon {
1244
+ margin-right: 5px;
1245
+ }
1246
+ .footer .main-info .detail .ctrl .icon {
1247
+ color: var(--text-default-color);
1248
+ opacity: 0.5;
1249
+ }
1250
+ .footer .main-info .detail .ctrl .icon:hover {
1251
+ opacity: 1;
1252
+ }
1253
+
1254
+ .footer .main-info .detail .title {
1255
+ text-align: center;
1256
+ font-size: 14px;
1257
+ color: var(--text-default-color);
1258
+ min-width: 0px;
1259
+ overflow: hidden;
1260
+ white-space: nowrap;
1261
+ text-overflow: ellipsis;
1262
+ margin: 3px 60px 0 60px;
1263
+ }
1264
+
1265
+ .footer .main-info .detail .more-info {
1266
+ padding: 0 10px;
1267
+ display: flex;
1268
+ color: var(--text-subtitle-color);
1269
+ }
1270
+
1271
+ .footer .main-info .detail .more-info .singer {
1272
+ flex: 1;
1273
+ text-align: center;
1274
+ font-size: 12px;
1275
+ min-width: 0px;
1276
+ overflow: hidden;
1277
+ white-space: nowrap;
1278
+ text-overflow: ellipsis;
1279
+ }
1280
+ .footer .main-info .detail .more-info .singer a {
1281
+ cursor: pointer;
1282
+ }
1283
+
1284
+ .footer .main-info .detail .more-info .current {
1285
+ width: 50px;
1286
+ font-size: 12px;
1287
+ }
1288
+
1289
+ .footer .main-info .detail .more-info .total {
1290
+ width: 50px;
1291
+ text-align: right;
1292
+ font-size: 12px;
1293
+ }
1294
+
1295
+ .footer .main-info .detail .playbar {
1296
+ width: 100%;
1297
+ }
1298
+ .footer .main-info .detail .playbar .playbar-clickable {
1299
+ padding: 8px 10px;
1300
+ }
1301
+ .footer .main-info .detail .playbar .barbg {
1302
+ height: 3px;
1303
+ background: var(--footer-player-bar-background-color);
1304
+ }
1305
+
1306
+ .footer .main-info .detail .playbar .barbg .cur {
1307
+ height: 100%;
1308
+ background: var(--footer-player-bar-cur-background-color);
1309
+ position: relative;
1310
+ }
1311
+
1312
+ .footer .main-info .detail .playbar .barbg .cur .btn {
1313
+ background: var(--footer-player-bar-cur-button-color);
1314
+ height: 8px;
1315
+ width: 2px;
1316
+ position: absolute;
1317
+ right: -2px;
1318
+ top: -5px;
1319
+ }
1320
+
1321
+ .footer .main-info .detail .playbar .playbar-clickable:hover .barbg .cur .btn {
1322
+ width: 10px;
1323
+ height: 10px;
1324
+ border-radius: 5px;
1325
+ top: -3px;
1326
+ }
1327
+
1328
+ .footer .menu-modal {
1329
+ left: 0;
1330
+ right: 0;
1331
+ top: 0;
1332
+ position: fixed;
1333
+ background: rgba(255, 255, 255, 0.2);
1334
+ }
1335
+ .footer .menu-modal.slideup {
1336
+ bottom: 60px;
1337
+ }
1338
+
1339
+ .footer .menu {
1340
+ background: var(--foot-background-color);
1341
+ border: solid 1px var(--foot-border-color);
1342
+ border-radius: 3px;
1343
+ position: fixed;
1344
+ height: 370px;
1345
+ bottom: -311px;
1346
+ left: 300px;
1347
+ right: 300px;
1348
+ -webkit-app-region: no-drag;
1349
+ transition: all 0.3s;
1350
+ overflow: hidden;
1351
+ }
1352
+ .footer .menu.slideup {
1353
+ bottom: 60px;
1354
+ }
1355
+
1356
+ .footer .menu .menu-header {
1357
+ height: 30px;
1358
+ border-bottom: solid 1px var(--footer-header-background-color);
1359
+ display: flex;
1360
+ align-items: center;
1361
+ color: #9e9e9e;
1362
+ font-size: 12px;
1363
+ }
1364
+
1365
+ .footer .menu .menu-header .menu-title {
1366
+ flex: 1;
1367
+ padding: 20px;
1368
+ }
1369
+
1370
+ .footer .menu .menu-header .add-all {
1371
+ border-right: solid 1px #e5e5e5;
1372
+ flex: 0 0 auto;
1373
+ display: flex;
1374
+ align-items: center;
1375
+ padding-right: 10px;
1376
+ }
1377
+
1378
+ .footer .menu .menu-header .remove-all {
1379
+ margin-left: 10px;
1380
+ flex: 0 0 auto;
1381
+ display: flex;
1382
+ align-items: center;
1383
+ }
1384
+
1385
+ .footer .menu .menu-header .close {
1386
+ margin-left: 10px;
1387
+ flex: 0 0 25px;
1388
+ align-items: center;
1389
+ cursor: pointer;
1390
+ }
1391
+ .footer .menu .menu-header .add-all span,
1392
+ .footer .menu .menu-header .remove-all span {
1393
+ cursor: pointer;
1394
+ }
1395
+
1396
+ .footer .menu .menu-header .add-all .icon,
1397
+ .footer .menu .menu-header .remove-all .icon {
1398
+ margin-right: 7px;
1399
+ }
1400
+
1401
+ .footer .menu .menu-header .close svg {
1402
+ margin-right: 3px;
1403
+ height: 16px;
1404
+ width: 16px;
1405
+ cursor: pointer;
1406
+ }
1407
+
1408
+ .footer .menu ul.menu-list {
1409
+ overflow-y: scroll;
1410
+ height: 340px;
1411
+ font-size: 12px;
1412
+ }
1413
+
1414
+ .footer .menu ul.menu-list li {
1415
+ display: flex;
1416
+ align-items: center;
1417
+ height: 30px;
1418
+ padding-right: 20px;
1419
+ position: relative;
1420
+ margin-bottom: -2px;
1421
+ border-top: solid 2px var(--songlist-border-color);
1422
+ border-bottom: solid 2px var(--songlist-border-color);
1423
+ }
1424
+
1425
+ .footer .menu ul.menu-list li.even {
1426
+ background: var(--footer-menu-even-background-color);
1427
+ }
1428
+
1429
+ .footer .menu ul.menu-list li:hover {
1430
+ background: var(--footer-menu-hover-background-color);
1431
+ }
1432
+
1433
+ .footer .menu ul.menu-list li.playing {
1434
+ color: var(--important-color);
1435
+ }
1436
+ .footer .menu ul.menu-list li .song-status-icon {
1437
+ flex: 0 0 20px;
1438
+ width: 20px;
1439
+ height: 30px;
1440
+ text-align: center;
1441
+ display: flex;
1442
+ align-items: center;
1443
+ }
1444
+ .footer .menu ul.menu-list li .song-status-icon svg {
1445
+ width: 10px;
1446
+ height: 10px;
1447
+ fill: var(--important-color);
1448
+ stroke: var(--important-color);
1449
+ flex: 1;
1450
+ }
1451
+ .footer .menu ul.menu-list li .song-title {
1452
+ flex: 2;
1453
+ overflow: hidden;
1454
+ white-space: nowrap;
1455
+ text-overflow: ellipsis;
1456
+ }
1457
+ .footer .menu ul.menu-list li .song-title.disabled {
1458
+ color: #777777;
1459
+ }
1460
+ .footer .menu ul.menu-list li .song-title a {
1461
+ cursor: pointer;
1462
+ }
1463
+
1464
+ .footer .menu ul.menu-list li .song-singer {
1465
+ flex: 1;
1466
+ overflow: hidden;
1467
+ white-space: nowrap;
1468
+ text-overflow: ellipsis;
1469
+ cursor: pointer;
1470
+ }
1471
+
1472
+ /* div.visited{
1473
+ color: green;
1474
+ } */
1475
+
1476
+ .footer .menu ul.menu-list li .tools {
1477
+ flex: 0 0 42px;
1478
+ width: 42px;
1479
+ }
1480
+ .footer .menu ul.menu-list li .tools .icon {
1481
+ color: #666666;
1482
+ cursor: pointer;
1483
+ opacity: 0.5;
1484
+ }
1485
+ .footer .menu ul.menu-list li .tools .icon:first-of-type {
1486
+ margin-right: 5px;
1487
+ }
1488
+ .footer .menu ul.menu-list li .tools .icon:hover {
1489
+ opacity: 1;
1490
+ }
1491
+
1492
+ .footer .menu ul.menu-list li .song-time {
1493
+ flex: 1;
1494
+ text-align: right;
1495
+ }
1496
+
1497
+ .footer .right-control {
1498
+ flex: 0 0 300px;
1499
+ background: var(--foot-background-color);
1500
+ display: flex;
1501
+ align-items: center;
1502
+ }
1503
+
1504
+ .footer .right-control .playlist-toggle {
1505
+ margin-left: 29px;
1506
+ cursor: pointer;
1507
+ }
1508
+
1509
+ .footer .right-control .playlist-toggle .icon {
1510
+ color: var(--player-right-icon-color);
1511
+ }
1512
+
1513
+ .footer .right-control .playlist-toggle .icon:hover {
1514
+ color: var(--player-right-icon-hover-color);
1515
+ }
1516
+
1517
+ .footer .right-control .lyric-toggle {
1518
+ margin-right: 30px;
1519
+ cursor: pointer;
1520
+ }
1521
+
1522
+ .footer .right-control .lyric-toggle .lyric-icon,
1523
+ .footer .right-control .lyric-toggle .lyric-icon.selected:hover {
1524
+ border: solid 1px #7f7f7f;
1525
+ height: 16px;
1526
+ line-height: 16px;
1527
+ font-size: 14px;
1528
+ color: #7f7f7f;
1529
+ background-color: var(--lyric-icon-background-color);
1530
+ user-select: none;
1531
+ }
1532
+
1533
+ .footer .right-control .lyric-toggle .lyric-icon.selected {
1534
+ border: solid 1px #7f7f7f;
1535
+ background-color: #7f7f7f;
1536
+ color: #fff;
1537
+ }
1538
+
1539
+ .footer .right-control .volume-ctrl {
1540
+ flex: 1;
1541
+ display: flex;
1542
+ }
1543
+
1544
+ .footer .right-control .volume-ctrl .icon {
1545
+ flex: 0 0 24px;
1546
+ color: var(--volume-icon-color);
1547
+ cursor: pointer;
1548
+ margin-left: 21px;
1549
+ }
1550
+ .footer .right-control .volume-ctrl .m-pbar {
1551
+ flex: 1;
1552
+ }
1553
+
1554
+ .footer .right-control .volume-ctrl .barbg {
1555
+ height: 3px;
1556
+
1557
+ background: var(--volume-bar-background-color);
1558
+ margin-top: 7px;
1559
+ width: 140px;
1560
+ }
1561
+
1562
+ .footer .right-control .volume-ctrl .barbg .cur {
1563
+ height: 100%;
1564
+ background: var(--volume-bar-current-background-color);
1565
+
1566
+ position: relative;
1567
+ }
1568
+
1569
+ .footer .right-control .volume-ctrl .barbg .cur .btn {
1570
+ background: #ffffff;
1571
+ height: 13px;
1572
+ width: 13px;
1573
+ border: solid 1px #e4e4e4;
1574
+ border-radius: 13px;
1575
+ position: absolute;
1576
+ right: -13px;
1577
+ top: -6px;
1578
+ }
1579
+
1580
+ /* footer end */
1581
+
1582
+ /* dialog start */
1583
+ .shadow {
1584
+ position: fixed;
1585
+ background: rgba(30, 30, 30, 0.9);
1586
+ _position: absolute;
1587
+ z-index: 9999;
1588
+ top: 0;
1589
+ bottom: 0;
1590
+ left: 0;
1591
+ right: 0;
1592
+ width: 100%;
1593
+ height: 100%;
1594
+ background-image: url(data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==);
1595
+ }
1596
+
1597
+ .dialog {
1598
+ position: absolute;
1599
+ top: 120px;
1600
+ width: 400px;
1601
+ height: 430px;
1602
+ z-index: 10000;
1603
+ overflow: hidden;
1604
+ border-radius: 4px;
1605
+ background-color: var(--dialog-background-color);
1606
+ color: var(--dialog-text-color);
1607
+ box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
1608
+ }
1609
+
1610
+ .dialog-header {
1611
+ height: 30px;
1612
+ font-size: 15px;
1613
+ font-weight: bold;
1614
+ text-align: left;
1615
+ padding: 12px;
1616
+ }
1617
+
1618
+ .dialog-header .dialog-close {
1619
+ float: right;
1620
+ font-size: 26px;
1621
+ cursor: pointer;
1622
+ margin-top: -10px;
1623
+ }
1624
+
1625
+ .dialog-body {
1626
+ padding: 0 20px;
1627
+ height: 370px;
1628
+ overflow-y: auto;
1629
+ background-color: var(--dialog-background-color);
1630
+ }
1631
+
1632
+ .dialog-body .buttons {
1633
+ display: flex;
1634
+ justify-content: center;
1635
+ margin-top: 20px;
1636
+ }
1637
+
1638
+ .dialog-body .buttons .confirm-button {
1639
+ margin-right: 20px;
1640
+ }
1641
+ .dialog .detail-songlist li:hover {
1642
+ background-color: #e3e3e5;
1643
+ cursor: pointer;
1644
+ }
1645
+ .dialog-body input {
1646
+ width: 100%;
1647
+ }
1648
+
1649
+ .dialog-playlist,
1650
+ .dialog-backuplist,
1651
+ .dialog-merge-playlist {
1652
+ padding-left: 0px;
1653
+ text-align: left;
1654
+ }
1655
+
1656
+ .dialog-playlist li,
1657
+ .dialog-backuplist li,
1658
+ .dialog-merge-playlist li {
1659
+ cursor: pointer;
1660
+ height: 48px;
1661
+ padding: 6px;
1662
+ }
1663
+ .dialog-backuplist li {
1664
+ height: 96px;
1665
+ padding: 6px;
1666
+ }
1667
+
1668
+ .dialog-playlist li:hover,
1669
+ .dialog-backuplist li:hover,
1670
+ .dialog-merge-playlist li:hover {
1671
+ background-color: var(--dialog-highlight-color);
1672
+ }
1673
+
1674
+ .dialog-playlist li img,
1675
+ .dialog-backuplist li img,
1676
+ .dialog-merge-playlist li img {
1677
+ float: left;
1678
+ height: 48px;
1679
+ width: 48px;
1680
+ }
1681
+
1682
+ .dialog-playlist li h2,
1683
+ .dialog-backuplist li h2,
1684
+ .dialog-merge-playlist li h2 {
1685
+ margin: 0 0 0 58px;
1686
+ font-size: 13px;
1687
+ font-weight: inherit;
1688
+ }
1689
+ .dialog-backuplist li h2 {
1690
+ margin-top: 0;
1691
+ }
1692
+ .dialog-newplaylist {
1693
+ padding: 10px;
1694
+ }
1695
+
1696
+ .dialog-newbackup {
1697
+ text-align: center;
1698
+ }
1699
+
1700
+ .dialog-editplaylist label,
1701
+ .dialog-open-url label {
1702
+ display: block;
1703
+ height: 30px;
1704
+ line-height: 30px;
1705
+ }
1706
+
1707
+ .dialog-editplaylist .dialog-footer {
1708
+ position: absolute;
1709
+ bottom: 20px;
1710
+ }
1711
+ .dialog-body .field-name {
1712
+ margin: 10px 0 5px 0;
1713
+ }
1714
+
1715
+ /* dialog end */
1716
+
1717
+ /* widget source-list start */
1718
+ .source-list {
1719
+ margin: 20px 26px 10px 26px;
1720
+ }
1721
+
1722
+ .source-list .source-button {
1723
+ display: inline-block;
1724
+ color: var(--link-inactive-color);
1725
+ cursor: pointer;
1726
+ padding-bottom: 4px;
1727
+ font-size: 14px;
1728
+ }
1729
+
1730
+ .source-list .source-button.active,
1731
+ .source-list .source-button:hover {
1732
+ color: var(--link-active-color);
1733
+ border-bottom: solid 1px var(--link-active-color);
1734
+ }
1735
+
1736
+ .source-list .splitter {
1737
+ display: inline-block;
1738
+ background: #a9a9a9;
1739
+ margin-top: 1px;
1740
+ height: 12px;
1741
+ width: 1px;
1742
+ margin: 0 10px;
1743
+ }
1744
+ .source-list .search-type {
1745
+ float: right;
1746
+ }
1747
+ /* widget source-list end */
1748
+
1749
+ /* widget playlist-filter start */
1750
+
1751
+ .playlist-filter {
1752
+ line-height: 38px;
1753
+ margin: 0 26px 10px 26px;
1754
+ }
1755
+
1756
+ .playlist-filter .filter-item {
1757
+ line-height: 20px;
1758
+ padding: 5px 15px;
1759
+ margin-right: 10px;
1760
+ }
1761
+
1762
+ .playlist-filter .filter-item.active {
1763
+ font-weight: 600;
1764
+ background: var(--button-hover-background-color);
1765
+ }
1766
+
1767
+ /* widget playlist-filter end */
1768
+
1769
+ /* widget all-playlist-filter start */
1770
+ .all-playlist-filter .category {
1771
+ margin-bottom: 10px;
1772
+ display: flex;
1773
+ }
1774
+
1775
+ .all-playlist-filter .category .category-title {
1776
+ margin-left: 30px;
1777
+ margin-top: 12px;
1778
+ min-width: 50px;
1779
+ font-size: 18px;
1780
+ }
1781
+ .all-playlist-filter .category .category-filters {
1782
+ margin-left: 10px;
1783
+ display: flex;
1784
+ flex-wrap: wrap;
1785
+ }
1786
+ .all-playlist-filter .category .category-filters .filter-item {
1787
+ min-width: 80px;
1788
+ margin-top: 10px;
1789
+ display: flex;
1790
+ }
1791
+ .all-playlist-filter .category .category-filters .filter-item span {
1792
+ display: flex;
1793
+ justify-content: center;
1794
+ align-items: center;
1795
+ cursor: pointer;
1796
+ padding: 5px 10px;
1797
+ }
1798
+ .all-playlist-filter .category .category-filters .filter-item span:hover {
1799
+ background-color: var(--button-background-color);
1800
+ border-radius: var(--default-border-radius);
1801
+ }
1802
+ /* widget all-playlist-filter end */
css/cover.css ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Globals
3
+ */
4
+
5
+ /* Links */
6
+ a,
7
+ a:focus,
8
+ a:hover {
9
+ color: #fff;
10
+ }
11
+
12
+ /* Custom default button */
13
+ .btn-default,
14
+ .btn-default:hover,
15
+ .btn-default:focus {
16
+ color: #333;
17
+ text-shadow: none; /* Prevent inheritence from `body` */
18
+ background-color: #fff;
19
+ border: 1px solid #fff;
20
+ }
21
+
22
+ /*
23
+ * Base structure
24
+ */
25
+
26
+ html,
27
+ body {
28
+ height: 100%;
29
+ background-color: #333;
30
+ }
31
+ body {
32
+ color: #fff;
33
+ overflow: hidden !important;
34
+ /* text-align: center;*/
35
+ /*text-shadow: 0 1px 3px rgba(0,0,0,.5);*/
36
+ }
37
+
38
+ /* Extra markup and styles for table-esque vertical and horizontal centering */
39
+ .site-wrapper {
40
+ display: table;
41
+ width: 100%;
42
+ height: 100%; /* For at least Firefox */
43
+ min-height: 100%;
44
+ /* -webkit-box-shadow: inset 0 0 100px rgba(0,0,0,.5);
45
+ box-shadow: inset 0 0 100px rgba(0,0,0,.5);*/
46
+ }
47
+ .site-wrapper-inner {
48
+ display: table-cell;
49
+ vertical-align: top;
50
+ }
51
+ .cover-container {
52
+ margin-right: auto;
53
+ margin-left: auto;
54
+ }
55
+
56
+ /* Padding for spacing */
57
+ .inner {
58
+ padding: 30px;
59
+ }
60
+
61
+ /*
62
+ * Header
63
+ */
64
+ .masthead-brand {
65
+ margin-top: 10px;
66
+ margin-bottom: 10px;
67
+ }
68
+
69
+ .masthead-nav > li {
70
+ display: inline-block;
71
+ }
72
+ .masthead-nav > li + li {
73
+ margin-left: 20px;
74
+ }
75
+ .masthead-nav > li > a {
76
+ padding-right: 0;
77
+ padding-left: 0;
78
+ font-size: 16px;
79
+ font-weight: bold;
80
+ color: #fff; /* IE8 proofing */
81
+ color: rgba(255, 255, 255, 0.75);
82
+ border-bottom: 2px solid transparent;
83
+ }
84
+ .masthead-nav > li > a:hover,
85
+ .masthead-nav > li > a:focus {
86
+ background-color: transparent;
87
+ border-bottom-color: #a9a9a9;
88
+ border-bottom-color: rgba(255, 255, 255, 0.25);
89
+ }
90
+ .masthead-nav > .active > a,
91
+ .masthead-nav > .active > a:hover,
92
+ .masthead-nav > .active > a:focus {
93
+ color: #fff;
94
+ border-bottom-color: #fff;
95
+ }
96
+
97
+ @media (min-width: 768px) {
98
+ .masthead-brand {
99
+ float: left;
100
+ }
101
+ .masthead-nav {
102
+ float: right;
103
+ }
104
+ }
105
+
106
+ /*
107
+ * Cover
108
+ */
109
+
110
+ .cover {
111
+ padding: 0 20px;
112
+ }
113
+ .cover .btn-lg {
114
+ padding: 10px 20px;
115
+ font-weight: bold;
116
+ }
117
+
118
+ /*
119
+ * Footer
120
+ */
121
+
122
+ .mastfoot {
123
+ color: #999; /* IE8 proofing */
124
+ color: rgba(255, 255, 255, 0.5);
125
+ }
126
+
127
+ /*
128
+ * Affix and center
129
+ */
130
+
131
+ @media (min-width: 768px) {
132
+ /* Pull out the header and footer */
133
+ .masthead {
134
+ position: fixed;
135
+ top: 0;
136
+ }
137
+ .mastfoot {
138
+ position: fixed;
139
+ bottom: 0;
140
+ }
141
+ /* Start the vertical centering */
142
+ .site-wrapper-inner {
143
+ vertical-align: middle;
144
+ }
145
+ /* Handle the widths */
146
+ .masthead,
147
+ .mastfoot,
148
+ .cover-container {
149
+ width: 100%; /* Must be percentage or pixels for horizontal alignment */
150
+ }
151
+ }
152
+
153
+ @media (min-width: 992px) {
154
+ .masthead,
155
+ .mastfoot,
156
+ .cover-container {
157
+ width: 880px;
158
+ }
159
+ }
160
+
161
+ /*
162
+ * Scroll bar
163
+ *
164
+ */
165
+ :root {
166
+ --scrollWidth: 10px;
167
+ }
168
+
169
+ ::-webkit-scrollbar {
170
+ width: var(--scrollWidth) !important;
171
+ height: var(--scrollWidth) !important;
172
+ }
173
+ ::-webkit-scrollbar-button {
174
+ width: 0 !important;
175
+ height: 0 !important;
176
+ }
177
+ ::-webkit-scrollbar-button:start:increment,
178
+ ::-webkit-scrollbar-button:end:decrement {
179
+ display: none !important;
180
+ }
181
+ ::-webkit-scrollbar-corner {
182
+ display: block !important;
183
+ }
184
+
185
+ ::-webkit-scrollbar-track,
186
+ ::-webkit-scrollbar-thumb {
187
+ border-radius: 8px !important;
188
+ border-right: 1px solid transparent !important;
189
+ border-left: 1px solid transparent !important;
190
+ }
191
+
192
+ ::-webkit-scrollbar-thumb {
193
+ background-color: rgba(255, 255, 255, 0.2);
194
+ }
195
+ ::-webkit-scrollbar-thumb:hover {
196
+ background-color: rgba(255, 255, 255, 0.3);
197
+ }
198
+ ::-webkit-scrollbar-track:hover {
199
+ background-color: rgba(255, 255, 255, 0.08) !important;
200
+ }
css/hotkeys.css ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * angular-hotkeys v1.7.0
3
+ * https://chieffancypants.github.io/angular-hotkeys
4
+ * Copyright (c) 2016 Wes Cruver
5
+ * License: MIT
6
+ */
7
+ .cfp-hotkeys-container {
8
+ display: table !important;
9
+ position: fixed;
10
+ width: 100%;
11
+ height: 100%;
12
+ top: 0;
13
+ left: 0;
14
+ color: #333;
15
+ font-size: 1em;
16
+ background-color: rgba(255,255,255,0.9);
17
+ }
18
+
19
+ .cfp-hotkeys-container.fade {
20
+ z-index: -1024;
21
+ visibility: hidden;
22
+ opacity: 0;
23
+ -webkit-transition: opacity 0.15s linear;
24
+ -moz-transition: opacity 0.15s linear;
25
+ -o-transition: opacity 0.15s linear;
26
+ transition: opacity 0.15s linear;
27
+ }
28
+
29
+ .cfp-hotkeys-container.fade.in {
30
+ z-index: 10002;
31
+ visibility: visible;
32
+ opacity: 1;
33
+ }
34
+
35
+ .cfp-hotkeys-title {
36
+ font-weight: bold;
37
+ text-align: center;
38
+ font-size: 1.2em;
39
+ }
40
+
41
+ .cfp-hotkeys {
42
+ width: 100%;
43
+ height: 100%;
44
+ display: table-cell;
45
+ vertical-align: middle;
46
+ }
47
+
48
+ .cfp-hotkeys table {
49
+ margin: auto;
50
+ color: #333;
51
+ }
52
+
53
+ .cfp-content {
54
+ display: table-cell;
55
+ vertical-align: middle;
56
+ }
57
+
58
+ .cfp-hotkeys-keys {
59
+ padding: 5px;
60
+ text-align: right;
61
+ }
62
+
63
+ .cfp-hotkeys-key {
64
+ display: inline-block;
65
+ color: #fff;
66
+ background-color: #333;
67
+ border: 1px solid #333;
68
+ border-radius: 5px;
69
+ text-align: center;
70
+ margin-right: 5px;
71
+ box-shadow: inset 0 1px 0 #666, 0 1px 0 #bbb;
72
+ padding: 5px 9px;
73
+ font-size: 1em;
74
+ }
75
+
76
+ .cfp-hotkeys-text {
77
+ padding-left: 10px;
78
+ font-size: 1em;
79
+ }
80
+
81
+ .cfp-hotkeys-close {
82
+ position: fixed;
83
+ top: 20px;
84
+ right: 20px;
85
+ font-size: 2em;
86
+ font-weight: bold;
87
+ padding: 5px 10px;
88
+ border: 1px solid #ddd;
89
+ border-radius: 5px;
90
+ min-height: 45px;
91
+ min-width: 45px;
92
+ text-align: center;
93
+ }
94
+
95
+ .cfp-hotkeys-close:hover {
96
+ background-color: #fff;
97
+ cursor: pointer;
98
+ }
99
+
100
+ @media all and (max-width: 500px) {
101
+ .cfp-hotkeys {
102
+ font-size: 0.8em;
103
+ }
104
+ }
105
+
106
+ @media all and (min-width: 750px) {
107
+ .cfp-hotkeys {
108
+ font-size: 1.2em;
109
+ }
110
+ }
css/icon.css ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @font-face {
2
+ font-family: 'listen1-icon';
3
+ src: url('../fonts/listen1-icon.eot?4ftssm');
4
+ src: url('../fonts/listen1-icon.eot?4ftssm#iefix') format('embedded-opentype'),
5
+ url('../fonts/listen1-icon.ttf?4ftssm') format('truetype'),
6
+ url('../fonts/listen1-icon.woff?4ftssm') format('woff'),
7
+ url('../fonts/listen1-icon.svg?4ftssm#listen1-icon') format('svg');
8
+ font-weight: normal;
9
+ font-style: normal;
10
+ }
11
+
12
+ [class^='li-'],
13
+ [class*=' li-'] {
14
+ /* use !important to prevent issues with browser extensions that change fonts */
15
+ font-family: 'listen1-icon' !important;
16
+ speak: none;
17
+ font-style: normal;
18
+ font-weight: normal;
19
+ font-variant: normal;
20
+ text-transform: none;
21
+ line-height: 1;
22
+
23
+ /* Better Font Rendering =========== */
24
+ -webkit-font-smoothing: antialiased;
25
+ -moz-osx-font-smoothing: grayscale;
26
+ }
27
+
28
+ .li-play:before {
29
+ content: '\e900';
30
+ }
31
+ .li-previous:before {
32
+ content: '\e901';
33
+ }
34
+ .li-next:before {
35
+ content: '\e902';
36
+ }
37
+ .li-pause:before {
38
+ content: '\e903';
39
+ }
40
+ .li-random-loop:before {
41
+ content: '\e904';
42
+ }
43
+ .li-single-cycle:before {
44
+ content: '\e905';
45
+ }
46
+ .li-mute:before {
47
+ content: '\e906';
48
+ }
49
+ .li-volume:before {
50
+ content: '\e907';
51
+ }
52
+ .li-list:before {
53
+ content: '\e908';
54
+ }
55
+ .li-loop:before {
56
+ content: '\e909';
57
+ }
58
+ .li-del:before {
59
+ content: '\e90a';
60
+ }
61
+ .li-close:before {
62
+ content: '\e90b';
63
+ }
64
+ .li-back:before {
65
+ content: '\e90c';
66
+ }
67
+ .li-play-s:before {
68
+ content: '\e90d';
69
+ }
70
+ .li-collapse:before {
71
+ content: '\e90e';
72
+ }
73
+ .li-add:before {
74
+ content: '\e90f';
75
+ }
76
+ .li-advance:before {
77
+ content: '\e910';
78
+ }
79
+ .li-link:before {
80
+ content: '\e911';
81
+ }
82
+ .li-setting:before {
83
+ content: '\e912';
84
+ }
85
+ .li-songlist:before {
86
+ content: '\e913';
87
+ }
88
+ .li-featured-list:before {
89
+ content: '\e914';
90
+ }
css/iparanoid.css ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* global settings (theme related) */
2
+ :root {
3
+ --icon-default-color: #666666;
4
+ --icon-highlight-color: #111111;
5
+
6
+ --text-default-color: #111111;
7
+ --text-subtitle-color: #666666;
8
+ --text-disable-color: #999999;
9
+
10
+ --lyric-default-color: #666666;
11
+ --lyric-on-cover-color: #333333;
12
+ --lyric-important-color: #ff4444;
13
+ --lyric-important-on-cover-color: #ffffff;
14
+
15
+ --link-default-color: #999999;
16
+ --link-highlight-color: #111111;
17
+ --link-active-color: #323232;
18
+ --link-inactive-color: #a9a9a9;
19
+
20
+ --line-default-color: #e5e5e5;
21
+
22
+ --sidebar-background-color: rgba(245, 245, 245, 0.7);
23
+ --sidebar-highlight-background-color: #4d4d4d;
24
+ --sidebar-highlight-text-color: #ffffff;
25
+ --sidebar-hover-background-color: #5f5f5f;
26
+ --sidebar-hover-text-color: #ffffff;
27
+
28
+ --content-background-color: #ffffff;
29
+
30
+ --foot-background-color: #ffffff;
31
+ --foot-border-color: #e5e5e5;
32
+ --footer-main-background-color: #f2f2f2;
33
+ --footer-player-bar-background-color: #e0e0e0;
34
+ --footer-player-bar-cur-background-color: #666666;
35
+ --footer-header-background-color: #e7e7e7;
36
+ --footer-menu-even-background-color: #f7f7f7;
37
+ --footer-menu-hover-background-color: #e5e5e5;
38
+ --search-input-background-color: #f2f2f2;
39
+ --footer-player-bar-cur-button-color: #111111;
40
+
41
+ --window-control-border-color: #dddddd;
42
+
43
+ --important-color: #ff4444;
44
+
45
+ --button-background-color: #eeeeee;
46
+ --button-border-color: #bebebe;
47
+ --button-hover-background-color: #dddddd;
48
+
49
+ --now-playing-page-background-color: #ffffff;
50
+ --now-playing-close-icon-color: #666666;
51
+
52
+ --disable-song-title-color: #b7b7b7;
53
+ --windows-border-color: #dddddd;
54
+
55
+ --default-border-radius: 2px;
56
+ --text-default-size: 13px;
57
+ --h2-title-font-size: 24px;
58
+ --badge-font-size: 12px;
59
+ --badge-font-color: #c75449;
60
+ --badge-border-color: #d98e87;
61
+ --songlist-odd-background-color: #f5f5f5;
62
+ --songlist-border-color: #f4f4f4;
63
+ --songlist-hover-background-color: #eeeeee;
64
+
65
+ --player-left-icon-color: #b1b1b1;
66
+ --player-icon-color: #666666;
67
+ --player-icon-hover-color: #666666;
68
+ --player-right-icon-color: #333333;
69
+ --player-right-icon-hover-color: #000000;
70
+
71
+ --dialog-highlight-color: #e3e3e5;
72
+ --dialog-background-color: #fafafa;
73
+ --dialog-text-color: #565656;
74
+
75
+ --volume-icon-color: #333333;
76
+ --volume-bar-background-color: #e0e0e0;
77
+ --volume-bar-current-background-color: #333333;
78
+
79
+ --scroll-color: #c2c2c2;
80
+
81
+ --lyric-icon-background-color: #ffffff;
82
+ --lyric-font-size: 15px;
83
+ --lyric-line-margin: 20px;
84
+ }
css/notyf.min.css ADDED
@@ -0,0 +1 @@
 
 
1
+ @-webkit-keyframes notyf-fadeinup{0%{opacity:0;transform:translateY(25%)}to{opacity:1;transform:translateY(0)}}@keyframes notyf-fadeinup{0%{opacity:0;transform:translateY(25%)}to{opacity:1;transform:translateY(0)}}@-webkit-keyframes notyf-fadeinleft{0%{opacity:0;transform:translateX(25%)}to{opacity:1;transform:translateX(0)}}@keyframes notyf-fadeinleft{0%{opacity:0;transform:translateX(25%)}to{opacity:1;transform:translateX(0)}}@-webkit-keyframes notyf-fadeoutright{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(25%)}}@keyframes notyf-fadeoutright{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(25%)}}@-webkit-keyframes notyf-fadeoutdown{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(25%)}}@keyframes notyf-fadeoutdown{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(25%)}}@-webkit-keyframes ripple{0%{transform:scale(0) translateY(-45%) translateX(13%)}to{transform:scale(1) translateY(-45%) translateX(13%)}}@keyframes ripple{0%{transform:scale(0) translateY(-45%) translateX(13%)}to{transform:scale(1) translateY(-45%) translateX(13%)}}.notyf{position:fixed;top:0;left:0;height:100%;width:100%;color:#fff;z-index:9999;display:flex;flex-direction:column;align-items:flex-end;justify-content:flex-end;pointer-events:none;box-sizing:border-box;padding:20px}.notyf__icon--error,.notyf__icon--success{height:21px;width:21px;background:#fff;border-radius:50%;display:block;margin:0 auto;position:relative}.notyf__icon--error:after,.notyf__icon--error:before{content:"";background:currentColor;display:block;position:absolute;width:3px;border-radius:3px;left:9px;height:12px;top:5px}.notyf__icon--error:after{transform:rotate(-45deg)}.notyf__icon--error:before{transform:rotate(45deg)}.notyf__icon--success:after,.notyf__icon--success:before{content:"";background:currentColor;display:block;position:absolute;width:3px;border-radius:3px}.notyf__icon--success:after{height:6px;transform:rotate(-45deg);top:9px;left:6px}.notyf__icon--success:before{height:11px;transform:rotate(45deg);top:5px;left:10px}.notyf__toast{display:block;overflow:hidden;pointer-events:auto;-webkit-animation:notyf-fadeinup .3s ease-in forwards;animation:notyf-fadeinup .3s ease-in forwards;box-shadow:0 3px 7px 0 rgba(0,0,0,.25);position:relative;padding:0 15px;border-radius:2px;max-width:300px;transform:translateY(25%);box-sizing:border-box;flex-shrink:0}.notyf__toast--disappear{transform:translateY(0);-webkit-animation:notyf-fadeoutdown .3s forwards;animation:notyf-fadeoutdown .3s forwards;-webkit-animation-delay:.25s;animation-delay:.25s}.notyf__toast--disappear .notyf__icon,.notyf__toast--disappear .notyf__message{-webkit-animation:notyf-fadeoutdown .3s forwards;animation:notyf-fadeoutdown .3s forwards;opacity:1;transform:translateY(0)}.notyf__toast--disappear .notyf__dismiss{-webkit-animation:notyf-fadeoutright .3s forwards;animation:notyf-fadeoutright .3s forwards;opacity:1;transform:translateX(0)}.notyf__toast--disappear .notyf__message{-webkit-animation-delay:.05s;animation-delay:.05s}.notyf__toast--upper{margin-bottom:20px}.notyf__toast--lower{margin-top:20px}.notyf__toast--dismissible .notyf__wrapper{padding-right:30px}.notyf__ripple{height:400px;width:400px;position:absolute;transform-origin:bottom right;right:0;top:0;border-radius:50%;transform:scale(0) translateY(-51%) translateX(13%);z-index:5;-webkit-animation:ripple .4s ease-out forwards;animation:ripple .4s ease-out forwards}.notyf__wrapper{display:flex;align-items:center;padding-top:17px;padding-bottom:17px;padding-right:15px;border-radius:3px;position:relative;z-index:10}.notyf__icon{width:22px;text-align:center;font-size:1.3em;opacity:0;-webkit-animation:notyf-fadeinup .3s forwards;animation:notyf-fadeinup .3s forwards;-webkit-animation-delay:.3s;animation-delay:.3s;margin-right:13px}.notyf__dismiss{position:absolute;top:0;right:0;height:100%;width:26px;margin-right:-15px;-webkit-animation:notyf-fadeinleft .3s forwards;animation:notyf-fadeinleft .3s forwards;-webkit-animation-delay:.35s;animation-delay:.35s;opacity:0}.notyf__dismiss-btn{background-color:rgba(0,0,0,.25);border:none;cursor:pointer;transition:opacity .2s ease,background-color .2s ease;outline:none;opacity:.35;height:100%;width:100%}.notyf__dismiss-btn:after,.notyf__dismiss-btn:before{content:"";background:#fff;height:12px;width:2px;border-radius:3px;position:absolute;left:calc(50% - 1px);top:calc(50% - 5px)}.notyf__dismiss-btn:after{transform:rotate(-45deg)}.notyf__dismiss-btn:before{transform:rotate(45deg)}.notyf__dismiss-btn:hover{opacity:.7;background-color:rgba(0,0,0,.15)}.notyf__dismiss-btn:active{opacity:.8}.notyf__message{vertical-align:middle;position:relative;opacity:0;-webkit-animation:notyf-fadeinup .3s forwards;animation:notyf-fadeinup .3s forwards;-webkit-animation-delay:.25s;animation-delay:.25s;line-height:1.5em}@media only screen and (max-width:480px){.notyf{padding:0}.notyf__ripple{height:600px;width:600px;-webkit-animation-duration:.5s;animation-duration:.5s}.notyf__toast{max-width:none;border-radius:0;box-shadow:0 -2px 7px 0 rgba(0,0,0,.13);width:100%}.notyf__dismiss{width:56px}}
css/origin.css ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* global settings (theme related) */
2
+ :root {
3
+ --icon-default-color: #666666;
4
+ --icon-highlight-color: #111111;
5
+
6
+ --text-default-color: #ffffff;
7
+ --text-subtitle-color: #666666;
8
+ --text-disable-color: #999999;
9
+
10
+ --lyric-default-color: #666666;
11
+ --lyric-on-cover-color: #bbbbbb;
12
+ --lyric-important-color: #ffffff;
13
+ --lyric-important-on-cover-color: #ffffff;
14
+
15
+ --link-default-color: #999999;
16
+ --link-highlight-color: #ffffff;
17
+ --link-active-color: #ffffff;
18
+ --link-inactive-color: rgba(255, 255, 255, 0.75);
19
+
20
+ --line-default-color: #333333;
21
+
22
+ --sidebar-background-color: #2e2e2e;
23
+ --sidebar-highlight-background-color: #4d4d4d;
24
+ --sidebar-highlight-text-color: #ffffff;
25
+ --sidebar-hover-background-color: #3c3c3c;
26
+ --sidebar-hover-text-color: #ffffff;
27
+
28
+ --content-background-color: #333333;
29
+
30
+ --foot-background-color: #222222;
31
+ --foot-border-color: #222222;
32
+ --footer-main-background-color: #222222;
33
+ --footer-player-bar-background-color: #666666;
34
+ --footer-player-bar-cur-background-color: #e0e0e0;
35
+ --footer-header-background-color: #333333;
36
+ --footer-menu-even-background-color: #2d2d2d;
37
+ --footer-menu-hover-background-color: #333333;
38
+ --search-input-background-color: #222222;
39
+ --footer-player-bar-cur-button-color: #e0e0e0;
40
+
41
+ --window-control-border-color: #dddddd;
42
+
43
+ --important-color: #fff;
44
+
45
+ --button-background-color: #222222;
46
+ --button-border-color: #222222;
47
+ --button-hover-background-color: #444444;
48
+
49
+ --now-playing-page-background-color: #333333;
50
+ --now-playing-close-icon-color: #b3b3b3;
51
+
52
+ --disable-song-title-color: #b7b7b7;
53
+ --windows-border-color: #333333;
54
+
55
+ --default-border-radius: 2px;
56
+ --text-default-size: 13px;
57
+ --h2-title-font-size: 24px;
58
+ --badge-font-size: 12px;
59
+ --badge-font-color: #c3473a;
60
+ --badge-border-color: #843932;
61
+ --songlist-odd-background-color: #2d2d2d;
62
+ --songlist-border-color: transparent;
63
+ --songlist-hover-background-color: #3e3e3e;
64
+
65
+ --player-left-icon-color: #b3b3b3;
66
+ --player-icon-color: #b3b3b3;
67
+ --player-icon-hover-color: #eeeeee;
68
+ --player-right-icon-color: #b3b3b3;
69
+ --player-right-icon-hover-color: #eeeeee;
70
+ --player-icon-hightlight-color: #eeeeee;
71
+
72
+ --dialog-highlight-color: #444444;
73
+ --dialog-background-color: #333;
74
+ --dialog-text-color: #ffffff;
75
+
76
+ --volume-icon-color: #b3b3b3;
77
+ --volume-bar-background-color: #333333;
78
+ --volume-bar-current-background-color: #e0e0e0;
79
+
80
+ --scroll-color: #444444;
81
+
82
+ --lyric-icon-background-color: #222222;
83
+ --lyric-font-size: 15px;
84
+ --lyric-line-margin: 20px;
85
+ }
css/player.css ADDED
@@ -0,0 +1,1225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ a {
2
+ cursor: pointer;
3
+ }
4
+
5
+ .shadow {
6
+ position: fixed;
7
+ background: rgba(30, 30, 30, 0.9);
8
+ _position: absolute;
9
+ z-index: 9999;
10
+ top: 0;
11
+ bottom: 0;
12
+ left: 0;
13
+ right: 0;
14
+ width: 100%;
15
+ height: 100%;
16
+ background-image: url(data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==);
17
+ }
18
+
19
+ .dialog {
20
+ position: absolute;
21
+ top: 120px;
22
+ width: 480px;
23
+ height: 420px;
24
+ z-index: 10000;
25
+ overflow: hidden;
26
+ border-radius: 4px 4px 4px 4px;
27
+ padding: 20px;
28
+ background-color: #333;
29
+ }
30
+
31
+ .dialog-header {
32
+ width: 100%;
33
+ height: 30px;
34
+ font-family: Arial, Helvetica, sans-serif;
35
+ font-size: 15px;
36
+ font-weight: bold;
37
+ text-align: left;
38
+ border-bottom: 1px solid;
39
+ margin-bottom: 20px;
40
+ }
41
+
42
+ .dialog-header .dialog-close {
43
+ float: right;
44
+ font-size: 33px;
45
+ cursor: pointer;
46
+ margin-top: -15px;
47
+ }
48
+
49
+ .dialog-body {
50
+ width: 100%;
51
+ height: 320px;
52
+ overflow-y: auto;
53
+ background-color: #333;
54
+ }
55
+
56
+ /*.masthead {
57
+ z-index: 999;
58
+ }*/
59
+
60
+ .masthead,
61
+ .mastfoot {
62
+ margin: 0 auto;
63
+ left: 0;
64
+ right: 0;
65
+ z-index: 999;
66
+ background-color: #222222;
67
+ }
68
+
69
+ .masthead {
70
+ background-color: #333;
71
+ height: 90px;
72
+ }
73
+
74
+ .masthead .logo {
75
+ float: left;
76
+ height: 50px;
77
+ width: 50px;
78
+ margin-right: 20px;
79
+ cursor: pointer;
80
+ }
81
+
82
+ .masthead .masthead-brand {
83
+ color: rgba(255, 255, 255, 1);
84
+ cursor: pointer;
85
+ }
86
+
87
+ .cover-container {
88
+ position: relative;
89
+ }
90
+
91
+ .container-placeholder {
92
+ margin-top: 90px;
93
+ }
94
+
95
+ .site-wrapper {
96
+ width: 100%;
97
+ overflow: hidden;
98
+ position: absolute;
99
+ padding-left: 17px;
100
+ }
101
+
102
+ .site-wrapper-innerd {
103
+ overflow-y: scroll;
104
+ margin-top: 90px;
105
+ /* uncomment the line below will hide the scroll bar */
106
+ /*padding-right: 17px;*/
107
+ box-sizing: content-box;
108
+ width: 100%;
109
+ background-color: #333;
110
+ }
111
+
112
+ .searchbox {
113
+ /* margin-top: 100px;*/
114
+ }
115
+
116
+ .searchbox .nav {
117
+ margin-top: 12px;
118
+ }
119
+
120
+ .searchbox .nav .searchspinner {
121
+ margin-top: 8px;
122
+ height: 25px;
123
+ }
124
+
125
+ .searchitem {
126
+ height: 92px;
127
+ }
128
+
129
+ .searchitem img {
130
+ float: left;
131
+ height: 90px;
132
+ width: 90px;
133
+ }
134
+
135
+ .searchitem div {
136
+ float: left;
137
+ margin-left: 48px;
138
+ margin-top: 38px;
139
+ width: 400px;
140
+ }
141
+
142
+ .playlist-covers {
143
+ margin-bottom: 0px;
144
+ }
145
+
146
+ .playlist-covers li {
147
+ float: left;
148
+ display: inline-block;
149
+ width: 140px;
150
+ height: 188px;
151
+ margin-right: 22px;
152
+ }
153
+
154
+ .playlist-covers .desc {
155
+ text-align: left;
156
+ }
157
+
158
+ .playlist-covers .u-cover {
159
+ position: relative;
160
+ display: block;
161
+ width: 140px;
162
+ height: 140px;
163
+ }
164
+
165
+ .playlist-covers .u-cover .bottom {
166
+ position: absolute;
167
+ bottom: 0;
168
+ left: 0;
169
+ width: 100%;
170
+ height: 27px;
171
+ color: #ccc;
172
+ }
173
+
174
+ .playlist-covers .loading_bottom {
175
+ clear: both;
176
+ text-align: center;
177
+ }
178
+
179
+ .playlist-covers .loading_bottom img {
180
+ height: 35px;
181
+ }
182
+
183
+ .u-cover .mask {
184
+ position: absolute;
185
+ top: 0;
186
+ left: 0;
187
+ width: 100%;
188
+ height: 100%;
189
+ }
190
+
191
+ .u-cover img {
192
+ display: block;
193
+ width: 100%;
194
+ height: 100%;
195
+ }
196
+
197
+ .u-cover .icon-play {
198
+ position: absolute;
199
+ right: 10px;
200
+ bottom: 5px;
201
+ width: 16px;
202
+ height: 17px;
203
+ background: url(../images/player_directplay.png);
204
+ }
205
+
206
+ .playlist-covers .u-cover .bottom .nb {
207
+ float: left;
208
+ margin: 7px 0 0 0;
209
+ }
210
+
211
+ .m-playbar {
212
+ position: absolute;
213
+ zoom: 1;
214
+ top: -90px;
215
+ left: 0;
216
+ width: 100%;
217
+ height: 90px;
218
+ margin: 0 auto;
219
+ background-color: #222222;
220
+ }
221
+
222
+ .m-playbar .btns {
223
+ width: 157px;
224
+ padding: 27px 0 0 19px;
225
+ }
226
+
227
+ .m-playbar .btns,
228
+ .m-playbar .head,
229
+ .m-playbar .play,
230
+ .m-playbar .volum,
231
+ .m-playbar .oper {
232
+ float: left;
233
+ }
234
+
235
+ .m-pbar .btn i {
236
+ visibility: hidden;
237
+ position: absolute;
238
+ left: 5px;
239
+ top: 5px;
240
+ width: 12px;
241
+ height: 12px;
242
+ background: url(../images/loading.gif);
243
+ }
244
+
245
+ .m-pbar .barbg,
246
+ .m-pbar .cur,
247
+ .m-pbar .left {
248
+ background: url(../images/statbar.png) no-repeat 0 9999px;
249
+ _background-image: url(../images/statbar.png);
250
+ }
251
+
252
+ .m-playbar .btns a {
253
+ background: url(../images/player_large.png) no-repeat 0 9999px;
254
+ _background-image: url(../images/player_large.png);
255
+ cursor: pointer;
256
+ }
257
+
258
+ .m-playbar .btns a {
259
+ display: block;
260
+ float: left;
261
+ width: 36px;
262
+ height: 36px;
263
+ margin-right: 8px;
264
+ margin-top: 0px;
265
+ text-indent: -9999px;
266
+ }
267
+
268
+ .m-playbar .btns .previous {
269
+ background-position: -72px 0px;
270
+ }
271
+
272
+ .m-playbar .btns .previous:hover {
273
+ background-position: -72px -36px;
274
+ }
275
+
276
+ .m-playbar .btns .play {
277
+ width: 36px;
278
+ height: 36px;
279
+ margin-top: 0;
280
+ background-position: 0px 0px;
281
+ }
282
+
283
+ .m-playbar .btns .play:hover {
284
+ background-position: 0px -36px;
285
+ }
286
+
287
+ .m-playbar .btns .pas {
288
+ background-position: -108px 0px;
289
+ }
290
+
291
+ .m-playbar .btns .pas:hover {
292
+ background-position: -108px -36px;
293
+ }
294
+
295
+ .m-playbar .btns .next {
296
+ /* pause icon distance adjust from 36 to 38 */
297
+ background-position: -38px 0px;
298
+ }
299
+
300
+ .m-playbar .btns .next:hover {
301
+ background-position: -38px -36px;
302
+ }
303
+
304
+ .m-playbar .head {
305
+ position: relative;
306
+ margin: 10px 15px 0 0;
307
+ }
308
+
309
+ .m-playbar .head,
310
+ .m-playbar .head img {
311
+ width: 70px;
312
+ height: 70px;
313
+ }
314
+
315
+ .m-playbar .head .mask {
316
+ position: absolute;
317
+ top: 0px;
318
+ left: 0px;
319
+ display: block;
320
+ width: 70px;
321
+ height: 70px;
322
+ }
323
+
324
+ .m-playbar .maininfo {
325
+ float: none;
326
+ margin-left: 245px;
327
+ margin-right: 120px;
328
+ }
329
+
330
+ .m-playbar .words .notextdeco {
331
+ text-decoration: none;
332
+ }
333
+
334
+ .m-playbar .words {
335
+ margin-top: 14px;
336
+ height: 28px;
337
+ overflow: hidden;
338
+ color: #e8e8e8;
339
+ text-shadow: 0 1px 0 #171717;
340
+ line-height: 28px;
341
+ }
342
+
343
+ .m-playbar .words .name {
344
+ max-width: 300px;
345
+ }
346
+
347
+ .m-playbar .words .by {
348
+ max-width: 220px;
349
+ margin-left: 15px;
350
+ color: #9b9b9b;
351
+ }
352
+
353
+ .m-playbar .words .by a {
354
+ color: #9b9b9b;
355
+ }
356
+
357
+ .m-playbar .words .src {
358
+ cursor: pointer;
359
+ float: left;
360
+ width: 25px;
361
+ height: 25px;
362
+ margin: 2px 0 0 13px;
363
+ background: url(../images/player_small.png) no-repeat 0 9999px;
364
+ background-position: -100px 0px;
365
+ }
366
+
367
+ .m-playbar .words .src:hover {
368
+ background-position: -100px -25px;
369
+ }
370
+
371
+ .m-playbar .words .fc1 {
372
+ color: #e8e8e8;
373
+ margin-left: 3px;
374
+ }
375
+
376
+ .overflowhide {
377
+ overflow: hidden;
378
+ text-overflow: ellipsis;
379
+ white-space: nowrap;
380
+ }
381
+
382
+ .floatleft {
383
+ float: left;
384
+ }
385
+
386
+ .m-pbar {
387
+ position: relative;
388
+ }
389
+
390
+ .m-pbar.play {
391
+ width: 80%;
392
+ margin-top: 14px;
393
+ }
394
+
395
+ .m-pbar.volume {
396
+ position: absolute;
397
+ right: 13px;
398
+ bottom: 11px;
399
+ float: right;
400
+ width: 56%;
401
+ margin-top: 0px;
402
+ }
403
+
404
+ .m-pbar .barbg,
405
+ .m-pbar .cur,
406
+ .m-pbar .rdy {
407
+ height: 7px;
408
+ }
409
+
410
+ .m-pbar .barbg {
411
+ background-position: right 0;
412
+ }
413
+
414
+ .m-pbar .cur {
415
+ position: absolute;
416
+ top: 0;
417
+ left: 0;
418
+ width: 1%;
419
+ background-position: left -9px;
420
+ }
421
+
422
+ .m-pbar .btn {
423
+ position: absolute;
424
+ top: -8px;
425
+ right: -13px;
426
+ width: 22px;
427
+ height: 24px;
428
+ margin-left: -11px;
429
+ background: url(../images/progress_indicator.png) no-repeat;
430
+ }
431
+
432
+ .m-playbar .time {
433
+ position: absolute;
434
+ right: -122px;
435
+ top: -6px;
436
+ }
437
+
438
+ .m-playbar .time {
439
+ float: right;
440
+ margin-right: 20px;
441
+ color: #797979;
442
+ text-shadow: 0 1px 0 #121212;
443
+ }
444
+
445
+ .m-playbar .time em {
446
+ color: #a1a1a1;
447
+ }
448
+
449
+ em,
450
+ i {
451
+ font-style: normal;
452
+ text-align: left;
453
+ font-size: inherit;
454
+ }
455
+
456
+ .m-playbar a {
457
+ background: url(../images/player_small.png) no-repeat 0 9999px;
458
+ }
459
+
460
+ .m-playbar .ctrl {
461
+ position: absolute;
462
+ right: 0px;
463
+ bottom: 48px;
464
+ z-index: 10;
465
+ width: 103px;
466
+ padding-left: 13px;
467
+ float: none;
468
+ }
469
+
470
+ .m-playbar .icn-add {
471
+ background-position: -25px 0px;
472
+ }
473
+
474
+ .m-playbar .icn-add:hover {
475
+ background-position: -25px -25px;
476
+ }
477
+
478
+ .m-playbar .icn-list {
479
+ background-position: -125px 0px;
480
+ }
481
+
482
+ .m-playbar .icn-vol {
483
+ background-position: -175px 0px;
484
+ }
485
+
486
+ .m-playbar .icn-vol-mute {
487
+ background-position: -200px 0px;
488
+ }
489
+
490
+ .m-playbar .icn-list:hover {
491
+ background-position: -125px -25px;
492
+ }
493
+
494
+ .m-playbar .icn-loop {
495
+ background-position: -50px 0px;
496
+ }
497
+
498
+ .m-playbar .icn-loop:hover {
499
+ background-position: -50px -25px;
500
+ }
501
+
502
+ .m-playbar .icn-vol:hover {
503
+ background-position: -175px -25px;
504
+ }
505
+
506
+ .m-playbar .icn-vol-mute:hover {
507
+ background-position: -200px -25px;
508
+ }
509
+
510
+ .m-playbar .icn-shuffle {
511
+ background-position: -150px 0px;
512
+ }
513
+
514
+ .m-playbar .icn-shuffle:hover {
515
+ background-position: -150px -25px;
516
+ }
517
+
518
+ .m-playbar .icn-repeatone {
519
+ background-position: -225px 0px;
520
+ }
521
+
522
+ .m-playbar .icn-repeatone:hover {
523
+ background-position: -225px -25px;
524
+ }
525
+
526
+ .m-playbar .icn {
527
+ float: left;
528
+ width: 25px;
529
+ height: 25px;
530
+ margin: 11px 2px 0 0;
531
+ text-indent: -9999px;
532
+ }
533
+
534
+ .m-playbar .icn-add {
535
+ margin-right: 5px;
536
+ }
537
+
538
+ .m-playbar .menu {
539
+ position: absolute;
540
+ bottom: 90px;
541
+ _bottom: 90px;
542
+ right: 0px;
543
+ _right: 0px;
544
+ height: 349px;
545
+ width: 60%;
546
+ background-color: #121212;
547
+ }
548
+
549
+ .m-playbar .menu ul {
550
+ display: inline-block;
551
+ padding-left: 0px;
552
+ height: 308px;
553
+ overflow-y: scroll;
554
+ margin-bottom: 0px;
555
+ }
556
+
557
+ .m-playbar .menu li {
558
+ float: left;
559
+ width: 100%;
560
+ display: block;
561
+ }
562
+
563
+ .m-playbar .menu .lyric {
564
+ text-align: center;
565
+ width: 39%;
566
+ display: inline-block;
567
+ height: 308px;
568
+ overflow-y: scroll;
569
+ position: relative;
570
+ }
571
+
572
+ .m-playbar .menu .lyric p {
573
+ min-height: 20px;
574
+ }
575
+
576
+ .m-playbar .menu .lyric .placeholder {
577
+ height: 50px;
578
+ }
579
+
580
+ .m-playbar .menu .lyric .highlight {
581
+ font-size: 15px;
582
+ color: #ffffff;
583
+ }
584
+
585
+ .m-playbar .menu .playing {
586
+ background-color: #555555;
587
+ }
588
+
589
+ .m-playbar .menu li:hover,
590
+ .m-playbar .menu li:focus {
591
+ background-color: #999999;
592
+ }
593
+
594
+ .m-playbar .menu .icn-remove {
595
+ height: 20px;
596
+ width: 20px;
597
+ background-position: -75px -25px;
598
+ display: inline-block;
599
+ }
600
+
601
+ .m-playbar .menu .icn-remove:hover {
602
+ background-position: -75px -25px;
603
+ }
604
+
605
+ .volume-ctrl {
606
+ position: absolute;
607
+ right: 0px;
608
+ bottom: 16px;
609
+ width: 110px;
610
+ }
611
+
612
+ li {
613
+ list-style: none;
614
+ }
615
+
616
+ .menu-header {
617
+ height: 40px;
618
+ background-color: #222222;
619
+ padding-top: 4px;
620
+ text-align: center;
621
+ }
622
+
623
+ .menu-header span {
624
+ position: absolute;
625
+ left: 19px;
626
+ top: 7px;
627
+ font-size: 18px;
628
+ color: #ffffff;
629
+ }
630
+
631
+ .menu-header small {
632
+ background-color: #333333;
633
+ color: #ffffff;
634
+ cursor: pointer;
635
+ vertical-align: middle;
636
+ display: inline-block;
637
+ width: 60px;
638
+ line-height: 20px;
639
+ }
640
+
641
+ .menu-header a:hover small {
642
+ background-color: #ffffff;
643
+ color: #333333;
644
+ }
645
+ .menu .add-all {
646
+ display: inline-block;
647
+ position: absolute;
648
+ left: 335px;
649
+ top: 9px;
650
+ }
651
+
652
+ .menu .remove-all {
653
+ display: inline-block;
654
+ position: absolute;
655
+ left: 410px;
656
+ top: 9px;
657
+ }
658
+
659
+ .menu .close-popup {
660
+ float: right;
661
+ margin-right: 14px;
662
+ font-size: 20px;
663
+ text-decoration: none;
664
+ color: #aaaaaa;
665
+ }
666
+
667
+ .menu .close-popup:hover {
668
+ color: #ffffff;
669
+ }
670
+
671
+ .menu .title {
672
+ width: 300px;
673
+ float: left;
674
+ height: 28px;
675
+ padding-top: 3px;
676
+ text-align: left;
677
+ padding-left: 20px;
678
+ overflow: hidden;
679
+ white-space: nowrap;
680
+ text-overflow: ellipsis;
681
+ cursor: pointer;
682
+ }
683
+
684
+ .menu .singer {
685
+ width: 180px;
686
+ float: right;
687
+ height: 28px;
688
+ padding-top: 3px;
689
+ text-align: left;
690
+ padding-left: 20px;
691
+ overflow: hidden;
692
+ white-space: nowrap;
693
+ text-overflow: ellipsis;
694
+ cursor: pointer;
695
+ }
696
+
697
+ .dbimport {
698
+ /*margin-top: 100px;*/
699
+ }
700
+
701
+ .form-signin {
702
+ width: 300px;
703
+ margin-left: auto;
704
+ margin-right: auto;
705
+ text-align: center;
706
+ }
707
+
708
+ .form-signin .form-control,
709
+ .form-signin .valid-img,
710
+ .form-signin .btn {
711
+ margin-top: 10px;
712
+ }
713
+
714
+ .form-signin .valid-img {
715
+ height: 40px;
716
+ width: 220px;
717
+ }
718
+
719
+ .form-signin .security-notice {
720
+ margin-top: 10px;
721
+ }
722
+
723
+ .playlist-detail {
724
+ position: absolute;
725
+ text-align: left;
726
+ background-color: #333;
727
+ width: 100%;
728
+ }
729
+
730
+ .playlist-detail .detail-head {
731
+ width: 200px;
732
+ position: fixed;
733
+ margin-bottom: 20px;
734
+ }
735
+
736
+ .playlist-detail .detail-head-cover {
737
+ height: 180px;
738
+ /* width: 225px;*/
739
+ float: left;
740
+ margin: 10px;
741
+ }
742
+
743
+ .playlist-detail .detail-head-cover img {
744
+ max-width: 100%;
745
+ max-height: 100%;
746
+ }
747
+
748
+ .detail-head-title {
749
+ float: left;
750
+ width: 100%;
751
+ text-align: center;
752
+ }
753
+
754
+ .detail-head-title a {
755
+ display: inline-block;
756
+ text-indent: -9999px;
757
+ width: 36px;
758
+ height: 36px;
759
+ margin-top: 0;
760
+ background: url(../images/player_large.png) no-repeat 0 9999px;
761
+ }
762
+
763
+ .detail-head-title .play {
764
+ background-position: 0px 0px;
765
+ }
766
+
767
+ .detail-head-title .play:hover {
768
+ background-position: 0px -36px;
769
+ }
770
+
771
+ .detail-head-title .add {
772
+ background-position: -216px 0px;
773
+ }
774
+
775
+ .detail-head-title .add:hover {
776
+ background-position: -216px -36px;
777
+ }
778
+
779
+ .detail-head-title .link {
780
+ background-position: -250px 0px;
781
+ }
782
+
783
+ .detail-head-title .link:hover {
784
+ background-position: -250px -36px;
785
+ }
786
+
787
+ .detail-head-title .edit {
788
+ background-position: -288px 0px;
789
+ }
790
+
791
+ .detail-head-title .edit:hover {
792
+ background-position: -288px -36px;
793
+ }
794
+
795
+ .detail-head-title .clone {
796
+ background-position: -144px 0px;
797
+ }
798
+
799
+ .detail-head-title .clone:hover {
800
+ background-position: -144px -36px;
801
+ }
802
+
803
+ .detail-head-title .merge {
804
+ background-position: -324px 0px;
805
+ }
806
+
807
+ .detail-head-title .merge:hover {
808
+ background-position: -324px -36px;
809
+ }
810
+
811
+ .detail-head-title .ply:hover {
812
+ background-position: -40px -204px;
813
+ }
814
+
815
+ .playlist-detail .detail-head-title h2 {
816
+ font-size: 17px;
817
+ margin-bottom: 35px;
818
+ }
819
+
820
+ .playlist-detail .detail-songlist {
821
+ margin-left: 220px;
822
+ margin-top: 6px;
823
+ margin-right: 14px;
824
+ }
825
+
826
+ .playsong-detail .detail-head {
827
+ width: 390px;
828
+ position: fixed;
829
+ margin-bottom: 20px;
830
+ }
831
+
832
+ .playsong-detail .detail-songinfo {
833
+ padding-left: 440px;
834
+ padding-right: 55px;
835
+ }
836
+
837
+ .playsong-detail .detail-head .detail-head-cover {
838
+ margin: 0 auto;
839
+ width: 200px;
840
+ }
841
+
842
+ .playsong-detail .detail-head .detail-head-cover img {
843
+ width: 240px;
844
+ height: 240px;
845
+ }
846
+
847
+ .playsong-detail .detail-songinfo h2 {
848
+ font-size: 22px;
849
+ }
850
+
851
+ .playsong-detail .detail-songinfo .info {
852
+ border-bottom: solid #444 1px;
853
+ margin-bottom: 5px;
854
+ padding-bottom: 10px;
855
+ }
856
+ .playsong-detail .detail-songinfo .info span {
857
+ color: #9b9b9b;
858
+ margin-right: 10px;
859
+ }
860
+ .playsong-detail .detail-songinfo .info span.album {
861
+ margin-left: 30px;
862
+ }
863
+ .playsong-detail .lyric {
864
+ color: #999; /* IE8 proofing */
865
+ color: rgba(255, 255, 255, 0.5);
866
+ text-align: left;
867
+ width: 100%;
868
+ display: inline-block;
869
+ height: 410px;
870
+ overflow-y: scroll;
871
+ position: relative;
872
+ font-size: 15px;
873
+ }
874
+
875
+ .playsong-detail .lyric p {
876
+ min-height: 20px;
877
+ }
878
+
879
+ .playsong-detail .lyric .placeholder {
880
+ height: 50px;
881
+ }
882
+
883
+ .playsong-detail .lyric .highlight {
884
+ color: #ffffff;
885
+ }
886
+
887
+ .detail-songlist {
888
+ padding-left: 0px;
889
+ text-align: left;
890
+ }
891
+
892
+ .detail-songlist li {
893
+ float: left;
894
+ width: 100%;
895
+ display: block;
896
+ padding: 10px;
897
+ }
898
+
899
+ .detail-songlist .col2 {
900
+ float: left;
901
+ width: 28%;
902
+ margin-left: 2%;
903
+ font-size: 15px;
904
+ }
905
+
906
+ .detail-songlist .col1 {
907
+ float: left;
908
+ width: 19%;
909
+ margin-left: 2%;
910
+ }
911
+
912
+ .detail-songlist .disabled {
913
+ color: #777777;
914
+ }
915
+
916
+ .detail-songlist .col-add {
917
+ float: left;
918
+ width: 75px;
919
+ margin-left: 2%;
920
+ }
921
+
922
+ .detail-songlist .detail-tools {
923
+ float: right;
924
+ height: 21px;
925
+ position: relative;
926
+ width: 118px;
927
+ }
928
+
929
+ .detail-songlist .detail-tools a {
930
+ background: url(../images/player_small.png) no-repeat 0 9999px;
931
+ height: 25px;
932
+ width: 25px;
933
+ cursor: pointer;
934
+ }
935
+
936
+ .detail-songlist .detail-tools .detail-add-button {
937
+ background-position: 0px 0px;
938
+ }
939
+
940
+ .detail-songlist .detail-tools .detail-fav-button {
941
+ background-position: -25px 0px;
942
+ }
943
+
944
+ .detail-songlist .detail-tools .detail-delete-button {
945
+ background-position: -75px 0px;
946
+ }
947
+
948
+ .detail-songlist .detail-tools .source-button {
949
+ background-position: -100px 0px;
950
+ }
951
+
952
+ .detail-songlist .detail-tools .detail-add-button:hover {
953
+ background-position: 0px -25px;
954
+ }
955
+
956
+ .detail-songlist .detail-tools .detail-fav-button:hover {
957
+ background-position: -25px -25px;
958
+ }
959
+
960
+ .detail-songlist .detail-tools .detail-delete-button:hover {
961
+ background-position: -75px -25px;
962
+ }
963
+
964
+ .detail-songlist .detail-tools .source-button:hover {
965
+ background-position: -100px -25px;
966
+ }
967
+
968
+ .detail-songlist .detail-tools a {
969
+ text-decoration: none;
970
+ display: inline-block;
971
+ }
972
+
973
+ .detail-songlist .detail-artist a {
974
+ color: #777777;
975
+ }
976
+
977
+ .detail-songlist .odd {
978
+ background-color: #333;
979
+ }
980
+
981
+ .detail-songlist .even,
982
+ .detail-songlist .detail-add {
983
+ background-color: #2d2d2d;
984
+ }
985
+
986
+ .dialog .detail-songlist li:hover {
987
+ background-color: #999999;
988
+ cursor: pointer;
989
+ }
990
+
991
+ /*.playlist-detail .detail-songlist li:hover {
992
+ background-color: #999999;
993
+ }*/
994
+
995
+ .playlist-detail .btn {
996
+ width: 88px;
997
+ margin-top: 0;
998
+ float: left;
999
+ }
1000
+
1001
+ .cover-container .detail-close {
1002
+ position: absolute;
1003
+ right: -32px;
1004
+ top: 0px;
1005
+ }
1006
+
1007
+ .cover-container .detail-close span {
1008
+ font-size: 34px;
1009
+ cursor: pointer;
1010
+ color: #aaaaaa;
1011
+ }
1012
+
1013
+ .cover-container .detail-close span:hover {
1014
+ color: #ffffff;
1015
+ }
1016
+
1017
+ .dialog-playlist {
1018
+ padding-left: 0px;
1019
+ text-align: left;
1020
+ }
1021
+
1022
+ .dialog-playlist li {
1023
+ cursor: pointer;
1024
+ height: 112px;
1025
+ padding: 6px;
1026
+ }
1027
+
1028
+ .dialog-playlist li:hover {
1029
+ background-color: #555555;
1030
+ }
1031
+
1032
+ .dialog-playlist li img {
1033
+ float: left;
1034
+ height: 100px;
1035
+ width: 100px;
1036
+ }
1037
+
1038
+ .dialog-playlist li h2 {
1039
+ margin-left: 125px;
1040
+ font-size: 17px;
1041
+ }
1042
+
1043
+ .dialog-backuplist {
1044
+ padding-left: 0px;
1045
+ text-align: left;
1046
+ }
1047
+
1048
+ .dialog-backuplist li {
1049
+ cursor: pointer;
1050
+ height: 112px;
1051
+ padding: 6px;
1052
+ }
1053
+
1054
+ .dialog-backuplist li:hover {
1055
+ background-color: #555555;
1056
+ }
1057
+
1058
+ .dialog-backuplist li img {
1059
+ float: left;
1060
+ height: 100px;
1061
+ width: 100px;
1062
+ }
1063
+
1064
+ .dialog-backuplist li h2 {
1065
+ margin-top: 10px;
1066
+ margin-left: 125px;
1067
+ font-size: 15px;
1068
+ }
1069
+
1070
+ .dialog-merge-playlist {
1071
+ padding-left: 0px;
1072
+ text-align: left;
1073
+ }
1074
+
1075
+ .dialog-merge-playlist li {
1076
+ cursor: pointer;
1077
+ height: 112px;
1078
+ padding: 6px;
1079
+ }
1080
+
1081
+ .dialog-merge-playlist li:hover {
1082
+ background-color: #555555;
1083
+ }
1084
+
1085
+ .dialog-merge-playlist li img {
1086
+ float: left;
1087
+ height: 100px;
1088
+ width: 100px;
1089
+ }
1090
+
1091
+ .dialog-merge-playlist li h2 {
1092
+ margin-left: 125px;
1093
+ font-size: 17px;
1094
+ }
1095
+
1096
+ .dialog-newplaylist input {
1097
+ margin-bottom: 22px;
1098
+ }
1099
+
1100
+ .dialog-newplaylist .confirm-button {
1101
+ margin-left: 76px;
1102
+ margin-right: 96px;
1103
+ }
1104
+
1105
+ .dialog-newbackup {
1106
+ text-align: center;
1107
+ }
1108
+
1109
+ .dialog-newbackup .confirm-button {
1110
+ margin-right: 12px;
1111
+ }
1112
+
1113
+ .dialog-editplaylist .dialog-footer {
1114
+ position: absolute;
1115
+ bottom: 20px;
1116
+ }
1117
+
1118
+ .dialog-editplaylist .confirm-button,
1119
+ .dialog-open-url .confirm-button {
1120
+ margin-right: 82px;
1121
+ margin-left: 93px;
1122
+ }
1123
+
1124
+ .dialog-connect-lastfm .buttons {
1125
+ margin-top: 30px;
1126
+ }
1127
+
1128
+ .dialog-connect-lastfm .confirm-button {
1129
+ margin-left: 40px;
1130
+ margin-right: 48px;
1131
+ }
1132
+
1133
+ .source-list {
1134
+ position: absolute;
1135
+ right: -32px;
1136
+ top: 0px;
1137
+ z-index: 10;
1138
+ text-align: center;
1139
+ }
1140
+
1141
+ .source-list div {
1142
+ background-color: #333333;
1143
+ color: #ffffff;
1144
+ height: 35px;
1145
+ border: 1px solid #ffffff;
1146
+ width: 75px;
1147
+ cursor: pointer;
1148
+ vertical-align: middle;
1149
+ padding-top: 6px;
1150
+ }
1151
+
1152
+ .source-list div:first-child:not(:last-child) {
1153
+ border-top-left-radius: 4px;
1154
+ border-top-right-radius: 4px;
1155
+ border-bottom-right-radius: 0;
1156
+ border-bottom-left-radius: 0;
1157
+ }
1158
+
1159
+ .source-list div:last-child:not(:first-child) {
1160
+ border-top-left-radius: 0;
1161
+ border-top-right-radius: 0;
1162
+ border-bottom-right-radius: 4px;
1163
+ border-bottom-left-radius: 4px;
1164
+ }
1165
+
1166
+ .source-list .active {
1167
+ background-color: #e6e6e6;
1168
+ color: #333333;
1169
+ }
1170
+
1171
+ .source-list div:hover {
1172
+ background-color: #ffffff;
1173
+ color: #333333;
1174
+ }
1175
+
1176
+ .source-list .open-url-button {
1177
+ border-radius: 4px;
1178
+ }
1179
+
1180
+ .settings-title {
1181
+ font-size: 20px;
1182
+ padding: 20px;
1183
+ border-bottom: 2px solid #aaaaaa;
1184
+ }
1185
+
1186
+ .settings-content {
1187
+ padding: 20px;
1188
+ }
1189
+
1190
+ .settings-content .btn {
1191
+ margin-right: 10px;
1192
+ }
1193
+
1194
+ .btn-group button,
1195
+ .btn-pagination,
1196
+ .btn-pagination:focus {
1197
+ background-color: #333333;
1198
+ color: #ffffff;
1199
+ border-color: #333333;
1200
+ }
1201
+
1202
+ .btn-group button:hover,
1203
+ .btn-pagination:hover {
1204
+ background-color: #ffffff;
1205
+ color: #333333;
1206
+ }
1207
+
1208
+ .searchbox li > a:hover {
1209
+ color: #333333;
1210
+ }
1211
+
1212
+ .search-pagination {
1213
+ text-align: center;
1214
+ display: block;
1215
+ vertical-align: middle;
1216
+ line-height: 45px;
1217
+ }
1218
+
1219
+ .search-pagination button:focus {
1220
+ outline: 0;
1221
+ }
1222
+
1223
+ .search-pagination label {
1224
+ margin: 0 15px;
1225
+ }
css/reset.css ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html,
2
+ body,
3
+ span,
4
+ applet,
5
+ object,
6
+ iframe,
7
+ h1,
8
+ h2,
9
+ h3,
10
+ h4,
11
+ h5,
12
+ h6,
13
+ p,
14
+ blockquote,
15
+ pre,
16
+ a,
17
+ abbr,
18
+ acronym,
19
+ address,
20
+ big,
21
+ cite,
22
+ code,
23
+ del,
24
+ dfn,
25
+ em,
26
+ font,
27
+ ins,
28
+ kbd,
29
+ q,
30
+ s,
31
+ samp,
32
+ small,
33
+ strike,
34
+ strong,
35
+ sub,
36
+ sup,
37
+ tt,
38
+ var,
39
+ dl,
40
+ dt,
41
+ dd,
42
+ ol,
43
+ ul,
44
+ li,
45
+ fieldset,
46
+ form,
47
+ label,
48
+ legend {
49
+ margin: 0;
50
+ padding: 0;
51
+ border: 0;
52
+ outline: 0;
53
+ font-weight: normal;
54
+ font-style: normal;
55
+ font-size: 100%;
56
+ vertical-align: baseline;
57
+ }
58
+
59
+ :focus {
60
+ outline: 0;
61
+ }
62
+
63
+ body {
64
+ line-height: 1.2;
65
+ color: black;
66
+ background: white;
67
+ }
68
+
69
+ h1,
70
+ h2,
71
+ h3,
72
+ h4,
73
+ h5,
74
+ h6 {
75
+ font-size: 100%;
76
+ font-weight: normal;
77
+ }
78
+
79
+ ol,
80
+ ul {
81
+ list-style: none;
82
+ }
83
+
84
+ /* tables still need 'cellspacing="0"' in the markup */
85
+ table {
86
+ border-collapse: separate;
87
+ border-spacing: 0;
88
+ }
89
+
90
+ caption,
91
+ th,
92
+ td {
93
+ text-align: left;
94
+ font-weight: normal;
95
+ }
96
+
97
+ blockquote:before,
98
+ blockquote:after,
99
+ q:before,
100
+ q:after {
101
+ content: '';
102
+ }
103
+
104
+ blockquote,
105
+ q {
106
+ quotes: '' '';
107
+ }
108
+
109
+ img {
110
+ border: none;
111
+ }
fonts/listen1-icon.eot ADDED
Binary file (3.64 kB). View file
 
fonts/listen1-icon.svg ADDED
fonts/listen1-icon.ttf ADDED
Binary file (3.46 kB). View file
 
fonts/listen1-icon.woff ADDED
Binary file (3.54 kB). View file
 
i18n/en-US.json ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "HELLO" : "hi",
3
+ "_ALL_MUSIC": "All Music",
4
+ "_NETEASE_MUSIC": "Netease",
5
+ "_QQ_MUSIC": "QQ",
6
+ "_XIAMI_MUSIC": "Xiami",
7
+ "_KUGOU_MUSIC": "Kugou",
8
+ "_KUWO_MUSIC": "Kuwo",
9
+ "_BILIBILI_MUSIC": "Bilibili",
10
+ "_MIGU_MUSIC": "Migu",
11
+ "_TAIHE_MUSIC": "Qianqian",
12
+ "_PLATFORM_UNION": "Platforms",
13
+ "_PLAYLISTS": "PLAYLISTS",
14
+ "_MY_MUSIC": "My Music",
15
+ "_CREATED_PLAYLIST": "My Playlist",
16
+ "_FAVORITED_PLAYLIST": "Favorite Playlist",
17
+ "_REFRESH_PLAYLIST": "Refresh Playlist",
18
+ "_FAVORITED": "Favorited",
19
+ "_FAVORITE": "Favorite",
20
+ "_PLAY_ALL": "Play All",
21
+ "_ADD_TO_PLAYLIST": "Add to Playlist",
22
+ "_EDIT": "Edit",
23
+ "_IMPORT": "Import",
24
+ "_IMPORT_PLAYLIST": "Import Playlist",
25
+ "_ORIGIN_LINK": "Origin link",
26
+ "_SONGS": "Songs",
27
+ "_ARTISTS": "Artists",
28
+ "_ALBUMS": "Albums",
29
+ "_OPERATION": "Tools",
30
+ "_CREATE_PLAYLIST": "Create Playlist",
31
+ "_CANCEL": "Cancel",
32
+ "_CREATE_AND_ADD_PLAYLIST": "Create and Add",
33
+ "_REMOVE_FROM_PLAYLIST": "Remove",
34
+ "_ADD_TO_QUEUE": "Add to Queue",
35
+ "_ARTIST": "Artist",
36
+ "_ALBUM": "Album",
37
+ "_PLAYLIST_TITLE": "Playlist Title",
38
+ "_PLAYLIST_AUTHOR": "Playlist Author",
39
+ "_PLAYLIST_SONG_COUNT": "Playlist Song Count",
40
+ "_PLAYLIST_COVER_IMAGE_URL": "Cover Image URL",
41
+ "_INPUT_PLAYLIST_TITLE": "Input Playlist Title",
42
+ "_INPUT_PLAYLIST_COVER_IMAGE_URL": "Input Cover Image URL",
43
+ "_EDIT_PLAYLIST": "Edit Playlist",
44
+ "_REMOVE_PLAYLIST": "Remove Playlist",
45
+ "_OPENING_LASTFM_PAGE":"Opening Last.fm页面...",
46
+ "_CONFIRM_NOTICE_LASTFM": "Please click \"Yes, all access\" in new page, allow Listen 1 access your account.",
47
+ "_AUTHORIZED_FINISHED": "Authorized Finished",
48
+ "_AUTHORIZED_REOPEN": "I Have Problem, try again",
49
+ "_PLAYLIST_LINK": "Playlist Link",
50
+ "_OPEN_PLAYLIST": "Open Playlist",
51
+ "_OPENING_GITHUB_PAGE": "Opening Github.com页面...",
52
+ "_CONFIRM_NOTICE_GITHUB": "Please click \"Authencate\" in new page, allow Listen 1 access your account",
53
+ "_CREATE_PLAYLIST_BACKUP": "Create Playlist Backup",
54
+ "_CREATE_PUBLIC_BACKUP": "Create Public Backup",
55
+ "_CREATE_PRIVATE_BACKUP": "Create Private Backup",
56
+ "_BACKUP_PLAYLIST": "Backup Playlists",
57
+ "_BACKUP_WARNING": "Reinstall or clear cache will lost all your playlists, backup is STORNG RECOMMENDED.",
58
+ "_EXPORT_TO_LOCAL_FILE": "Export to Local File",
59
+ "_EXPORT_TO_GITHUB_GIST": "Export to Github Gist",
60
+ "_RECOVER_PLAYLIST": "Recover Playlists",
61
+ "_RECOVER_WARNING": "Choose Backup File. Notice: It will overwrite current playlists.",
62
+ "_RECOVER_FROM_LOCAL_FILE": "Recover from Local File",
63
+ "_RECOVER_FROM_GITHUB_GIST": "Recover from Github Gist",
64
+ "_CONNECT_TO_GITHUB": "Connect to Github.com",
65
+ "_STATUS": "Status",
66
+ "_RECONNECT": "Reconnect",
67
+ "_CANCEL_CONNECT": "Cancel Connect",
68
+ "_SHORTCUTS": "Shortcuts",
69
+ "_VIEW_SHORTCUTS_LIST": "View Shortcuts List",
70
+ "_GLOBAL_SHORTCUTS_NOTICE": "Enable Global Shortcuts",
71
+ "_LYRIC_DISPLAY": "Lyric Display",
72
+ "_SHOW_DESKTOP_LYRIC": "Show desktop lyric",
73
+ "_SHOW_LYRIC_TRANSLATION": "Show lyric translation",
74
+ "_SHOW_DESKTOP_LYRIC_TRANSLATION": "Show lyric translation with desktop lyric",
75
+ "_CONNECT_TO_LASTFM": "Connect to Last.fm",
76
+ "_ABOUT": "About",
77
+ "_HOMEPAGE": "Homepage",
78
+ "_EMAIL": "Email",
79
+ "_FEEDBACK": "Feedback",
80
+ "_DESIGNER": "Designer",
81
+ "_VERSION": "Version",
82
+ "_LATEST_VERSION": "Latest Version",
83
+ "_LICENSE_NOTICE": "(This software is free and open source under MIT license)",
84
+ "_TOTAL_SONG_PREFIX": "Total ",
85
+ "_TOTAL_SONG_POSTFIX": " Songs",
86
+ "_CLEAR_ALL": "Clear All",
87
+ "_SEARCH_PLACEHOLDER": "Search for song, artist or album",
88
+ "_LANGUAGE": "Language",
89
+ "_ADD_TO_PLAYLIST_SUCCESS": "Success: Add to My Playlist",
90
+ "_FAVORITE_PLAYLIST_SUCCESS": "Success: Favorite Playlist",
91
+ "_EDIT_PLAYLIST_SUCCESS": "Success: Edit Playlist",
92
+ "_IMPORTING_PLAYLIST": "Importing playlists...",
93
+ "_IMPORTING_PLAYLIST_SUCCESS": "Success: Import Playlists",
94
+ "_REMOVE_PLAYLIST_SUCCESS": "Success: Remove Playlist",
95
+ "_UNFAVORITE_PLAYLIST_SUCCESS": "Success: UnFavorite Playlist",
96
+ "_REMOVE_SONG_FROM_PLAYLIST_SUCCESS": "Success: Remove Song from Playlist",
97
+ "_ADD_TO_QUEUE_SUCCESS": "Success: Add to Queue",
98
+ "_COPYRIGHT_ISSUE": "Fail because copyright issue, Search other platform instead",
99
+ "_INPUT_NEW_PLAYLIST_TITLE": "Input New Playlist Title",
100
+ "_FAIL_OPEN_PLAYLIST_URL": "Fail to Open Playlist url",
101
+ "_EXAMPLE": "Example:",
102
+ "_CONFIRM": "Confirm",
103
+ "_THEME": "Theme",
104
+ "_THEME_WHITE": "White Theme",
105
+ "_THEME_BLACK": "Black Theme",
106
+ "_AUTO_CHOOSE_SOURCE": "Auto Choose Source",
107
+ "_AUTO_CHOOSE_SOURCE_NOTICE": "Enable choose source from other music platform after fail.",
108
+ "_AUTO_CHOOSE_SOURCE_LIST": "Music Platform to try after fail",
109
+ "_NOWPLAYING_DISPLAY": "Now Playing Display",
110
+ "_NOWPLAYING_COVER_BACKGROUND_NOTICE": "Show Cover Image for Now Playing",
111
+ "_NOWPLAYING_BITRATE_NOTICE": "Show Bitrate",
112
+ "_NOWPLAYING_PLATFORM_NOTICE": "Show Music Platform",
113
+ "_LOCAL_MUSIC": "Local Music",
114
+ "_ADD_LOCAL_SONGS": "Add Local Songs",
115
+ "netease": "netease",
116
+ "bilibili": "bili",
117
+ "kugou": "kugou",
118
+ "kuwo": "kuwo",
119
+ "migu": "migu",
120
+ "qq": "qq",
121
+ "xiami": "xiami",
122
+ "taihe": "qianqian",
123
+ "localmusic": "local music",
124
+ "_CLOSE_TAB_ACTION": "Close tab action",
125
+ "_VALID_AFTER_RESTART": "Valid after restart",
126
+ "_QUIT_APPLICATION": "Quit Application",
127
+ "_MINIMIZE_TO_BACKGROUND": "Minimize to background",
128
+ "_MY_CREATED_PLAYLIST": "My Created Playlist",
129
+ "_MY_FAVORITE_PLAYLIST": "My Favorite Playlist",
130
+ "_RECOMMEND_PLAYLIST": "Recommend Playlist",
131
+ "_LISTEN1_LOGIN_NOTICE": "Listen1 WILL NOT transfer your account data to any server other than music platform server.",
132
+ "_PASSWORD": "Password",
133
+ "_LOGIN": "Login",
134
+ "_LOGOUT": "Logout",
135
+ "_LOGIN_ERROR": "Login error, please check user name and password",
136
+ "_LOGIN_EMAIL_ERROR": "Please enter correct email address",
137
+ "_LOGIN_COUNTRYCODE_ERROR": "Please enter correct country code",
138
+ "_LOGIN_PHONE_ERROR": "Please enter correct phone number",
139
+ "_LOGIN_PASSWORD_ERROR": "Password can't be empty",
140
+ "_LOGIN_NETEASE": "Login Netease",
141
+ "_LOGIN_BY_MOBILE_PHONE": "Login by mobile phone",
142
+ "_LOGIN_BY_EMAIL": "Login by email",
143
+ "_MOBILE_PHONE": "Mobile Phone",
144
+ "_MY_NETEASE": "My Netease",
145
+ "_MY_QQ": "My QQ Music",
146
+ "_FAIL_ALL_NOTICE": "No available track in current playlist",
147
+ "_SHORTCUTS_FUNCTION": "Shortcuts Function",
148
+ "_GLOBAL_SHORTCUTS": "Global Shortcuts",
149
+ "_PLAY_OR_PAUSE": "Play/Pause",
150
+ "_PREVIOUS_TRACK": "Previous Track",
151
+ "_NEXT_TRACK": "Next Track",
152
+ "_VOLUME_UP": "Volume Up",
153
+ "_VOLUME_DOWN": "Volume Down",
154
+ "_SHORTCUTS_NOT_SET": "N/A",
155
+ "_QUICK_SEARCH": "Quick Search",
156
+ "_SEARCH_PLAYLIST": "Search Playlist",
157
+ "_KEYBOARD_SPACE": "Space",
158
+ "_NOT_LOGIN_NICKNAME": "Anonymous",
159
+ "_LOGIN_DIALOG_NOTICE": "Opening Login page, please login in that page and click finish login",
160
+ "_LOGIN_SUCCESS": "Finish Login",
161
+ "_LOGIN_FAIL_RETRY": "Something wrong. Reopen Login page",
162
+ "_PROXY_SYSTEM": "Use System Proxy",
163
+ "_PROXY_DIRECT": "No Proxy",
164
+ "_PROXY_CUSTOM": "Custom Proxy",
165
+ "_PROXY_CONFIG": "Proxy Config",
166
+ "_PROXY_NOT_SET": "Proxy Not Set",
167
+ "_MODIFY": "Modify",
168
+ "_PROTOCOL": "Protocol",
169
+ "_HOST": "Host",
170
+ "_PORT": "Port",
171
+ "ZOOM_IN_OUT": "Zoom In/Out"
172
+ }
i18n/fr-FR.json ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "HELLO" : "Bonjour",
3
+ "_ALL_MUSIC": "All Music",
4
+ "_NETEASE_MUSIC": "Netease",
5
+ "_QQ_MUSIC": "QQ",
6
+ "_XIAMI_MUSIC": "Xiami",
7
+ "_KUGOU_MUSIC": "Kugou",
8
+ "_KUWO_MUSIC": "Kuwo",
9
+ "_BILIBILI_MUSIC": "Bilibili",
10
+ "_MIGU_MUSIC": "Migu",
11
+ "_TAIHE_MUSIC": "Qianqian",
12
+ "_PLATFORM_UNION": "Platforms",
13
+ "_PLAYLISTS": "PLAYLISTS",
14
+ "_MY_MUSIC": "Ma Musique",
15
+ "_CREATED_PLAYLIST": "Ma Playlist",
16
+ "_FAVORITED_PLAYLIST": "Favorite Playlist",
17
+ "_REFRESH_PLAYLIST": "Refresh Playlist",
18
+ "_FAVORITED": "Favorited",
19
+ "_FAVORITE": "Favorite",
20
+ "_PLAY_ALL": "Lire tout",
21
+ "_ADD_TO_PLAYLIST": "Ajouter à la Playlist",
22
+ "_EDIT": "Editer",
23
+ "_IMPORT": "Importer",
24
+ "_IMPORT_PLAYLIST": "Importer la Playlist",
25
+ "_ORIGIN_LINK": "Lien original",
26
+ "_SONGS": "Musiques",
27
+ "_ARTISTS": "Artistes",
28
+ "_ALBUMS": "Albums",
29
+ "_OPERATION": "Outils",
30
+ "_CREATE_PLAYLIST": "Créer une Playlist",
31
+ "_CANCEL": "Annuler",
32
+ "_CREATE_AND_ADD_PLAYLIST": "Créer et ajouter",
33
+ "_REMOVE_FROM_PLAYLIST": "Retirer",
34
+ "_ADD_TO_QUEUE": "Ajouter à la liste de lecture",
35
+ "_ARTIST": "Artiste",
36
+ "_ALBUM": "Album",
37
+ "_PLAYLIST_TITLE": "Titre de la Playlist",
38
+ "_PLAYLIST_AUTHOR": "Playlist Author",
39
+ "_PLAYLIST_SONG_COUNT": "Playlist Song Count",
40
+ "_PLAYLIST_COVER_IMAGE_URL": "URL de la pochette",
41
+ "_INPUT_PLAYLIST_TITLE": "Inserez le titre de la playlist",
42
+ "_INPUT_PLAYLIST_COVER_IMAGE_URL": "Entrez l'URL de la pochette",
43
+ "_EDIT_PLAYLIST": "Editer la playlist",
44
+ "_REMOVE_PLAYLIST": "Supprimer la Playlist",
45
+ "_OPENING_LASTFM_PAGE":"Ouverture de Last.fm页面...",
46
+ "_CONFIRM_NOTICE_LASTFM": "Cliquez sur \"Oui, tout autoriser\" dans la nouvelle page, autoriser l'accès à votre compte.",
47
+ "_AUTHORIZED_FINISHED": "Autorisation finie",
48
+ "_AUTHORIZED_REOPEN": "J'ai un problème, veuillez réessayer",
49
+ "_PLAYLIST_LINK": "Lien de la playslit",
50
+ "_OPEN_PLAYLIST": "Ouvrir la laylist",
51
+ "_OPENING_GITHUB_PAGE": "Ouverture de Github.com页面...",
52
+ "_CONFIRM_NOTICE_GITHUB": "Cliquez sur \"S'identifier\" dans la nouvelle page, autoriser l'accès à votre compte.",
53
+ "_CREATE_PLAYLIST_BACKUP": "Créer une sauvegarde de playslist",
54
+ "_CREATE_PUBLIC_BACKUP": "Créer une sauvegarde public de playslist",
55
+ "_CREATE_PRIVATE_BACKUP": "Créer une sauvegarde privée de playslist",
56
+ "_BACKUP_PLAYLIST": "Sauvegardes de playslite",
57
+ "_BACKUP_WARNING": "Réinstallez ou effacez votre cache vous fera perdre toutes vos playlists, les sauvegardes sont recommandés.",
58
+ "_EXPORT_TO_LOCAL_FILE": "Exporter vers un fichier local",
59
+ "_EXPORT_TO_GITHUB_GIST": "Exporter vers un Github Gist",
60
+ "_RECOVER_PLAYLIST": "Récuperer une playlist",
61
+ "_RECOVER_WARNING": "Séléctionner un fichier de sauvegarde. Notice: Elle remplacera la playliste actuelle.",
62
+ "_RECOVER_FROM_LOCAL_FILE": "Récuperer par un fichier local",
63
+ "_RECOVER_FROM_GITHUB_GIST": "Récuperer par un Github Gist",
64
+ "_CONNECT_TO_GITHUB": "Connecter à Github.com",
65
+ "_STATUS": "Statut",
66
+ "_RECONNECT": "Reconnecter",
67
+ "_CANCEL_CONNECT": "Annuler la connexion",
68
+ "_SHORTCUTS": "Raccourcis",
69
+ "_VIEW_SHORTCUTS_LIST": "Voir la liste des raccourcis",
70
+ "_GLOBAL_SHORTCUTS_NOTICE": "Activer les raccourcis généraux",
71
+ "_LYRIC_DISPLAY": "Affichage des parole",
72
+ "_SHOW_DESKTOP_LYRIC": "Afficher les parole du bureau",
73
+ "_SHOW_LYRIC_TRANSLATION": "Affiche la traduction des paroles",
74
+ "_SHOW_DESKTOP_LYRIC_TRANSLATION": "Afficher les traductions de paroles du bureau",
75
+ "_CONNECT_TO_LASTFM": "Ouverture de Last.fm",
76
+ "_ABOUT": "A Propos",
77
+ "_HOMEPAGE": "Accueil",
78
+ "_EMAIL": "Email",
79
+ "_FEEDBACK": "Feedback",
80
+ "_DESIGNER": "Designer",
81
+ "_VERSION": "Version",
82
+ "_LATEST_VERSION": "Latest Version",
83
+ "_LICENSE_NOTICE": "(Ce programme est gratuit et tout license MIT)",
84
+ "_TOTAL_SONG_PREFIX": "Total ",
85
+ "_TOTAL_SONG_POSTFIX": " Morceaux",
86
+ "_CLEAR_ALL": "Tout effacer",
87
+ "_SEARCH_PLACEHOLDER": "Rechercher un morceau, un artiste ou un album",
88
+ "_LANGUAGE": "Langage",
89
+ "_ADD_TO_PLAYLIST_SUCCESS": "Succès: Ajouter à la Playlist",
90
+ "_FAVORITE_PLAYLIST_SUCCESS": "Success: Favorite Playlist",
91
+ "_EDIT_PLAYLIST_SUCCESS": "Succès: Editer la Playlist",
92
+ "_IMPORTING_PLAYLIST": "Importation des playlists...",
93
+ "_IMPORTING_PLAYLIST_SUCCESS": "Succès: Importer les Playlists",
94
+ "_REMOVE_PLAYLIST_SUCCESS": "Succès: Effacer Playlist",
95
+ "_UNFAVORITE_PLAYLIST_SUCCESS": "Succès: UnFavorite Playlist",
96
+ "_REMOVE_SONG_FROM_PLAYLIST_SUCCESS": "Succès: Retirer la morceau de la Playlist",
97
+ "_ADD_TO_QUEUE_SUCCESS": "Succès: Ajouter à la file de lecture",
98
+ "_COPYRIGHT_ISSUE": "Errur due au copyright, Cherchez sur une autre plateforme",
99
+ "_INPUT_NEW_PLAYLIST_TITLE": "Entrez un nouveau titre de Playlist",
100
+ "_FAIL_OPEN_PLAYLIST_URL": "Erreur ouverture de l'url",
101
+ "_EXAMPLE": "Exemple:",
102
+ "_CONFIRM": "Confirmer",
103
+ "_THEME": "Thème",
104
+ "_THEME_WHITE": "Thème blanc",
105
+ "_THEME_BLACK": "Thème foncé",
106
+ "_AUTO_CHOOSE_SOURCE": "Auto Choose Source",
107
+ "_AUTO_CHOOSE_SOURCE_NOTICE": "If play fail, auto choose source from other music platform.",
108
+ "_AUTO_CHOOSE_SOURCE_LIST": "Music Platform to try after fail",
109
+ "_NOWPLAYING_DISPLAY": "Now Playing Display",
110
+ "_NOWPLAYING_COVER_BACKGROUND_NOTICE": "Show Cover Image for Now Playing",
111
+ "_NOWPLAYING_BITRATE_NOTICE": "Show Bitrate",
112
+ "_NOWPLAYING_PLATFORM_NOTICE": "Show Music Platform",
113
+ "_LOCAL_MUSIC": "Local Music",
114
+ "_ADD_LOCAL_SONGS": "Add Local Songs",
115
+ "netease": "netease",
116
+ "bilibili": "bili",
117
+ "kugou": "kugou",
118
+ "kuwo": "kuwo",
119
+ "migu": "migu",
120
+ "qq": "qq",
121
+ "xiami": "xiami",
122
+ "taihe": "qianqian",
123
+ "localmusic": "local music",
124
+ "_CLOSE_TAB_ACTION": "Close tab action",
125
+ "_VALID_AFTER_RESTART": "Valid after restart",
126
+ "_QUIT_APPLICATION": "Quit Application",
127
+ "_MINIMIZE_TO_BACKGROUND": "Minimize to background",
128
+ "_MY_CREATED_PLAYLIST": "My Created Playlist",
129
+ "_MY_FAVORITE_PLAYLIST": "My Favorite Playlist",
130
+ "_RECOMMEND_PLAYLIST": "Recommend Playlist",
131
+ "_LISTEN1_LOGIN_NOTICE": "Listen1 NE transférera PAS les données de votre compte vers un serveur autre que le serveur de la plate-forme musicale.",
132
+ "_PASSWORD": "Password",
133
+ "_LOGIN": "Login",
134
+ "_LOGOUT": "Logout",
135
+ "_LOGIN_ERROR": "Login error, please check user name and password",
136
+ "_LOGIN_EMAIL_ERROR": "Please enter correct email address",
137
+ "_LOGIN_COUNTRYCODE_ERROR": "Please enter correct country code",
138
+ "_LOGIN_PHONE_ERROR": "Please enter correct phone number",
139
+ "_LOGIN_PASSWORD_ERROR": "Password can't be empty",
140
+ "_LOGIN_NETEASE": "Login Netease",
141
+ "_LOGIN_BY_MOBILE_PHONE": "Login by mobile phone",
142
+ "_LOGIN_BY_EMAIL": "Login by email",
143
+ "_MOBILE_PHONE": "Mobile Phone",
144
+ "_MY_NETEASE": "My Netease",
145
+ "_MY_QQ": "My QQ Music",
146
+ "_FAIL_ALL_NOTICE": "No available track in current playlist",
147
+ "_SHORTCUTS_FUNCTION": "Shortcuts Function",
148
+ "_GLOBAL_SHORTCUTS": "Global Raccourcis",
149
+ "_PLAY_OR_PAUSE": "Play/Pause",
150
+ "_PREVIOUS_TRACK": "Previous Track",
151
+ "_NEXT_TRACK": "Next Track",
152
+ "_VOLUME_UP": "Volume Up",
153
+ "_VOLUME_DOWN": "Volume Down",
154
+ "_SHORTCUTS_NOT_SET": "N/A",
155
+ "_QUICK_SEARCH": "Quick Search",
156
+ "_SEARCH_PLAYLIST": "Search Playlist",
157
+ "_KEYBOARD_SPACE": "Space",
158
+ "_NOT_LOGIN_NICKNAME": "Anonymous",
159
+ "_LOGIN_DIALOG_NOTICE": "Opening Login page, please login in that page and click finish login",
160
+ "_LOGIN_SUCCESS": "Finish Login",
161
+ "_LOGIN_FAIL_RETRY": "Something wrong. Reopen Login page",
162
+ "_PROXY_SYSTEM": "Use System Proxy",
163
+ "_PROXY_DIRECT": "No Proxy",
164
+ "_PROXY_CUSTOM": "Custom Proxy",
165
+ "_PROXY_CONFIG": "Proxy Config",
166
+ "_PROXY_NOT_SET": "Proxy Not Set",
167
+ "_MODIFY": "Modify",
168
+ "_PROTOCOL": "Protocol",
169
+ "_HOST": "Host",
170
+ "_PORT": "Port",
171
+ "ZOOM_IN_OUT": "Zoom In/Out"
172
+ }
i18n/zh-CN.json ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "HELLO" : "你好",
3
+ "_ALL_MUSIC": "所有音乐",
4
+ "_NETEASE_MUSIC": "网易云音乐",
5
+ "_QQ_MUSIC": "QQ音乐",
6
+ "_XIAMI_MUSIC": "虾米音乐",
7
+ "_KUGOU_MUSIC": "酷狗音乐",
8
+ "_KUWO_MUSIC": "酷我音乐",
9
+ "_BILIBILI_MUSIC": "哔哩哔哩",
10
+ "_MIGU_MUSIC": "咪咕音乐",
11
+ "_TAIHE_MUSIC": "千千音乐",
12
+ "_PLATFORM_UNION": "平台聚合",
13
+ "_PLAYLISTS": "精选歌单",
14
+ "_MY_MUSIC": "我的音乐",
15
+ "_CREATED_PLAYLIST": "创建的歌单",
16
+ "_FAVORITED_PLAYLIST": "收藏的歌单",
17
+ "_REFRESH_PLAYLIST": "刷新",
18
+ "_FAVORITED": "已收藏",
19
+ "_FAVORITE": "收藏",
20
+ "_PLAY_ALL": "播放全部",
21
+ "_ADD_TO_PLAYLIST": "添加到我的歌单",
22
+ "_EDIT": "编辑",
23
+ "_IMPORT": "导入",
24
+ "_IMPORT_PLAYLIST": "导入歌单",
25
+ "_ORIGIN_LINK": "链接",
26
+ "_SONGS": "歌曲名",
27
+ "_ARTISTS": "歌手",
28
+ "_ALBUMS": "专辑名",
29
+ "_OPERATION": "操作",
30
+ "_CREATE_PLAYLIST": "新建歌单",
31
+ "_CANCEL": "取消",
32
+ "_CREATE_AND_ADD_PLAYLIST": "创建并添加",
33
+ "_REMOVE_FROM_PLAYLIST": "从歌单删除",
34
+ "_ADD_TO_QUEUE": "添加到当前播放",
35
+ "_ARTIST": "歌手",
36
+ "_ALBUM": "专辑名",
37
+ "_PLAYLIST_TITLE": "歌单名称",
38
+ "_PLAYLIST_AUTHOR": "歌单作者",
39
+ "_PLAYLIST_SONG_COUNT": "歌曲数",
40
+ "_PLAYLIST_COVER_IMAGE_URL": "封面图片url",
41
+ "_INPUT_PLAYLIST_TITLE": "输入歌单名称",
42
+ "_INPUT_PLAYLIST_COVER_IMAGE_URL": "输入封面URL",
43
+ "_EDIT_PLAYLIST": "编辑歌单",
44
+ "_REMOVE_PLAYLIST": "删除歌单",
45
+ "_OPENING_LASTFM_PAGE":"正在打开Last.fm页面...",
46
+ "_CONFIRM_NOTICE_LASTFM": "请在打开的页面点击\"Yes, all access\", 允许Listen 1访问你的账户。",
47
+ "_AUTHORIZED_FINISHED": "已经完成授权",
48
+ "_AUTHORIZED_REOPEN": "遇到问题,再次打开授权页",
49
+ "_PLAYLIST_LINK": "歌单链接",
50
+ "_OPEN_PLAYLIST": "打开歌单",
51
+ "_OPENING_GITHUB_PAGE": "正在打开Github.com页面...",
52
+ "_CONFIRM_NOTICE_GITHUB": "请在打开的页面点击\"Authencate\", 允许Listen 1访问你的账户。",
53
+ "_CREATE_PLAYLIST_BACKUP": "创建歌单备份",
54
+ "_CREATE_PUBLIC_BACKUP": "创建公开备份",
55
+ "_CREATE_PRIVATE_BACKUP": "创建私有备份",
56
+ "_BACKUP_PLAYLIST": "备份歌单",
57
+ "_BACKUP_WARNING": "重装插件或清除缓存数据会导致我的歌单数据丢失,强烈建议在这些操作前,备份我的歌单。",
58
+ "_EXPORT_TO_LOCAL_FILE": "导出到本地文件",
59
+ "_EXPORT_TO_GITHUB_GIST": "导出到Github Gist",
60
+ "_RECOVER_PLAYLIST": "从备份恢复",
61
+ "_RECOVER_WARNING": "选择备份文件,恢复我的歌单。注意:恢复我的歌单会覆盖现有的歌单。",
62
+ "_RECOVER_FROM_LOCAL_FILE": "从本地文件导入",
63
+ "_RECOVER_FROM_GITHUB_GIST": "从Github Gist导入",
64
+ "_CONNECT_TO_GITHUB": "连接到Github.com",
65
+ "_STATUS": "状态",
66
+ "_RECONNECT": "重新连接",
67
+ "_CANCEL_CONNECT": "取消连接",
68
+ "_SHORTCUTS": "快捷键",
69
+ "_VIEW_SHORTCUTS_LIST": "查看快捷键列表",
70
+ "_GLOBAL_SHORTCUTS_NOTICE": "启用全局快捷键",
71
+ "_LYRIC_DISPLAY": "歌词显示",
72
+ "_SHOW_DESKTOP_LYRIC": "启用桌面歌词",
73
+ "_SHOW_LYRIC_TRANSLATION": "外文歌词显示翻译(播放页)",
74
+ "_SHOW_DESKTOP_LYRIC_TRANSLATION": "外文歌词显示翻译(桌面歌词)",
75
+ "_CONNECT_TO_LASTFM": "连接到last.fm",
76
+ "_ABOUT": "关于",
77
+ "_HOMEPAGE": "主页",
78
+ "_EMAIL": "邮箱",
79
+ "_FEEDBACK": "反馈问题",
80
+ "_DESIGNER": "主题设计",
81
+ "_VERSION": "当前版本",
82
+ "_LATEST_VERSION": "最新版本",
83
+ "_LICENSE_NOTICE": "(本软件基于MIT协议开源免费)",
84
+ "_TOTAL_SONG_PREFIX": "共",
85
+ "_TOTAL_SONG_POSTFIX": "首",
86
+ "_CLEAR_ALL": "清空",
87
+ "_SEARCH_PLACEHOLDER": "输入歌曲名,歌手或专辑",
88
+ "_LANGUAGE": "语言",
89
+ "_ADD_TO_PLAYLIST_SUCCESS": "成功添加到我创建的歌单",
90
+ "_FAVORITE_PLAYLIST_SUCCESS": "收藏成功",
91
+ "_EDIT_PLAYLIST_SUCCESS": "编辑歌单成功",
92
+ "_IMPORTING_PLAYLIST": "正在导入歌单...",
93
+ "_IMPORTING_PLAYLIST_SUCCESS": "导入歌单成功",
94
+ "_REMOVE_PLAYLIST_SUCCESS": "删除歌单成功",
95
+ "_UNFAVORITE_PLAYLIST_SUCCESS": "取消收藏成功",
96
+ "_REMOVE_SONG_FROM_PLAYLIST_SUCCESS": "删除歌曲成功",
97
+ "_ADD_TO_QUEUE_SUCCESS": "添加到当前播放成功",
98
+ "_COPYRIGHT_ISSUE": "版权原因无法播放,请搜索其他平台",
99
+ "_INPUT_NEW_PLAYLIST_TITLE": "输入新歌单名称",
100
+ "_FAIL_OPEN_PLAYLIST_URL": "未能打开输入的歌单地址",
101
+ "_EXAMPLE": "例如",
102
+ "_CONFIRM": "确认",
103
+ "_THEME": "主题",
104
+ "_THEME_WHITE": "简约白",
105
+ "_THEME_BLACK": "深空灰",
106
+ "_AUTO_CHOOSE_SOURCE": "自动切换源",
107
+ "_AUTO_CHOOSE_SOURCE_NOTICE": "是否自动切换播放源(仅在播放音乐失败后切换)",
108
+ "_AUTO_CHOOSE_SOURCE_LIST": "从以下平台搜索可用源",
109
+ "_NOWPLAYING_DISPLAY": "正在播放显示",
110
+ "_NOWPLAYING_COVER_BACKGROUND_NOTICE": "显示专辑封面作为背景",
111
+ "_NOWPLAYING_BITRATE_NOTICE": "显示比特率",
112
+ "_NOWPLAYING_PLATFORM_NOTICE": "显示音乐平台",
113
+ "_LOCAL_MUSIC": "本地音乐",
114
+ "_ADD_LOCAL_SONGS": "添加歌曲",
115
+ "netease": "网易",
116
+ "bilibili": "哔��",
117
+ "kugou": "酷狗",
118
+ "kuwo": "酷我",
119
+ "migu": "咪咕",
120
+ "qq": "QQ",
121
+ "xiami": "虾米",
122
+ "taihe": "千千",
123
+ "localmusic": "本地",
124
+ "_CLOSE_TAB_ACTION": "关闭标签页时行为",
125
+ "_VALID_AFTER_RESTART": "需重启生效",
126
+ "_QUIT_APPLICATION": "退出应用",
127
+ "_MINIMIZE_TO_BACKGROUND": "最小化到后台",
128
+ "_MY_CREATED_PLAYLIST": "我创建的歌单",
129
+ "_MY_FAVORITE_PLAYLIST": "我收藏的歌单",
130
+ "_RECOMMEND_PLAYLIST": "推荐歌单",
131
+ "_LISTEN1_LOGIN_NOTICE": "Listen1不会传输你的账号数据到任何第三方服务器。",
132
+ "_PASSWORD": "密码",
133
+ "_LOGIN": "登录",
134
+ "_LOGOUT": "退出登录",
135
+ "_LOGIN_ERROR": "登录失败,请检查用户名和密码",
136
+ "_LOGIN_EMAIL_ERROR": "请输入正确的邮箱地址",
137
+ "_LOGIN_COUNTRYCODE_ERROR": "请输入正确的国家或地区代码",
138
+ "_LOGIN_PHONE_ERROR": "请输入正确的手机号",
139
+ "_LOGIN_PASSWORD_ERROR": "密码不能为空",
140
+ "_LOGIN_NETEASE": "登录网易云音乐",
141
+ "_LOGIN_BY_MOBILE_PHONE": "手机号登录",
142
+ "_LOGIN_BY_EMAIL": "邮箱登录",
143
+ "_MOBILE_PHONE": "手机号",
144
+ "_MY_NETEASE": "我的网易云音乐",
145
+ "_MY_QQ": "我的QQ音乐",
146
+ "_FAIL_ALL_NOTICE": "当前播放列表没有可播放的歌曲",
147
+ "_SHORTCUTS_FUNCTION": "功能说明",
148
+ "_GLOBAL_SHORTCUTS": "全局快捷键",
149
+ "_PLAY_OR_PAUSE": "播放/暂停",
150
+ "_PREVIOUS_TRACK": "上一首",
151
+ "_NEXT_TRACK": "下一首",
152
+ "_VOLUME_UP": "音量加",
153
+ "_VOLUME_DOWN": "音量减",
154
+ "_SHORTCUTS_NOT_SET": "空",
155
+ "_QUICK_SEARCH": "快速搜索",
156
+ "_SEARCH_PLAYLIST": "搜索歌单",
157
+ "_KEYBOARD_SPACE": "空格",
158
+ "_NOT_LOGIN_NICKNAME": "未登录",
159
+ "_LOGIN_DIALOG_NOTICE": "正在打开音乐平台的登录页,请在打开网页中完成登录流程,然后点击登录完成",
160
+ "_LOGIN_SUCCESS": "登录完成",
161
+ "_LOGIN_FAIL_RETRY": "登录遇到问题,再打开登录页面",
162
+ "_PROXY_SYSTEM": "使用系统代理",
163
+ "_PROXY_DIRECT": "不使用代理",
164
+ "_PROXY_CUSTOM": "自定义代理",
165
+ "_PROXY_CONFIG": "代理设置",
166
+ "_PROXY_NOT_SET": "未设置",
167
+ "_MODIFY": "修改",
168
+ "_PROTOCOL": "代理协议",
169
+ "_HOST": "主机地址",
170
+ "_PORT": "端口",
171
+ "ZOOM_IN_OUT": "放大/缩小"
172
+ }
i18n/zh-TC.json ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "HELLO" : "你好",
3
+ "_ALL_MUSIC": "所有音樂",
4
+ "_NETEASE_MUSIC": "網易雲音樂",
5
+ "_QQ_MUSIC": "QQ音樂",
6
+ "_XIAMI_MUSIC": "蝦米音樂",
7
+ "_KUGOU_MUSIC": "酷狗音樂",
8
+ "_KUWO_MUSIC": "酷我音樂",
9
+ "_BILIBILI_MUSIC": "嗶哩嗶哩",
10
+ "_MIGU_MUSIC": "咪咕音樂",
11
+ "_TAIHE_MUSIC": "千千音樂",
12
+ "_PLATFORM_UNION": "平臺聚合",
13
+ "_PLAYLISTS": "精選歌單",
14
+ "_MY_MUSIC": "我的音樂",
15
+ "_CREATED_PLAYLIST": "創建的歌單",
16
+ "_FAVORITED_PLAYLIST": "收藏的歌單",
17
+ "_REFRESH_PLAYLIST": "刷新",
18
+ "_FAVORITED": "已收藏",
19
+ "_FAVORITE": "收藏",
20
+ "_PLAY_ALL": "播放全部",
21
+ "_ADD_TO_PLAYLIST": "添加到我的歌單",
22
+ "_EDIT": "編輯",
23
+ "_IMPORT": "導入",
24
+ "_IMPORT_PLAYLIST": "導入歌單",
25
+ "_ORIGIN_LINK": "連結",
26
+ "_SONGS": "曲目",
27
+ "_ARTISTS": "歌手",
28
+ "_ALBUMS": "專輯名",
29
+ "_OPERATION": "操作",
30
+ "_CREATE_PLAYLIST": "創建歌單",
31
+ "_CANCEL": "取消",
32
+ "_CREATE_AND_ADD_PLAYLIST": "創建並添加",
33
+ "_REMOVE_FROM_PLAYLIST": "從歌單裡移除",
34
+ "_ADD_TO_QUEUE": "加入到當前播放",
35
+ "_ARTIST": "歌手",
36
+ "_ALBUM": "專輯名",
37
+ "_PLAYLIST_TITLE": "歌單名稱",
38
+ "_PLAYLIST_AUTHOR": "歌單作者",
39
+ "_PLAYLIST_SONG_COUNT": "曲目數",
40
+ "_PLAYLIST_COVER_IMAGE_URL": "封面圖片url",
41
+ "_INPUT_PLAYLIST_TITLE": "鍵入歌單名稱",
42
+ "_INPUT_PLAYLIST_COVER_IMAGE_URL": "鍵入封面圖片URL",
43
+ "_EDIT_PLAYLIST": "編輯歌單",
44
+ "_REMOVE_PLAYLIST": "移除歌單",
45
+ "_OPENING_LASTFM_PAGE":"正在打開Last.fm頁面...",
46
+ "_CONFIRM_NOTICE_LASTFM": "請在打開的頁面點擊\"Yes, all access\", 允許Listen 1訪問你的帳戶。",
47
+ "_AUTHORIZED_FINISHED": "已經完成授權",
48
+ "_AUTHORIZED_REOPEN": "遇到問題,再次打開授權頁",
49
+ "_PLAYLIST_LINK": "歌單連結",
50
+ "_OPEN_PLAYLIST": "打開歌單",
51
+ "_OPENING_GITHUB_PAGE": "正在打開Github.com頁面...",
52
+ "_CONFIRM_NOTICE_GITHUB": "請在打開的頁面點擊\"Authencate\", 允許Listen 1訪問你的帳戶。",
53
+ "_CREATE_PLAYLIST_BACKUP": "創建歌單備份",
54
+ "_CREATE_PUBLIC_BACKUP": "創建公開備份",
55
+ "_CREATE_PRIVATE_BACKUP": "創建私有備份",
56
+ "_BACKUP_PLAYLIST": "備份歌單",
57
+ "_BACKUP_WARNING": "重建應用程式或清除緩存檔案會導致我的歌單數據丟失,強烈建議在做這件事,備份我的歌單。",
58
+ "_EXPORT_TO_LOCAL_FILE": "匯出到本地檔案",
59
+ "_EXPORT_TO_GITHUB_GIST": "匯出到Github Gist",
60
+ "_RECOVER_PLAYLIST": "從備份恢復",
61
+ "_RECOVER_WARNING": "選擇備份檔案,恢復我的歌單。危險:恢復我的歌單會覆蓋現有的歌單。",
62
+ "_RECOVER_FROM_LOCAL_FILE": "從本地檔案導入",
63
+ "_RECOVER_FROM_GITHUB_GIST": "從Github Gist導入",
64
+ "_CONNECT_TO_GITHUB": "連結到Github.com",
65
+ "_STATUS": "狀態",
66
+ "_RECONNECT": "重新連結",
67
+ "_CANCEL_CONNECT": "取消連結",
68
+ "_SHORTCUTS": "快速鍵",
69
+ "_VIEW_SHORTCUTS_LIST": "查看快速鍵列表",
70
+ "_GLOBAL_SHORTCUTS_NOTICE": "啟用全域快速鍵",
71
+ "_LYRIC_DISPLAY": "歌詞顯示",
72
+ "_SHOW_DESKTOP_LYRIC": "啟用桌面歌詞",
73
+ "_SHOW_LYRIC_TRANSLATION": "外文歌詞顯示翻譯(播放頁)",
74
+ "_SHOW_DESKTOP_LYRIC_TRANSLATION": "外文歌詞顯示翻譯(桌面歌詞)",
75
+ "_CONNECT_TO_LASTFM": "連結到last.fm",
76
+ "_ABOUT": "關於",
77
+ "_HOMEPAGE": "主頁",
78
+ "_EMAIL": "電郵",
79
+ "_FEEDBACK": "回饋問題",
80
+ "_DESIGNER": "主題設計",
81
+ "_VERSION": "當前版本",
82
+ "_LATEST_VERSION": "最新版本",
83
+ "_LICENSE_NOTICE": "(本軟體基於MIT協定開源免費)",
84
+ "_TOTAL_SONG_PREFIX": "總 ",
85
+ "_TOTAL_SONG_POSTFIX": " 首",
86
+ "_CLEAR_ALL": "清空",
87
+ "_SEARCH_PLACEHOLDER": "鍵入曲目名,歌手或專輯",
88
+ "_LANGUAGE": "語言",
89
+ "_ADD_TO_PLAYLIST_SUCCESS": "成功添加到我創建的歌單",
90
+ "_FAVORITE_PLAYLIST_SUCCESS": "收藏成功",
91
+ "_EDIT_PLAYLIST_SUCCESS": "編輯歌單成功",
92
+ "_IMPORTING_PLAYLIST": "正在導入歌單...",
93
+ "_IMPORTING_PLAYLIST_SUCCESS": "導入歌單成功",
94
+ "_REMOVE_PLAYLIST_SUCCESS": "移除歌單成功",
95
+ "_UNFAVORITE_PLAYLIST_SUCCESS": "移除收藏成功",
96
+ "_REMOVE_SONG_FROM_PLAYLIST_SUCCESS": "移除曲目成功",
97
+ "_ADD_TO_QUEUE_SUCCESS": "添加到當前播放成功",
98
+ "_COPYRIGHT_ISSUE": "版權限定無法播放,請搜索其他平臺",
99
+ "_INPUT_NEW_PLAYLIST_TITLE": "鍵入新歌單名稱",
100
+ "_FAIL_OPEN_PLAYLIST_URL": "未能打開鍵入的歌單位址",
101
+ "_EXAMPLE": "例如 ",
102
+ "_CONFIRM": "確認",
103
+ "_THEME": "樣式",
104
+ "_THEME_WHITE": "簡約白",
105
+ "_THEME_BLACK": "深空灰",
106
+ "_AUTO_CHOOSE_SOURCE": "自動切換源",
107
+ "_AUTO_CHOOSE_SOURCE_NOTICE": "是否自動切換播放源(僅在播放音樂失敗後切換)",
108
+ "_AUTO_CHOOSE_SOURCE_LIST": "從以下平臺搜索可用源",
109
+ "_NOWPLAYING_DISPLAY": "正在播放顯示",
110
+ "_NOWPLAYING_COVER_BACKGROUND_NOTICE": "顯示專輯封面作為背景",
111
+ "_NOWPLAYING_BITRATE_NOTICE": "顯示比特率",
112
+ "_NOWPLAYING_PLATFORM_NOTICE": "顯示音���平臺",
113
+ "_LOCAL_MUSIC": "本機音樂",
114
+ "_ADD_LOCAL_SONGS": "添加本機音樂",
115
+ "netease": "網易",
116
+ "bilibili": "嗶哩嗶哩",
117
+ "kugou": "酷狗",
118
+ "kuwo": "酷我",
119
+ "migu": "咪咕",
120
+ "qq": "QQ",
121
+ "xiami": "蝦米",
122
+ "taihe": "千千",
123
+ "localmusic": "本地",
124
+ "_CLOSE_TAB_ACTION": "關閉標籤頁時行為",
125
+ "_VALID_AFTER_RESTART": "需重載生效",
126
+ "_QUIT_APPLICATION": "退出軟體",
127
+ "_MINIMIZE_TO_BACKGROUND": "最小化到後臺",
128
+ "_MY_CREATED_PLAYLIST": "我創建的歌單",
129
+ "_MY_FAVORITE_PLAYLIST": "我收藏的歌單",
130
+ "_RECOMMEND_PLAYLIST": "推薦歌單",
131
+ "_LISTEN1_LOGIN_NOTICE": "Listen1不會傳輸你的賬號數據到任何第三方伺服器。",
132
+ "_PASSWORD": "密碼",
133
+ "_LOGIN": "登錄",
134
+ "_LOGOUT": "退出登錄",
135
+ "_LOGIN_ERROR": "登錄失敗,請檢查用戶名和密碼",
136
+ "_LOGIN_EMAIL_ERROR": "請輸入正確的郵箱地址",
137
+ "_LOGIN_COUNTRYCODE_ERROR": "請輸入正確的國家或地區代碼",
138
+ "_LOGIN_PHONE_ERROR": "請輸入正確的手機號",
139
+ "_LOGIN_PASSWORD_ERROR": "密碼不能為空",
140
+ "_LOGIN_NETEASE": "登錄網易雲音樂",
141
+ "_LOGIN_BY_MOBILE_PHONE": "手機號登錄",
142
+ "_LOGIN_BY_EMAIL": "郵箱登錄",
143
+ "_MOBILE_PHONE": "手機號",
144
+ "_MY_NETEASE": "我的網易雲音樂",
145
+ "_MY_QQ": "我的QQ音樂",
146
+ "_FAIL_ALL_NOTICE": "當前播放列表沒有可播放的歌曲",
147
+ "_SHORTCUTS_FUNCTION": "功能說明",
148
+ "_GLOBAL_SHORTCUTS": "全局快捷鍵",
149
+ "_PLAY_OR_PAUSE": "播放/暫停",
150
+ "_PREVIOUS_TRACK": "上一首",
151
+ "_NEXT_TRACK": "下一首",
152
+ "_VOLUME_UP": "音量加",
153
+ "_VOLUME_DOWN": "音量減",
154
+ "_SHORTCUTS_NOT_SET": "空",
155
+ "_QUICK_SEARCH": "快速搜索",
156
+ "_SEARCH_PLAYLIST": "搜索歌單",
157
+ "_KEYBOARD_SPACE": "空格",
158
+ "_NOT_LOGIN_NICKNAME": "未登錄",
159
+ "_LOGIN_DIALOG_NOTICE": "正在打開音樂平臺的登錄頁,請在打開網頁中完成登錄流程,然後點擊登錄完成",
160
+ "_LOGIN_SUCCESS": "登錄完成",
161
+ "_LOGIN_FAIL_RETRY": "登錄遇到問題,再打開登錄頁面",
162
+ "_PROXY_SYSTEM": "使用系統代理",
163
+ "_PROXY_DIRECT": "不使用代理",
164
+ "_PROXY_CUSTOM": "自定義代理",
165
+ "_PROXY_CONFIG": "代理設置",
166
+ "_PROXY_NOT_SET": "未設置",
167
+ "_MODIFY": "修改",
168
+ "_PROTOCOL": "代理協議",
169
+ "_HOST": "主機地址",
170
+ "_PORT": "端口",
171
+ "ZOOM_IN_OUT": "放大/縮小"
172
+ }
images/favicon.ico ADDED
images/feather-sprite.svg ADDED
images/loading-1.gif ADDED
images/loading.gif ADDED
images/loading.svg ADDED
images/logo.png ADDED
images/logo_16.png ADDED
images/logo_48.png ADDED
images/mycover.jpg ADDED
images/netease-logo.png ADDED
images/placeholder.png ADDED
images/player_directplay.png ADDED
images/player_large.png ADDED
images/player_small.png ADDED
images/progress_indicator.png ADDED
images/statbar.png ADDED
js/app.js ADDED
@@ -0,0 +1,500 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable no-shadow */
2
+ /* global l1Player require */
3
+ /* global angular isElectron i18next i18nextHttpBackend Notyf notyf */
4
+ /* global setPrototypeOfLocalStorage */
5
+ /* eslint-disable global-require */
6
+ /* eslint-disable no-unused-vars */
7
+ /* eslint-disable no-param-reassign */
8
+ /* eslint-disable import/no-unresolved */
9
+
10
+ const sourceList = [
11
+ {
12
+ name: 'netease',
13
+ displayId: '_NETEASE_MUSIC',
14
+ },
15
+ {
16
+ name: 'qq',
17
+ displayId: '_QQ_MUSIC',
18
+ },
19
+ {
20
+ name: 'kugou',
21
+ displayId: '_KUGOU_MUSIC',
22
+ },
23
+ {
24
+ name: 'kuwo',
25
+ displayId: '_KUWO_MUSIC',
26
+ },
27
+ {
28
+ name: 'bilibili',
29
+ displayId: '_BILIBILI_MUSIC',
30
+ searchable: false,
31
+ },
32
+ {
33
+ name: 'migu',
34
+ displayId: '_MIGU_MUSIC',
35
+ },
36
+ {
37
+ name: 'taihe',
38
+ displayId: '_TAIHE_MUSIC',
39
+ },
40
+ ];
41
+
42
+ const main = () => {
43
+ const app = angular.module('listenone', []);
44
+ setPrototypeOfLocalStorage();
45
+ app.config([
46
+ '$compileProvider',
47
+ ($compileProvider) => {
48
+ $compileProvider.imgSrcSanitizationWhitelist(
49
+ /^\s*(https?|ftp|mailto|chrome-extension|moz-extension|file):/
50
+ );
51
+ },
52
+ ]);
53
+
54
+ app.run([
55
+ '$q',
56
+ ($q) => {
57
+ axios.Axios.prototype.request_original = axios.Axios.prototype.request;
58
+ axios.Axios.prototype.request = function new_req(config) {
59
+ return $q.when(this.request_original(config));
60
+ };
61
+ window.notyf = new Notyf({
62
+ duration: 5000,
63
+ ripple: true,
64
+ position: { x: 'center', y: 'top' },
65
+ types: [
66
+ {
67
+ type: 'warning',
68
+ background: 'darkorange',
69
+ icon: false,
70
+ },
71
+ {
72
+ type: 'info',
73
+ background: 'deepskyblue',
74
+ icon: false,
75
+ },
76
+ ],
77
+ });
78
+ window.notyf.warning = (msg, replace) => {
79
+ if (replace) {
80
+ notyf.dismissAll();
81
+ }
82
+ window.notyf.open({
83
+ type: 'warning',
84
+ message: msg,
85
+ });
86
+ };
87
+ window.notyf.info = (msg, replace) => {
88
+ if (replace) {
89
+ notyf.dismissAll();
90
+ }
91
+ window.notyf.open({
92
+ type: 'info',
93
+ message: msg,
94
+ });
95
+ };
96
+ axios.get('images/feather-sprite.svg').then((res) => {
97
+ document.getElementById('feather-container').innerHTML = res.data;
98
+ });
99
+ },
100
+ ]);
101
+
102
+ l1Player.injectDirectives(app);
103
+
104
+ app.filter('playmode_title', () => (input) => {
105
+ switch (input) {
106
+ case 0:
107
+ return '顺序';
108
+ case 1:
109
+ return '随机';
110
+ case 2:
111
+ return '单曲循环';
112
+ default:
113
+ return '';
114
+ }
115
+ });
116
+
117
+ app.directive('customOnChange', () => {
118
+ const ret = {
119
+ restrict: 'A',
120
+ link: (scope, element, attrs) => {
121
+ const onChangeHandler = scope.$eval(attrs.customOnChange);
122
+ element.bind('change', onChangeHandler);
123
+ },
124
+ };
125
+ return ret;
126
+ });
127
+
128
+ app.directive('volumeWheel', () => (scope, element, attrs) => {
129
+ element.bind('mousewheel', () => {
130
+ l1Player.adjustVolume(window.event.wheelDelta > 0);
131
+ });
132
+ });
133
+
134
+ app.directive('pagination', () => ({
135
+ restrict: 'EA',
136
+ replace: false,
137
+ template: ` <button class="btn btn-sm btn-pagination" ng-click="previousPage()" ng-disabled="curpage==1"> 上一页</button>
138
+ <label> {{curpage}}/{{totalpage}} 页 </label>
139
+ <button class="btn btn-sm btn-pagination" ng-click="nextPage()" ng-disabled="curpage==totalpage"> 下一页</button>`,
140
+ }));
141
+
142
+ app.directive('errSrc', () => ({
143
+ // https://stackoverflow.com/questions/16310298/if-a-ngsrc-path-resolves-to-a-404-is-there-a-way-to-fallback-to-a-default
144
+ link: (scope, element, attrs) => {
145
+ element.bind('error', () => {
146
+ if (attrs.src !== attrs.errSrc) {
147
+ attrs.$set('src', attrs.errSrc);
148
+ }
149
+ });
150
+ attrs.$observe('ngSrc', (value) => {
151
+ if (!value && attrs.errSrc) {
152
+ attrs.$set('src', attrs.errSrc);
153
+ }
154
+ });
155
+ },
156
+ }));
157
+
158
+ app.directive('resize', ($window) => (scope, element) => {
159
+ const w = angular.element($window);
160
+ const changeHeight = () => {
161
+ const headerHeight = 90;
162
+ const footerHeight = 90;
163
+ element.css('height', `${w.height() - headerHeight - footerHeight}px`);
164
+ };
165
+ w.bind('resize', () => {
166
+ changeHeight(); // when window size gets changed
167
+ });
168
+ changeHeight(); // when page loads
169
+ });
170
+
171
+ app.directive('addAndPlay', [
172
+ () => ({
173
+ restrict: 'EA',
174
+ scope: {
175
+ song: '=addAndPlay',
176
+ },
177
+ link(scope, element, attrs) {
178
+ element.bind('click', (event) => {
179
+ l1Player.addTrack(scope.song);
180
+ l1Player.playById(scope.song.id);
181
+ });
182
+ },
183
+ }),
184
+ ]);
185
+
186
+ app.directive('addWithoutPlay', [
187
+ () => ({
188
+ restrict: 'EA',
189
+ scope: {
190
+ song: '=addWithoutPlay',
191
+ },
192
+ link(scope, element, attrs) {
193
+ element.bind('click', (event) => {
194
+ l1Player.addTrack(scope.song);
195
+ notyf.success(i18next.t('_ADD_TO_QUEUE_SUCCESS'));
196
+ });
197
+ },
198
+ }),
199
+ ]);
200
+
201
+ app.directive('openUrl', [
202
+ '$window',
203
+ ($window) => ({
204
+ restrict: 'EA',
205
+ scope: {
206
+ url: '=openUrl',
207
+ },
208
+ link(scope, element, attrs) {
209
+ element.bind('click', (event) => {
210
+ if (isElectron()) {
211
+ const { shell } = require('electron');
212
+ shell.openExternal(scope.url);
213
+ } else {
214
+ $window.open(scope.url, '_blank');
215
+ }
216
+ });
217
+ },
218
+ }),
219
+ ]);
220
+
221
+ app.directive('windowControl', [
222
+ '$window',
223
+ ($window) => ({
224
+ restrict: 'EA',
225
+ scope: {
226
+ action: '@windowControl',
227
+ },
228
+ link(scope, element, attrs) {
229
+ element.bind('click', (event) => {
230
+ if (isElectron()) {
231
+ const { ipcRenderer } = require('electron');
232
+ ipcRenderer.send('control', scope.action);
233
+ }
234
+ });
235
+ },
236
+ }),
237
+ ]);
238
+
239
+ app.directive('infiniteScroll', [
240
+ '$window',
241
+ '$rootScope',
242
+ ($window, $rootScope) => ({
243
+ restrict: 'EA',
244
+ scope: {
245
+ infiniteScroll: '&',
246
+ contentSelector: '=contentSelector',
247
+ },
248
+ link(scope, elements, attrs) {
249
+ elements.bind('scroll', (event) => {
250
+ if (scope.loading) {
251
+ return;
252
+ }
253
+ const containerElement = elements[0];
254
+ const contentElement = document.querySelector(scope.contentSelector);
255
+
256
+ const baseTop = containerElement.getBoundingClientRect().top;
257
+ const currentTop = contentElement.getBoundingClientRect().top;
258
+ const baseHeight = containerElement.offsetHeight;
259
+ const offset = baseTop - currentTop;
260
+
261
+ const bottom = offset + baseHeight;
262
+ const height = contentElement.offsetHeight;
263
+
264
+ const remain = height - bottom;
265
+ if (remain < 0) {
266
+ // page not shown
267
+ return;
268
+ }
269
+ const offsetToload = 10;
270
+ if (remain <= offsetToload) {
271
+ $rootScope.$broadcast('infinite_scroll:hit_bottom', '');
272
+ }
273
+ });
274
+ },
275
+ }),
276
+ ]);
277
+
278
+ /* drag drop support */
279
+ app.directive('dragDropZone', [
280
+ '$window',
281
+ ($window) => ({
282
+ restrict: 'A',
283
+ scope: {
284
+ dragobject: '=dragZoneObject',
285
+ dragtitle: '=dragZoneTitle',
286
+ dragtype: '=dragZoneType',
287
+ ondrop: '&dropZoneOndrop',
288
+ ondragleave: '&dropZoneOndragleave',
289
+ sortable: '=',
290
+ },
291
+ link(scope, element, attrs) {
292
+ // https://stackoverflow.com/questions/34200023/drag-drop-set-custom-html-as-drag-image
293
+ element.on('dragstart', (ev) => {
294
+ if (scope.dragobject === undefined) {
295
+ return;
296
+ }
297
+ if (scope.dragtype === undefined) {
298
+ return;
299
+ }
300
+ ev.dataTransfer.setData(
301
+ scope.dragtype,
302
+ JSON.stringify(scope.dragobject)
303
+ );
304
+ const elem = document.createElement('div');
305
+ elem.id = 'drag-ghost';
306
+ elem.innerHTML = scope.dragtitle;
307
+ elem.style.position = 'absolute';
308
+ elem.style.top = '-1000px';
309
+ elem.style.padding = '3px';
310
+ elem.style.background = '#eeeeee';
311
+ elem.style.color = '#333';
312
+ elem.style['border-radius'] = '3px';
313
+
314
+ document.body.appendChild(elem);
315
+ ev.dataTransfer.setDragImage(elem, 0, 40);
316
+ });
317
+ element.on('dragend', () => {
318
+ const ghost = document.getElementById('drag-ghost');
319
+ if (ghost.parentNode) {
320
+ ghost.parentNode.removeChild(ghost);
321
+ }
322
+ });
323
+ element.on('dragenter', (event) => {
324
+ let dragType = '';
325
+ if (event.dataTransfer.types.length > 0) {
326
+ [dragType] = event.dataTransfer.types;
327
+ }
328
+ if (
329
+ scope.dragtype === 'application/listen1-myplaylist' &&
330
+ dragType === 'application/listen1-song'
331
+ ) {
332
+ element[0].classList.add('dragover');
333
+ }
334
+ });
335
+ element.on('dragleave', (event) => {
336
+ element[0].classList.remove('dragover');
337
+ if (scope.ondragleave !== undefined) {
338
+ scope.ondragleave();
339
+ }
340
+ if (scope.sortable) {
341
+ const target = element[0];
342
+ target.style['z-index'] = '0';
343
+ target.style['border-bottom'] = 'solid 2px transparent';
344
+ target.style['border-top'] = 'solid 2px transparent';
345
+ }
346
+ });
347
+
348
+ element.on('dragover', (event) => {
349
+ event.preventDefault();
350
+ const dragLineColor = '#FF4444';
351
+ let dragType = '';
352
+ if (event.dataTransfer.types.length > 0) {
353
+ [dragType] = event.dataTransfer.types;
354
+ }
355
+
356
+ if (scope.dragtype === dragType && scope.sortable) {
357
+ event.dataTransfer.dropEffect = 'move';
358
+ const bounding = event.target.getBoundingClientRect();
359
+ const offset = bounding.y + bounding.height / 2;
360
+
361
+ const direction = event.clientY - offset > 0 ? 'bottom' : 'top';
362
+ const target = element[0];
363
+ if (direction === 'bottom') {
364
+ target.style['border-bottom'] = `solid 2px ${dragLineColor}`;
365
+ target.style['border-top'] = 'solid 2px transparent';
366
+ target.style['z-index'] = '9';
367
+ } else if (direction === 'top') {
368
+ target.style['border-top'] = `solid 2px ${dragLineColor}`;
369
+ target.style['border-bottom'] = 'solid 2px transparent';
370
+ target.style['z-index'] = '9';
371
+ }
372
+ } else if (
373
+ scope.dragtype === 'application/listen1-myplaylist' &&
374
+ dragType === 'application/listen1-song'
375
+ ) {
376
+ event.dataTransfer.dropEffect = 'copy';
377
+ }
378
+ });
379
+
380
+ element.on('drop', (event) => {
381
+ if (scope.ondrop === undefined) {
382
+ return;
383
+ }
384
+ const [dragType] = event.dataTransfer.types;
385
+ const jsonString = event.dataTransfer.getData(dragType);
386
+ const data = JSON.parse(jsonString);
387
+ let direction = '';
388
+ const bounding = event.target.getBoundingClientRect();
389
+ const offset = bounding.y + bounding.height / 2;
390
+ direction = event.clientY - offset > 0 ? 'bottom' : 'top';
391
+ // https://stackoverflow.com/questions/19889615/can-an-angular-directive-pass-arguments-to-functions-in-expressions-specified-in
392
+ scope.ondrop({ arg1: data, arg2: dragType, arg3: direction });
393
+
394
+ element[0].classList.remove('dragover');
395
+ if (scope.sortable) {
396
+ const target = element[0];
397
+ target.style['border-top'] = 'solid 2px transparent';
398
+ target.style['border-bottom'] = 'solid 2px transparent';
399
+ }
400
+ });
401
+ },
402
+ }),
403
+ ]);
404
+
405
+ app.directive('draggableBar', [
406
+ '$document',
407
+ '$rootScope',
408
+ ($document, $rootScope) => (scope, element, attrs) => {
409
+ let x;
410
+ let container;
411
+ const { mode } = attrs;
412
+
413
+ function onMyMousedown() {
414
+ if (mode === 'play') {
415
+ scope.changingProgress = true;
416
+ }
417
+ }
418
+
419
+ function onMyMouseup() {
420
+ if (mode === 'play') {
421
+ scope.changingProgress = false;
422
+ }
423
+ }
424
+
425
+ function onMyUpdateProgress(progress) {
426
+ if (mode === 'play') {
427
+ $rootScope.$broadcast('track:myprogress', progress * 100);
428
+ }
429
+ if (mode === 'volume') {
430
+ l1Player.setVolume(progress * 100);
431
+ l1Player.unmute();
432
+ }
433
+ }
434
+
435
+ function onMyCommitProgress(progress) {
436
+ if (mode === 'play') {
437
+ l1Player.seek(progress);
438
+ }
439
+ if (mode === 'volume') {
440
+ const current = localStorage.getObject('player-settings');
441
+ current.volume = progress * 100;
442
+ localStorage.setObject('player-settings', current);
443
+ }
444
+ }
445
+
446
+ function commitProgress(progress) {
447
+ onMyCommitProgress(progress);
448
+ }
449
+
450
+ function updateProgress() {
451
+ if (container) {
452
+ if (x < 0) {
453
+ x = 0;
454
+ } else if (x > container.right - container.left) {
455
+ x = container.right - container.left;
456
+ }
457
+ }
458
+ const progress = x / (container.right - container.left);
459
+ onMyUpdateProgress(progress);
460
+ }
461
+
462
+ function mousemove(event) {
463
+ x = event.clientX - container.left;
464
+ updateProgress();
465
+ }
466
+
467
+ function mouseup() {
468
+ const progress = x / (container.right - container.left);
469
+ commitProgress(progress);
470
+ $document.off('mousemove', mousemove);
471
+ $document.off('mouseup', mouseup);
472
+ onMyMouseup();
473
+ }
474
+
475
+ element.on('mousedown', (event) => {
476
+ onMyMousedown();
477
+ container = document.getElementById(attrs.id).getBoundingClientRect();
478
+ // Prevent default dragging of selected content
479
+ event.preventDefault();
480
+ x = event.clientX - container.left;
481
+ updateProgress();
482
+ $document.on('mousemove', mousemove);
483
+ $document.on('mouseup', mouseup);
484
+ });
485
+ },
486
+ ]);
487
+ };
488
+
489
+ i18next.use(i18nextHttpBackend).init({
490
+ lng: 'zh-CN',
491
+ fallbackLng: 'zh-CN',
492
+ supportedLngs: ['zh-CN', 'zh-TC', 'en-US', 'fr-FR'],
493
+ preload: ['zh-CN', 'zh-TC', 'en-US', 'fr-FR'],
494
+ debug: false,
495
+ backend: {
496
+ loadPath: 'i18n/{{lng}}.json',
497
+ },
498
+ });
499
+
500
+ main();
js/background.js ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable no-unused-vars */
2
+ /* global GithubClient */
3
+ chrome.browserAction.onClicked.addListener((tab) => {
4
+ chrome.tabs.create(
5
+ {
6
+ url: chrome.extension.getURL('listen1.html'),
7
+ },
8
+ (new_tab) => {
9
+ // Tab opened.
10
+ }
11
+ );
12
+ });
13
+
14
+ function hack_referer_header(details) {
15
+ const replace_referer = true;
16
+ let replace_origin = true;
17
+ let add_referer = true;
18
+ let add_origin = true;
19
+
20
+ let referer_value = '';
21
+ let origin_value = '';
22
+ let ua_value = '';
23
+
24
+ if (details.url.includes('://music.163.com/')) {
25
+ referer_value = 'https://music.163.com/';
26
+ }
27
+ if (details.url.includes('://interface3.music.163.com/')) {
28
+ referer_value = 'https://music.163.com/';
29
+ }
30
+ if (details.url.includes('://gist.githubusercontent.com/')) {
31
+ referer_value = 'https://gist.githubusercontent.com/';
32
+ }
33
+
34
+ if (details.url.includes('.xiami.com/')) {
35
+ add_origin = false;
36
+ add_referer = false;
37
+ // referer_value = "https://www.xiami.com";
38
+ }
39
+
40
+ if (details.url.includes('c.y.qq.com/')) {
41
+ referer_value = 'https://y.qq.com/';
42
+ origin_value = 'https://y.qq.com';
43
+ }
44
+ if (
45
+ details.url.includes('i.y.qq.com/') ||
46
+ details.url.includes('qqmusic.qq.com/') ||
47
+ details.url.includes('music.qq.com/') ||
48
+ details.url.includes('imgcache.qq.com/')
49
+ ) {
50
+ referer_value = 'https://y.qq.com/';
51
+ }
52
+
53
+ if (details.url.includes('.kugou.com/')) {
54
+ referer_value = 'https://www.kugou.com/';
55
+ }
56
+
57
+ if (details.url.includes('.kuwo.cn/')) {
58
+ referer_value = 'https://www.kuwo.cn/';
59
+ }
60
+
61
+ if (
62
+ details.url.includes('.bilibili.com/') ||
63
+ details.url.includes('.bilivideo.com/')
64
+ ) {
65
+ referer_value = 'https://www.bilibili.com/';
66
+ replace_origin = false;
67
+ add_origin = false;
68
+ }
69
+
70
+ if (details.url.includes('.migu.cn')) {
71
+ referer_value = 'https://music.migu.cn/v3/music/player/audio?from=migu';
72
+ }
73
+
74
+ if (details.url.includes('m.music.migu.cn')) {
75
+ referer_value = 'https://m.music.migu.cn/';
76
+ }
77
+
78
+ if (
79
+ details.url.includes('app.c.nf.migu.cn') ||
80
+ details.url.includes('d.musicapp.migu.cn')
81
+ ) {
82
+ ua_value =
83
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30';
84
+ add_origin = false;
85
+ add_referer = false;
86
+ }
87
+
88
+ if (details.url.includes('jadeite.migu.cn')) {
89
+ ua_value = 'okhttp/3.12.12';
90
+ add_origin = false;
91
+ add_referer = false;
92
+ }
93
+
94
+ if (origin_value === '') {
95
+ origin_value = referer_value;
96
+ }
97
+
98
+ let isRefererSet = false;
99
+ let isOriginSet = false;
100
+ let isUASet = false;
101
+ const headers = details.requestHeaders;
102
+ const blockingResponse = {};
103
+
104
+ for (let i = 0, l = headers.length; i < l; i += 1) {
105
+ if (
106
+ replace_referer &&
107
+ headers[i].name === 'Referer' &&
108
+ referer_value !== ''
109
+ ) {
110
+ headers[i].value = referer_value;
111
+ isRefererSet = true;
112
+ }
113
+ if (replace_origin && headers[i].name === 'Origin' && origin_value !== '') {
114
+ headers[i].value = origin_value;
115
+ isOriginSet = true;
116
+ }
117
+ if (headers[i].name === 'User-Agent' && ua_value !== '') {
118
+ headers[i].value = ua_value;
119
+ isUASet = true;
120
+ }
121
+ }
122
+
123
+ if (add_referer && !isRefererSet && referer_value !== '') {
124
+ headers.push({
125
+ name: 'Referer',
126
+ value: referer_value,
127
+ });
128
+ }
129
+
130
+ if (add_origin && !isOriginSet && origin_value !== '') {
131
+ headers.push({
132
+ name: 'Origin',
133
+ value: origin_value,
134
+ });
135
+ }
136
+
137
+ if (!isUASet && ua_value !== '') {
138
+ headers.push({
139
+ name: 'User-Agent',
140
+ value: ua_value,
141
+ });
142
+ }
143
+
144
+ blockingResponse.requestHeaders = headers;
145
+ return blockingResponse;
146
+ }
147
+
148
+ const urls = [
149
+ '*://*.music.163.com/*',
150
+ '*://music.163.com/*',
151
+ '*://*.xiami.com/*',
152
+ '*://i.y.qq.com/*',
153
+ '*://c.y.qq.com/*',
154
+ '*://*.kugou.com/*',
155
+ '*://*.kuwo.cn/*',
156
+ '*://*.bilibili.com/*',
157
+ '*://*.bilivideo.com/*',
158
+ '*://*.migu.cn/*',
159
+ '*://*.githubusercontent.com/*',
160
+ ];
161
+
162
+ try {
163
+ chrome.webRequest.onBeforeSendHeaders.addListener(
164
+ hack_referer_header,
165
+ {
166
+ urls,
167
+ },
168
+ ['requestHeaders', 'blocking', 'extraHeaders']
169
+ );
170
+ } catch (err) {
171
+ // before chrome v72, extraHeader is not supported
172
+ chrome.webRequest.onBeforeSendHeaders.addListener(
173
+ hack_referer_header,
174
+ {
175
+ urls,
176
+ },
177
+ ['requestHeaders', 'blocking']
178
+ );
179
+ }
180
+
181
+ /**
182
+ * Get tokens.
183
+ */
184
+
185
+ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
186
+ if (request.type !== 'code') {
187
+ return;
188
+ }
189
+
190
+ GithubClient.github.handleCallback(request.code);
191
+ sendResponse();
192
+ });
193
+
194
+ // at end of background.js
195
+ chrome.commands.onCommand.addListener((command) => {
196
+ const [viewWindow] = chrome.extension
197
+ .getViews()
198
+ .filter((p) => p.location.href.endsWith('listen1.html'));
199
+
200
+ switch (command) {
201
+ case 'play_next':
202
+ viewWindow.document.querySelector('.li-next').click();
203
+ break;
204
+ case 'play_prev':
205
+ viewWindow.document.querySelector('.li-previous').click();
206
+ break;
207
+ case 'play_pause':
208
+ viewWindow.document.querySelector('.play').click();
209
+ break;
210
+ default:
211
+ // console.log('不支持的快捷键')
212
+ }
213
+ });
js/bridge.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable no-unused-vars */
2
+ /*
3
+ build a bridge between UI and audio player
4
+
5
+ audio player has 2 modes, but share same protocol: front and background.
6
+
7
+ * front: audio player and UI are in same environment
8
+ * background: audio player is in background page.
9
+
10
+ */
11
+
12
+ function getFrontPlayer() {
13
+ return window.threadPlayer;
14
+ }
15
+
16
+ function getBackgroundPlayer() {
17
+ return chrome.extension.getBackgroundPage().threadPlayer;
18
+ }
19
+
20
+ function getBackgroundPlayerAsync(callback) {
21
+ (chrome || browser).runtime.getBackgroundPage((w) => {
22
+ callback(w.threadPlayer);
23
+ });
24
+ }
25
+
26
+ function getPlayer(mode) {
27
+ if (mode === 'front') {
28
+ return getFrontPlayer();
29
+ }
30
+ if (mode === 'background') {
31
+ return getBackgroundPlayer();
32
+ }
33
+ return undefined;
34
+ }
35
+
36
+ function getPlayerAsync(mode, callback) {
37
+ if (mode === 'front') {
38
+ const player = getFrontPlayer();
39
+ return callback(player);
40
+ }
41
+ if (mode === 'background') {
42
+ return getBackgroundPlayerAsync(callback);
43
+ }
44
+ return undefined;
45
+ }
46
+ const frontPlayerListener = [];
47
+ function addFrontPlayerListener(listener) {
48
+ frontPlayerListener.push(listener);
49
+ }
50
+
51
+ function addBackgroundPlayerListener(listener) {
52
+ return (chrome || browser).runtime.onMessage.addListener(
53
+ (msg, sender, res) => {
54
+ if (!msg.type.startsWith('BG_PLAYER:')) {
55
+ return null;
56
+ }
57
+ return listener(msg, sender, res);
58
+ },
59
+ );
60
+ }
61
+
62
+ function addPlayerListener(mode, listener) {
63
+ if (mode === 'front') {
64
+ return addFrontPlayerListener(listener);
65
+ }
66
+ if (mode === 'background') {
67
+ return addBackgroundPlayerListener(listener);
68
+ }
69
+ return null;
70
+ }
71
+
72
+ function frontPlayerSendMessage(message) {
73
+ if (frontPlayerListener !== []) {
74
+ frontPlayerListener.forEach((listener) => {
75
+ listener(message);
76
+ });
77
+ }
78
+ }
79
+
80
+ function backgroundPlayerSendMessage(message) {
81
+ (chrome || browser).runtime.sendMessage(message);
82
+ }
83
+
84
+ function playerSendMessage(mode, message) {
85
+ if (mode === 'front') {
86
+ frontPlayerSendMessage(message);
87
+ }
88
+ if (mode === 'background') {
89
+ backgroundPlayerSendMessage(message);
90
+ }
91
+ }
js/controller/auth.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable import/no-unresolved */
2
+ /* eslint-disable global-require */
3
+ /* global angular MediaService isElectron require */
4
+ angular.module('listenone').controller('AuthController', [
5
+ '$scope',
6
+ ($scope) => {
7
+ $scope.loginProgress = false;
8
+ $scope.loginType = 'email';
9
+ $scope.loginSourceList = MediaService.getLoginProviders().map(
10
+ (i) => i.name
11
+ );
12
+ $scope.refreshAuthStatus = () => {
13
+ $scope.loginSourceList.map((source) =>
14
+ MediaService.getUser(source).success((data) => {
15
+ if (data.status === 'success') {
16
+ $scope.setMusicAuth(source, data.data);
17
+ } else {
18
+ $scope.setMusicAuth(source, {});
19
+ }
20
+ })
21
+ );
22
+ };
23
+
24
+ $scope.logout = (source) => {
25
+ $scope.setMusicAuth(source, {});
26
+ MediaService.logout(source);
27
+ };
28
+
29
+ $scope.is_login = (source) =>
30
+ $scope.musicAuth[source] && $scope.musicAuth[source].is_login;
31
+
32
+ $scope.musicAuth = {};
33
+
34
+ $scope.setMusicAuth = (source, data) => {
35
+ $scope.musicAuth[source] = data;
36
+ };
37
+
38
+ $scope.getLoginUrl = (source) => MediaService.getLoginUrl(source);
39
+
40
+ $scope.openLogin = (source) => {
41
+ const url = $scope.getLoginUrl(source);
42
+ if (isElectron()) {
43
+ const { ipcRenderer } = require('electron');
44
+ return ipcRenderer.send('openUrl', url);
45
+ }
46
+ return window.open(url, '_blank');
47
+ };
48
+ },
49
+ ]);
js/controller/instant_search.js ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable no-param-reassign */
2
+ /* global angular i18next MediaService sourceList */
3
+ angular.module('listenone').controller('InstantSearchController', [
4
+ '$scope',
5
+ '$timeout',
6
+ '$rootScope',
7
+ ($scope, $timeout, $rootScope) => {
8
+ $scope.originpagelog = { allmusic: 1 };
9
+ sourceList.forEach((i) => {
10
+ $scope.originpagelog[i.name] = 1;
11
+ });
12
+ $scope.sourceList = sourceList.filter((i) => i.searchable !== false);
13
+ $scope.tab = sourceList[0].name;
14
+ $scope.keywords = '';
15
+ $scope.loading = false;
16
+ $scope.curpagelog = { ...$scope.originpagelog };
17
+ $scope.totalpagelog = { ...$scope.originpagelog };
18
+ $scope.curpage = 1;
19
+ $scope.totalpage = 1;
20
+ $scope.searchType = 0;
21
+
22
+ function updateCurrentPage(cp) {
23
+ if (cp === -1) {
24
+ // when search words changes,pagenums should be reset.
25
+ $scope.curpagelog = { ...$scope.originpagelog };
26
+ $scope.curpage = 1;
27
+ } else if (cp >= 0) {
28
+ $scope.curpagelog[$scope.tab] = cp;
29
+ $scope.curpage = $scope.curpagelog[$scope.tab];
30
+ } else {
31
+ // only tab changed
32
+ $scope.curpage = $scope.curpagelog[$scope.tab];
33
+ }
34
+ }
35
+
36
+ function updateTotalPage(totalItem) {
37
+ if (totalItem === -1) {
38
+ $scope.totalpagelog = { ...$scope.originpagelog };
39
+ $scope.totalpage = 1;
40
+ } else if (totalItem >= 0) {
41
+ $scope.totalpage = Math.ceil(totalItem / 20);
42
+ $scope.totalpagelog[$scope.tab] = $scope.totalpage;
43
+ } else {
44
+ // just switch tab
45
+ $scope.totalpage = $scope.totalpagelog[$scope.tab];
46
+ }
47
+ }
48
+
49
+ function performSearch() {
50
+ $rootScope.$broadcast('search:keyword_change', $scope.keywords);
51
+ MediaService.search($scope.tab, {
52
+ keywords: $scope.keywords,
53
+ curpage: $scope.curpage,
54
+ type: $scope.searchType,
55
+ }).success((data) => {
56
+ // update the textarea
57
+ data.result.forEach((r) => {
58
+ r.sourceName = i18next.t(r.source);
59
+ });
60
+ $scope.result = data.result;
61
+ updateTotalPage(data.total);
62
+ $scope.loading = false;
63
+ // scroll back to top when finish searching
64
+ document.querySelector('.site-wrapper-innerd').scrollTo({ top: 0 });
65
+ });
66
+ }
67
+
68
+ $scope.changeSourceTab = (newTab) => {
69
+ $scope.loading = true;
70
+ $scope.tab = newTab;
71
+ $scope.result = [];
72
+ updateCurrentPage();
73
+ updateTotalPage();
74
+
75
+ if ($scope.keywords === '') {
76
+ $scope.loading = false;
77
+ } else {
78
+ performSearch();
79
+ }
80
+ };
81
+
82
+ $scope.changeSearchType = (newSearchType) => {
83
+ $scope.loading = true;
84
+ $scope.searchType = newSearchType;
85
+ $scope.result = [];
86
+ updateCurrentPage();
87
+ updateTotalPage();
88
+
89
+ if ($scope.keywords === '') {
90
+ $scope.loading = false;
91
+ } else {
92
+ performSearch();
93
+ }
94
+ };
95
+ $scope.isActiveTab = (tab) => $scope.tab === tab;
96
+
97
+ $scope.isSearchType = (searchType) => $scope.searchType === searchType;
98
+
99
+ // eslint-disable-next-line consistent-return
100
+ function renderSearchPage() {
101
+ updateCurrentPage(-1);
102
+ updateTotalPage(-1);
103
+ if (!$scope.keywords || $scope.keywords.length === 0) {
104
+ $scope.result = [];
105
+ return 0;
106
+ }
107
+
108
+ performSearch();
109
+ }
110
+
111
+ $scope.$watch('keywords', (tmpStr) => {
112
+ if (tmpStr === $scope.keywords) {
113
+ // if searchStr is still the same..
114
+ // go ahead and retrieve the data
115
+ renderSearchPage();
116
+ }
117
+ });
118
+
119
+ $scope.enterEvent = (e) => {
120
+ const keycode = window.event ? e.keyCode : e.which;
121
+ if (keycode === 13) {
122
+ // enter key
123
+ renderSearchPage();
124
+ }
125
+ };
126
+
127
+ $scope.nextPage = () => {
128
+ $scope.curpagelog[$scope.tab] += 1;
129
+ $scope.curpage = $scope.curpagelog[$scope.tab];
130
+ performSearch();
131
+ };
132
+
133
+ $scope.previousPage = () => {
134
+ $scope.curpagelog[$scope.tab] -= 1;
135
+ $scope.curpage = $scope.curpagelog[$scope.tab];
136
+ performSearch();
137
+ };
138
+ },
139
+ ]);
js/controller/my_playlist.js ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable no-unused-vars */
2
+ /* global angular MediaService */
3
+
4
+ angular.module('listenone').controller('MyPlayListController', [
5
+ '$scope',
6
+ '$timeout',
7
+ ($scope, $timeout) => {
8
+ $scope.myplaylists = [];
9
+ $scope.favoriteplaylists = [];
10
+
11
+ $scope.loadMyPlaylist = () => {
12
+ MediaService.showMyPlaylist().success((data) => {
13
+ $scope.$evalAsync(() => {
14
+ $scope.myplaylists = data.result;
15
+ });
16
+ });
17
+ };
18
+
19
+ $scope.loadFavoritePlaylist = () => {
20
+ MediaService.showFavPlaylist().success((data) => {
21
+ $scope.$evalAsync(() => {
22
+ $scope.favoriteplaylists = data.result;
23
+ });
24
+ });
25
+ };
26
+
27
+ $scope.$watch('current_tag', (newValue, oldValue) => {
28
+ if (newValue !== oldValue) {
29
+ if (newValue === '1') {
30
+ $scope.myplaylists = [];
31
+ $scope.loadMyPlaylist();
32
+ }
33
+ }
34
+ });
35
+ $scope.$on('myplaylist:update', (event, data) => {
36
+ $scope.loadMyPlaylist();
37
+ });
38
+
39
+ $scope.$on('favoriteplaylist:update', (event, data) => {
40
+ $scope.loadFavoritePlaylist();
41
+ });
42
+ },
43
+ ]);
js/controller/navigation.js ADDED
@@ -0,0 +1,680 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable import/no-unresolved */
2
+ /* eslint-disable global-require */
3
+ /* eslint-disable no-shadow */
4
+ /* eslint-disable no-unused-vars */
5
+ /* eslint-disable no-param-reassign */
6
+ /* global angular notyf i18next MediaService l1Player hotkeys isElectron require GithubClient lastfm */
7
+
8
+ // control main view of page, it can be called any place
9
+ angular.module('listenone').controller('NavigationController', [
10
+ '$scope',
11
+ '$timeout',
12
+ '$rootScope',
13
+ ($scope, $timeout, $rootScope) => {
14
+ $rootScope.page_title = { title: 'Listen 1', artist: '', status: '' }; // eslint-disable-line no-param-reassign
15
+ $scope.window_url_stack = [];
16
+ $scope.window_poped_url_stack = [];
17
+ $scope.current_tag = 2;
18
+ $scope.is_window_hidden = 1;
19
+ $scope.is_dialog_hidden = 1;
20
+ $scope.tag_params = {};
21
+
22
+ $scope.songs = [];
23
+ $scope.current_list_id = -1;
24
+
25
+ $scope.dialog_song = '';
26
+ $scope.dialog_type = 0;
27
+ $scope.dialog_title = '';
28
+
29
+ $scope.isDoubanLogin = false;
30
+
31
+ $scope.lastfm = lastfm;
32
+
33
+ $scope.$on('isdoubanlogin:update', (event, data) => {
34
+ $scope.isDoubanLogin = data;
35
+ });
36
+
37
+ // tag
38
+ $scope.showTag = (tag_id, tag_params) => {
39
+ $scope.current_tag = tag_id;
40
+ $scope.is_window_hidden = 1;
41
+ $scope.window_url_stack = [];
42
+ $scope.window_poped_url_stack = [];
43
+ $scope.tag_params = tag_params;
44
+ if (tag_id === 6) {
45
+ $rootScope.$broadcast('myplatform:update', tag_params.user);
46
+ }
47
+ $scope.closeWindow();
48
+ };
49
+
50
+ $scope.$on('search:keyword_change', (event, data) => {
51
+ $scope.showTag(3);
52
+ });
53
+
54
+ // playlist window
55
+ $scope.resetWindow = (offset) => {
56
+ if (offset === undefined) {
57
+ offset = 0;
58
+ }
59
+ $scope.cover_img_url = 'images/loading.svg';
60
+ $scope.playlist_title = '';
61
+ $scope.playlist_source_url = '';
62
+ $scope.songs = [];
63
+ $scope.window_type = 'list';
64
+ $timeout(() => {
65
+ document.getElementsByClassName('browser')[0].scrollTop = offset;
66
+ }, 0);
67
+ };
68
+
69
+ $scope.closeWindow = (offset) => {
70
+ if (offset === undefined) {
71
+ offset = 0;
72
+ }
73
+ $scope.is_window_hidden = 1;
74
+ $scope.resetWindow(offset);
75
+ $scope.window_url_stack = [];
76
+ $scope.window_poped_url_stack = [];
77
+ };
78
+
79
+ function refreshWindow(url, offset = 0) {
80
+ if (url === '/now_playing') {
81
+ $scope.window_type = 'track';
82
+ return;
83
+ }
84
+ const listId = new URL(url, window.location).searchParams.get('list_id');
85
+ MediaService.getPlaylist(listId).success((data) => {
86
+ $scope.songs = data.tracks;
87
+ $scope.list_id = data.info.id;
88
+ $scope.cover_img_url = data.info.cover_img_url;
89
+ $scope.playlist_title = data.info.title;
90
+ $scope.playlist_source_url = data.info.source_url;
91
+ $scope.is_mine = data.info.id.slice(0, 2) === 'my';
92
+ $scope.is_local = data.info.id.slice(0, 2) === 'lm';
93
+ $timeout(() => {
94
+ document.getElementsByClassName('browser')[0].scrollTop = offset;
95
+ }, 0);
96
+ });
97
+ }
98
+ $scope.popWindow = () => {
99
+ if ($scope.window_url_stack.length === 0) {
100
+ return;
101
+ }
102
+ let poped = $scope.window_url_stack.pop();
103
+ if ($scope.getCurrentUrl() === '/now_playing') {
104
+ poped = $scope.window_url_stack.pop();
105
+ }
106
+ $scope.window_poped_url_stack.push(poped.url);
107
+ if ($scope.window_url_stack.length === 0) {
108
+ $scope.closeWindow(poped.offset);
109
+ } else {
110
+ $scope.resetWindow(poped.offset);
111
+ const lastWindow = $scope.window_url_stack.slice(-1)[0];
112
+ refreshWindow(lastWindow.url, poped.offset);
113
+ }
114
+ };
115
+
116
+ $scope.toggleNowPlaying = () => {
117
+ if ($scope.getCurrentUrl() === '/now_playing') {
118
+ $scope.popWindow();
119
+ return;
120
+ }
121
+ // save current scrolltop
122
+ $scope.is_window_hidden = 0;
123
+ $scope.resetWindow();
124
+
125
+ $scope.window_url_stack.push({
126
+ url: '/now_playing',
127
+ offset: document.getElementsByClassName('browser')[0].scrollTop,
128
+ });
129
+ $scope.window_poped_url_stack = [];
130
+
131
+ $scope.window_type = 'track';
132
+ };
133
+
134
+ $scope.forwardWindow = () => {
135
+ if ($scope.window_poped_url_stack.length === 0) {
136
+ return;
137
+ }
138
+
139
+ $scope.resetWindow();
140
+ const url = $scope.window_poped_url_stack.pop();
141
+ $scope.window_url_stack.push({
142
+ url,
143
+ offset: 0,
144
+ });
145
+ refreshWindow(url);
146
+ };
147
+
148
+ $scope.getCurrentUrl = () =>
149
+ ($scope.window_url_stack.slice(-1)[0] || {}).url;
150
+
151
+ $scope.showPlaylist = (list_id, useCache) => {
152
+ $scope.clearFilter();
153
+ const url = `/playlist?list_id=${list_id}`;
154
+ // save current scrolltop
155
+ const offset = document.getElementsByClassName('browser')[0].scrollTop;
156
+ if ($scope.getCurrentUrl() === url) {
157
+ return;
158
+ }
159
+ $scope.is_window_hidden = 0;
160
+ $scope.resetWindow();
161
+
162
+ if ($scope.getCurrentUrl() === '/now_playing') {
163
+ // if now playing is top, pop it
164
+ $scope.window_url_stack.pop();
165
+ }
166
+ $scope.window_url_stack.push({ url, offset });
167
+ $scope.window_poped_url_stack = [];
168
+
169
+ const listId = new URL(url, window.location).searchParams.get('list_id');
170
+ MediaService.getPlaylist(listId, useCache).success((data) => {
171
+ if (data.status === '0') {
172
+ notyf.info(data.reason);
173
+ $scope.popWindow();
174
+ return;
175
+ }
176
+ $scope.songs = data.tracks;
177
+ $scope.cover_img_url = data.info.cover_img_url;
178
+ $scope.playlist_title = data.info.title;
179
+ $scope.playlist_source_url = data.info.source_url;
180
+ $scope.list_id = data.info.id;
181
+ $scope.is_mine = data.info.id.slice(0, 2) === 'my';
182
+ $scope.is_local = data.info.id.slice(0, 2) === 'lm';
183
+
184
+ MediaService.queryPlaylist(data.info.id, 'favorite').success((res) => {
185
+ $scope.is_favorite = res.result;
186
+ });
187
+
188
+ $scope.window_type = 'list';
189
+ });
190
+ };
191
+
192
+ $scope.directplaylist = (list_id) => {
193
+ MediaService.getPlaylist(list_id).success((data) => {
194
+ $scope.songs = data.tracks;
195
+ $scope.current_list_id = list_id;
196
+ l1Player.setNewPlaylist($scope.songs);
197
+ l1Player.play();
198
+ });
199
+ };
200
+
201
+ $scope.showDialog = (dialog_type, data) => {
202
+ $scope.is_dialog_hidden = 0;
203
+ $scope.dialog_data = data;
204
+ const dialogWidth = 400;
205
+ const dialogHeight = 430;
206
+ const left = window.innerWidth / 2 - dialogWidth / 2;
207
+ const top = window.innerHeight / 2 - dialogHeight / 2;
208
+
209
+ $scope.myStyle = {
210
+ left: `${left}px`,
211
+ top: `${top}px`,
212
+ };
213
+ $scope.dialog_type = dialog_type;
214
+ if (dialog_type === 0) {
215
+ $scope.dialog_title = i18next.t('_ADD_TO_PLAYLIST');
216
+ $scope.dialog_song = data;
217
+ MediaService.showMyPlaylist().success((res) => {
218
+ $scope.myplaylist = res.result;
219
+ });
220
+ }
221
+
222
+ // if (dialog_type === 2) {
223
+ // $scope.dialog_title = '登录豆瓣';
224
+ // $scope.dialog_type = 2;
225
+ // }
226
+
227
+ if (dialog_type === 3) {
228
+ $scope.dialog_title = i18next.t('_EDIT_PLAYLIST');
229
+ $scope.dialog_cover_img_url = data.cover_img_url;
230
+ $scope.dialog_playlist_title = data.playlist_title;
231
+ }
232
+ if (dialog_type === 4) {
233
+ $scope.dialog_title = i18next.t('_CONNECT_TO_LASTFM');
234
+ }
235
+ if (dialog_type === 5) {
236
+ $scope.dialog_title = i18next.t('_OPEN_PLAYLIST');
237
+ }
238
+ if (dialog_type === 6) {
239
+ $scope.dialog_title = i18next.t('_IMPORT_PLAYLIST');
240
+ MediaService.showMyPlaylist().success((res) => {
241
+ $scope.myplaylist = res.result;
242
+ });
243
+ }
244
+ if (dialog_type === 7) {
245
+ $scope.dialog_title = i18next.t('_CONNECT_TO_GITHUB');
246
+ }
247
+ if (dialog_type === 8) {
248
+ $scope.dialog_title = i18next.t('_EXPORT_TO_GITHUB_GIST');
249
+ GithubClient.gist.listExistBackup().then(
250
+ (res) => {
251
+ $scope.myBackup = res;
252
+ },
253
+ (err) => {
254
+ $scope.myBackup = [];
255
+ }
256
+ );
257
+ }
258
+ if (dialog_type === 10) {
259
+ $scope.dialog_title = i18next.t('_RECOVER_FROM_GITHUB_GIST');
260
+ GithubClient.gist.listExistBackup().then(
261
+ (res) => {
262
+ $scope.myBackup = res;
263
+ },
264
+ (err) => {
265
+ $scope.myBackup = [];
266
+ }
267
+ );
268
+ }
269
+ if (dialog_type === 11) {
270
+ $scope.dialog_title = i18next.t('_LOGIN');
271
+ }
272
+ if (dialog_type === 12) {
273
+ $scope.dialog_title = i18next.t('_PROXY_CONFIG');
274
+ }
275
+ };
276
+
277
+ $scope.onSidebarPlaylistDrop = (
278
+ playlistType,
279
+ list_id,
280
+ data,
281
+ dataType,
282
+ direction
283
+ ) => {
284
+ if (playlistType === 'my' && dataType === 'application/listen1-song') {
285
+ $scope.addMyPlaylist(list_id, data);
286
+ } else if (
287
+ (playlistType === 'my' &&
288
+ dataType === 'application/listen1-myplaylist') ||
289
+ (playlistType === 'favorite' &&
290
+ dataType === 'application/listen1-favoriteplaylist')
291
+ ) {
292
+ MediaService.insertMyplaylistToMyplaylists(
293
+ playlistType,
294
+ data.info.id,
295
+ list_id,
296
+ direction
297
+ ).success(() => {
298
+ if (playlistType === 'my') {
299
+ $rootScope.$broadcast('myplaylist:update');
300
+ }
301
+ if (playlistType === 'favorite') {
302
+ $rootScope.$broadcast('favoriteplaylist:update');
303
+ }
304
+ });
305
+ }
306
+ };
307
+ $scope.playlistFilter = { key: '' };
308
+
309
+ $scope.clearFilter = () => {
310
+ $scope.playlistFilter.key = '';
311
+ };
312
+ $scope.fieldFilter = (song) => {
313
+ if ($scope.playlistFilter.key === '') {
314
+ return true;
315
+ }
316
+ return (
317
+ song.title.indexOf($scope.playlistFilter.key) > -1 ||
318
+ song.artist.indexOf($scope.playlistFilter.key) > -1 ||
319
+ song.album.indexOf($scope.playlistFilter.key) > -1
320
+ );
321
+ };
322
+ $scope.onPlaylistSongDrop = (list_id, song, data, dataType, direction) => {
323
+ if (dataType === 'application/listen1-song') {
324
+ // insert song
325
+ MediaService.insertTrackToMyPlaylist(
326
+ list_id,
327
+ data,
328
+ song,
329
+ direction
330
+ ).success((playlist) => {
331
+ $scope.closeDialog();
332
+ if (list_id === $scope.list_id) {
333
+ $scope.$evalAsync(() => {
334
+ $scope.songs = playlist.tracks;
335
+ });
336
+ }
337
+ });
338
+ }
339
+ };
340
+
341
+ $scope.onCurrentPlayingSongDrop = (song, data, dataType, direction) => {
342
+ if (dataType === 'application/listen1-song') {
343
+ l1Player.insertTrack(data, song, direction);
344
+ }
345
+ };
346
+
347
+ $scope.addMyPlaylist = (option_id, song) => {
348
+ MediaService.addMyPlaylist(option_id, song).success((playlist) => {
349
+ notyf.success(i18next.t('_ADD_TO_PLAYLIST_SUCCESS'));
350
+ $scope.closeDialog();
351
+ // add to current playing list
352
+ if (option_id === $scope.current_list_id) {
353
+ l1Player.addTrack($scope.dialog_song);
354
+ }
355
+ if (option_id === $scope.list_id) {
356
+ $scope.songs = playlist.tracks;
357
+ }
358
+ });
359
+ };
360
+
361
+ $scope.chooseDialogOption = (option_id) => {
362
+ $scope.addMyPlaylist(option_id, $scope.dialog_song);
363
+ };
364
+
365
+ $scope.newDialogOption = (option) => {
366
+ $scope.dialog_type = option;
367
+ };
368
+
369
+ $scope.cancelNewDialog = (option) => {
370
+ $scope.dialog_type = option;
371
+ };
372
+
373
+ $scope.createAndAddPlaylist = () => {
374
+ MediaService.createMyPlaylist(
375
+ $scope.newlist_title,
376
+ $scope.dialog_song
377
+ ).success(() => {
378
+ $rootScope.$broadcast('myplaylist:update');
379
+ notyf.success(i18next.t('_ADD_TO_PLAYLIST_SUCCESS'));
380
+ $scope.closeDialog();
381
+ });
382
+ };
383
+
384
+ $scope.editMyPlaylist = () => {
385
+ MediaService.editMyPlaylist(
386
+ $scope.list_id,
387
+ $scope.dialog_playlist_title,
388
+ $scope.dialog_cover_img_url
389
+ ).success(() => {
390
+ $rootScope.$broadcast('myplaylist:update');
391
+ $scope.playlist_title = $scope.dialog_playlist_title;
392
+ $scope.cover_img_url = $scope.dialog_cover_img_url;
393
+ notyf.success(i18next.t('_EDIT_PLAYLIST_SUCCESS'));
394
+ $scope.closeDialog();
395
+ });
396
+ };
397
+
398
+ $scope.mergePlaylist = (target_list_id) => {
399
+ notyf.info(i18next.t('_IMPORTING_PLAYLIST'));
400
+ MediaService.mergePlaylist($scope.list_id, target_list_id).success(() => {
401
+ notyf.success(i18next.t('_IMPORTING_PLAYLIST_SUCCESS'));
402
+ $scope.closeDialog();
403
+ $scope.popWindow();
404
+ $scope.showPlaylist($scope.list_id);
405
+ });
406
+ };
407
+
408
+ $scope.removeSongFromPlaylist = (song, list_id) => {
409
+ let removeFunc = null;
410
+ if (list_id.slice(0, 2) === 'my') {
411
+ removeFunc = MediaService.removeTrackFromMyPlaylist;
412
+ } else if (list_id.slice(0, 2) === 'lm') {
413
+ removeFunc = MediaService.removeTrackFromPlaylist;
414
+ }
415
+
416
+ removeFunc(list_id, song.id).success(() => {
417
+ // remove song from songs
418
+ const index = $scope.songs.indexOf(song);
419
+ if (index > -1) {
420
+ $scope.songs.splice(index, 1);
421
+ }
422
+ notyf.success(i18next.t('_REMOVE_SONG_FROM_PLAYLIST_SUCCESS'));
423
+ });
424
+ };
425
+
426
+ $scope.closeDialog = () => {
427
+ $scope.is_dialog_hidden = 1;
428
+ $scope.dialog_type = 0;
429
+ // update lastfm status if not authorized
430
+ if (lastfm.isAuthRequested()) {
431
+ lastfm.updateStatus();
432
+ }
433
+ };
434
+
435
+ $scope.setCurrentList = (list_id) => {
436
+ $scope.current_list_id = list_id;
437
+ };
438
+
439
+ $scope.playMylist = (list_id) => {
440
+ l1Player.setNewPlaylist($scope.songs);
441
+ l1Player.play();
442
+ $scope.setCurrentList(list_id);
443
+ };
444
+
445
+ $scope.addMylist = (list_id) => {
446
+ $timeout(() => {
447
+ // add songs to playlist
448
+ l1Player.addTracks($scope.songs);
449
+ notyf.success(i18next.t('_ADD_TO_QUEUE_SUCCESS'));
450
+ }, 0);
451
+ };
452
+
453
+ $scope.clonePlaylist = (list_id) => {
454
+ MediaService.clonePlaylist(list_id, 'my').success(() => {
455
+ $rootScope.$broadcast('myplaylist:update');
456
+ $scope.closeWindow();
457
+ notyf.success(i18next.t('_ADD_TO_PLAYLIST_SUCCESS'));
458
+ });
459
+ };
460
+
461
+ $scope.removeMyPlaylist = (list_id) => {
462
+ MediaService.removeMyPlaylist(list_id, 'my').success(() => {
463
+ $rootScope.$broadcast('myplaylist:update');
464
+ $scope.closeDialog();
465
+ $scope.closeWindow();
466
+ notyf.success(i18next.t('_REMOVE_PLAYLIST_SUCCESS'));
467
+ });
468
+ };
469
+
470
+ $scope.downloadFile = (fileName, fileType, content) => {
471
+ window.URL = window.URL || window.webkitURL;
472
+ const blob = new Blob([content], {
473
+ type: fileType,
474
+ });
475
+ const link = document.createElement('a');
476
+ link.download = fileName;
477
+ link.href = window.URL.createObjectURL(blob);
478
+ link.style.display = 'none';
479
+ document.body.appendChild(link);
480
+ link.click();
481
+ link.remove();
482
+ };
483
+
484
+ $scope.backupMySettings = () => {
485
+ const items = {};
486
+ Object.keys(localStorage).forEach((key) => {
487
+ items[key] = localStorage.getObject(key);
488
+ });
489
+
490
+ const content = JSON.stringify(items);
491
+ $scope.downloadFile('listen1_backup.json', 'application/json', content);
492
+ };
493
+
494
+ $scope.importMySettings = (event) => {
495
+ const fileObject = event.target.files[0];
496
+ if (fileObject === null) {
497
+ notyf.warning('请选择备份文件');
498
+ return;
499
+ }
500
+ const reader = new FileReader();
501
+ reader.onloadend = (readerEvent) => {
502
+ if (readerEvent.target.readyState === FileReader.DONE) {
503
+ const data_json = readerEvent.target.result;
504
+ // parse json
505
+ let data = null;
506
+ try {
507
+ data = JSON.parse(data_json);
508
+ } catch (e) {
509
+ notyf.warning('备份文件格式错误,请重新选择');
510
+ return;
511
+ }
512
+
513
+ Object.keys(data).forEach((item) =>
514
+ localStorage.setObject(item, data[item])
515
+ );
516
+ $rootScope.$broadcast('myplaylist:update');
517
+ notyf.success('成功导入我的歌单');
518
+ }
519
+ };
520
+ reader.readAsText(fileObject);
521
+ };
522
+
523
+ $scope.gistBackupLoading = false;
524
+ $scope.backupMySettings2Gist = (gistId, isPublic) => {
525
+ const items = {};
526
+ Object.keys(localStorage).forEach((key) => {
527
+ if (key !== 'gistid' && key !== 'githubOauthAccessKey') {
528
+ // avoid token leak
529
+ items[key] = localStorage.getObject(key);
530
+ }
531
+ });
532
+ const gistFiles = GithubClient.gist.json2gist(items);
533
+ $scope.gistBackupLoading = true;
534
+ GithubClient.gist.backupMySettings2Gist(gistFiles, gistId, isPublic).then(
535
+ () => {
536
+ notyf.dismissAll();
537
+ notyf.success('成功导出我的歌单到Gist');
538
+ $scope.gistBackupLoading = false;
539
+ },
540
+ (err) => {
541
+ notyf.dismissAll();
542
+ notyf.warning('导出我的歌单失败,检查后重试');
543
+ $scope.gistBackupLoading = false;
544
+ }
545
+ );
546
+ notyf.info('正在导出我的歌单到Gist...');
547
+ };
548
+
549
+ $scope.gistRestoreLoading = false;
550
+ $scope.importMySettingsFromGist = (gistId) => {
551
+ $scope.gistRestoreLoading = true;
552
+ GithubClient.gist.importMySettingsFromGist(gistId).then(
553
+ (raw) => {
554
+ GithubClient.gist.gist2json(raw, (data) => {
555
+ Object.keys(data).forEach((item) =>
556
+ localStorage.setObject(item, data[item])
557
+ );
558
+ notyf.dismissAll();
559
+ notyf.success('导入我的歌单成功');
560
+ $scope.gistRestoreLoading = false;
561
+ $rootScope.$broadcast('myplaylist:update');
562
+ });
563
+ },
564
+ (err) => {
565
+ notyf.dismissAll();
566
+ if (err === 404) {
567
+ notyf.warning('未找到备份歌单,请先备份');
568
+ } else {
569
+ notyf.warning('导入我的歌单失败,检查后重试');
570
+ }
571
+ $scope.gistRestoreLoading = false;
572
+ }
573
+ );
574
+ notyf.info('正在从Gist导入我的歌单...');
575
+ };
576
+
577
+ $scope.showShortcuts = () => {};
578
+
579
+ // description: '快速搜索',
580
+ hotkeys('f', () => {
581
+ $scope.showTag(3);
582
+ $timeout(() => {
583
+ document.getElementById('search-input').focus();
584
+ }, 0);
585
+ });
586
+
587
+ $scope.openUrl = (url) => {
588
+ MediaService.parseURL(url).success((data) => {
589
+ const { result } = data;
590
+ if (result !== undefined) {
591
+ $scope.showPlaylist(result.id);
592
+ } else {
593
+ notyf.info(i18next.t('_FAIL_OPEN_PLAYLIST_URL'));
594
+ }
595
+ });
596
+ };
597
+
598
+ $scope.favoritePlaylist = (list_id) => {
599
+ if ($scope.is_favorite) {
600
+ $scope.removeFavoritePlaylist(list_id);
601
+ $scope.is_favorite = 0;
602
+ } else {
603
+ $scope.addFavoritePlaylist(list_id);
604
+ $scope.is_favorite = 1;
605
+ }
606
+ };
607
+ $scope.addFavoritePlaylist = (list_id) => {
608
+ MediaService.clonePlaylist(list_id, 'favorite').success((addResult) => {
609
+ $rootScope.$broadcast('favoriteplaylist:update');
610
+ notyf.success(i18next.t('_FAVORITE_PLAYLIST_SUCCESS'));
611
+ });
612
+ };
613
+
614
+ $scope.removeFavoritePlaylist = (list_id) => {
615
+ MediaService.removeMyPlaylist(list_id, 'favorite').success(() => {
616
+ $rootScope.$broadcast('favoriteplaylist:update');
617
+ // $scope.closeWindow();
618
+ notyf.success(i18next.t('_UNFAVORITE_PLAYLIST_SUCCESS'));
619
+ });
620
+ };
621
+
622
+ $scope.addLocalMusic = (list_id) => {
623
+ if (isElectron()) {
624
+ const { remote } = require('electron');
625
+ const remoteFunctions = remote.require('./functions.js');
626
+ remote.dialog
627
+ .showOpenDialog({
628
+ title: '添加歌曲',
629
+ properties: ['openFile', 'multiSelections'],
630
+ filters: [
631
+ {
632
+ name: 'Music Files',
633
+ extensions: ['mp3', 'flac', 'ape'],
634
+ },
635
+ ],
636
+ })
637
+ .then((result) => {
638
+ if (result.canceled) {
639
+ return;
640
+ }
641
+
642
+ result.filePaths.forEach((fp) => {
643
+ remoteFunctions.readAudioTags(fp).then((md) => {
644
+ const track = {
645
+ id: `lmtrack_${fp}`,
646
+ title: md.common.title,
647
+ artist: md.common.artist,
648
+ artist_id: `lmartist_${md.common.artist}`,
649
+ album: md.common.album,
650
+ album_id: `lmalbum_${md.common.album}`,
651
+ source: 'localmusic',
652
+ source_url: '',
653
+ img_url: '',
654
+ lyrics: md.common.lyrics,
655
+ // url: "lmtrack_"+fp,
656
+ sound_url: `file://${fp}`,
657
+ };
658
+
659
+ const list_id = 'lmplaylist_reserve';
660
+ MediaService.addPlaylist(list_id, [track]).success((res) => {
661
+ const { playlist } = res;
662
+ $scope.songs = playlist.tracks;
663
+ $scope.list_id = playlist.info.id;
664
+ $scope.cover_img_url = playlist.info.cover_img_url;
665
+ $scope.playlist_title = playlist.info.title;
666
+ $scope.playlist_source_url = playlist.info.source_url;
667
+ $scope.is_mine = playlist.info.id.slice(0, 2) === 'my';
668
+ $scope.is_local = playlist.info.id.slice(0, 2) === 'lm';
669
+ $scope.$evalAsync();
670
+ });
671
+ });
672
+ });
673
+ })
674
+ .catch((err) => {
675
+ // console.log(err);
676
+ });
677
+ }
678
+ };
679
+ },
680
+ ]);
js/controller/platform.js ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* global angular MediaService */
2
+ const platformSourceList = [
3
+ {
4
+ name: 'my_created_playlist',
5
+ displayId: '_MY_CREATED_PLAYLIST',
6
+ },
7
+ {
8
+ name: 'my_favorite_playlist',
9
+ displayId: '_MY_FAVORITE_PLAYLIST',
10
+ },
11
+ {
12
+ name: 'recommend_playlist',
13
+ displayId: '_RECOMMEND_PLAYLIST',
14
+ },
15
+ ];
16
+ angular.module('listenone').controller('PlatformController', [
17
+ '$scope',
18
+ ($scope) => {
19
+ $scope.myPlatformPlaylists = [];
20
+ $scope.myPlatformUser = {};
21
+ $scope.platformSourceList = platformSourceList;
22
+ $scope.tab = platformSourceList[0].name;
23
+
24
+ $scope.loadPlatformPlaylists = () => {
25
+ if ($scope.myPlatformUser.platform === undefined) {
26
+ return;
27
+ }
28
+ let getPlaylistFn = MediaService.getUserCreatedPlaylist;
29
+ if ($scope.tab === 'recommend_playlist') {
30
+ getPlaylistFn = MediaService.getRecommendPlaylist;
31
+ } else if ($scope.tab === 'my_favorite_playlist') {
32
+ getPlaylistFn = MediaService.getUserFavoritePlaylist;
33
+ }
34
+ const user = $scope.myPlatformUser;
35
+ getPlaylistFn(user.platform, {
36
+ user_id: user.user_id,
37
+ }).success((response) => {
38
+ const { data } = response;
39
+ $scope.myPlatformPlaylists = data.playlists;
40
+ });
41
+ };
42
+
43
+ $scope.initPlatformController = (user) => {
44
+ $scope.tab = platformSourceList[0].name;
45
+ $scope.myPlatformUser = user;
46
+ $scope.loadPlatformPlaylists();
47
+ };
48
+
49
+ $scope.$on('myplatform:update', (event, user) => {
50
+ $scope.initPlatformController(user);
51
+ });
52
+
53
+ $scope.changeTab = (name) => {
54
+ $scope.tab = name;
55
+ $scope.loadPlatformPlaylists();
56
+ };
57
+ },
58
+ ]);
js/controller/play.js ADDED
@@ -0,0 +1,827 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable no-param-reassign */
2
+ /* eslint-disable no-shadow */
3
+ /* eslint-disable import/no-unresolved */
4
+ /* eslint-disable global-require */
5
+ /* global angular notyf i18next MediaService l1Player hotkeys GithubClient isElectron require getLocalStorageValue getPlayer getPlayerAsync addPlayerListener smoothScrollTo lastfm */
6
+
7
+ function getCSSStringFromSetting(setting) {
8
+ let { backgroundAlpha } = setting;
9
+ if (backgroundAlpha === 0) {
10
+ // NOTE: background alpha 0 results total transparent
11
+ // which will cause mouse leave event not trigger
12
+ // correct in windows platform for lyic window if disable
13
+ // hardware accelerate
14
+ backgroundAlpha = 0.01;
15
+ }
16
+ return `div.content.lyric-content{
17
+ font-size: ${setting.fontSize}px;
18
+ color: ${setting.color};
19
+ background: rgba(36, 36, 36, ${backgroundAlpha});
20
+ }
21
+ div.content.lyric-content span.contentTrans {
22
+ font-size: ${setting.fontSize - 4}px;
23
+ }
24
+ `;
25
+ }
26
+
27
+ angular.module('listenone').controller('PlayController', [
28
+ '$scope',
29
+ '$timeout',
30
+ '$log',
31
+ '$anchorScroll',
32
+ '$location',
33
+ '$rootScope',
34
+ ($scope, $timeout, $log, $anchorScroll, $location, $rootScope) => {
35
+ $scope.menuHidden = true;
36
+ $scope.volume = l1Player.status.volume;
37
+ $scope.mute = l1Player.status.muted;
38
+ $scope.settings = {
39
+ playmode: 0,
40
+ nowplaying_track_id: -1,
41
+ };
42
+ $scope.lyricArray = [];
43
+ $scope.lyricLineNumber = -1;
44
+ $scope.lastTrackId = null;
45
+
46
+ $scope.enableGloablShortcut = false;
47
+ $scope.isChrome = !isElectron();
48
+ $scope.isMac = false;
49
+
50
+ $scope.currentDuration = '0:00';
51
+ $scope.currentPosition = '0:00';
52
+
53
+ if (!$scope.isChrome) {
54
+ // eslint-disable-next-line no-undef
55
+ $scope.isMac = process.platform === 'darwin';
56
+ }
57
+
58
+ function switchMode(mode) {
59
+ // playmode 0:loop 1:shuffle 2:repeat one
60
+ switch (mode) {
61
+ case 0:
62
+ l1Player.setLoopMode('all');
63
+ break;
64
+ case 1:
65
+ l1Player.setLoopMode('shuffle');
66
+ break;
67
+ case 2:
68
+ l1Player.setLoopMode('one');
69
+ break;
70
+ default:
71
+ }
72
+ }
73
+
74
+ $scope.loadLocalSettings = () => {
75
+ const defaultSettings = {
76
+ playmode: 0,
77
+ nowplaying_track_id: -1,
78
+ volume: 90,
79
+ };
80
+ const localSettings = localStorage.getObject('player-settings');
81
+ if (localSettings === null) {
82
+ $scope.settings = defaultSettings;
83
+ $scope.saveLocalSettings();
84
+ } else {
85
+ $scope.settings = localSettings;
86
+ }
87
+ // apply settings
88
+ switchMode($scope.settings.playmode);
89
+
90
+ $scope.volume = $scope.settings.volume;
91
+ if ($scope.volume === null) {
92
+ $scope.volume = 90;
93
+ $scope.saveLocalSettings();
94
+ } else {
95
+ l1Player.setVolume($scope.volume);
96
+ }
97
+ $scope.enableGlobalShortCut = localStorage.getObject(
98
+ 'enable_global_shortcut'
99
+ );
100
+ $scope.enableLyricFloatingWindow = localStorage.getObject(
101
+ 'enable_lyric_floating_window'
102
+ );
103
+ $scope.enableLyricTranslation = localStorage.getObject(
104
+ 'enable_lyric_translation'
105
+ );
106
+ $scope.enableLyricFloatingWindowTranslation = localStorage.getObject(
107
+ 'enable_lyric_floating_window_translation'
108
+ );
109
+ $scope.enableAutoChooseSource = getLocalStorageValue(
110
+ 'enable_auto_choose_source',
111
+ true
112
+ );
113
+ $scope.autoChooseSourceList = getLocalStorageValue(
114
+ 'auto_choose_source_list',
115
+ ['kuwo', 'qq', 'migu']
116
+ );
117
+ $scope.enableStopWhenClose =
118
+ isElectron() || getLocalStorageValue('enable_stop_when_close', true);
119
+ $scope.enableNowplayingCoverBackground = getLocalStorageValue(
120
+ 'enable_nowplaying_cover_background',
121
+ false
122
+ );
123
+ $scope.enableNowplayingBitrate = getLocalStorageValue(
124
+ 'enable_nowplaying_bitrate',
125
+ false
126
+ );
127
+ $scope.enableNowplayingPlatform = getLocalStorageValue(
128
+ 'enable_nowplaying_platform',
129
+ false
130
+ );
131
+
132
+ const defaultFloatWindowSetting = {
133
+ fontSize: 20,
134
+ color: '#ffffff',
135
+ backgroundAlpha: 0.2,
136
+ };
137
+
138
+ $scope.floatWindowSetting = getLocalStorageValue(
139
+ 'float_window_setting',
140
+ defaultFloatWindowSetting
141
+ );
142
+
143
+ $scope.applyGlobalShortcut();
144
+ $scope.openLyricFloatingWindow();
145
+ };
146
+
147
+ // electron global shortcuts
148
+ $scope.applyGlobalShortcut = (toggle) => {
149
+ if (!isElectron()) {
150
+ return;
151
+ }
152
+ let message = '';
153
+ if (toggle === true) {
154
+ $scope.enableGlobalShortCut = !$scope.enableGlobalShortCut;
155
+ }
156
+ if ($scope.enableGlobalShortCut === true) {
157
+ message = 'enable_global_shortcut';
158
+ } else {
159
+ message = 'disable_global_shortcut';
160
+ }
161
+
162
+ // check if globalShortcuts is allowed
163
+ localStorage.setObject(
164
+ 'enable_global_shortcut',
165
+ $scope.enableGlobalShortCut
166
+ );
167
+
168
+ const { ipcRenderer } = require('electron');
169
+ ipcRenderer.send('control', message);
170
+ };
171
+
172
+ $scope.openLyricFloatingWindow = (toggle) => {
173
+ if (!isElectron()) {
174
+ return;
175
+ }
176
+ let message = '';
177
+ if (toggle === true) {
178
+ $scope.enableLyricFloatingWindow = !$scope.enableLyricFloatingWindow;
179
+ }
180
+ if ($scope.enableLyricFloatingWindow === true) {
181
+ message = 'enable_lyric_floating_window';
182
+ } else {
183
+ message = 'disable_lyric_floating_window';
184
+ }
185
+ localStorage.setObject(
186
+ 'enable_lyric_floating_window',
187
+ $scope.enableLyricFloatingWindow
188
+ );
189
+ const { ipcRenderer } = require('electron');
190
+ ipcRenderer.send(
191
+ 'control',
192
+ message,
193
+ getCSSStringFromSetting($scope.floatWindowSetting)
194
+ );
195
+ };
196
+
197
+ if (isElectron()) {
198
+ const { webFrame, ipcRenderer } = require('electron');
199
+ // webFrame.setVisualZoomLevelLimits(1, 3);
200
+ ipcRenderer.on('setZoomLevel', (event, level) => {
201
+ webFrame.setZoomLevel(level);
202
+ });
203
+ ipcRenderer.on('lyricWindow', (event, arg) => {
204
+ if (arg === 'float_window_close') {
205
+ $scope.openLyricFloatingWindow(true);
206
+ } else if (
207
+ arg === 'float_window_font_small' ||
208
+ arg === 'float_window_font_large'
209
+ ) {
210
+ const MIN_FONT_SIZE = 12;
211
+ const MAX_FONT_SIZE = 50;
212
+ const offset = arg === 'float_window_font_small' ? -1 : 1;
213
+ $scope.floatWindowSetting.fontSize += offset;
214
+ if ($scope.floatWindowSetting.fontSize < MIN_FONT_SIZE) {
215
+ $scope.floatWindowSetting.fontSize = MIN_FONT_SIZE;
216
+ } else if ($scope.floatWindowSetting.fontSize > MAX_FONT_SIZE) {
217
+ $scope.floatWindowSetting.fontSize = MAX_FONT_SIZE;
218
+ }
219
+ } else if (
220
+ arg === 'float_window_background_light' ||
221
+ arg === 'float_window_background_dark'
222
+ ) {
223
+ const MIN_BACKGROUND_ALPHA = 0;
224
+ const MAX_BACKGROUND_ALPHA = 1;
225
+ const offset = arg === 'float_window_background_light' ? -0.1 : 0.1;
226
+ $scope.floatWindowSetting.backgroundAlpha += offset;
227
+ if (
228
+ $scope.floatWindowSetting.backgroundAlpha < MIN_BACKGROUND_ALPHA
229
+ ) {
230
+ $scope.floatWindowSetting.backgroundAlpha = MIN_BACKGROUND_ALPHA;
231
+ } else if (
232
+ $scope.floatWindowSetting.backgroundAlpha > MAX_BACKGROUND_ALPHA
233
+ ) {
234
+ $scope.floatWindowSetting.backgroundAlpha = MAX_BACKGROUND_ALPHA;
235
+ }
236
+ } else if (arg === 'float_window_font_change_color') {
237
+ const floatWindowlyricColors = [
238
+ '#ffffff',
239
+ '#65d29f',
240
+ '#3c87eb',
241
+ '#ec63af',
242
+ '#4f5455',
243
+ '#eb605b',
244
+ ];
245
+ const currentIndex = floatWindowlyricColors.indexOf(
246
+ $scope.floatWindowSetting.color
247
+ );
248
+ const nextIndex = (currentIndex + 1) % floatWindowlyricColors.length;
249
+ $scope.floatWindowSetting.color = floatWindowlyricColors[nextIndex];
250
+ }
251
+ localStorage.setObject(
252
+ 'float_window_setting',
253
+ $scope.floatWindowSetting
254
+ );
255
+ const { ipcRenderer } = require('electron');
256
+ const message = 'update_lyric_floating_window_css';
257
+ ipcRenderer.send(
258
+ 'control',
259
+ message,
260
+ getCSSStringFromSetting($scope.floatWindowSetting)
261
+ );
262
+ });
263
+ }
264
+
265
+ $scope.saveLocalSettings = () => {
266
+ localStorage.setObject('player-settings', $scope.settings);
267
+ };
268
+
269
+ $scope.changePlaymode = () => {
270
+ const playmodeCount = 3;
271
+ $scope.settings.playmode = ($scope.settings.playmode + 1) % playmodeCount;
272
+ switchMode($scope.settings.playmode);
273
+ $scope.saveLocalSettings();
274
+ };
275
+
276
+ $rootScope.openGithubAuth = GithubClient.github.openAuthUrl;
277
+ $rootScope.GithubLogout = () => {
278
+ GithubClient.github.logout();
279
+ $scope.$evalAsync(() => {
280
+ $scope.githubStatus = 0;
281
+ $scope.githubStatusText = GithubClient.github.getStatusText();
282
+ });
283
+ };
284
+ $rootScope.updateGithubStatus = () => {
285
+ GithubClient.github.updateStatus((data) => {
286
+ $scope.$evalAsync(() => {
287
+ $scope.githubStatus = data;
288
+ $scope.githubStatusText = GithubClient.github.getStatusText();
289
+ });
290
+ });
291
+ };
292
+
293
+ $scope.togglePlaylist = () => {
294
+ const anchor = `song${l1Player.status.playing.id}`;
295
+ $scope.menuHidden = !$scope.menuHidden;
296
+ if (!$scope.menuHidden) {
297
+ $anchorScroll(anchor);
298
+ }
299
+ };
300
+
301
+ $scope.toggleMuteStatus = () => {
302
+ // mute function is indeed toggle mute status.
303
+ l1Player.toggleMute();
304
+ };
305
+
306
+ $scope.myProgress = 0;
307
+ $scope.changingProgress = false;
308
+
309
+ $scope.copyrightNotice = () => {
310
+ notyf.info(i18next.t('_COPYRIGHT_ISSUE'), true);
311
+ };
312
+ $scope.failAllNotice = () => {
313
+ notyf.warning(i18next.t('_FAIL_ALL_NOTICE'), true);
314
+ };
315
+ $rootScope.$on('track:myprogress', (event, data) => {
316
+ $scope.$evalAsync(() => {
317
+ // should use apply to force refresh ui
318
+ $scope.myProgress = data;
319
+ });
320
+ });
321
+
322
+ function parseLyric(lyric, tlyric) {
323
+ const lines = lyric.split('\n');
324
+ let result = [];
325
+ const timeResult = [];
326
+
327
+ if (typeof tlyric !== 'string') {
328
+ tlyric = '';
329
+ }
330
+ const linesTrans = tlyric.split('\n');
331
+ const resultTrans = [];
332
+ const timeResultTrans = [];
333
+ if (tlyric === '') {
334
+ linesTrans.splice(0);
335
+ }
336
+
337
+ function rightPadding(str, length, padChar) {
338
+ const newstr = str + new Array(length - str.length + 1).join(padChar);
339
+ return newstr;
340
+ }
341
+
342
+ const process =
343
+ (result, timeResult, translationFlag) => (line, index) => {
344
+ const tagReg = /\[\D*:([^\]]+)\]/g;
345
+ const tagRegResult = tagReg.exec(line);
346
+ if (tagRegResult) {
347
+ const lyricObject = {};
348
+ lyricObject.seconds = 0;
349
+ [lyricObject.content] = tagRegResult;
350
+ result.push(lyricObject);
351
+ return;
352
+ }
353
+
354
+ const timeReg = /\[(\d{2,})\:(\d{2})(?:\.(\d{1,3}))?\]/g; // eslint-disable-line no-useless-escape
355
+
356
+ let timeRegResult = null;
357
+ // eslint-disable-next-line no-cond-assign
358
+ while ((timeRegResult = timeReg.exec(line)) !== null) {
359
+ const htmlUnescapes = {
360
+ '&amp;': '&',
361
+ '&lt;': '<',
362
+ '&gt;': '>',
363
+ '&quot;': '"',
364
+ '&#39;': "'",
365
+ '&apos;': "'",
366
+ };
367
+ timeResult.push({
368
+ content: line
369
+ .replace(timeRegResult[0], '')
370
+ .replace(
371
+ /&(?:amp|lt|gt|quot|#39|apos);/g,
372
+ (match) => htmlUnescapes[match]
373
+ ),
374
+ seconds:
375
+ parseInt(timeRegResult[1], 10) * 60 * 1000 + // min
376
+ parseInt(timeRegResult[2], 10) * 1000 + // sec
377
+ (timeRegResult[3]
378
+ ? parseInt(rightPadding(timeRegResult[3], 3, '0'), 10)
379
+ : 0), // microsec
380
+ translationFlag,
381
+ index,
382
+ });
383
+ }
384
+ };
385
+
386
+ lines.forEach(process(result, timeResult, false));
387
+ linesTrans.forEach(process(resultTrans, timeResultTrans, true));
388
+
389
+ // sort time line
390
+ result = timeResult.concat(timeResultTrans).sort((a, b) => {
391
+ const keyA = a.seconds;
392
+ const keyB = b.seconds;
393
+
394
+ // Compare the 2 dates
395
+ if (keyA < keyB) return -1;
396
+ if (keyA > keyB) return 1;
397
+ if (a.translationFlag !== b.translationFlag) {
398
+ if (a.translationFlag === false) {
399
+ return -1;
400
+ }
401
+ return 1;
402
+ }
403
+ if (a.index < b.index) return -1;
404
+ if (a.index > b.index) return 1;
405
+ return 0;
406
+ });
407
+ // disable tag info, because music provider always write
408
+ // tag info in lyric timeline.
409
+ // result.push.apply(result, timeResult);
410
+ // result = timeResult; // executed up there
411
+
412
+ for (let i = 0; i < result.length; i += 1) {
413
+ result[i].lineNumber = i;
414
+ }
415
+
416
+ return result;
417
+ }
418
+ const mode =
419
+ isElectron() || getLocalStorageValue('enable_stop_when_close', true)
420
+ ? 'front'
421
+ : 'background';
422
+
423
+ getPlayer(mode).setMode(mode);
424
+ if (mode === 'front') {
425
+ if (!isElectron()) {
426
+ // avoid background keep playing when change to front mode
427
+ getPlayerAsync('background', (player) => {
428
+ player.pause();
429
+ });
430
+ }
431
+ }
432
+
433
+ addPlayerListener(mode, (msg, sender, sendResponse) => {
434
+ if (
435
+ typeof msg.type === 'string' &&
436
+ msg.type.split(':')[0] === 'BG_PLAYER'
437
+ ) {
438
+ switch (msg.type.split(':').slice(1).join('')) {
439
+ case 'READY': {
440
+ break;
441
+ }
442
+ case 'PLAY_FAILED': {
443
+ notyf.info(i18next.t('_COPYRIGHT_ISSUE'), true);
444
+ break;
445
+ }
446
+
447
+ case 'VOLUME': {
448
+ $scope.$evalAsync(() => {
449
+ $scope.volume = msg.data;
450
+ });
451
+ break;
452
+ }
453
+
454
+ case 'FRAME_UPDATE': {
455
+ // 'currentTrack:position'
456
+ // update lyric position
457
+ if (!l1Player.status.playing.id) break;
458
+ const currentSeconds = msg.data.pos;
459
+ let lastObject = null;
460
+ let lastObjectTrans = null;
461
+ $scope.lyricArray.forEach((lyric) => {
462
+ if (currentSeconds >= lyric.seconds / 1000) {
463
+ if (lyric.translationFlag !== true) {
464
+ lastObject = lyric;
465
+ } else {
466
+ lastObjectTrans = lyric;
467
+ }
468
+ }
469
+ });
470
+ if (
471
+ lastObject &&
472
+ lastObject.lineNumber !== $scope.lyricLineNumber
473
+ ) {
474
+ const lineElement = document.querySelector(
475
+ `.page .playsong-detail .detail-songinfo .lyric p[data-line="${lastObject.lineNumber}"]`
476
+ );
477
+ const windowHeight = document.querySelector(
478
+ '.page .playsong-detail .detail-songinfo .lyric'
479
+ ).offsetHeight;
480
+
481
+ const adjustOffset = 30;
482
+ const offset =
483
+ lineElement.offsetTop - windowHeight / 2 + adjustOffset;
484
+ smoothScrollTo(document.querySelector('.lyric'), offset, 500);
485
+ $scope.lyricLineNumber = lastObject.lineNumber;
486
+ if (
487
+ lastObjectTrans &&
488
+ lastObjectTrans.lineNumber !== $scope.lyricLineNumberTrans
489
+ ) {
490
+ $scope.lyricLineNumberTrans = lastObjectTrans.lineNumber;
491
+ }
492
+ if (isElectron()) {
493
+ const { ipcRenderer } = require('electron');
494
+ const currentLyric =
495
+ $scope.lyricArray[lastObject.lineNumber].content;
496
+ let currentLyricTrans = '';
497
+ if (
498
+ $scope.enableLyricFloatingWindowTranslation === true &&
499
+ lastObjectTrans
500
+ ) {
501
+ currentLyricTrans =
502
+ $scope.lyricArray[lastObjectTrans.lineNumber].content;
503
+ }
504
+ ipcRenderer.send('currentLyric', {
505
+ lyric: currentLyric,
506
+ tlyric: currentLyricTrans,
507
+ });
508
+ }
509
+ }
510
+
511
+ // 'currentTrack:duration'
512
+ (() => {
513
+ const durationSec = Math.floor(msg.data.duration);
514
+ const durationStr = `${Math.floor(durationSec / 60)}:${`0${
515
+ durationSec % 60
516
+ }`.substr(-2)}`;
517
+ if (
518
+ msg.data.duration === 0 ||
519
+ $scope.currentDuration === durationStr
520
+ ) {
521
+ return;
522
+ }
523
+ $scope.currentDuration = durationStr;
524
+ })();
525
+
526
+ // 'track:progress'
527
+ if ($scope.changingProgress === false) {
528
+ $scope.$evalAsync(() => {
529
+ if (msg.data.duration === 0) {
530
+ $scope.myProgress = 0;
531
+ } else {
532
+ $scope.myProgress = (msg.data.pos / msg.data.duration) * 100;
533
+ }
534
+ const posSec = Math.floor(msg.data.pos);
535
+ const posStr = `${Math.floor(posSec / 60)}:${`0${
536
+ posSec % 60
537
+ }`.substr(-2)}`;
538
+ $scope.currentPosition = posStr;
539
+ });
540
+ }
541
+ break;
542
+ }
543
+
544
+ case 'LOAD': {
545
+ $scope.currentPlaying = msg.data;
546
+ if (msg.data.id === undefined) {
547
+ break;
548
+ }
549
+ $scope.currentPlaying.platformText = i18next.t(
550
+ $scope.currentPlaying.platform
551
+ );
552
+ $scope.myProgress = 0;
553
+ if ($scope.lastTrackId === msg.data.id) {
554
+ break;
555
+ }
556
+ const current = localStorage.getObject('player-settings') || {};
557
+ current.nowplaying_track_id = msg.data.id;
558
+ localStorage.setObject('player-settings', current);
559
+ // update lyric
560
+ $scope.lyricArray = [];
561
+ $scope.lyricLineNumber = -1;
562
+ $scope.lyricLineNumberTrans = -1;
563
+ smoothScrollTo(document.querySelector('.lyric'), 0, 300);
564
+ const track = msg.data;
565
+ $rootScope.page_title = {
566
+ title: track.title,
567
+ artist: track.artist,
568
+ status: 'playing',
569
+ };
570
+ if (lastfm.isAuthorized()) {
571
+ lastfm.sendNowPlaying(track.title, track.artist, () => {});
572
+ }
573
+
574
+ MediaService.getLyric(
575
+ msg.data.id,
576
+ msg.data.album_id,
577
+ track.lyric_url,
578
+ track.tlyric_url
579
+ ).success((res) => {
580
+ const { lyric, tlyric } = res;
581
+ if (!lyric) {
582
+ return;
583
+ }
584
+ $scope.lyricArray = parseLyric(lyric, tlyric);
585
+ });
586
+ $scope.lastTrackId = msg.data.id;
587
+ if (isElectron()) {
588
+ const { ipcRenderer } = require('electron');
589
+ ipcRenderer.send('currentLyric', track.title);
590
+ ipcRenderer.send('trackPlayingNow', track);
591
+ }
592
+ break;
593
+ }
594
+
595
+ case 'MUTE': {
596
+ // 'music:mute'
597
+ $scope.$evalAsync(() => {
598
+ $scope.mute = msg.data;
599
+ });
600
+ break;
601
+ }
602
+
603
+ case 'PLAYLIST': {
604
+ // 'player:playlist'
605
+ $scope.$evalAsync(() => {
606
+ $scope.playlist = msg.data;
607
+ localStorage.setObject('current-playing', msg.data);
608
+ });
609
+
610
+ break;
611
+ }
612
+
613
+ case 'PLAY_STATE': {
614
+ // 'music:isPlaying'
615
+ $scope.$evalAsync(() => {
616
+ $scope.isPlaying = !!msg.data.isPlaying;
617
+ });
618
+ let title = 'Listen 1';
619
+ if ($rootScope.page_title !== undefined) {
620
+ title = '';
621
+ if (msg.data.isPlaying) {
622
+ $rootScope.page_title.status = 'playing';
623
+ } else {
624
+ $rootScope.page_title.status = 'paused';
625
+ }
626
+ if ($rootScope.page_title.status !== '') {
627
+ if ($rootScope.page_title.status === 'playing') {
628
+ title += '▶ ';
629
+ } else if ($rootScope.page_title.status === 'paused') {
630
+ title += '❚❚ ';
631
+ }
632
+ }
633
+ title += $rootScope.page_title.title;
634
+ if ($rootScope.page_title.artist !== '') {
635
+ title += ` - ${$rootScope.page_title.artist}`;
636
+ }
637
+ }
638
+
639
+ $rootScope.document_title = title;
640
+ if (isElectron()) {
641
+ const { ipcRenderer } = require('electron');
642
+ if (msg.data.isPlaying) {
643
+ ipcRenderer.send('isPlaying', true);
644
+ } else {
645
+ ipcRenderer.send('isPlaying', false);
646
+ }
647
+ }
648
+
649
+ if (msg.data.reason === 'Ended') {
650
+ if (!lastfm.isAuthorized()) {
651
+ break;
652
+ }
653
+ // send lastfm scrobble
654
+ const track = l1Player.getTrackById(l1Player.status.playing.id);
655
+ lastfm.scrobble(
656
+ l1Player.status.playing.playedFrom,
657
+ track.title,
658
+ track.artist,
659
+ track.album,
660
+ () => {}
661
+ );
662
+ }
663
+
664
+ break;
665
+ }
666
+ case 'RETRIEVE_URL_SUCCESS': {
667
+ $scope.currentPlaying = msg.data;
668
+ // update translate whenever set value
669
+ $scope.currentPlaying.platformText = i18next.t(
670
+ $scope.currentPlaying.platform
671
+ );
672
+ break;
673
+ }
674
+ case 'RETRIEVE_URL_FAIL': {
675
+ $scope.copyrightNotice();
676
+ break;
677
+ }
678
+ case 'RETRIEVE_URL_FAIL_ALL': {
679
+ $scope.failAllNotice();
680
+ break;
681
+ }
682
+ default:
683
+ break;
684
+ }
685
+ }
686
+ if (sendResponse !== undefined) {
687
+ sendResponse();
688
+ }
689
+ });
690
+
691
+ // connect player should run after all addListener function finished
692
+ l1Player.connectPlayer();
693
+
694
+ // define keybind
695
+ // description: '播放/暂停',
696
+ hotkeys('p', l1Player.togglePlayPause);
697
+
698
+ // description: '上一首',
699
+ hotkeys('[', l1Player.prev);
700
+
701
+ // description: '下一首',
702
+ hotkeys(']', l1Player.next);
703
+
704
+ // description: '静音/取消静音',
705
+ hotkeys('m', l1Player.toggleMute);
706
+
707
+ // description: '打开/关闭播放列表',
708
+ hotkeys('l', $scope.togglePlaylist);
709
+
710
+ // description: '切换播放模式(顺序/随机/单曲循环)',
711
+ hotkeys('s', $scope.changePlaymode);
712
+
713
+ // description: '音量增加',
714
+ hotkeys('u', () => {
715
+ $timeout(() => {
716
+ l1Player.adjustVolume(true);
717
+ });
718
+ });
719
+
720
+ // description: '音量减少',
721
+ hotkeys('d', () => {
722
+ $timeout(() => {
723
+ l1Player.adjustVolume(false);
724
+ });
725
+ });
726
+
727
+ $scope.toggleLyricTranslation = () => {
728
+ $scope.enableLyricTranslation = !$scope.enableLyricTranslation;
729
+ localStorage.setObject(
730
+ 'enable_lyric_translation',
731
+ $scope.enableLyricTranslation
732
+ );
733
+ };
734
+
735
+ $scope.toggleLyricFloatingWindowTranslation = () => {
736
+ $scope.enableLyricFloatingWindowTranslation =
737
+ !$scope.enableLyricFloatingWindowTranslation;
738
+ localStorage.setObject(
739
+ 'enable_lyric_floating_window_translation',
740
+ $scope.enableLyricFloatingWindowTranslation
741
+ );
742
+ };
743
+
744
+ if (isElectron()) {
745
+ require('electron').ipcRenderer.on('globalShortcut', (event, message) => {
746
+ if (message === 'right') {
747
+ l1Player.next();
748
+ } else if (message === 'left') {
749
+ l1Player.prev();
750
+ } else if (message === 'space') {
751
+ l1Player.togglePlayPause();
752
+ }
753
+ });
754
+ }
755
+
756
+ $scope.setAutoChooseSource = (toggle) => {
757
+ if (toggle === true) {
758
+ $scope.enableAutoChooseSource = !$scope.enableAutoChooseSource;
759
+ }
760
+ localStorage.setObject(
761
+ 'enable_auto_choose_source',
762
+ $scope.enableAutoChooseSource
763
+ );
764
+ };
765
+
766
+ $scope.enableSource = (source) => {
767
+ if ($scope.autoChooseSourceList.indexOf(source) > -1) {
768
+ return;
769
+ }
770
+ $scope.autoChooseSourceList = [...$scope.autoChooseSourceList, source];
771
+ localStorage.setObject(
772
+ 'auto_choose_source_list',
773
+ $scope.autoChooseSourceList
774
+ );
775
+ };
776
+
777
+ $scope.disableSource = (source) => {
778
+ if ($scope.autoChooseSourceList.indexOf(source) === -1) {
779
+ return;
780
+ }
781
+ $scope.autoChooseSourceList = $scope.autoChooseSourceList.filter(
782
+ (i) => i !== source
783
+ );
784
+ localStorage.setObject(
785
+ 'auto_choose_source_list',
786
+ $scope.autoChooseSourceList
787
+ );
788
+ };
789
+
790
+ $scope.setStopWhenClose = (status) => {
791
+ $scope.enableStopWhenClose = status;
792
+ localStorage.setObject(
793
+ 'enable_stop_when_close',
794
+ $scope.enableStopWhenClose
795
+ );
796
+ };
797
+
798
+ $scope.setNowplayingCoverBackground = (toggle) => {
799
+ if (toggle === true) {
800
+ $scope.enableNowplayingCoverBackground =
801
+ !$scope.enableNowplayingCoverBackground;
802
+ }
803
+ localStorage.setObject(
804
+ 'enable_nowplaying_cover_background',
805
+ $scope.enableNowplayingCoverBackground
806
+ );
807
+ };
808
+ $scope.setNowplayingBitrate = (toggle) => {
809
+ if (toggle === true) {
810
+ $scope.enableNowplayingBitrate = !$scope.enableNowplayingBitrate;
811
+ }
812
+ localStorage.setObject(
813
+ 'enable_nowplaying_bitrate',
814
+ $scope.enableNowplayingBitrate
815
+ );
816
+ };
817
+ $scope.setNowplayingPlatform = (toggle) => {
818
+ if (toggle === true) {
819
+ $scope.enableNowplayingPlatform = !$scope.enableNowplayingPlatform;
820
+ }
821
+ localStorage.setObject(
822
+ 'enable_nowplaying_platform',
823
+ $scope.enableNowplayingPlatform
824
+ );
825
+ };
826
+ },
827
+ ]);
js/controller/playlist.js ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable no-unused-vars */
2
+ /* global angular MediaService sourceList */
3
+
4
+ angular.module('listenone').controller('PlayListController', [
5
+ '$scope',
6
+ '$timeout',
7
+ ($scope) => {
8
+ $scope.result = [];
9
+ $scope.tab = sourceList[0].name;
10
+ $scope.sourceList = sourceList;
11
+ $scope.playlistFilters = {};
12
+ $scope.allPlaylistFilters = {};
13
+ $scope.currentFilterId = '';
14
+ $scope.loading = true;
15
+ $scope.showMore = false;
16
+
17
+ $scope.$on('infinite_scroll:hit_bottom', (event, data) => {
18
+ if ($scope.loading === true) {
19
+ return;
20
+ }
21
+ $scope.loading = true;
22
+ const offset = $scope.result.length;
23
+ MediaService.showPlaylistArray(
24
+ $scope.tab,
25
+ offset,
26
+ $scope.currentFilterId
27
+ ).success((res) => {
28
+ $scope.result = $scope.result.concat(res.result);
29
+ $scope.loading = false;
30
+ });
31
+ });
32
+
33
+ $scope.loadPlaylist = () => {
34
+ const offset = 0;
35
+ $scope.showMore = false;
36
+ MediaService.showPlaylistArray(
37
+ $scope.tab,
38
+ offset,
39
+ $scope.currentFilterId
40
+ ).success((res) => {
41
+ $scope.result = res.result;
42
+ $scope.loading = false;
43
+ });
44
+
45
+ if (
46
+ $scope.playlistFilters[$scope.tab] === undefined &&
47
+ $scope.allPlaylistFilters[$scope.tab] === undefined
48
+ ) {
49
+ MediaService.getPlaylistFilters($scope.tab).success((res) => {
50
+ $scope.playlistFilters[$scope.tab] = res.recommend;
51
+ $scope.allPlaylistFilters[$scope.tab] = res.all;
52
+ });
53
+ }
54
+ };
55
+
56
+ $scope.changeTab = (newTab) => {
57
+ $scope.tab = newTab;
58
+ $scope.result = [];
59
+ $scope.currentFilterId = '';
60
+ $scope.loadPlaylist();
61
+ };
62
+
63
+ $scope.changeFilter = (filterId) => {
64
+ $scope.result = [];
65
+ $scope.currentFilterId = filterId;
66
+ $scope.loadPlaylist();
67
+ };
68
+
69
+ $scope.toggleMorePlaylists = () => {
70
+ $scope.showMore = !$scope.showMore;
71
+ };
72
+ },
73
+ ]);