kashi 1.0.2 → 1.1.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 +75 -33
- package/kashi.d.ts +28 -0
- package/kashi.js +1 -1
- package/kashi.mjs +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="https://gitlab.com/lucasmc64/kashi">GitLab</a> •
|
|
3
|
+
<a href="https://www.npmjs.com/package/kashi">NPM</a> •
|
|
4
|
+
<a href="https://unpkg.com/browse/kashi@latest">CDN</a> •
|
|
5
|
+
<a href="https://ko-fi.com/lucasmc64">Ko-fi</a>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
1
8
|

|
|
2
9
|
|
|
3
10
|
# 🎤 Kashi
|
|
@@ -6,32 +13,16 @@
|
|
|
6
13
|
|
|
7
14
|
This project is a dependency-free library that aims to provide a way to correctly read, format, structure, and display song lyrics.
|
|
8
15
|
|
|
9
|
-
## 📜
|
|
10
|
-
|
|
11
|
-
This project was created using tools to make development as easy as possible:
|
|
12
|
-
|
|
13
|
-
- [Node.JS](https://nodejs.org/)
|
|
14
|
-
It is only necessary to globally install the following packages that facilitate development.
|
|
15
|
-
|
|
16
|
-
- [Webpack](https://webpack.js.org/)
|
|
17
|
-
A static module bundler for modern JavaScript/TypeScript applications.
|
|
18
|
-
|
|
19
|
-
## 🪬 Overview
|
|
20
|
-
|
|
21
|
-
### 🔗 Useful links
|
|
22
|
-
|
|
23
|
-
**Repository**: https://gitlab.com/lucasmc64/kashi
|
|
16
|
+
## 📜 Features
|
|
24
17
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- **main**: If there is more than one developer or the sprint tasks are divided, this will be the place to gather the codes and resolve conflicts before the modifications go to testing.
|
|
34
|
-
- **TG-(number)**: It covers all the code produced to meet the needs exposed by a task and uses the same code as the task created in Taiga.
|
|
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
|
|
20
|
+
- [x] ✉️ Implements the Observer pattern and emits events at each step of the process whenever something changes
|
|
21
|
+
- [x] ✏️ Allows you to enter custom text when the lyrics line is empty
|
|
22
|
+
- [ ] 🎤 Synchronizes the lyrics with the music that is playing
|
|
23
|
+
- [ ] 🎩 Supports multiple lyrics for the same song (useful to keep track of the original lyrics and their translation)
|
|
24
|
+
- [ ] 🧞 Supports the [Walaoke extension](https://en.wikipedia.org/wiki/LRC_(file_format)#Walaoke_extension)
|
|
25
|
+
- [ ] 🕖️ Supports the [A2 extension](https://en.wikipedia.org/wiki/LRC_(file_format)#A2_extension_(Enhanced_LRC_format))
|
|
35
26
|
|
|
36
27
|
## 🎨 How to use it in my project?
|
|
37
28
|
|
|
@@ -77,7 +68,11 @@ The HTML is very similar to the classic scripts version, just change the `.js` e
|
|
|
77
68
|
> **Note**: Please read the [previous section](#classic-scripts) to get the details involved in importing and choosing a package version, they are the same here.
|
|
78
69
|
|
|
79
70
|
```html
|
|
80
|
-
<script
|
|
71
|
+
<script
|
|
72
|
+
src="https://unpkg.com/kashi@X.Y.Z/kashi.mjs"
|
|
73
|
+
type="module"
|
|
74
|
+
defer
|
|
75
|
+
></script>
|
|
81
76
|
```
|
|
82
77
|
|
|
83
78
|
```javascript
|
|
@@ -133,18 +128,65 @@ export const KashiWrapper = (props: KashiProps) => {
|
|
|
133
128
|
};
|
|
134
129
|
```
|
|
135
130
|
|
|
136
|
-
## 🧐
|
|
131
|
+
## 🧐 Constructor properties
|
|
137
132
|
|
|
138
133
|
You must pass some properties to Kashi to define what lyrics display and where. Here are its specifications:
|
|
139
134
|
|
|
140
|
-
| Property
|
|
141
|
-
|
|
|
142
|
-
| `file`
|
|
143
|
-
| `url`
|
|
144
|
-
| `container`
|
|
135
|
+
| Property | Type | Default value | Is required? | Description |
|
|
136
|
+
| --------------- | ------------------ | ------------- | ------------ | ----------------------------------------- |
|
|
137
|
+
| `file` | `Blob` (or `File`) | - | No | Lyrics file |
|
|
138
|
+
| `url` | `string` | - | No | Lyrics url |
|
|
139
|
+
| `container` | `HTMLDivElement` | - | Yes | Element where the lyrics will be inserted |
|
|
140
|
+
| `emptyLineText` | `string` | `🎝` | No | Custom text for empty lines of the lyrics |
|
|
145
141
|
|
|
146
142
|
> Neither `file` nor `url` are “mandatory”, but **at least one of these properties must be specified**, otherwise an error will be thrown.
|
|
147
143
|
|
|
144
|
+
## 👾 Generated HTML structure
|
|
145
|
+
|
|
146
|
+
The `div#kashi` represents the `container` passed to `Kashi` where the song lyrics will be inserted.
|
|
147
|
+
|
|
148
|
+
Each line of lyrics present in the lrc file will be wrapped by a `<p></p>` tag and inserted into the `container`.
|
|
149
|
+
|
|
150
|
+
Here's an example:
|
|
151
|
+
|
|
152
|
+
```html
|
|
153
|
+
<div id="kashi">
|
|
154
|
+
<!-- ... -->
|
|
155
|
+
<p>Binkusu no sake wo todoke ni yuku yo</p>
|
|
156
|
+
<p>Umikaze kimakase namimakase</p>
|
|
157
|
+
<p>Shio no mukou de yuuhi mo sawagu</p>
|
|
158
|
+
<p>Sora nya wa wo kaku tori no uta</p>
|
|
159
|
+
<!-- ... -->
|
|
160
|
+
</div>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## 📂 Methods and attributes
|
|
164
|
+
|
|
165
|
+
The instance generated by `Kashi` has some public methods and attributes that can be used to query or change properties on the fly.
|
|
166
|
+
|
|
167
|
+
| Name | Type | Description |
|
|
168
|
+
| ------------------ | --------- | ------------------------------------------------------------ |
|
|
169
|
+
| `url` | Attribute | Returns the url from the current lyrics if it was fetched from a link |
|
|
170
|
+
| `file` | Attribute | Returns the file from the current lyrics |
|
|
171
|
+
| `emptyLineText` | Attribute | Returns the default text set for empty lines |
|
|
172
|
+
| `setUrl` | Method | Function capable of changing the current lyrics file by passing the **url** of the new file |
|
|
173
|
+
| `setFile` | Method | Function capable of changing the current lyrics file by passing the the new **file** |
|
|
174
|
+
| `setEmptyLineText` | Method | Function capable of changing the text defined for empty lines |
|
|
175
|
+
| `subscribe` | Method | Function capable of defining a callback to be executed when a given event is triggered |
|
|
176
|
+
| `unsubscribe` | Method | Function capable of making a callback to stop listening to an event |
|
|
177
|
+
| `notify` | Method | Function capable of triggering an event |
|
|
178
|
+
|
|
179
|
+
## 🍾 Events
|
|
180
|
+
|
|
181
|
+
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:
|
|
182
|
+
|
|
183
|
+
| Event | Data | Trigger |
|
|
184
|
+
| ------------------- | --------------------------- | ------------------------------------------------------------ |
|
|
185
|
+
| `urlSet` | `{ url: string }` | When instantiating by informing `url` or calling the `setUrl` method |
|
|
186
|
+
| `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) |
|
|
187
|
+
| `emptyLineTextSet` | `{ emptyLineText: string }` | When instantiating by informing `emptyLineText` or calling the `setEmptyLineText` method |
|
|
188
|
+
| `lyricLinesUpdated` | `{ lyricLines: string[] }` | When inserting/updating lyrics in HTML |
|
|
189
|
+
|
|
148
190
|
## 🤔 How do I run the project on my machine?
|
|
149
191
|
|
|
150
192
|
The first step is to clone the project, either via terminal or even by downloading the compressed file (.zip). After that, go ahead.
|
|
@@ -165,7 +207,7 @@ You can also create a demo project using React and use [npm link](https://docs.n
|
|
|
165
207
|
|
|
166
208
|
Now you are running the project beautifully!
|
|
167
209
|
|
|
168
|
-
|
|
210
|
+
## ✏️ License
|
|
169
211
|
|
|
170
212
|
This project is under the GPL v3 license. See the [LICENSE](https://gitlab.com/lucasmc64/kashi/-/blob/main/LICENSE) for more information.
|
|
171
213
|
|
package/kashi.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Generated by dts-bundle-generator v9.5.1
|
|
2
|
+
|
|
3
|
+
export type KashiUrl = string | null;
|
|
4
|
+
export type KashiFile = Blob | null;
|
|
5
|
+
export type EventName = string;
|
|
6
|
+
export type EventData = any;
|
|
7
|
+
export type EventCallback = (data: EventData) => void;
|
|
8
|
+
export interface KashiProps {
|
|
9
|
+
url?: KashiUrl;
|
|
10
|
+
file?: KashiFile;
|
|
11
|
+
container: HTMLDivElement;
|
|
12
|
+
emptyLineText?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class Kashi {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(props: KashiProps);
|
|
17
|
+
get url(): KashiUrl;
|
|
18
|
+
get file(): KashiFile;
|
|
19
|
+
get emptyLineText(): string;
|
|
20
|
+
setUrl(url: string): Promise<void>;
|
|
21
|
+
setFile(file: Blob): Promise<void>;
|
|
22
|
+
setEmptyLineText(text: string): void;
|
|
23
|
+
subscribe(event: EventName, fn: EventCallback): void;
|
|
24
|
+
unsubscribe(event: EventName, fn: EventCallback): void;
|
|
25
|
+
notify(event: EventName, data?: EventData): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {};
|
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:()=>w});const r=/\[\d{2}:\d{2}.\d{2}\]/,i=/^\[\d{2}:\d{2}.\d{2}\](.*)$/;var n,o,
|
|
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:()=>w});const r=/\[\d{2}:\d{2}.\d{2}\]/,i=/^\[\d{2}:\d{2}.\d{2}\](.*)$/;var n,s,o,a,f,l,h,c,d,u,p=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},y=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 w{constructor(e){if(n.add(this),s.set(this,null),o.set(this,null),a.set(this,[]),f.set(this,void 0),l.set(this,new Map),h.set(this,void 0),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");e.emptyLineText&&this.setEmptyLineText(e.emptyLineText),p(this,f,e.container,"f"),this.subscribe("fileSet",y(this,n,"m",u).bind(this)),this.subscribe("emptyLineTextSet",y(this,n,"m",u).bind(this)),e.url?this.setUrl(e.url):e.file&&this.setFile(e.file)}get url(){return y(this,s,"f")}get file(){return y(this,o,"f")}get emptyLineText(){return y(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();p(this,s,e,"f"),this.notify("urlSet",{url:e}),this.setFile(i)}catch(e){throw new Error("Failed to fetch the lyric file.")}}async setFile(e){const t=await y(this,n,"m",c).call(this,e);p(this,a,y(this,n,"m",d).call(this,t),"f"),p(this,o,e,"f"),this.notify("fileSet",{file:e})}setEmptyLineText(e){p(this,h,e,"f"),this.notify("emptyLineTextSet",{emptyLineText:e})}subscribe(e,t){y(this,l,"f").set(e,[...y(this,l,"f").get(e)??[],t])}unsubscribe(e,t){const r=y(this,l,"f").get(e);r&&r.length>1?y(this,l,"f").set(e,[...r.filter(e=>e!==t)]):y(this,l,"f").delete(e)}notify(e,t){[...y(this,l,"f").get(e)??[]].forEach(e=>{e(t)})}}return s=new WeakMap,o=new WeakMap,a=new WeakMap,f=new WeakMap,l=new WeakMap,h=new WeakMap,n=new WeakSet,c=async function(e){return new Promise((t,r)=>{const i=new FileReader;i.onload=e=>{"string"==typeof e.target?.result?t(e.target.result):r(new Error("Failed to read file content."))},i.onerror=()=>r(new Error("Error reading file.")),i.readAsText(e)})},d=function(e){return e.split("\n").reduce((e,t)=>{const r=t.trim();return i.test(r)?[...e,r]:e},[])},u=function(){y(this,f,"f").innerHTML=y(this,a,"f").map(e=>`<p>${e.replace(r,"")||(y(this,h,"f")??"🎝")}</p>`).join(""),this.notify("lyricLinesUpdated",{lyricLines:y(this,a,"f")})},t})());
|
package/kashi.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const t=/\[\d{2}:\d{2}.\d{2}\]/,e=/^\[\d{2}:\d{2}.\d{2}\](.*)$/;var r,
|
|
1
|
+
const t=/\[\d{2}:\d{2}.\d{2}\]/,e=/^\[\d{2}:\d{2}.\d{2}\](.*)$/;var i,r,s,n,o,a,h,l,f,c,u=function(t,e,i,r,s){if("m"===r)throw new TypeError("Private method is not writable");if("a"===r&&!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"===r?s.call(t,i):s?s.value=i:e.set(t,i),i},w=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 d{constructor(t){if(i.add(this),r.set(this,null),s.set(this,null),n.set(this,[]),o.set(this,void 0),a.set(this,new Map),h.set(this,void 0),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");t.emptyLineText&&this.setEmptyLineText(t.emptyLineText),u(this,o,t.container,"f"),this.subscribe("fileSet",w(this,i,"m",c).bind(this)),this.subscribe("emptyLineTextSet",w(this,i,"m",c).bind(this)),t.url?this.setUrl(t.url):t.file&&this.setFile(t.file)}get url(){return w(this,r,"f")}get file(){return w(this,s,"f")}get emptyLineText(){return w(this,h,"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 s=await e.blob();u(this,r,t,"f"),this.notify("urlSet",{url:t}),this.setFile(s)}catch(t){throw new Error("Failed to fetch the lyric file.")}}async setFile(t){const e=await w(this,i,"m",l).call(this,t);u(this,n,w(this,i,"m",f).call(this,e),"f"),u(this,s,t,"f"),this.notify("fileSet",{file:t})}setEmptyLineText(t){u(this,h,t,"f"),this.notify("emptyLineTextSet",{emptyLineText: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)})}}r=new WeakMap,s=new WeakMap,n=new WeakMap,o=new WeakMap,a=new WeakMap,h=new WeakMap,i=new WeakSet,l=async function(t){return new Promise((e,i)=>{const r=new FileReader;r.onload=t=>{"string"==typeof t.target?.result?e(t.target.result):i(new Error("Failed to read file content."))},r.onerror=()=>i(new Error("Error reading file.")),r.readAsText(t)})},f=function(t){return t.split("\n").reduce((t,i)=>{const r=i.trim();return e.test(r)?[...t,r]:t},[])},c=function(){w(this,o,"f").innerHTML=w(this,n,"f").map(e=>`<p>${e.replace(t,"")||(w(this,h,"f")??"🎝")}</p>`).join(""),this.notify("lyricLinesUpdated",{lyricLines:w(this,n,"f")})};export{d as Kashi};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kashi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Singing at the top of my lungs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "lucasmc64",
|
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
"song"
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
|
+
"prestart": "npm run build",
|
|
24
25
|
"start": "gulp watchAll",
|
|
25
|
-
"build": "rm -rf dist && gulp compileAll && cat ./package.json | grep -v '\"private\":' > dist/package.json && \\cp LICENSE dist && \\cp README.md dist",
|
|
26
|
+
"build": "rm -rf dist && gulp compileAll && ./node_modules/.bin/dts-bundle-generator -o ./dist/kashi.d.ts ./src/index.ts && cat ./package.json | grep -v '\"private\":' > dist/package.json && \\cp LICENSE dist && \\cp README.md dist",
|
|
26
27
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
27
28
|
"test:watch": "npm test -- --watch",
|
|
28
29
|
"test:coverage": "npm test -- --coverage"
|