<div align="center">
  <a href="https://gitlab.com/lucasmc64/kashi">GitLab</a> • 
  <a href="https://www.npmjs.com/package/kashi">NPM</a> • 
  <a href="https://unpkg.com/browse/kashi@latest">CDN</a> • 
  <a href="https://ko-fi.com/lucasmc64">Ko-fi</a>
</div>

![Kashi](https://gitlab.com/lucasmc64/kashi/-/raw/main/readme/cover.png)

# 🎤 Kashi

## 🎯 Goal

This project is a dependency-free library that aims to provide a way to correctly read, format, structure, and display song lyrics.

## 📜 Features

- [x] 🎵 Process and list song lyrics in .lrc files
- [x] 💪 Supports both directly inputting the .lrc file and a URL that returns it
- [x] ✉️ Implements the Observer pattern and emits events at each step of the process whenever something changes
- [x] ✏️ Allows you to enter custom text when the lyrics line is empty
- [ ] 🎤 Synchronizes the lyrics with the music that is playing
- [ ] 🎩 Supports multiple lyrics for the same song (useful to keep track of the original lyrics and their translation)
- [ ] 🧞 Supports the [Walaoke extension](https://en.wikipedia.org/wiki/LRC_(file_format)#Walaoke_extension)
- [ ] 🕖️ Supports the [A2 extension](https://en.wikipedia.org/wiki/LRC_(file_format)#A2_extension_(Enhanced_LRC_format))

## 🎨 How to use it in my project?

### <span id="classic-scripts">🙌 Classic scripts</span>

The project is also exported using [UMD](https://github.com/umdjs/umd), which means that all variables and classes such as the `Kashi` class is exposed globally, and can be used in any script imported after the library.

> **Note**: You should **change `X.Y.Z` to the library version number** according to your needs, this is just an example. It is recommended to always use the latest version.
>
> It is recommended that you pin to the latest stable version of the lib. However, if you always want to use the latest version, you can also use `latest` instead of a predefined version number. **Be careful** though, breaking changes may be introduced and your project may need to adapt to the changes.

```html
<!DOCTYPE html>

<html>
  <head>
    <!-- ... -->
    <script src="https://unpkg.com/kashi@X.Y.Z/kashi.js" defer></script>
    <!-- ... -->
  </head>

  <body>
    <div id="kashi"></div>
  </body>
</html>
```

```js
"use strict";

// Using the file or using a url, both works fine
new Kashi({
  // url, // Public link that returns the file when a GET request is made OR...
  file. // Loaded from an input[type="file"] or anywhere else
  container: document.getElementById("kashi"),
});
```

### 📦️ ES6 Modules

The HTML is very similar to the classic scripts version, just change the `.js` extension to `.mjs` (and maybe will be necessary to adds a `type="module"` too), which will result in something similar to this:

> **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.

```html
<script
  src="https://unpkg.com/kashi@X.Y.Z/kashi.mjs"
  type="module"
  defer
></script>
```

```javascript
import { Kashi } from "kashi";

// Usage is essentially the same as in the previous section

// Using the file or using a url, both works fine
new Kashi({
  // url, // Public link that returns the file when a GET request is made OR...
  file. // Loaded from an input[type="file"] or anywhere else
  container: document.getElementById("kashi"),
});
```

### ⚛️ React

Install the lib using your favorite package manager.

```bash
npm install kashi
```

Since the library was designed primarily to be used with vanilla JS, a _helper_ component needs to be created to encapsulate Kashi's behavior and make it simple to reuse throughout the application.

> **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.

```tsx
import { useEffect, useRef } from "react";
import { Kashi, KashiProps } from "kashi";

// Example using Vite, React and TypeScript
export const KashiWrapper = (props: KashiProps) => {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (ref.current) {
      new Kashi({
        ...props,
        container: ref.current,
      });
    }

    return () => {
      // Required to avoid duplication when React is in Strict Mode
      if (ref.current) {
        ref.current.innerHTML = "";
      }
    };
  }, [ref.current]);

  return <div className="kashi-wrapper" ref={ref} />;
};
```

## 🧐 Constructor properties

You must pass some properties to Kashi to define what lyrics display and where. Here are its specifications:

| Property        | Type               | Default value | Is required? | Description                               |
| --------------- | ------------------ | ------------- | ------------ | ----------------------------------------- |
| `file`          | `Blob` (or `File`) | -             | No           | Lyrics file                               |
| `url`           | `string`           | -             | No           | Lyrics url                                |
| `container`     | `HTMLDivElement`   | -             | Yes          | Element where the lyrics will be inserted |
| `emptyLineText` | `string`           | `🎝`           | No           | Custom text for empty lines of the lyrics |

> Neither `file` nor `url` are “mandatory”, but **at least one of these properties must be specified**, otherwise an error will be thrown.

## 👾 Generated HTML structure

The `div#kashi` represents the `container` passed to `Kashi` where the song lyrics will be inserted.

Each line of lyrics present in the lrc file will be wrapped by a `<p></p>` tag and inserted into the `container`.

Here's an example:

```html
<div id="kashi">
  <!-- ... -->
  <p>Binkusu no sake wo todoke ni yuku yo</p>
  <p>Umikaze kimakase namimakase</p>
  <p>Shio no mukou de yuuhi mo sawagu</p>
  <p>Sora nya wa wo kaku tori no uta</p>
  <!-- ... -->
</div>
```

## 📂 Methods and attributes

The instance generated by `Kashi` has some public methods and attributes that can be used to query or change properties on the fly.

| Name               | Type      | Description                                                  |
| ------------------ | --------- | ------------------------------------------------------------ |
| `url`              | Attribute | Returns the url from the current lyrics if it was fetched from a link |
| `file`             | Attribute | Returns the file from the current lyrics                     |
| `emptyLineText`    | Attribute | Returns the default text set for empty lines                 |
| `setUrl`           | Method    | Function capable of changing the current lyrics file by passing the **url** of the new file |
| `setFile`          | Method    | Function capable of changing the current lyrics file by passing the the new **file** |
| `setEmptyLineText` | Method    | Function capable of changing the text defined for empty lines |
| `subscribe`        | Method    | Function capable of defining a callback to be executed when a given event is triggered |
| `unsubscribe`      | Method    | Function capable of making a callback to stop listening to an event |
| `notify`           | Method    | Function capable of triggering an event                      |

## 🍾 Events

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:

| Event               | Data                        | Trigger                                                      |
| ------------------- | --------------------------- | ------------------------------------------------------------ |
| `urlSet`            | `{ url: string }`           | When instantiating by informing `url` or calling the `setUrl` method |
| `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) |
| `emptyLineTextSet`  | `{ emptyLineText: string }` | When instantiating by informing `emptyLineText` or calling the `setEmptyLineText` method |
| `lyricLinesUpdated` | `{ lyricLines: string[] }`  | When inserting/updating lyrics in HTML                       |

## 🤔 How do I run the project on my machine?

The first step is to clone the project, either via terminal or even by downloading the compressed file (.zip). After that, go ahead.

### 🛠️ Requirements

- [NodeJS and NPM](https://nodejs.org)

### ✨ Running the project

With the dependencies properly installed, still in the terminal, run `npm start`.

Create a simple demo project using vanilla HTML/JS and use the files in the `dist` folder for testing.

You can also create a demo project using React and use [npm link](https://docs.npmjs.com/cli/v9/commands/npm-link).

### 🎉 If everything went well...

Now you are running the project beautifully!

## ✏️ License

This project is under the GPL v3 license. See the [LICENSE](https://gitlab.com/lucasmc64/kashi/-/blob/main/LICENSE) for more information.

---

Made with 💙 by lucasmc64 👋 [Get in touch!](https://www.linkedin.com/in/lucasmc64/)
