kashi 2.0.0 → 3.0.1

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 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] 💪 Supports both directly inputting the .lrc file and a URL that returns it
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
  });
@@ -101,31 +101,82 @@ Since the library was designed primarily to be used with vanilla JS, a _helper_
101
101
  > **Note**: There is TypeScript support, and even if your project doesn't use the JS superset, it should help VSCode and other editors provide autocomplete/code suggestions.
102
102
 
103
103
  ```tsx
104
- import { useEffect, useRef } from "react";
104
+ import { memo, useEffect, useRef } from "react";
105
105
  import { Kashi, KashiProps } from "kashi";
106
106
 
107
+ import { api } from "@/services/axios";
108
+
107
109
  // Example using Vite, React and TypeScript
108
- export const KashiWrapper = (props: KashiProps) => {
109
- const ref = useRef<HTMLDivElement>(null);
110
-
111
- useEffect(() => {
112
- if (ref.current) {
113
- new Kashi({
114
- ...props,
115
- container: ref.current,
116
- });
117
- }
110
+ export const KashiWrapper = memo(
111
+ (props: Omit<KashiProps, "container"> & { url: string }) => {
112
+ const ref = useRef<HTMLDivElement>(null);
113
+
114
+ useEffect(() => {
115
+ async function loadKashi() {
116
+ if (!ref.current) {
117
+ return;
118
+ }
119
+
120
+ if (!props.url) {
121
+ ref.current.innerHTML = "";
122
+ return;
123
+ }
124
+
125
+ try {
126
+ const { url, ...rest } = props;
127
+
128
+ const response = await api.get(url, { responseType: "blob" });
129
+
130
+ new Kashi({
131
+ ...rest,
132
+ file: response.data,
133
+ container: ref.current,
134
+ });
135
+ } catch (error) {
136
+ console.error("Error loading Kashi:", error);
137
+ }
138
+ }
118
139
 
119
- return () => {
120
- // Required to avoid duplication when React is in Strict Mode
121
- if (ref.current) {
122
- ref.current.innerHTML = "";
140
+ loadKashi();
141
+
142
+ return () => {
143
+ // Required to avoid duplication when React is in Strict Mode
144
+ if (ref.current) {
145
+ ref.current.innerHTML = "";
146
+ }
147
+ };
148
+ }, [ref.current, props]);
149
+
150
+ return <div className="kashi-wrapper" ref={ref} />;
151
+ },
152
+ (prevProps, nextProps) => {
153
+ function compareObjects(
154
+ obj1: Omit<KashiProps, "container">,
155
+ obj2: Omit<KashiProps, "container">,
156
+ ) {
157
+ const keys1 = Object.keys(obj1) as Array<
158
+ keyof Omit<KashiProps, "container">
159
+ >;
160
+ const keys2 = Object.keys(obj2) as Array<
161
+ keyof Omit<KashiProps, "container">
162
+ >;
163
+
164
+ if (keys1.length !== keys2.length) {
165
+ return false;
123
166
  }
124
- };
125
- }, [ref.current]);
126
167
 
127
- return <div className="kashi-wrapper" ref={ref} />;
128
- };
168
+ for (const key of keys1) {
169
+ if (obj1[key] !== obj2[key]) {
170
+ return false;
171
+ }
172
+ }
173
+
174
+ return true;
175
+ }
176
+
177
+ return compareObjects(prevProps, nextProps);
178
+ },
179
+ );
129
180
  ```
130
181
 
131
182
  ## 🧐 Constructor properties
@@ -134,12 +185,10 @@ You must pass some properties to Kashi to define what lyrics display and where.
134
185
 
135
186
  | Property | Type | Default value | Is required? | Description |
136
187
  | --------------- | ------------------ | ------------- | ------------ | ----------------------------------------- |
137
- | `file` | `Blob` (or `File`) | - | No | Lyrics file |
138
- | `url` | `string` | - | No | Lyrics url |
188
+ | `file` | `Blob` (or `File`) | - | Yes | Lyrics file |
139
189
  | `container` | `HTMLDivElement` | - | Yes | Element where the lyrics will be inserted |
140
- | `emptyLineText` | `string` | `🎝` | No | Custom text for empty lines of the lyrics |
141
-
142
- > Neither `file` nor `url` are “mandatory”, but **at least one of these properties must be specified**, otherwise an error will be thrown.
190
+ | `emptyLineText` | `string` | `...` | No | Custom text for empty lines of the lyrics |
191
+ | `noLyricsText` | `string` | - | No | Custom text for when there are no lyrics |
143
192
 
