kashi 2.0.0 β 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -20
- package/kashi.d.ts +4 -6
- package/kashi.js +1 -1
- package/kashi.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ This project is a dependency-free library that aims to provide a way to correctl
|
|
|
16
16
|
## π Features
|
|
17
17
|
|
|
18
18
|
- [x] π΅ Process and list song lyrics in .lrc files
|
|
19
|
-
- [x] πͺ
|
|
19
|
+
- [x] πͺ Easily integrates with React and other frameworks
|
|
20
20
|
- [x] βοΈ Implements the Observer pattern and emits events at each step of the process whenever something changes
|
|
21
21
|
- [x] βοΈ Allows you to enter custom text when the lyrics line is empty
|
|
22
22
|
- [x] π€ Synchronizes the lyrics with the music that is playing
|
|
@@ -24,6 +24,10 @@ This project is a dependency-free library that aims to provide a way to correctl
|
|
|
24
24
|
- [ ] π§ Supports the [Walaoke extension](https://en.wikipedia.org/wiki/LRC_(file_format)#Walaoke_extension)
|
|
25
25
|
- [ ] ποΈ Supports the [A2 extension](https://en.wikipedia.org/wiki/LRC_(file_format)#A2_extension_(Enhanced_LRC_format))
|
|
26
26
|
|
|
27
|
+
> **Note:** Support for fetching lyrics using a URL has been removed because it only supported public URLs.
|
|
28
|
+
>
|
|
29
|
+
> Therefore, instead of refactoring to handle requests to private APIs, I opted to support only files. If a request is necessary, you can make it externally and then pass the returned file to Kashi!
|
|
30
|
+
|
|
27
31
|
## π¨ How to use it in my project?
|
|
28
32
|
|
|
29
33
|
### <span id="classic-scripts">π Classic scripts</span>
|
|
@@ -53,9 +57,7 @@ The project is also exported using [UMD](https://github.com/umdjs/umd), which me
|
|
|
53
57
|
```js
|
|
54
58
|
"use strict";
|
|
55
59
|
|
|
56
|
-
// Using the file or using a url, both works fine
|
|
57
60
|
new Kashi({
|
|
58
|
-
// url, // Public link that returns the file when a GET request is made OR...
|
|
59
61
|
file. // Loaded from an input[type="file"] or anywhere else
|
|
60
62
|
container: document.getElementById("kashi"),
|
|
61
63
|
});
|
|
@@ -80,9 +82,7 @@ import { Kashi } from "kashi";
|
|
|
80
82
|
|
|
81
83
|
// Usage is essentially the same as in the previous section
|
|
82
84
|
|
|
83
|
-
// Using the file or using a url, both works fine
|
|
84
85
|
new Kashi({
|
|
85
|
-
// url, // Public link that returns the file when a GET request is made OR...
|
|
86
86
|
file. // Loaded from an input[type="file"] or anywhere else
|
|
87
87
|
container: document.getElementById("kashi"),
|
|
88
88
|
});
|
|
@@ -105,7 +105,7 @@ import { useEffect, useRef } from "react";
|
|
|
105
105
|
import { Kashi, KashiProps } from "kashi";
|
|
106
106
|
|
|
107
107
|
// Example using Vite, React and TypeScript
|
|
108
|
-
export const KashiWrapper = (props: KashiProps) => {
|
|
108
|
+
export const KashiWrapper = (props: Omit<KashiProps, "container">) => {
|
|
109
109
|
const ref = useRef<HTMLDivElement>(null);
|
|
110
110
|
|
|
111
111
|
useEffect(() => {
|
|
@@ -134,12 +134,10 @@ You must pass some properties to Kashi to define what lyrics display and where.
|
|
|
134
134
|
|
|
135
135
|
| Property | Type | Default value | Is required? | Description |
|
|
136
136
|
| --------------- | ------------------ | ------------- | ------------ | ----------------------------------------- |
|
|
137
|
-
| `file` | `Blob` (or `File`) | - |
|
|
138
|
-
| `url` | `string` | - | No | Lyrics url |
|
|
137
|
+
| `file` | `Blob` (or `File`) | - | Yes | Lyrics file |
|
|
139
138
|
| `container` | `HTMLDivElement` | - | Yes | Element where the lyrics will be inserted |
|
|
140
|
-
| `emptyLineText` | `string` |
|
|
141
|
-
|
|
142
|
-
> Neither `file` nor `url` are βmandatoryβ, but **at least one of these properties must be specified**, otherwise an error will be thrown.
|
|
139
|
+
| `emptyLineText` | `string` | `...` | No | Custom text for empty lines of the lyrics |
|
|
140
|
+
| `noLyricsText` | `string` | - | No | Custom text for when there are no lyrics |
|
|
143
141
|
|
|
144
142
|
## πΎ Generated HTML structure
|
|
145
143
|
|
|
@@ -186,12 +184,12 @@ The instance generated by `Kashi` has some public methods and attributes that ca
|
|
|
186
184
|
|
|
187
185
|
| Name | Type | Description |
|
|
188
186
|
| ------------------ | --------- | ------------------------------------------------------------ |
|
|
189
|
-
| `url` | Attribute | Returns the url from the current lyrics if it was fetched from a link |
|
|
190
187
|
| `file` | Attribute | Returns the file from the current lyrics |
|
|
191
|
-
| `emptyLineText` | Attribute | Returns the
|
|
192
|
-
| `
|
|
188
|
+
| `emptyLineText` | Attribute | Returns the text set for empty lines |
|
|
189
|
+
| `noLyricsText` | Attribute | Returns the text set for when there are no lyrics |
|
|
193
190
|
| `setFile` | Method | Function capable of changing the current lyrics file by passing the the new **file** |
|
|
194
191
|
| `setEmptyLineText` | Method | Function capable of changing the text defined for empty lines |
|
|
192
|
+
| `noLyricsText` | Method | Function capable of changing the text defined for when there are no lyrics |
|
|
195
193
|
| `subscribe` | Method | Function capable of defining a callback to be executed when a given event is triggered |
|
|
196
194
|
| `unsubscribe` | Method | Function capable of making a callback to stop listening to an event |
|
|
197
195
|
| `notify` | Method | Function capable of triggering an event |
|
|
@@ -200,12 +198,12 @@ The instance generated by `Kashi` has some public methods and attributes that ca
|
|
|
200
198
|
|
|
201
199
|
When creating a new instance using `Kashi` you will have access to the `subscribe`, `unsubscribe` and `notify` methods, these methods can be used respectively to listen for an event, stop listening for an event and manually trigger an event. Below is the list of events triggered internally:
|
|
202
200
|
|
|
203
|
-
| Event | Data | Trigger
|
|
204
|
-
| ------------------- | --------------------------- |
|
|
205
|
-
| `
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
208
|
-
| `lyricLinesUpdated` | `{ lyricLines: string[] }` | When inserting/updating lyrics in HTML
|
|
201
|
+
| Event | Data | Trigger |
|
|
202
|
+
| ------------------- | --------------------------- | ------------------------------------------ |
|
|
203
|
+
| `fileSet` | `{ file: Blob }` | When calling the `setFile` method |
|
|
204
|
+
| `emptyLineTextSet` | `{ emptyLineText: string }` | When calling the `setEmptyLineText` method |
|
|
205
|
+
| `noLyricsText` | `{ noLyricsText: text }` | When calling the `setNoLyricsText` method |
|
|
206
|
+
| `lyricLinesUpdated` | `{ lyricLines: string[] }` | When inserting/updating lyrics in HTML |
|
|
209
207
|
|
|
210
208
|
## π€ How do I run the project on my machine?
|
|
211
209
|
|
package/kashi.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
// Generated by dts-bundle-generator v9.5.1
|
|
2
2
|
|
|
3
|
-
export type
|
|
4
|
-
export type KashiFile = Blob | null;
|
|
3
|
+
export type KashiFile = Blob | undefined;
|
|
5
4
|
export type EventName = string;
|
|
6
5
|
export type EventData = any;
|
|
7
6
|
export type EventCallback = (data: EventData) => void;
|
|
8
7
|
export interface KashiProps {
|
|
9
|
-
url?: KashiUrl;
|
|
10
8
|
file?: KashiFile;
|
|
11
9
|
container: HTMLDivElement;
|
|
12
10
|
emptyLineText?: string;
|
|
@@ -16,12 +14,12 @@ export interface KashiProps {
|
|
|
16
14
|
export declare class Kashi {
|
|
17
15
|
#private;
|
|
18
16
|
constructor(props: KashiProps);
|
|
19
|
-
get url(): KashiUrl;
|
|
20
17
|
get file(): KashiFile;
|
|
21
18
|
get emptyLineText(): string;
|
|
22
|
-
|
|
23
|
-
setFile(file:
|
|
19
|
+
get noLyricsText(): string;
|
|
20
|
+
setFile(file: KashiFile): Promise<void>;
|
|
24
21
|
setEmptyLineText(text: string): void;
|
|
22
|
+
setNoLyricsText(text: string): void;
|
|
25
23
|
subscribe(event: EventName, fn: EventCallback): void;
|
|
26
24
|
unsubscribe(event: EventName, fn: EventCallback): void;
|
|
27
25
|
notify(event: EventName, data?: EventData): void;
|
package/kashi.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var
|
|
1
|
+
!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var i=t();for(var n in i)("object"==typeof exports?exports:e)[n]=i[n]}}(self,()=>(()=>{"use strict";var e={d:(t,i)=>{for(var n in i)e.o(i,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:i[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};e.r(t),e.d(t,{Kashi:()=>T});const i=/\[\d{2}:\d{2}.\d{2}\]/,n=/^\[\d{2}:\d{2}.\d{2}\](.*)$/;var r,s,a,o,l,f,h,c,d,u,p,y,m,w=function(e,t,i,n,r){if("m"===n)throw new TypeError("Private method is not writable");if("a"===n&&!r)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!r:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===n?r.call(e,i):r?r.value=i:t.set(e,i),i},b=function(e,t,i,n){if("a"===i&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!n:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===i?n:"a"===i?n.call(e):n?n.value:t.get(e)};class T{constructor(e){if(r.add(this),s.set(this,void 0),a.set(this,[]),o.set(this,void 0),l.set(this,new Map),f.set(this,void 0),h.set(this,null),c.set(this,!1),d.set(this,void 0),!(e.container&&e.container instanceof HTMLDivElement))throw new Error("Container must be an instance of HTMLDivElement.");if(e.emptyLineText&&this.setEmptyLineText(e.emptyLineText),e.audioPlayer&&!(e.audioPlayer instanceof HTMLAudioElement))throw new Error("Audio player must be an instance of HTMLAudioElement.");e.audioPlayer&&(w(this,h,e.audioPlayer,"f"),b(this,h,"f").addEventListener("timeupdate",()=>{b(this,r,"m",m).call(this)}),b(this,h,"f").addEventListener("seeked",()=>{b(this,o,"f").querySelectorAll("p > span").forEach(e=>{e instanceof HTMLSpanElement&&(e.dataset.ariaCurrent="false")}),b(this,r,"m",m).call(this)})),e.enableAutoScroll&&w(this,c,e.enableAutoScroll,"f"),w(this,o,e.container,"f"),this.subscribe("fileSet",b(this,r,"m",y).bind(this)),this.subscribe("emptyLineTextSet",b(this,r,"m",y).bind(this)),this.subscribe("lyricLinesUpdated",b(this,r,"m",m).bind(this)),this.setFile(e.file)}get file(){return b(this,s,"f")}get emptyLineText(){return b(this,f,"f")??"..."}get noLyricsText(){return b(this,d,"f")}async setFile(e){if(e){const t=await b(this,r,"m",u).call(this,e);w(this,a,b(this,r,"m",p).call(this,t),"f")}else w(this,a,[],"f");w(this,s,e,"f"),this.notify("fileSet",{file:e})}setEmptyLineText(e){w(this,f,e,"f"),this.notify("emptyLineTextSet",{emptyLineText:e})}setNoLyricsText(e){w(this,d,e,"f"),this.notify("noLyricsTextSet",{noLyricsText:e})}subscribe(e,t){b(this,l,"f").set(e,[...b(this,l,"f").get(e)??[],t])}unsubscribe(e,t){const i=b(this,l,"f").get(e);i&&i.length>1?b(this,l,"f").set(e,[...i.filter(e=>e!==t)]):b(this,l,"f").delete(e)}notify(e,t){[...b(this,l,"f").get(e)??[]].forEach(e=>{e(t)})}}return s=new WeakMap,a=new WeakMap,o=new WeakMap,l=new WeakMap,f=new WeakMap,h=new WeakMap,c=new WeakMap,d=new WeakMap,r=new WeakSet,u=async function(e){return new Promise((t,i)=>{const n=new FileReader;n.onload=e=>{"string"==typeof e.target?.result&&e.target?.result.trim().length?t(e.target.result):i(new Error("Failed to read file content."))},n.onerror=()=>i(new Error("Error reading file.")),n.readAsText(e)})},p=function(e){const t=e.split("\n").reduce((e,t)=>{const i=t.trim();return n.test(i)?[...e,i]:e},[]);if(0===t.length)throw new Error("No valid lyric lines found in the file.");return t},y=function(){const e=b(this,a,"f").map(e=>{const t=e.replace(i,""),n=e.match(i)?.[0]?.slice(1,-1);if(!n)return"";const r=n.split(":"),s=r[1].split(".");return`\n <span \n data-time="${n}" \n data-ms-time="${60*parseInt(r[0],10)*1e3+1e3*parseInt(s[0],10)+10*parseInt(s[1],10)}"\n data-empty="${0===t.length}"\n data-aria-current="false"\n >\n ${t||(b(this,f,"f")??"...")}\n </span>\n `}).join("<br/>");b(this,o,"f").innerHTML=`<p>${e.length?e:`\n <span data-no-lyrics="true">${b(this,d,"f")??""}</span>\n `}</p>`,this.notify("lyricLinesUpdated",{lyricLines:b(this,a,"f")})},m=function(){if(!b(this,h,"f")||!b(this,a,"f").length)return;const e=1e3*b(this,h,"f").currentTime,t=b(this,o,"f").querySelectorAll("p > span");let i=null;for(let n=0;n<t.length;n++){const r=t[n],s=parseInt(r.getAttribute("data-ms-time")||"0",10);if(r.dataset.ariaCurrent="false",!(e>=s))break;i=r}i&&(i.dataset.ariaCurrent="true",b(this,c,"f")&&i.scrollIntoView({behavior:"smooth",block:"center"}))},t})());
|
package/kashi.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const t=/\[\d{2}:\d{2}.\d{2}\]/,e=/^\[\d{2}:\d{2}.\d{2}\](.*)$/;var i,
|
|
1
|
+
const t=/\[\d{2}:\d{2}.\d{2}\]/,e=/^\[\d{2}:\d{2}.\d{2}\](.*)$/;var i,n,s,r,a,o,l,h,f,c,d,u,p,m=function(t,e,i,n,s){if("m"===n)throw new TypeError("Private method is not writable");if("a"===n&&!s)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===n?s.call(t,i):s?s.value=i:e.set(t,i),i},w=function(t,e,i,n){if("a"===i&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!n:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===i?n:"a"===i?n.call(t):n?n.value:e.get(t)};class y{constructor(t){if(i.add(this),n.set(this,void 0),s.set(this,[]),r.set(this,void 0),a.set(this,new Map),o.set(this,void 0),l.set(this,null),h.set(this,!1),f.set(this,void 0),!(t.container&&t.container instanceof HTMLDivElement))throw new Error("Container must be an instance of HTMLDivElement.");if(t.emptyLineText&&this.setEmptyLineText(t.emptyLineText),t.audioPlayer&&!(t.audioPlayer instanceof HTMLAudioElement))throw new Error("Audio player must be an instance of HTMLAudioElement.");t.audioPlayer&&(m(this,l,t.audioPlayer,"f"),w(this,l,"f").addEventListener("timeupdate",()=>{w(this,i,"m",p).call(this)}),w(this,l,"f").addEventListener("seeked",()=>{w(this,r,"f").querySelectorAll("p > span").forEach(t=>{t instanceof HTMLSpanElement&&(t.dataset.ariaCurrent="false")}),w(this,i,"m",p).call(this)})),t.enableAutoScroll&&m(this,h,t.enableAutoScroll,"f"),m(this,r,t.container,"f"),this.subscribe("fileSet",w(this,i,"m",u).bind(this)),this.subscribe("emptyLineTextSet",w(this,i,"m",u).bind(this)),this.subscribe("lyricLinesUpdated",w(this,i,"m",p).bind(this)),this.setFile(t.file)}get file(){return w(this,n,"f")}get emptyLineText(){return w(this,o,"f")??"..."}get noLyricsText(){return w(this,f,"f")}async setFile(t){if(t){const e=await w(this,i,"m",c).call(this,t);m(this,s,w(this,i,"m",d).call(this,e),"f")}else m(this,s,[],"f");m(this,n,t,"f"),this.notify("fileSet",{file:t})}setEmptyLineText(t){m(this,o,t,"f"),this.notify("emptyLineTextSet",{emptyLineText:t})}setNoLyricsText(t){m(this,f,t,"f"),this.notify("noLyricsTextSet",{noLyricsText:t})}subscribe(t,e){w(this,a,"f").set(t,[...w(this,a,"f").get(t)??[],e])}unsubscribe(t,e){const i=w(this,a,"f").get(t);i&&i.length>1?w(this,a,"f").set(t,[...i.filter(t=>t!==e)]):w(this,a,"f").delete(t)}notify(t,e){[...w(this,a,"f").get(t)??[]].forEach(t=>{t(e)})}}n=new WeakMap,s=new WeakMap,r=new WeakMap,a=new WeakMap,o=new WeakMap,l=new WeakMap,h=new WeakMap,f=new WeakMap,i=new WeakSet,c=async function(t){return new Promise((e,i)=>{const n=new FileReader;n.onload=t=>{"string"==typeof t.target?.result&&t.target?.result.trim().length?e(t.target.result):i(new Error("Failed to read file content."))},n.onerror=()=>i(new Error("Error reading file.")),n.readAsText(t)})},d=function(t){const i=t.split("\n").reduce((t,i)=>{const n=i.trim();return e.test(n)?[...t,n]:t},[]);if(0===i.length)throw new Error("No valid lyric lines found in the file.");return i},u=function(){const e=w(this,s,"f").map(e=>{const i=e.replace(t,""),n=e.match(t)?.[0]?.slice(1,-1);if(!n)return"";const s=n.split(":"),r=s[1].split(".");return`\n <span \n data-time="${n}" \n data-ms-time="${60*parseInt(s[0],10)*1e3+1e3*parseInt(r[0],10)+10*parseInt(r[1],10)}"\n data-empty="${0===i.length}"\n data-aria-current="false"\n >\n ${i||(w(this,o,"f")??"...")}\n </span>\n `}).join("<br/>");w(this,r,"f").innerHTML=`<p>${e.length?e:`\n <span data-no-lyrics="true">${w(this,f,"f")??""}</span>\n `}</p>`,this.notify("lyricLinesUpdated",{lyricLines:w(this,s,"f")})},p=function(){if(!w(this,l,"f")||!w(this,s,"f").length)return;const t=1e3*w(this,l,"f").currentTime,e=w(this,r,"f").querySelectorAll("p > span");let i=null;for(let n=0;n<e.length;n++){const s=e[n],r=parseInt(s.getAttribute("data-ms-time")||"0",10);if(s.dataset.ariaCurrent="false",!(t>=r))break;i=s}i&&(i.dataset.ariaCurrent="true",w(this,h,"f")&&i.scrollIntoView({behavior:"smooth",block:"center"}))};export{y as Kashi};
|