144
193
  ## 👾 Generated HTML structure
145
194
 
@@ -151,7 +200,7 @@ Here's an example:
151
200
 
152
201
  ```html
153
202
  <div id="kashi">
154
- <sp>
203
+ <p>
155
204
  <span data-time="00:17.55" data-ms-time="17550" data-empty="false" data-aria-current="false">
156
205
  Telling myself, "I won't go there"
157
206
  </span>
@@ -176,7 +225,7 @@ Here's an example:
176
225
  Souls tied, intertwined by our pride and guilt
177
226
  </span>
178
227
  <!-- ... -->
179
- </sp>
228
+ </p>
180
229
  </div>
181
230
  ```
182
231
 
@@ -186,12 +235,12 @@ The instance generated by `Kashi` has some public methods and attributes that ca
186
235
 
187
236
  | Name | Type | Description |
188
237
  | ------------------ | --------- | ------------------------------------------------------------ |
189
- | `url` | Attribute | Returns the url from the current lyrics if it was fetched from a link |
190
238
  | `file` | Attribute | Returns the file from the current lyrics |
191
- | `emptyLineText` | Attribute | Returns the default text set for empty lines |
192
- | `setUrl` | Method | Function capable of changing the current lyrics file by passing the **url** of the new file |
239
+ | `emptyLineText` | Attribute | Returns the text set for empty lines |
240
+ | `noLyricsText` | Attribute | Returns the text set for when there are no lyrics |
193
241
  | `setFile` | Method | Function capable of changing the current lyrics file by passing the the new **file** |
194
242
  | `setEmptyLineText` | Method | Function capable of changing the text defined for empty lines |
243
+ | `noLyricsText` | Method | Function capable of changing the text defined for when there are no lyrics |
195
244
  | `subscribe` | Method | Function capable of defining a callback to be executed when a given event is triggered |
196
245
  | `unsubscribe` | Method | Function capable of making a callback to stop listening to an event |
197
246
  | `notify` | Method | Function capable of triggering an event |
@@ -200,12 +249,12 @@ The instance generated by `Kashi` has some public methods and attributes that ca
200
249
 
201
250
  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
251
 
203
- | Event | Data | Trigger |
204
- | ------------------- | --------------------------- | ------------------------------------------------------------ |
205
- | `urlSet` | `{ url: string }` | When instantiating by informing `url` or calling the `setUrl` method |
206
- | `fileSet` | `{ file: Blob }` | When instantiating or calling the `setFile` method (when calling the `setUrl` method the file will be fetched and the `setFile` method called) |
207
- | `emptyLineTextSet` | `{ emptyLineText: string }` | When instantiating by informing `emptyLineText` or calling the `setEmptyLineText` method |
208
- | `lyricLinesUpdated` | `{ lyricLines: string[] }` | When inserting/updating lyrics in HTML |
252
+ | Event | Data | Trigger |
253
+ | ------------------- | --------------------------- | ------------------------------------------ |
254
+ | `fileSet` | `{ file: Blob }` | When calling the `setFile` method |
255
+ | `emptyLineTextSet` | `{ emptyLineText: string }` | When calling the `setEmptyLineText` method |
256
+ | `noLyricsText` | `{ noLyricsText: text }` | When calling the `setNoLyricsText` method |
257
+ | `lyricLinesUpdated` | `{ lyricLines: string[] }` | When inserting/updating lyrics in HTML |
209
258
 
210
259
  ## 🤔 How do I run the project on my machine?
211
260
 
package/kashi.d.ts CHANGED
@@ -1,12 +1,10 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
- export type KashiUrl = string | null;
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
- setUrl(url: string): Promise<void>;
23
- setFile(file: Blob): Promise<void>;
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 r=t();for(var i in r)("object"==typeof exports?exports:e)[i]=r[i]}}(self,()=>(()=>{"use strict";var e={d:(t,r)=>{for(var i in r)e.o(r,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:r[i]})},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:()=>E});const r=/\[\d{2}:\d{2}.\d{2}\]/,i=/^\[\d{2}:\d{2}.\d{2}\](.*)$/;var n,s,a,o,l,f,h,c,u,d,p,y,m,w=function(e,t,r,i,n){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!n)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!n:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===i?n.call(e,r):n?n.value=r:t.set(e,r),r},b=function(e,t,r,i){if("a"===r&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?i:"a"===r?i.call(e):i?i.value:t.get(e)};class E{constructor(e){if(n.add(this),s.set(this,null),a.set(this,null),o.set(this,[]),l.set(this,void 0),f.set(this,new Map),h.set(this,void 0),c.set(this,null),u.set(this,!1),e.url&&e.file)throw new Error("Cannot specify both url and file.");if(!e.url&&!e.file)throw new Error("Must specify either url or file.");if(!(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,c,e.audioPlayer,"f"),b(this,c,"f").addEventListener("timeupdate",()=>{b(this,n,"m",m).call(this)}),b(this,c,"f").addEventListener("seeked",()=>{b(this,l,"f").querySelectorAll("p > span").forEach(e=>{e instanceof HTMLSpanElement&&(e.dataset.ariaCurrent="false")}),b(this,n,"m",m).call(this)})),e.enableAutoScroll&&w(this,u,e.enableAutoScroll,"f"),w(this,l,e.container,"f"),this.subscribe("fileSet",b(this,n,"m",y).bind(this)),this.subscribe("emptyLineTextSet",b(this,n,"m",y).bind(this)),this.subscribe("lyricLinesUpdated",b(this,n,"m",m).bind(this)),e.url?this.setUrl(e.url):e.file&&this.setFile(e.file)}get url(){return b(this,s,"f")}get file(){return b(this,a,"f")}get emptyLineText(){return b(this,h,"f")??"..."}async setUrl(e){try{const t=await fetch(e);if(!t.ok)throw new Error(`HTTP error! Status: ${t.status}.`);const r=t.headers.get("Content-Type");if(!r||!r.includes("text/plain"))throw new Error("Invalid content type. Expected text/plain.");const i=await t.blob();w(this,s,e,"f"),this.notify("urlSet",{url:e}),this.setFile(i)}catch(e){throw new Error((e instanceof Error&&e.message.length?e.message+" ":"")+"Failed to fetch the lyric file.")}}async setFile(e){const t=await b(this,n,"m",d).call(this,e);w(this,o,b(this,n,"m",p).call(this,t),"f"),w(this,a,e,"f"),this.notify("fileSet",{file:e})}setEmptyLineText(e){w(this,h,e,"f"),this.notify("emptyLineTextSet",{emptyLineText:e})}subscribe(e,t){b(this,f,"f").set(e,[...b(this,f,"f").get(e)??[],t])}unsubscribe(e,t){const r=b(this,f,"f").get(e);r&&r.length>1?b(this,f,"f").set(e,[...r.filter(e=>e!==t)]):b(this,f,"f").delete(e)}notify(e,t){[...b(this,f,"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,u=new WeakMap,n=new WeakSet,d=async function(e){return new Promise((t,r)=>{const i=new FileReader;i.onload=e=>{"string"==typeof e.target?.result&&e.target?.result.trim().length?t(e.target.result):r(new Error("Failed to read file content."))},i.onerror=()=>r(new Error("Error reading file.")),i.readAsText(e)})},p=function(e){const t=e.split("\n").reduce((e,t)=>{const r=t.trim();return i.test(r)?[...e,r]:e},[]);if(0===t.length)throw new Error("No valid lyric lines found in the file.");return t},y=function(){const e=b(this,o,"f").map(e=>{const t=e.replace(r,""),i=e.match(r)?.[0]?.slice(1,-1);if(!i)return"";const n=i.split(":"),s=n[1].split(".");return`\n <span \n data-time="${i}" \n data-ms-time="${60*parseInt(n[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,h,"f")??"...")}\n </span>\n `}).join("<br/>");b(this,l,"f").innerHTML=`<p>${e}</p>`,this.notify("lyricLinesUpdated",{lyricLines:b(this,o,"f")})},m=function(){if(!b(this,c,"f"))return;const e=1e3*b(this,c,"f").currentTime,t=b(this,l,"f").querySelectorAll("p > span");let r=null;for(let i=0;i<t.length;i++){const n=t[i],s=parseInt(n.getAttribute("data-ms-time")||"0",10);if(n.dataset.ariaCurrent="false",!(e>=s))break;r=n}r&&(r.dataset.ariaCurrent="true",b(this,u,"f")&&r.scrollIntoView({behavior:"smooth",block:"center"}))},t})());
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,r,n,s,a,o,l,h,f,c,u,d,p,w=function(t,e,i,r,n){if("m"===r)throw new TypeError("Private method is not writable");if("a"===r&&!n)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof e?t!==e||!n:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===r?n.call(t,i):n?n.value=i:e.set(t,i),i},m=function(t,e,i,r){if("a"===i&&!r)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!r:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===i?r:"a"===i?r.call(t):r?r.value:e.get(t)};class y{constructor(t){if(i.add(this),r.set(this,null),n.set(this,null),s.set(this,[]),a.set(this,void 0),o.set(this,new Map),l.set(this,void 0),h.set(this,null),f.set(this,!1),t.url&&t.file)throw new Error("Cannot specify both url and file.");if(!t.url&&!t.file)throw new Error("Must specify either url or file.");if(!(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&&(w(this,h,t.audioPlayer,"f"),m(this,h,"f").addEventListener("timeupdate",()=>{m(this,i,"m",p).call(this)}),m(this,h,"f").addEventListener("seeked",()=>{m(this,a,"f").querySelectorAll("p > span").forEach(t=>{t instanceof HTMLSpanElement&&(t.dataset.ariaCurrent="false")}),m(this,i,"m",p).call(this)})),t.enableAutoScroll&&w(this,f,t.enableAutoScroll,"f"),w(this,a,t.container,"f"),this.subscribe("fileSet",m(this,i,"m",d).bind(this)),this.subscribe("emptyLineTextSet",m(this,i,"m",d).bind(this)),this.subscribe("lyricLinesUpdated",m(this,i,"m",p).bind(this)),t.url?this.setUrl(t.url):t.file&&this.setFile(t.file)}get url(){return m(this,r,"f")}get file(){return m(this,n,"f")}get emptyLineText(){return m(this,l,"f")??"..."}async setUrl(t){try{const e=await fetch(t);if(!e.ok)throw new Error(`HTTP error! Status: ${e.status}.`);const i=e.headers.get("Content-Type");if(!i||!i.includes("text/plain"))throw new Error("Invalid content type. Expected text/plain.");const n=await e.blob();w(this,r,t,"f"),this.notify("urlSet",{url:t}),this.setFile(n)}catch(t){throw new Error((t instanceof Error&&t.message.length?t.message+" ":"")+"Failed to fetch the lyric file.")}}async setFile(t){const e=await m(this,i,"m",c).call(this,t);w(this,s,m(this,i,"m",u).call(this,e),"f"),w(this,n,t,"f"),this.notify("fileSet",{file:t})}setEmptyLineText(t){w(this,l,t,"f"),this.notify("emptyLineTextSet",{emptyLineText:t})}subscribe(t,e){m(this,o,"f").set(t,[...m(this,o,"f").get(t)??[],e])}unsubscribe(t,e){const i=m(this,o,"f").get(t);i&&i.length>1?m(this,o,"f").set(t,[...i.filter(t=>t!==e)]):m(this,o,"f").delete(t)}notify(t,e){[...m(this,o,"f").get(t)??[]].forEach(t=>{t(e)})}}r=new WeakMap,n=new WeakMap,s=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 r=new FileReader;r.onload=t=>{"string"==typeof t.target?.result&&t.target?.result.trim().length?e(t.target.result):i(new Error("Failed to read file content."))},r.onerror=()=>i(new Error("Error reading file.")),r.readAsText(t)})},u=function(t){const i=t.split("\n").reduce((t,i)=>{const r=i.trim();return e.test(r)?[...t,r]:t},[]);if(0===i.length)throw new Error("No valid lyric lines found in the file.");return i},d=function(){const e=m(this,s,"f").map(e=>{const i=e.replace(t,""),r=e.match(t)?.[0]?.slice(1,-1);if(!r)return"";const n=r.split(":"),s=n[1].split(".");return`\n <span \n data-time="${r}" \n data-ms-time="${60*parseInt(n[0],10)*1e3+1e3*parseInt(s[0],10)+10*parseInt(s[1],10)}"\n data-empty="${0===i.length}"\n data-aria-current="false"\n >\n ${i||(m(this,l,"f")??"...")}\n </span>\n `}).join("<br/>");m(this,a,"f").innerHTML=`<p>${e}</p>`,this.notify("lyricLinesUpdated",{lyricLines:m(this,s,"f")})},p=function(){if(!m(this,h,"f"))return;const t=1e3*m(this,h,"f").currentTime,e=m(this,a,"f").querySelectorAll("p > span");let i=null;for(let r=0;r<e.length;r++){const n=e[r],s=parseInt(n.getAttribute("data-ms-time")||"0",10);if(n.dataset.ariaCurrent="false",!(t>=s))break;i=n}i&&(i.dataset.ariaCurrent="true",m(this,f,"f")&&i.scrollIntoView({behavior:"smooth",block:"center"}))};export{y as Kashi};
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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kashi",
3
- "version": "2.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "Singing at the top of my lungs",
5
5
  "type": "module",
6
6
  "author": "lucasmc64",