diff --git a/README.md b/README.md index 3cfb0bb8..34cc627f 100644 --- a/README.md +++ b/README.md @@ -38,135 +38,21 @@ QUL is implemented using Ruby on Rails, and Active Admin for implementing the ad - Import Data: Import data from different sources. - Quranic grammar and morphology: Manage Quranic grammar and morphology data. -## Setup Guide -This guide will help you set up the QUL project on your local machine. Follow the steps below to get started. +## Documentation +Start with the docs index: [docs/README.md](docs/README.md) -### Prerequisites -- **Ruby**: Version 3.3.3 -- **Rails**: Version 7.0.3 -- **RVM or rbenv**: For managing Ruby versions -- **PostgreSQL**: 14.3 or higher -- **Redis**: 7.0.0 or higher +Website docs index: [https://qul.tarteel.ai/docs](https://qul.tarteel.ai/docs) -#### 1. Clone the repository -```bash -git clone git@github.com:TarteelAI/quranic-universal-library.git -cd quranic-universal-library -``` +Primary path for resource users: -#### 2. Install Ruby and setup environment -```bash -rvm install 3.3.3 -rvm use 3.3.3 -rvm gemset create qul -rvm gemset use qul -``` +- Getting Started: [docs/getting-started.md](docs/getting-started.md) +- Downloading and Using Data: [docs/downloading-data.md](docs/downloading-data.md) +- Resource Guides Index: [docs/resource-guides-index.md](docs/resource-guides-index.md) +- Datasets: [docs/datasets.md](docs/datasets.md) +- Data Model: [docs/data-model.md](docs/data-model.md) +- Tutorials: [docs/tutorials.md](docs/tutorials.md) +- FAQ: [docs/faq.md](docs/faq.md) -#### 3. Install PostgreSQL -- **Ubuntu/Debian** -```bash -sudo apt-get update -sudo apt-get install postgresql postgresql-contrib libpq-dev -``` -- **MacOS** -```bash -brew install postgresql -``` -- **Windows** -Download and install the latest version of PostgreSQL from the [official website](https://www.postgresql.org/download/windows/). +## Contributing -#### 4. Install Dependencies -```bash -gem install bundler -bundle install -``` - -* **Tip:** To prevent Bundler from reinstalling due to a version mismatch, you can install the specific version listed under `BUNDLED_WITH` in `Gemfile.lock` using the command `gem install bundler -v [version]` - -#### 5. Database Configuration - -**QUL requires two databases:** - -1. **`quran_dev`**: This database holds all Quranic data, including translations, tafsirs, audio etc. It's accessed through `quran_api_db_dev` for the development environment. -2. **`quran_community_tarteel`**: This database hold data about user accounts, permissions, and user generated content and change log about translations. - -#### 6. Create Databases -Create the **`quran_community_tarteel`** database for managing user content. -```bash -rails db:create -``` - -For **`quran_dev`** you can create it manually or change the database name to `quran_dev` for `development` group in database.yml file and run `rails db:create` again. - -#### 7. Load the data for **`quran_dev`** database -The `quran_dev` database dump is available in both SQL and binary formats. Follow the appropriate instructions below to restore the database. - -> **Mini Database Dump for Development** -> -> The database backup provided below contains a limited subset of data, specifically selected for local development and testing. It is not a full production database and is intended solely for development and contribution purposes. -> -> We **do not provide or share** the full database backup. This mini dump contains all the essential data required to run QUL locally or contribute new features. If you're working on a feature that need more data, please open an issue. -> If you're looking for specific resource, please visit our [Resources page](https://qul.tarteel.ai/resources/) to download it. - - -#### Restoring from SQL Dump -7.1 **Restore using SQL Dump:** -Download the [SQL dump file](https://static-cdn.tarteel.ai/qul/mini-dumps/mini_quran_dev.sql.zip) and restore it using -```bash - psql quran_dev < "path to sql dump file" -``` -If it didn't work try running the following command: -```bash -psql -U postgres -d quran_dev -f path/to/quran_dev.sql -``` - -7.2 **Restore using binary dump:** -Download the [Binary dump file](https://static-cdn.tarteel.ai/qul/mini-dumps/mini_quran_dev.dump.zip) and restore it using -```bash -pg_restore --host localhost --port 5432 --no-owner --no-privileges --no-tablespaces --no-acl --dbname quran_dev -v "path to binary dump file" -``` - -### 8. Run the migrations for **`quran_community_tarteel`** database -```ruby -rails db:migrate -rails db:migrate -rails db:seed -``` - -#### 9. Run the Application -```bash -bin/dev -``` - -🌟Insha`Allah! Your application should be up and running at time point! 🌟 - -You can now visit [http://localhost:3000](http://localhost:3000) in your browser to explore the app. - -🔐 Head over to the admin panel at [http://localhost:3000/admin](http://localhost:3000/admin) - -### 10. Contributing to QUL -We welcome contributions to enhance the QUL project! If you'd like to contribute, please follow these steps: - -10.1 **Fork the Repository:** -Click on the "Fork" button at the top right of this page to create your own copy of the repository. - -10.2 **Clone Your Fork:** -```bash - git clone https://github.com/your_username/quranic-universal-library.git -``` - -10.3. **Create a new feature branch:** - ```bash - git checkout -b making-qul - ``` -10.4 **Make Your Changes:** - -10.5 **Push Your Changes:** -```bash -git add . -git commit -m "Add a brief description of your changes" -git push origin your-feature-branch -``` -10.6 **Create a Pull Request:** - -May Allah reward your efforts and make them beneficial for the community! 🤲 +Use [docs/contributing.md](docs/contributing.md) for the complete contribution flow and PR checklist. diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index e7cf5ccb..334bb41c 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,4 +1,9 @@ //= link_tree ../images //= link_tree ../fonts //= link_tree ../builds - +//= link application.css +//= link active_admin.css +//= link landing.css +//= link export.css +//= link tinymce_custom.css +//= link pdf.css diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index f86ea66d..11237f36 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -72,10 +72,10 @@ } .tw-docs pre { - @apply tw-p-4 tw-rounded-md tw-overflow-x-auto; - background-color: #ecf0f1; - font-size: 1.1em; - color: #2c3e50; + @apply tw-p-4 tw-overflow-x-auto; + background-color: #0f172a; + color: #e2e8f0; + font-size: 0.95rem; } .tw-docs code { @@ -85,6 +85,59 @@ color: #2c3e50; } + .tw-docs pre code { + @apply tw-p-0 tw-bg-transparent tw-rounded-none; + color: inherit; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + } + + .tw-docs-code-block, + .tw-docs-playground { + @apply tw-my-6 tw-overflow-hidden tw-rounded-xl tw-border tw-border-gray-200 tw-bg-white tw-shadow-sm; + } + + .tw-docs-code-header { + @apply tw-flex tw-items-center tw-justify-between tw-gap-2 tw-bg-gray-900 tw-px-3 tw-py-2 tw-text-gray-100; + } + + .tw-docs-code-language { + @apply tw-text-xs tw-font-semibold tw-uppercase tw-tracking-wide; + } + + .tw-docs-code-actions { + @apply tw-flex tw-items-center tw-gap-2; + } + + .tw-docs-code-button { + @apply tw-rounded tw-border-0 tw-bg-gray-700 tw-px-2 tw-py-1 tw-text-xs tw-font-medium tw-text-gray-100 hover:tw-bg-gray-600; + } + + .tw-docs-playground-grid { + @apply tw-grid xl:tw-grid-cols-2; + } + + .tw-docs-playground-pane { + @apply tw-flex tw-flex-col tw-bg-white; + } + + .tw-docs-playground-pane + .tw-docs-playground-pane { + @apply tw-border-t tw-border-gray-200 xl:tw-border-l xl:tw-border-t-0; + } + + .tw-docs-playground-label { + @apply tw-bg-gray-50 tw-px-4 tw-py-2 tw-text-xs tw-font-semibold tw-uppercase tw-tracking-wide tw-text-gray-500; + } + + .tw-docs-playground-editor { + @apply tw-min-h-[340px] lg:tw-min-h-[420px] tw-w-full tw-resize-y tw-border-0 tw-p-4 tw-font-mono tw-text-[13px] tw-leading-6 focus:tw-ring-2 focus:tw-ring-blue-500; + tab-size: 2; + background: linear-gradient(#ffffff, #ffffff) padding-box, linear-gradient(to bottom, #f8fafc, #f1f5f9) border-box; + } + + .tw-docs-playground-preview { + @apply tw-min-h-[340px] lg:tw-min-h-[420px] tw-w-full tw-border-0 tw-bg-white; + } + .tw-docs .example { @apply tw-p-4 tw-rounded-lg tw-mt-3 tw-mb-3; background-color: #eafaf1; @@ -163,4 +216,4 @@ .contributor-logo:hover .logo { transform: scale(1.05); -} \ No newline at end of file +} diff --git a/app/controllers/community_controller.rb b/app/controllers/community_controller.rb index 56243c8b..44cd3daa 100644 --- a/app/controllers/community_controller.rb +++ b/app/controllers/community_controller.rb @@ -24,6 +24,27 @@ def stt_validation end def docs + docs_page_service = DocsPageService.new + @docs_page = docs_page_service.find(params[:key]) + + if @docs_page.present? + @docs_source = :markdown + elsif (@docs_tag = DownloadableResourceTag.find_by(slug: params[:key])) + @docs_source = :tag + elsif docs_partial_exists?(params[:key]) + @docs_source = :partial + @docs_partial = params[:key] + else + raise ActionController::RoutingError, "Not Found" + end + + render layout: false if request.xhr? + end + + def docs_index + @docs_page = DocsPageService.new.readme + raise ActionController::RoutingError, "Not Found" unless @docs_page.present? + render layout: false if request.xhr? end @@ -82,4 +103,10 @@ def authorize_access! def init_presenter @presenter = CommunityPresenter.new(self) end + + def docs_partial_exists?(key) + return false unless key.to_s.match?(/\A[a-z0-9_-]+\z/) + + lookup_context.exists?("docs/#{key}", [], true) + end end diff --git a/app/javascript/controllers/docs_code_controller.js b/app/javascript/controllers/docs_code_controller.js new file mode 100644 index 00000000..711a0c1e --- /dev/null +++ b/app/javascript/controllers/docs_code_controller.js @@ -0,0 +1,175 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + static targets = ["code", "copyButton", "editor", "preview"]; + static values = { + playground: Boolean, + language: String + }; + + connect() { + this.initialCode = this.currentCode(); + this.applyEditorFontFallbacks(); + if (this.playgroundValue && this.hasEditorTarget && this.hasPreviewTarget) { + this.run(); + } + } + + copy(event) { + const sourceButton = event?.currentTarget || this.copyButtonTarget; + this.copyToClipboard(this.currentCode()) + .then(() => this.flashButton(sourceButton, "Copied")) + .catch(() => this.flashButton(sourceButton, "Copy failed")); + } + + run(event) { + event?.preventDefault(); + if (!this.playgroundValue || !this.hasEditorTarget || !this.hasPreviewTarget) return; + + const language = (this.languageValue || "javascript").toLowerCase(); + if (language !== "javascript") { + this.previewTarget.srcdoc = this.unsupportedPreview(language); + return; + } + + const safeCode = this.editorTarget.value.replace(/<\/script/gi, "<\\/script"); + this.previewTarget.srcdoc = this.javascriptPreview(safeCode, this.stylesheetLinksMarkup()); + } + + reset(event) { + event?.preventDefault(); + if (!this.playgroundValue || !this.hasEditorTarget) return; + + this.editorTarget.value = this.initialCode || ""; + this.run(); + } + + currentCode() { + if (this.hasEditorTarget) return this.editorTarget.value; + if (this.hasCodeTarget) return this.codeTarget.textContent || ""; + return ""; + } + + applyEditorFontFallbacks() { + if (!this.hasEditorTarget) return; + + // Keep monospace behavior, but add Arabic-capable fallbacks so Quranic glyphs don't render as boxes. + this.editorTarget.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', 'Noto Naskh Arabic', 'Amiri Quran', 'Scheherazade New', 'Geeza Pro', 'Arial Unicode MS', monospace"; + this.editorTarget.style.fontFeatureSettings = "\"liga\" 1, \"calt\" 1"; + } + + flashButton(button, message) { + if (!button) return; + + const originalLabel = button.dataset.originalLabel || button.textContent; + button.dataset.originalLabel = originalLabel; + button.textContent = message; + + if (button._docsCodeTimeout) clearTimeout(button._docsCodeTimeout); + button._docsCodeTimeout = setTimeout(() => { + button.textContent = originalLabel; + }, 1200); + } + + copyToClipboard(text) { + if (navigator.clipboard?.writeText) { + return navigator.clipboard.writeText(text); + } + + return new Promise((resolve, reject) => { + const element = document.createElement("textarea"); + element.value = text; + element.style.position = "fixed"; + element.style.opacity = "0"; + document.body.appendChild(element); + element.select(); + + try { + document.execCommand("copy"); + resolve(); + } catch (error) { + reject(error); + } finally { + element.remove(); + } + }); + } + + javascriptPreview(code, stylesheetLinksMarkup = "") { + return ` + + + + ${stylesheetLinksMarkup} + + + +
+ + +`; + } + + stylesheetLinksMarkup() { + const links = Array.from(document.querySelectorAll('link[rel="stylesheet"][href]')) + .map((link) => link.getAttribute("href")) + .filter(Boolean) + .map((href) => ``) + .join(""); + + return links; + } + + unsupportedPreview(language) { + return `

Preview for "${language}" is not supported yet.

`; + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 9f8b55f0..2ba4b48d 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -221,3 +221,6 @@ application.register("offcanvas-reset", OffcanvasResetController); import OffcanvasController from "./offcanvas_controller.js"; application.register("offcanvas", OffcanvasController); + +import DocsCodeController from "./docs_code_controller.js"; +application.register("docs-code", DocsCodeController); diff --git a/app/services/docs_page_service.rb b/app/services/docs_page_service.rb new file mode 100644 index 00000000..167179f3 --- /dev/null +++ b/app/services/docs_page_service.rb @@ -0,0 +1,266 @@ +require "redcarpet" +require "uri" +require "nokogiri" + +class DocsPageService + Page = Struct.new(:slug, :title, :html, keyword_init: true) + + DOCS_DIR = Rails.root.join("docs").freeze + SLUG_PATTERN = /\A[a-z0-9][a-z0-9-]*\z/ + ALLOWED_TAGS = %w[ + h1 h2 h3 h4 h5 h6 p br hr ul ol li pre code blockquote strong em a table thead tbody tr th td + div span button textarea iframe + ].freeze + ALLOWED_ATTRIBUTES = %w[ + href title class rel target type aria-label sandbox + data-controller data-action data-docs-code-target data-docs-code-language-value data-docs-code-playground-value + ].freeze + PLAYGROUND_LANGUAGE = "playground-js".freeze + + def initialize + @markdown = Redcarpet::Markdown.new( + Redcarpet::Render::HTML.new(filter_html: true, hard_wrap: true), + autolink: true, + fenced_code_blocks: true, + no_intra_emphasis: true, + strikethrough: true, + tables: true + ) + @sanitizer = Rails::Html::SafeListSanitizer.new + end + + def readme + render_file("README.md", slug: "index") + end + + def find(slug) + return nil unless valid_slug?(slug) + + render_file("#{slug}.md", slug: slug) + end + + private + + def render_file(filename, slug:) + path = resolve_path(filename) + return nil unless path&.file? + + markdown = path.read + html = @markdown.render(markdown) + html = rewrite_internal_links(html) + html = decorate_code_blocks(html) + html = @sanitizer.sanitize(html, tags: ALLOWED_TAGS, attributes: ALLOWED_ATTRIBUTES) + + Page.new( + slug: slug, + title: extract_title(markdown, slug), + html: html + ) + rescue Errno::ENOENT + nil + end + + def resolve_path(filename) + docs_root = DOCS_DIR.expand_path + candidate = docs_root.join(filename).cleanpath + docs_root_prefix = "#{docs_root}/" + return nil unless candidate.extname == ".md" + return nil unless candidate.to_s.start_with?(docs_root_prefix) + + candidate + end + + def valid_slug?(slug) + slug.is_a?(String) && slug.match?(SLUG_PATTERN) + end + + def extract_title(markdown, slug) + heading = markdown.each_line.find { |line| line.start_with?("# ") } + return heading.sub("# ", "").strip if heading.present? + + slug.to_s.humanize + end + + def rewrite_internal_links(html) + fragment = Nokogiri::HTML.fragment(html) + fragment.css("a[href]").each do |link| + href = link["href"].to_s.strip + link["href"] = rewrite_href(href) + end + fragment.to_html + end + + def decorate_code_blocks(html) + fragment = Nokogiri::HTML.fragment(html) + fragment.css("pre > code").each do |code_node| + pre_node = code_node.parent + next unless pre_node + + language = extract_code_language(code_node["class"]) + if language == PLAYGROUND_LANGUAGE + pre_node.replace(build_playground_block(fragment, code_node.text)) + else + pre_node.replace(build_code_block(fragment, pre_node, language)) + end + end + fragment.to_html + end + + def build_code_block(fragment, pre_node, language) + normalized_language = normalize_code_language(language) + cloned_pre = pre_node.dup(1) + cloned_pre["data-docs-code-target"] = "code" + cloned_code = cloned_pre.at_css("code") + if cloned_code && normalized_language != "text" + cloned_code["class"] = "language-#{normalized_language}" + end + + wrapper = Nokogiri::XML::Node.new("div", fragment) + wrapper["class"] = "tw-docs-code-block" + wrapper["data-controller"] = "docs-code" + wrapper.add_child(build_code_header(fragment, display_language(normalized_language), copy_only: true)) + wrapper.add_child(cloned_pre) + wrapper + end + + def build_playground_block(fragment, code_text) + wrapper = Nokogiri::XML::Node.new("div", fragment) + wrapper["class"] = "tw-docs-playground" + wrapper["data-controller"] = "docs-code" + wrapper["data-docs-code-playground-value"] = "true" + wrapper["data-docs-code-language-value"] = "javascript" + wrapper.add_child(build_code_header(fragment, "JavaScript Playground", copy_only: false)) + + grid = Nokogiri::XML::Node.new("div", fragment) + grid["class"] = "tw-docs-playground-grid" + + editor_pane = Nokogiri::XML::Node.new("div", fragment) + editor_pane["class"] = "tw-docs-playground-pane" + editor_pane.add_child(build_playground_label(fragment, "Editor")) + + editor = Nokogiri::XML::Node.new("textarea", fragment) + editor["class"] = "tw-docs-playground-editor" + editor["data-docs-code-target"] = "editor" + editor.content = code_text + editor_pane.add_child(editor) + + preview_pane = Nokogiri::XML::Node.new("div", fragment) + preview_pane["class"] = "tw-docs-playground-pane" + preview_pane.add_child(build_playground_label(fragment, "Preview")) + + preview = Nokogiri::XML::Node.new("iframe", fragment) + preview["class"] = "tw-docs-playground-preview" + preview["title"] = "Code preview" + preview["sandbox"] = "allow-scripts" + preview["data-docs-code-target"] = "preview" + preview_pane.add_child(preview) + + grid.add_child(editor_pane) + grid.add_child(preview_pane) + wrapper.add_child(grid) + wrapper + end + + def build_code_header(fragment, language_label, copy_only:) + header = Nokogiri::XML::Node.new("div", fragment) + header["class"] = "tw-docs-code-header" + + language = Nokogiri::XML::Node.new("span", fragment) + language["class"] = "tw-docs-code-language" + language.content = language_label + header.add_child(language) + + actions = Nokogiri::XML::Node.new("div", fragment) + actions["class"] = "tw-docs-code-actions" + actions.add_child(build_code_button(fragment, "Copy", "docs-code#copy", "copyButton")) + + unless copy_only + actions.add_child(build_code_button(fragment, "Run", "docs-code#run")) + actions.add_child(build_code_button(fragment, "Reset", "docs-code#reset")) + end + + header.add_child(actions) + header + end + + def build_code_button(fragment, text, action, target = nil) + button = Nokogiri::XML::Node.new("button", fragment) + button["type"] = "button" + button["class"] = "tw-docs-code-button" + button["data-action"] = action + button["data-docs-code-target"] = target if target.present? + button.content = text + button + end + + def build_playground_label(fragment, text) + label = Nokogiri::XML::Node.new("div", fragment) + label["class"] = "tw-docs-playground-label" + label.content = text + label + end + + def extract_code_language(class_name) + return "text" if class_name.blank? + + tokens = class_name.to_s.split(/\s+/) + explicit = tokens.find { |token| token.start_with?("language-") } + language = explicit ? explicit.delete_prefix("language-") : tokens.first + language.to_s.downcase + end + + def normalize_code_language(language) + case language.to_s.downcase + when "", "plain", "plaintext" + "text" + when "js", "node" + "javascript" + when "shell", "sh", "zsh" + "bash" + else + language.to_s.downcase + end + end + + def display_language(language) + case language + when "javascript" + "JavaScript" + when "python" + "Python" + when "ruby" + "Ruby" + when "bash" + "Shell" + when "json" + "JSON" + when "sql" + "SQL" + else + language.to_s.titleize + end + end + + def rewrite_href(href) + return href if href.blank? || href.start_with?("#") + + begin + uri = URI.parse(href) + return href if uri.scheme.present? || uri.host.present? + rescue URI::InvalidURIError + return href + end + + path_part = href.split(/[?#]/, 2).first.to_s + suffix = href.delete_prefix(path_part) + normalized = path_part.sub(%r{\A\./}, "").sub(%r{\Adocs/}, "") + + return href unless normalized.end_with?(".md") + + slug = normalized.delete_suffix(".md") + return "/docs#{suffix}" if slug.casecmp("README").zero? || slug.blank? + return href unless valid_slug?(slug) + + "/docs/#{slug}#{suffix}" + end +end diff --git a/app/views/community/_docs_markdown.html.erb b/app/views/community/_docs_markdown.html.erb new file mode 100644 index 00000000..c5e2f1bc --- /dev/null +++ b/app/views/community/_docs_markdown.html.erb @@ -0,0 +1,5 @@ +
+
+ <%= safe_html docs_page.html %> +
+
diff --git a/app/views/community/docs.html.erb b/app/views/community/docs.html.erb index cdfd1dc2..cedf0604 100644 --- a/app/views/community/docs.html.erb +++ b/app/views/community/docs.html.erb @@ -18,9 +18,11 @@ <% end %> - <% if tag = DownloadableResourceTag.find_by(slug: params[:key]) %> - <%= render partial: "docs/tag", locals: { tag: tag } %> - <% else %> - <%= render partial: "docs/#{params[:key]}" %> + <% if @docs_source == :markdown %> + <%= render "community/docs_markdown", docs_page: @docs_page %> + <% elsif @docs_source == :tag %> + <%= render partial: "docs/tag", locals: { tag: @docs_tag } %> + <% elsif @docs_source == :partial %> + <%= render partial: "docs/#{@docs_partial}" %> <% end %> - \ No newline at end of file + diff --git a/app/views/community/docs_index.html.erb b/app/views/community/docs_index.html.erb new file mode 100644 index 00000000..29b32ba2 --- /dev/null +++ b/app/views/community/docs_index.html.erb @@ -0,0 +1,15 @@ +
+ <% unless request.xhr? %> +
+
+

Documentation

+

QUL Documentation

+

+ Start here to download QUL resources and integrate them in your app. Contributor setup docs are available as a secondary track. +

+
+
+ <% end %> + + <%= render "community/docs_markdown", docs_page: @docs_page %> +
diff --git a/app/views/landing/_header.html.erb b/app/views/landing/_header.html.erb index e15645b6..3674ddb3 100644 --- a/app/views/landing/_header.html.erb +++ b/app/views/landing/_header.html.erb @@ -19,6 +19,7 @@
<%= link_to 'Resources', resources_path, class: 'tw-text-black hover:tw-text-[#46ac7a] tw-transition-colors tw-hidden md:tw-flex' %> <%= link_to 'Tools', tools_path, class: 'tw-text-black hover:tw-text-[#46ac7a] tw-transition-colors tw-hidden md:tw-flex' %> + <%= link_to 'Docs', docs_index_path, class: 'tw-text-black hover:tw-text-[#46ac7a] tw-transition-colors tw-hidden md:tw-flex' %> <%= link_to 'Community', 'https://discord.gg/HAcGh8mfmj', target: '_blank', class: 'tw-text-black hover:tw-text-[#46ac7a] tw-transition-colors tw-hidden md:tw-flex' %> <%= link_to 'FAQ', faq_path, class: 'tw-text-black hover:tw-text-[#46ac7a] tw-transition-colors tw-hidden md:tw-flex' %> <%= link_to 'https://github.com/TarteelAI/quranic-universal-library', class: 'tw-text-black hover:tw-text-[#46ac7a] tw-transition-colors tw-hidden md:tw-flex tw-items-center', target: '_blank' do %> @@ -65,6 +66,7 @@
<%= link_to 'Resources', resources_path, class: 'tw-text-black hover:tw-text-[#46ac7a] tw-transition-colors tw-text-lg' %> <%= link_to 'Tools', tools_path, class: 'tw-text-black hover:tw-text-[#46ac7a] tw-transition-colors tw-text-lg' %> + <%= link_to 'Docs', docs_index_path, class: 'tw-text-black hover:tw-text-[#46ac7a] tw-transition-colors tw-text-lg' %> <%= link_to 'Credits', credits_path, class: 'tw-text-black hover:tw-text-[#46ac7a] tw-transition-colors tw-text-lg' %> <%= link_to 'Community', 'https://discord.gg/HAcGh8mfmj', target: '_blank', class: 'tw-text-black hover:tw-text-[#46ac7a] tw-transition-colors tw-text-lg' %> <%= link_to 'FAQ', faq_path, class: 'tw-text-black hover:tw-text-[#46ac7a] tw-transition-colors tw-text-lg' %> diff --git a/app/views/shared/_nav.html.erb b/app/views/shared/_nav.html.erb index 613f7815..0a4cccde 100644 --- a/app/views/shared/_nav.html.erb +++ b/app/views/shared/_nav.html.erb @@ -17,6 +17,9 @@
  • Resources
  • +
  • + <%= link_to 'Docs', docs_index_path, class: 'tw-text-gray-700 tw-font-medium tw-no-underline tw-px-3 tw-py-2 tw-rounded hover:tw-bg-gray-200 tw-block' %> +
  • diff --git a/config/routes.rb b/config/routes.rb index 34a38952..cd85ac1e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -54,6 +54,7 @@ get 'tools', to: 'community#tools', as: :tools get :quran_scripts_comparison, to: 'quran_scripts_comparison#compare_words', as: :compare_words_quran_scripts_comparison get 'ayah-boundaries', to: 'community#ayah_boundaries', as: :ayah_boundaries + get 'docs', to: 'community#docs_index', as: :docs_index get 'docs/:key', to: 'community#docs', as: :docs get 'tools/help/:key', to: 'community#tool_help', as: :tools_help get 'community/chars_info', as: :chars_info diff --git a/config/tailwind.config.js b/config/tailwind.config.js index ea6b0bb9..44ee1a0e 100644 --- a/config/tailwind.config.js +++ b/config/tailwind.config.js @@ -40,6 +40,17 @@ module.exports = { 'tw-link-button', 'tw-docs', 'tw-docs *', + 'tw-docs-code-block', + 'tw-docs-code-header', + 'tw-docs-code-language', + 'tw-docs-code-actions', + 'tw-docs-code-button', + 'tw-docs-playground', + 'tw-docs-playground-grid', + 'tw-docs-playground-pane', + 'tw-docs-playground-label', + 'tw-docs-playground-editor', + 'tw-docs-playground-preview', 'tw-btn', 'tw-btn-sm', 'tw-btn-info', diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..52fd7b1d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,37 @@ +# QUL Documentation + +Use this documentation to discover, download, and integrate QUL resources in your own app or research workflow. + +You do not need to run the QUL CMS locally to use most QUL datasets. + +The pages here are published on the website at `/docs` and mirrored in the repository for versioned updates. + +## Audience + +- Resource users downloading datasets from QUL. +- Developers and researchers integrating QUL datasets. +- Contributors preparing documentation and code pull requests. + +## Resource Users (Primary) + +1. [Getting Started](getting-started.md) +2. [Downloading and Using Data](downloading-data.md) +3. [Tutorials](tutorials.md) +4. [Datasets](datasets.md) +5. [Data Model](data-model.md) +6. [FAQ](faq.md) + +## Documentation Maintenance (Secondary) + +1. [Resource Guide Template](resource-guide-template.md) +2. [Best Practices](best-practices.md) +3. [Contributing](contributing.md) +4. [Resource to Tutorial Mapping](resource-tutorial-map.md) + +## Primary Links + +- Project home: [https://qul.tarteel.ai/](https://qul.tarteel.ai/) +- Resources directory: [https://qul.tarteel.ai/resources](https://qul.tarteel.ai/resources) +- Source code: [https://github.com/TarteelAI/quranic-universal-library](https://github.com/TarteelAI/quranic-universal-library) +- Issues: [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) +- Community Discord: [https://discord.gg/HAcGh8mfmj](https://discord.gg/HAcGh8mfmj) diff --git a/docs/best-practices.md b/docs/best-practices.md new file mode 100644 index 00000000..494efaf3 --- /dev/null +++ b/docs/best-practices.md @@ -0,0 +1,34 @@ +# Best Practices + +## Data and Schema + +- Keep `surah_id`, `ayah_number`, and `word_position` as integers. +- Add indexes for `surah_id + ayah_number` in verse-level tables. +- Keep translations and tafsir in separate tables to support multiple sources and languages. +- Avoid duplicating large text blobs when ID-based references are enough. + +## Performance + +- Cache frequently requested verse and tafsir payloads. +- Use SQLite/PostgreSQL for large datasets instead of in-memory-only workflows. +- Stream large JSON files instead of loading them entirely. +- Benchmark expensive joins on morphology/topic workloads. + +## Data Quality and Validation + +- Validate required keys (`surah_id`, `ayah_number`, `word_position`) early. +- Keep a small verification script to test joins across selected resource categories. +- Confirm UTF-8 handling in your runtime, storage, and API outputs. +- Track dataset source/version in your metadata for traceability. + +## Documentation Quality + +- Keep setup commands copy/paste ready. +- Put new-user steps before advanced implementation details. +- Include likely failure cases and exact remediation commands. +- Keep GitHub markdown files as canonical content for website docs. + +## Change Management + +- Keep pull requests focused by concern (docs, integrations, feature). +- Add concise release notes when docs change user-facing resource workflows. diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 00000000..614d5c7f --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,46 @@ +# Contributing + +Thanks for helping improve QUL. + +## Where to Contribute + +- Repository: [https://github.com/TarteelAI/quranic-universal-library](https://github.com/TarteelAI/quranic-universal-library) +- Issues: [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Standard Flow + +1. Fork the repository. +2. Clone your fork. +3. Add upstream remote. +4. Create a feature branch. +5. Make focused changes. +6. Push branch and open PR. + +```bash +git clone https://github.com/YOUR-USERNAME/quranic-universal-library.git +cd quranic-universal-library +git remote add upstream https://github.com/TarteelAI/quranic-universal-library.git +git switch -c docs/your-topic +``` + +## Documentation Contributions + +- Edit files in `docs/` first (source of truth). +- Ensure links are valid in both GitHub and website docs rendering. +- Update root `README.md` only when entrypoint links or quick-start instructions change. + +## Reporting Data Issues + +When opening an issue for dataset problems, include: + +- Dataset URL and format (JSON/SQLite) +- Exact identifiers (`surah_id`, `ayah_number`, `word_position` when relevant) +- Expected vs actual output +- Minimal reproducible snippet + +## PR Checklist + +- Scope is clear and focused. +- Commands in docs were re-validated. +- New user onboarding path remains intact. +- Backward-compatible website docs behavior is preserved. diff --git a/docs/data-model.md b/docs/data-model.md new file mode 100644 index 00000000..3269437c --- /dev/null +++ b/docs/data-model.md @@ -0,0 +1,33 @@ +# Data Model + +This page explains how resource users should model and join downloaded QUL datasets. + +## Quran Hierarchy + +Quran -> Surah -> Ayah -> Word + +Common fields by level: + +- Surah: `surah_id`, names, revelation type, ayah count +- Ayah: `surah_id`, `ayah_number`, text, juz/hizb/manzil context +- Word: `surah_id`, `ayah_number`, `word_position`, text, root/lemma/POS + +## Core Shared Identifiers + +- `surah_id` (or `surah`) +- `ayah_number` (or `ayah`) +- `word_position` (or `position`) + +## Recommended Join Patterns + +- Translations: `surah_id + ayah_number` +- Tafsir: `surah_id + ayah_number` +- Topics/themes: `surah_id + ayah_number` +- Morphology: `surah_id + ayah_number + word_position` + +## Practical Integration Notes + +- Keep numeric identity fields as integers for stable joins. +- Normalize by resource type (script, translation, tafsir, morphology) instead of duplicating large text fields. +- Add indexes on common join keys before shipping production workloads. +- Validate key consistency on a sample batch before full import. diff --git a/docs/datasets.md b/docs/datasets.md new file mode 100644 index 00000000..d96221e8 --- /dev/null +++ b/docs/datasets.md @@ -0,0 +1,97 @@ +# Datasets + +Use this page to choose the right QUL category before integration. + +Resources directory: [https://qul.tarteel.ai/resources](https://qul.tarteel.ai/resources) + +## Quran Script + +Arabic Quran text in multiple scripts and related representations. + +- Link: [https://qul.tarteel.ai/resources/quran-script](https://qul.tarteel.ai/resources/quran-script) +- Typical use: Quran readers, script rendering, typography work +- Tutorial: [tutorial-quran-script-end-to-end.md](tutorial-quran-script-end-to-end.md) + +## Translations + +Ayah-level and word-level translations across languages. + +- Link: [https://qul.tarteel.ai/resources/translation](https://qul.tarteel.ai/resources/translation) +- Typical use: multilingual readers, search, localization +- Tutorial: [tutorial-translation-end-to-end.md](tutorial-translation-end-to-end.md) + +## Tafsir + +Tafsir datasets in multiple languages and styles. + +- Link: [https://qul.tarteel.ai/resources/tafsir](https://qul.tarteel.ai/resources/tafsir) +- Typical use: commentary panels, study tools +- Tutorial: [tutorial-tafsir-end-to-end.md](tutorial-tafsir-end-to-end.md) + +## Recitations + +Audio recitation data and segment timing. + +- Link: [https://qul.tarteel.ai/resources/recitation](https://qul.tarteel.ai/resources/recitation) +- Typical use: memorization tools, synchronized playback +- Tutorial: [tutorial-recitation-end-to-end.md](tutorial-recitation-end-to-end.md) + +## Morphology and Grammar + +Word-level linguistic features such as roots, lemmas, and POS tags. + +- Link: [https://qul.tarteel.ai/resources/morphology](https://qul.tarteel.ai/resources/morphology) +- Typical use: NLP pipelines, grammar exploration, word study +- Tutorial: [tutorial-morphology-end-to-end.md](tutorial-morphology-end-to-end.md) + +## Topics and Concepts + +Theme/topic tagging for ayahs and semantic exploration. + +- Link: [https://qul.tarteel.ai/resources/ayah-topics](https://qul.tarteel.ai/resources/ayah-topics) +- Typical use: thematic search and discovery +- Tutorials: + - [tutorial-ayah-topics-end-to-end.md](tutorial-ayah-topics-end-to-end.md) + - [tutorial-mutashabihat-end-to-end.md](tutorial-mutashabihat-end-to-end.md) + - [tutorial-similar-ayah-end-to-end.md](tutorial-similar-ayah-end-to-end.md) + - [tutorial-ayah-theme-end-to-end.md](tutorial-ayah-theme-end-to-end.md) + +## Surah Information + +Contextual and descriptive metadata about surahs. + +- Link: [https://qul.tarteel.ai/resources/surah-info](https://qul.tarteel.ai/resources/surah-info) +- Typical use: educational views and chapter overviews +- Tutorial: [tutorial-surah-information-end-to-end.md](tutorial-surah-information-end-to-end.md) + +## Structural Metadata + +Navigation-oriented entities like juz, hizb, rub, and manzil. + +- Link: [https://qul.tarteel.ai/resources/quran-metadata](https://qul.tarteel.ai/resources/quran-metadata) +- Typical use: navigation UIs and indexing +- Tutorial: [tutorial-quran-metadata-end-to-end.md](tutorial-quran-metadata-end-to-end.md) + +## Transliteration + +Non-Arabic script/phonetic representation for pronunciation support. + +- Link: [https://qul.tarteel.ai/resources/transliteration](https://qul.tarteel.ai/resources/transliteration) +- Typical use: pronunciation support and beginner-friendly reading +- Tutorial: [tutorial-transliteration-end-to-end.md](tutorial-transliteration-end-to-end.md) + +## Fonts + +Quran-focused fonts and related usage assets. + +- Link: [https://qul.tarteel.ai/resources/font](https://qul.tarteel.ai/resources/font) +- Typical use: script rendering and web/app typography +- Tutorial: [tutorial-font-end-to-end.md](tutorial-font-end-to-end.md) + +## Mushaf Layouts + +Page-oriented layout resources for mushaf-style viewing. + +- Link: [https://qul.tarteel.ai/resources/mushaf-layout](https://qul.tarteel.ai/resources/mushaf-layout) +- Typical use: page-by-page Quran navigation and rendering +- Tutorial: [tutorial-mushaf-layout-end-to-end.md](tutorial-mushaf-layout-end-to-end.md) diff --git a/docs/downloading-data.md b/docs/downloading-data.md new file mode 100644 index 00000000..93330f88 --- /dev/null +++ b/docs/downloading-data.md @@ -0,0 +1,84 @@ +# Downloading and Using Data + +Use this guide when consuming QUL data in your own application or research pipeline. + +Resources directory: [https://qul.tarteel.ai/resources](https://qul.tarteel.ai/resources) + +Many resources are available in: + +- JSON: simple integration, scripts, prototypes +- SQLite: better for larger datasets and query-heavy workflows + +SQLite docs: [https://www.sqlite.org/docs.html](https://www.sqlite.org/docs.html) + +## Typical Integration Workflow + +1. Select a dataset from `/resources`. +2. Download JSON or SQLite. +3. Inspect keys (`surah_id`, `ayah_number`, `word_position`, etc.). +4. Load into your runtime. +5. Import into your database for production workloads. +6. Add indexes on high-traffic query keys. + +## Load Examples + +### JavaScript + +```javascript +const fs = require("fs") +const data = JSON.parse(fs.readFileSync("data.json", "utf8")) +console.log(data[0]) +``` + +### Python + +```python +import json + +with open("data.json", "r", encoding="utf-8") as f: + data = json.load(f) + +print(data[0]) +``` + +### Ruby + +```ruby +require "json" + +data = JSON.parse(File.read("data.json")) +puts data.first +``` + +## Performance Tips + +- Index by `surah_id` + `ayah_number`. +- For large datasets, prefer SQLite or PostgreSQL over in-memory JSON processing. +- Cache frequently accessed verses/tafsir payloads. +- Stream very large JSON files instead of loading full files into memory. + +## Troubleshooting + +- Download is slow/fails: retry with a stable connection and verify file size after download. +- Schema mismatch in your app: inspect fields first and map keys explicitly. +- Encoding issues: ensure UTF-8 handling in runtime and database. +- Memory issues on large files: prefer SQLite or stream JSON. + +## When to Request Updates or Changes + +Open a GitHub issue when: + +- download links are broken +- files are missing expected records +- key mappings are inconsistent +- you need coverage not available in current packages + +Include: + +- resource URL +- chosen format (JSON/SQLite) +- identifiers involved (`surah_id`, `ayah_number`, `word_position` if relevant) +- expected vs actual behavior +- a minimal reproducible snippet + +Issue tracker: [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 00000000..9a0ad9e8 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,38 @@ +# FAQ + +## Is there an API? + +QUL primarily provides downloadable datasets. Integrators usually package data into their own API or database layer. + +## Do I need to clone this repository to use QUL resources? + +No. Most users can work directly from downloadable resources at [https://qul.tarteel.ai/resources](https://qul.tarteel.ai/resources). + +## Should I choose JSON or SQLite? + +- JSON: best for quick scripts and prototypes. +- SQLite: best for larger datasets and query-heavy applications. + +## How do I join different resources together? + +Start with shared identifiers: + +- `surah_id` +- `ayah_number` +- `word_position` (for word-level resources) + +See [data-model.md](data-model.md) for join guidance. + +## What should I do if data looks wrong or missing? + +Open an issue with: + +- resource URL +- format (JSON/SQLite) +- exact identifiers involved +- expected vs actual output +- minimal reproducible snippet + +## Can I use QUL data commercially? + +Check repository license terms and dataset-specific licensing details before production use. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 00000000..05983063 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,78 @@ +# Getting Started + +This guide is for users who want to download QUL resources and use them in their own applications. + +You can start here without cloning the repository or running QUL locally. + +## Before You Start (Important Clarifications) + +- You usually do **not** need to clone the QUL repository to use resources. +- QUL is primarily a downloadable resource platform (not a hosted public API service). +- Resource schemas are related but not always identical, so inspect fields before integration. +- Use JSON for speed of integration and SQLite for larger, query-heavy workloads. + +## What You Can Build + +- Quran reader apps (Arabic + translation) +- Verse/topic search experiences +- Tafsir exploration features +- Word-by-word learning tools (root, lemma, POS) +- NLP/AI pipelines using structured Quran datasets + +## 5-Minute Quick Start (Resource Users) + +1. Open the resources directory: [https://qul.tarteel.ai/resources](https://qul.tarteel.ai/resources) +2. Pick a dataset category (start with Quran Script or Translation). +3. Choose format: + - JSON for quick integration and scripts + - SQLite for larger datasets and query-heavy usage +4. Download the file(s). +5. Load data in your app and render one ayah. + +## Common Dataset Categories + +- Quran Script: [https://qul.tarteel.ai/resources/quran-script](https://qul.tarteel.ai/resources/quran-script) +- Translations: [https://qul.tarteel.ai/resources/translation](https://qul.tarteel.ai/resources/translation) +- Tafsir: [https://qul.tarteel.ai/resources/tafsir](https://qul.tarteel.ai/resources/tafsir) +- Recitations: [https://qul.tarteel.ai/resources/recitation](https://qul.tarteel.ai/resources/recitation) +- Morphology: [https://qul.tarteel.ai/resources/morphology](https://qul.tarteel.ai/resources/morphology) +- Topics: [https://qul.tarteel.ai/resources/ayah-topics](https://qul.tarteel.ai/resources/ayah-topics) +- Metadata: [https://qul.tarteel.ai/resources/quran-metadata](https://qul.tarteel.ai/resources/quran-metadata) + +## Minimal Load Example + +### JavaScript + +```javascript +const fs = require("fs") +const data = JSON.parse(fs.readFileSync("data.json", "utf8")) +console.log(data[0]) +``` + +### Python + +```python +import json + +with open("data.json", "r", encoding="utf-8") as f: + data = json.load(f) + +print(data[0]) +``` + +## Identifiers to Learn First + +Most datasets can be joined using: + +- `surah_id` +- `ayah_number` +- `word_position` (word-level resources) + +For details, see [data-model.md](data-model.md). + +## Next Pages + +- Detailed download and format guidance: [downloading-data.md](downloading-data.md) +- Category-by-category overview: [datasets.md](datasets.md) +- Canonical step-by-step implementations: [tutorials.md](tutorials.md) +- Common questions: [faq.md](faq.md) diff --git a/docs/resource-ayah-theme.md b/docs/resource-ayah-theme.md new file mode 100644 index 00000000..b4f94f5b --- /dev/null +++ b/docs/resource-ayah-theme.md @@ -0,0 +1,15 @@ +# Ayah Theme Guide + +This page is kept for compatibility with older links. + +Canonical implementation guide: + +- [Tutorial 14: Ayah Theme End-to-End](tutorial-ayah-theme-end-to-end.md) + +Recommended starting point for new users: + +- [Tutorials Index](tutorials.md) + +Resource directory entry: + +- [https://qul.tarteel.ai/resources/ayah-theme](https://qul.tarteel.ai/resources/ayah-theme) diff --git a/docs/resource-ayah-topics.md b/docs/resource-ayah-topics.md new file mode 100644 index 00000000..ddc64baa --- /dev/null +++ b/docs/resource-ayah-topics.md @@ -0,0 +1,15 @@ +# Ayah Topics Guide + +This page is kept for compatibility with older links. + +Canonical implementation guide: + +- [Tutorial 10: Ayah Topics End-to-End](tutorial-ayah-topics-end-to-end.md) + +Recommended starting point for new users: + +- [Tutorials Index](tutorials.md) + +Resource directory entry: + +- [https://qul.tarteel.ai/resources/ayah-topics](https://qul.tarteel.ai/resources/ayah-topics) diff --git a/docs/resource-fonts.md b/docs/resource-fonts.md new file mode 100644 index 00000000..9355be03 --- /dev/null +++ b/docs/resource-fonts.md @@ -0,0 +1,15 @@ +# Quran Fonts Guide + +This page is kept for compatibility with older links. + +Canonical implementation guide: + +- [Tutorial 6: Font End-to-End](tutorial-font-end-to-end.md) + +Recommended starting point for new users: + +- [Tutorials Index](tutorials.md) + +Resource directory entry: + +- [https://qul.tarteel.ai/resources/font](https://qul.tarteel.ai/resources/font) diff --git a/docs/resource-guide-template.md b/docs/resource-guide-template.md new file mode 100644 index 00000000..54febbae --- /dev/null +++ b/docs/resource-guide-template.md @@ -0,0 +1,63 @@ +# Resource Guide Template + +Use this template when writing or updating any QUL resource documentation page. + +## 1) What This Resource Is + +- Short definition of the dataset/resource. +- Scope of data it includes. +- Typical formats available (JSON, SQLite, other files). + +## 2) When to Use It + +- Practical use cases. +- Who benefits most (app developers, educators, researchers, etc.). +- When another resource category may be a better fit. + +## 3) How to Download or Access It + +1. Open the resource category URL. +2. Select a specific resource package/version. +3. Choose format (JSON/SQLite/etc.). +4. Download and validate file integrity. +5. Inspect fields before integration. + +Include: + +- Direct category URL. +- Notes on format tradeoffs. +- Any known prerequisites. + +## 4) Step-by-Step Integration + +1. Load the file. +2. Validate required keys. +3. Store/import in your app database. +4. Add indexes for common lookup fields. +5. Build one minimal feature end-to-end. + +## 5) Real-World Usage Example + +Include one realistic mini scenario with: + +- Goal +- Required resources +- Data flow +- Expected output + +## 6) When to Request Updates or Changes + +Open an issue when: + +- Data appears incorrect or missing. +- Links or downloadable files are broken. +- Required fields are absent/inconsistent. +- You need additional resource coverage. + +Issue report should include: + +- Resource URL +- Format +- Exact identifiers (`surah_id`, `ayah_number`, `word_position` if relevant) +- Expected vs actual behavior +- Minimal reproducible snippet diff --git a/docs/resource-morphology.md b/docs/resource-morphology.md new file mode 100644 index 00000000..b1899cfb --- /dev/null +++ b/docs/resource-morphology.md @@ -0,0 +1,15 @@ +# Morphology Guide + +This page is kept for compatibility with older links. + +Canonical implementation guide: + +- [Tutorial 11: Morphology End-to-End](tutorial-morphology-end-to-end.md) + +Recommended starting point for new users: + +- [Tutorials Index](tutorials.md) + +Resource directory entry: + +- [https://qul.tarteel.ai/resources/morphology](https://qul.tarteel.ai/resources/morphology) diff --git a/docs/resource-mushaf-layouts.md b/docs/resource-mushaf-layouts.md new file mode 100644 index 00000000..91b12821 --- /dev/null +++ b/docs/resource-mushaf-layouts.md @@ -0,0 +1,15 @@ +# Mushaf Layouts Guide + +This page is kept for compatibility with older links. + +Canonical implementation guide: + +- [Tutorial 2: Mushaf Layout End-to-End](tutorial-mushaf-layout-end-to-end.md) + +Recommended starting point for new users: + +- [Tutorials Index](tutorials.md) + +Resource directory entry: + +- [https://qul.tarteel.ai/resources/mushaf-layout](https://qul.tarteel.ai/resources/mushaf-layout) diff --git a/docs/resource-mutashabihat.md b/docs/resource-mutashabihat.md new file mode 100644 index 00000000..394f18b4 --- /dev/null +++ b/docs/resource-mutashabihat.md @@ -0,0 +1,15 @@ +# Mutashabihat Guide + +This page is kept for compatibility with older links. + +Canonical implementation guide: + +- [Tutorial 12: Mutashabihat End-to-End](tutorial-mutashabihat-end-to-end.md) + +Recommended starting point for new users: + +- [Tutorials Index](tutorials.md) + +Resource directory entry: + +- [https://qul.tarteel.ai/resources/mutashabihat](https://qul.tarteel.ai/resources/mutashabihat) diff --git a/docs/resource-quran-metadata.md b/docs/resource-quran-metadata.md new file mode 100644 index 00000000..06f728ea --- /dev/null +++ b/docs/resource-quran-metadata.md @@ -0,0 +1,15 @@ +# Quran Metadata Guide + +This page is kept for compatibility with older links. + +Canonical implementation guide: + +- [Tutorial 7: Quran Metadata End-to-End](tutorial-quran-metadata-end-to-end.md) + +Recommended starting point for new users: + +- [Tutorials Index](tutorials.md) + +Resource directory entry: + +- [https://qul.tarteel.ai/resources/quran-metadata](https://qul.tarteel.ai/resources/quran-metadata) diff --git a/docs/resource-quran-script.md b/docs/resource-quran-script.md new file mode 100644 index 00000000..27e97540 --- /dev/null +++ b/docs/resource-quran-script.md @@ -0,0 +1,15 @@ +# Quran Script Guide + +This page is kept for compatibility with older links. + +Canonical implementation guide: + +- [Tutorial 5: Quran Script End-to-End](tutorial-quran-script-end-to-end.md) + +Recommended starting point for new users: + +- [Tutorials Index](tutorials.md) + +Resource directory entry: + +- [https://qul.tarteel.ai/resources/quran-script](https://qul.tarteel.ai/resources/quran-script) diff --git a/docs/resource-recitations.md b/docs/resource-recitations.md new file mode 100644 index 00000000..5ad14f69 --- /dev/null +++ b/docs/resource-recitations.md @@ -0,0 +1,15 @@ +# Recitations Guide + +This page is kept for compatibility with older links. + +Canonical implementation guide: + +- [Tutorial 1: Recitation End-to-End](tutorial-recitation-end-to-end.md) + +Recommended starting point for new users: + +- [Tutorials Index](tutorials.md) + +Resource directory entry: + +- [https://qul.tarteel.ai/resources/recitation](https://qul.tarteel.ai/resources/recitation) diff --git a/docs/resource-similar-ayah.md b/docs/resource-similar-ayah.md new file mode 100644 index 00000000..b0a686b7 --- /dev/null +++ b/docs/resource-similar-ayah.md @@ -0,0 +1,15 @@ +# Similar Ayah Guide + +This page is kept for compatibility with older links. + +Canonical implementation guide: + +- [Tutorial 13: Similar Ayah End-to-End](tutorial-similar-ayah-end-to-end.md) + +Recommended starting point for new users: + +- [Tutorials Index](tutorials.md) + +Resource directory entry: + +- [https://qul.tarteel.ai/resources/similar-ayah](https://qul.tarteel.ai/resources/similar-ayah) diff --git a/docs/resource-surah-information.md b/docs/resource-surah-information.md new file mode 100644 index 00000000..76586e50 --- /dev/null +++ b/docs/resource-surah-information.md @@ -0,0 +1,15 @@ +# Surah Information Guide + +This page is kept for compatibility with older links. + +Canonical implementation guide: + +- [Tutorial 9: Surah Information End-to-End](tutorial-surah-information-end-to-end.md) + +Recommended starting point for new users: + +- [Tutorials Index](tutorials.md) + +Resource directory entry: + +- [https://qul.tarteel.ai/resources/surah-info](https://qul.tarteel.ai/resources/surah-info) diff --git a/docs/resource-tafsirs.md b/docs/resource-tafsirs.md new file mode 100644 index 00000000..3029f9b6 --- /dev/null +++ b/docs/resource-tafsirs.md @@ -0,0 +1,15 @@ +# Tafsirs Guide + +This page is kept for compatibility with older links. + +Canonical implementation guide: + +- [Tutorial 4: Tafsir End-to-End](tutorial-tafsir-end-to-end.md) + +Recommended starting point for new users: + +- [Tutorials Index](tutorials.md) + +Resource directory entry: + +- [https://qul.tarteel.ai/resources/tafsir](https://qul.tarteel.ai/resources/tafsir) diff --git a/docs/resource-topics-and-concepts.md b/docs/resource-topics-and-concepts.md new file mode 100644 index 00000000..034b84dc --- /dev/null +++ b/docs/resource-topics-and-concepts.md @@ -0,0 +1,14 @@ +# Topics and Concepts Guide (Legacy Umbrella) + +This page is kept for compatibility with older links. + +Canonical tutorials for this area: + +1. [Tutorial 10: Ayah Topics End-to-End](tutorial-ayah-topics-end-to-end.md) +2. [Tutorial 12: Mutashabihat End-to-End](tutorial-mutashabihat-end-to-end.md) +3. [Tutorial 13: Similar Ayah End-to-End](tutorial-similar-ayah-end-to-end.md) +4. [Tutorial 14: Ayah Theme End-to-End](tutorial-ayah-theme-end-to-end.md) + +Recommended starting point: + +- [Tutorials Index](tutorials.md) diff --git a/docs/resource-translations.md b/docs/resource-translations.md new file mode 100644 index 00000000..7fe3add6 --- /dev/null +++ b/docs/resource-translations.md @@ -0,0 +1,15 @@ +# Translations Guide + +This page is kept for compatibility with older links. + +Canonical implementation guide: + +- [Tutorial 3: Translation End-to-End](tutorial-translation-end-to-end.md) + +Recommended starting point for new users: + +- [Tutorials Index](tutorials.md) + +Resource directory entry: + +- [https://qul.tarteel.ai/resources/translation](https://qul.tarteel.ai/resources/translation) diff --git a/docs/resource-transliteration.md b/docs/resource-transliteration.md new file mode 100644 index 00000000..3f2d535c --- /dev/null +++ b/docs/resource-transliteration.md @@ -0,0 +1,15 @@ +# Transliteration Guide + +This page is kept for compatibility with older links. + +Canonical implementation guide: + +- [Tutorial 8: Transliteration End-to-End](tutorial-transliteration-end-to-end.md) + +Recommended starting point for new users: + +- [Tutorials Index](tutorials.md) + +Resource directory entry: + +- [https://qul.tarteel.ai/resources/transliteration](https://qul.tarteel.ai/resources/transliteration) diff --git a/docs/resource-tutorial-map.md b/docs/resource-tutorial-map.md new file mode 100644 index 00000000..ecbe6363 --- /dev/null +++ b/docs/resource-tutorial-map.md @@ -0,0 +1,22 @@ +# Resource to Tutorial Mapping + +This file is the single mapping reference used to keep compatibility pages and canonical tutorial pages aligned. + +## Mapping + +| Compatibility Page | Canonical Tutorial | +|---|---| +| resource-recitations.md | tutorial-recitation-end-to-end.md | +| resource-mushaf-layouts.md | tutorial-mushaf-layout-end-to-end.md | +| resource-translations.md | tutorial-translation-end-to-end.md | +| resource-tafsirs.md | tutorial-tafsir-end-to-end.md | +| resource-quran-script.md | tutorial-quran-script-end-to-end.md | +| resource-fonts.md | tutorial-font-end-to-end.md | +| resource-quran-metadata.md | tutorial-quran-metadata-end-to-end.md | +| resource-transliteration.md | tutorial-transliteration-end-to-end.md | +| resource-surah-information.md | tutorial-surah-information-end-to-end.md | +| resource-ayah-topics.md | tutorial-ayah-topics-end-to-end.md | +| resource-morphology.md | tutorial-morphology-end-to-end.md | +| resource-mutashabihat.md | tutorial-mutashabihat-end-to-end.md | +| resource-similar-ayah.md | tutorial-similar-ayah-end-to-end.md | +| resource-ayah-theme.md | tutorial-ayah-theme-end-to-end.md | diff --git a/docs/tutorial-ayah-theme-end-to-end.md b/docs/tutorial-ayah-theme-end-to-end.md new file mode 100644 index 00000000..9d718b81 --- /dev/null +++ b/docs/tutorial-ayah-theme-end-to-end.md @@ -0,0 +1,197 @@ +# Tutorial 14: Ayah Theme End-to-End + +This tutorial is for users who want to show concise thematic summaries for ayah groups. + +## 1) What This Resource Is + +Ayah Theme resources provide themes linked to one ayah or a range of ayahs. + +Typical fields include: + +- `theme` text +- range fields (`ayah_from`, `ayah_to` or equivalents) +- keywords/tags +- coverage counts + +Primary category: + +- [https://qul.tarteel.ai/resources/ayah-theme](https://qul.tarteel.ai/resources/ayah-theme) + +## 2) When to Use It + +Use ayah-theme data when building: + +- Passage summaries +- Theme-first study and reflection flows +- Quick contextual hints above ayah groups + +## 3) How to Get Your First Example Resource + +1. Open [https://qul.tarteel.ai/resources/ayah-theme](https://qul.tarteel.ai/resources/ayah-theme). +2. Keep default listing order and open the first published card. +3. Confirm the detail page includes: + - `Theme Preview` tab + - `Help` tab +4. Confirm available download format(s) (commonly `sqlite`). + +This keeps onboarding concrete without hardcoded IDs. + +## 4) What the Preview Shows (Website-Aligned) + +On ayah-theme detail pages: + +- `Theme Preview` tab: + - `Jump to Ayah` + - Theme summary for selected ayah/range + - Range coverage hints (for example multiple ayahs) +- `Help` tab: + - Theme field definitions + - Range logic (`ayah_from` to `ayah_to`) + +Practical meaning: + +- Theme entries can represent groups, not only single ayahs. +- You should resolve theme by range inclusion, not exact ayah equality only. + +## 5) Download and Use (Step-by-Step) + +1. Download ayah-theme package (commonly `sqlite`). +2. Import theme rows with range fields. +3. Build resolver to find matching theme for selected ayah. +4. Render theme text + keywords + covered range. +5. Join with script/translation display context. + +Starter integration snippet (JavaScript): + +```javascript +const findThemeForAyah = (themeRows, surah, ayah) => + themeRows.find((row) => + row.surah_number === surah && ayah >= row.ayah_from && ayah <= row.ayah_to + ) || null; +``` + +## 6) Real-World Example: Passage Theme Banner + +Goal: + +- User opens an ayah and sees the current passage theme. + +Inputs: + +- Ayah Theme package +- Quran Script package + +Processing: + +1. Resolve current ayah. +2. Find theme row where ayah falls in range. +3. Display theme banner above ayah text. + +Expected output: + +- Users get quick thematic context for current passage. + +Interactive preview (temporary sandbox): + +You can edit this code for testing. Edits are not saved and may not persist after refresh. + +```playground-js +// This sandbox demonstrates ayah-to-theme resolution using ayah ranges. + +const themeRows = [ + { + surah_number: 1, + ayah_from: 1, + ayah_to: 7, + theme: "Supplication to Allah for guidance taught by Allah Himself", + keywords: ["guidance", "worship", "mercy"] + }, + { + surah_number: 73, + ayah_from: 1, + ayah_to: 19, + theme: "Night prayer discipline and preparing for revelation", + keywords: ["qiyam", "recitation", "steadfastness"] + } +]; + +const findTheme = (surah, ayah) => + themeRows.find((r) => r.surah_number === surah && ayah >= r.ayah_from && ayah <= r.ayah_to) || null; + +const app = document.getElementById("app"); +const dropdownStyle = [ + "margin-bottom:12px", + "padding:8px", + "border:1px solid #cbd5e1", + "border-radius:8px", + "background:#fff", + "color:#0f172a", + "font-size:0.95rem", + "line-height:1.3", + "min-width:220px" +].join(";"); + +app.innerHTML = ` +

    Ayah Theme Preview

    +

    Resolve thematic summary by ayah range

    +
    + + +
    Pick an ayah to resolve its theme range.
    +
    +
    +`; + +const ayahSelect = app.querySelector("#ayah"); +const themeBox = app.querySelector("#theme"); + +const render = () => { + const [surahStr, ayahStr] = ayahSelect.value.split(":"); + const surah = Number(surahStr); + const ayah = Number(ayahStr); + const row = findTheme(surah, ayah); + + if (!row) { + themeBox.textContent = "No theme found for selected ayah."; + return; + } + + themeBox.innerHTML = ` +
    Theme: ${row.theme}
    +
    Range: ${row.surah_number}:${row.ayah_from} to ${row.surah_number}:${row.ayah_to}
    +
    Keywords: ${row.keywords.join(", ")}
    + `; +}; + +ayahSelect.addEventListener("change", render); +render(); +``` + +## 7) Common Mistakes to Avoid + +- Matching themes only by exact ayah instead of range. +- Ignoring multi-ayah group coverage. +- Showing stale theme when user navigates between ayahs. + +## 8) When to Request Updates or Changes + +Open an issue if you find: + +- Broken range mappings +- Missing theme text/keywords +- Broken download links + +Issue tracker: + +- [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Related Docs + +- [Tutorials Index](tutorials.md) +- [Ayah Theme Guide](resource-ayah-theme.md) +- [Ayah Topics Guide](resource-ayah-topics.md) +- [Quran Script Guide](resource-quran-script.md) diff --git a/docs/tutorial-ayah-topics-end-to-end.md b/docs/tutorial-ayah-topics-end-to-end.md new file mode 100644 index 00000000..24f18788 --- /dev/null +++ b/docs/tutorial-ayah-topics-end-to-end.md @@ -0,0 +1,189 @@ +# Tutorial 10: Ayah Topics End-to-End + +This tutorial is for users who want to browse Quran content by topics and concept groups. + +## 1) What This Resource Is + +Ayah Topics resources map topics/concepts to related ayahs and often include topic taxonomy details. + +Typical content includes: + +- Topic entities (name, category, description) +- Topic-to-ayah mappings +- Topic counts and searchable labels + +Primary category: + +- [https://qul.tarteel.ai/resources/ayah-topics](https://qul.tarteel.ai/resources/ayah-topics) + +## 2) When to Use It + +Use ayah-topics data when building: + +- Topic-first discovery experiences +- Educational thematic study flows +- Search interfaces by concept/theme + +## 3) How to Get Your First Example Resource + +1. Open [https://qul.tarteel.ai/resources/ayah-topics](https://qul.tarteel.ai/resources/ayah-topics). +2. Keep default listing order and open the first published card. +3. Confirm page sections include: + - Topics listing/search area + - `Help` tab +4. Confirm available download formats (commonly `sqlite` on this resource type). + +This keeps onboarding concrete without hardcoded IDs. + +## 4) What the Preview Shows (Website-Aligned) + +On ayah-topics detail pages: + +- Topics pane: + - Search input for topic names/metadata + - Topic list with ayah counts + - Topic detail navigation +- `Help` tab: + - Explains topic families and usage + - Documents mapping behavior between topics and ayahs + +Practical meaning: + +- Topic rows and topic-ayah mapping rows should be treated as separate layers. +- Build topic indexes first, then resolve ayah lists for display. + +## 5) Download and Use (Step-by-Step) + +1. Download the package (commonly `sqlite`). +2. Import topic tables and mapping tables. +3. Normalize topic IDs and ayah keys. +4. Build searchable topic index. +5. On topic selection, fetch mapped ayah keys and join with script/translation. + +Starter integration snippet (JavaScript): + +```javascript +const buildTopicIndex = (topics) => + topics.reduce((index, topic) => { + index[topic.topic_id] = topic; + return index; + }, {}); + +const ayahKeysForTopic = (topicAyahMappings, topicId) => + topicAyahMappings + .filter((row) => row.topic_id === topicId) + .map((row) => row.ayah_key); +``` + +## 6) Real-World Example: Browse by Topic + +Goal: + +- User picks a topic and sees related ayahs. + +Inputs: + +- Ayah Topics package +- Quran Script package + +Processing: + +1. User searches/selects topic. +2. App loads mapped ayah keys. +3. App resolves ayah text and displays results. + +Expected output: + +- Topic-driven ayah browsing works with stable mapping. + +Interactive preview (temporary sandbox): + +You can edit this code for testing. Edits are not saved and may not persist after refresh. + +```playground-js +// This sandbox demonstrates topic search and topic->ayah mapping. + +const topics = [ + { topic_id: 101, name: "Patience", category: "General", ayahs_count: 3 }, + { topic_id: 102, name: "Prayer", category: "General", ayahs_count: 2 }, + { topic_id: 103, name: "Mercy", category: "Theme", ayahs_count: 2 } +]; + +const topicAyahMappings = [ + { topic_id: 101, ayah_key: "2:153" }, + { topic_id: 101, ayah_key: "3:200" }, + { topic_id: 101, ayah_key: "39:10" }, + { topic_id: 102, ayah_key: "2:43" }, + { topic_id: 102, ayah_key: "20:14" }, + { topic_id: 103, ayah_key: "7:156" }, + { topic_id: 103, ayah_key: "39:53" } +]; + +const app = document.getElementById("app"); +app.innerHTML = ` +

    Ayah Topics Preview

    +

    Search a topic and list mapped ayah keys

    + +
    +
    +`; + +const searchInput = app.querySelector("#search"); +const topicsBox = app.querySelector("#topics"); +const resultBox = app.querySelector("#result"); + +const renderTopics = () => { + const query = searchInput.value.trim().toLowerCase(); + const filtered = topics.filter((t) => t.name.toLowerCase().includes(query)); + + topicsBox.innerHTML = ""; + filtered.forEach((topic) => { + const btn = document.createElement("button"); + btn.type = "button"; + btn.textContent = `${topic.name} (${topic.ayahs_count})`; + btn.style.marginRight = "8px"; + btn.style.marginBottom = "8px"; + btn.style.padding = "6px 10px"; + btn.style.border = "1px solid #cbd5e1"; + btn.style.borderRadius = "8px"; + btn.style.background = "#fff"; + + btn.addEventListener("click", () => { + const ayahKeys = topicAyahMappings.filter((r) => r.topic_id === topic.topic_id).map((r) => r.ayah_key); + resultBox.innerHTML = `${topic.name}
    Ayah Keys: ${ayahKeys.join(", ") || "none"}`; + }); + + topicsBox.appendChild(btn); + }); + + if (filtered.length === 0) resultBox.textContent = "No topics found for this query."; +}; + +searchInput.addEventListener("input", renderTopics); +renderTopics(); +``` + +## 7) Common Mistakes to Avoid + +- Mixing topic metadata and topic-ayah mapping tables. +- Assuming topic IDs are globally stable across different resources. +- Rendering topic results without pagination for large topic sets. + +## 8) When to Request Updates or Changes + +Open an issue if you find: + +- Broken topic-to-ayah mappings +- Missing topic labels/descriptions +- Broken download links + +Issue tracker: + +- [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Related Docs + +- [Tutorials Index](tutorials.md) +- [Ayah Topics Guide](resource-ayah-topics.md) +- [Quran Script Guide](resource-quran-script.md) +- [Translations Guide](resource-translations.md) diff --git a/docs/tutorial-font-end-to-end.md b/docs/tutorial-font-end-to-end.md new file mode 100644 index 00000000..581aacee --- /dev/null +++ b/docs/tutorial-font-end-to-end.md @@ -0,0 +1,231 @@ +# Tutorial 6: Font End-to-End + +This tutorial is for users who want to download Quran font assets and apply them correctly in app/web rendering. + +## 1) What This Resource Is + +Font resources provide downloadable Quran-related font files used to render script-specific text and glyphs. + +Common file types include: + +- `woff` +- `woff2` +- `ttf` + +Primary category: + +- [https://qul.tarteel.ai/resources/font](https://qul.tarteel.ai/resources/font) + +## 2) When to Use It + +Use font resources when you are building: + +- Quran readers with script-specific display requirements +- Word-by-word/Glyph interfaces that depend on a specific font family +- Apps that must match a known Mushaf style + +## 3) How to Get Your First Example Resource + +1. Open [https://qul.tarteel.ai/resources/font](https://qul.tarteel.ai/resources/font). +2. Keep default listing order and open the first published card. +3. Confirm the detail page includes: + - `Glyph Preview` tab + - `Help` tab +4. Confirm available download files (`woff`, `woff2`, `ttf`). + +This keeps onboarding concrete without hardcoded resource IDs. + +## 4) What the Preview Shows (Website-Aligned) + +On the font detail page: + +- `Glyph Preview` tab: + - Shows font glyph behavior on sample Quran text + - Helps validate readability and shaping before download +- `Help` tab: + - Shows practical usage notes + - Includes live preview guidance for the selected font + +Practical meaning: + +- You should validate the chosen font against your target script content. +- You should always define fallbacks in case a user device cannot load the primary font. + +## 5) Download and Use (Step-by-Step) + +1. Download font files from the resource (`woff2` preferred for web, keep `ttf` for fallback). +2. Place font files in your static/public assets. +3. Register with `@font-face`. +4. Apply the font family to Quran text containers. +5. Validate on desktop + mobile and compare shaping. + +Starter integration snippet (CSS + JavaScript): + +```javascript +// 1) CSS (inject or place in stylesheet) +const css = ` +@font-face { + font-family: 'qpc-hafs'; + src: url('/fonts/qpc-hafs.woff2') format('woff2'), + url('/fonts/qpc-hafs.woff') format('woff'); + font-display: swap; +} + +.quran-script { + direction: rtl; + text-align: right; + font-family: 'qpc-hafs', 'Amiri Quran', 'Noto Naskh Arabic', serif; +} +`; + +// 2) Register style once +const style = document.createElement('style'); +style.textContent = css; +document.head.appendChild(style); + +// 3) Apply class to verse container +document.getElementById('verse').classList.add('quran-script'); +``` + +## 6) Real-World Example: Font-Safe Verse Rendering + +Goal: + +- Show one ayah with the selected Quran font and safe fallbacks. + +Inputs: + +- Font files (`woff2`, `woff`, optional `ttf`) +- Quran Script text + +Processing: + +1. Load font via `@font-face`. +2. Render ayah in RTL block with configured font stack. +3. Verify character shaping and spacing. + +Expected output: + +- Verse renders with intended visual style. +- Fallback still renders readable Arabic if primary font is unavailable. + +Interactive preview (temporary sandbox): + +You can edit this code for testing. Edits are not saved and may not persist after refresh. + +```playground-js +// This sandbox demonstrates font stack switching for Quran text rendering. + +const samples = { + "1:1": "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ", + "73:4": "أَوۡ زِدۡ عَلَيۡهِ وَرَتِّلِ ٱلۡقُرۡءَانَ تَرۡتِيلًا" +}; + +const fontPresets = { + "qpc-hafs (with fallbacks)": { + family: "'KFGQPC Uthmanic Script HAFS','Amiri Quran','Noto Naskh Arabic','Scheherazade New',serif", + candidates: ["KFGQPC Uthmanic Script HAFS", "Amiri Quran", "Noto Naskh Arabic", "Scheherazade New"], + label: "QPC stack (if installed) + safe fallbacks", + accent: "#0f766e", + letterSpacing: "0px", + fontSize: "1.28rem" + }, + "Amiri Quran": { + family: "'Amiri Quran','Noto Naskh Arabic',serif", + candidates: ["Amiri Quran", "Noto Naskh Arabic"], + label: "Amiri-first stack", + accent: "#1d4ed8", + letterSpacing: "0.1px", + fontSize: "1.24rem" + }, + "System fallback": { + // First font name is intentionally fake to force fallback behavior in demo. + family: "'QUL Missing Font Demo','Noto Naskh Arabic','Geeza Pro','Tahoma','Arial',serif", + candidates: ["QUL Missing Font Demo", "Noto Naskh Arabic", "Geeza Pro", "Tahoma", "Arial"], + label: "System-safe fallback stack (forced fallback demo)", + accent: "#7c2d12", + letterSpacing: "0.25px", + fontSize: "1.2rem" + } +}; + +const app = document.getElementById("app"); +const dropdownStyle = [ + "margin-bottom:12px", + "padding:8px", + "border:1px solid #cbd5e1", + "border-radius:8px", + "background:#fff", + "color:#0f172a", + "font-size:0.95rem", + "line-height:1.3", + "min-width:220px" +].join(";"); + +app.innerHTML = ` +

    Font Preview (Quran Script)

    +

    Test verse rendering with different font stacks

    + + + + +
    +`; + +const ayahSelect = app.querySelector("#ayah"); +const stackSelect = app.querySelector("#stack"); +const verseBox = app.querySelector("#verse"); + +Object.keys(fontPresets).forEach((label) => { + const opt = document.createElement("option"); + opt.value = label; + opt.textContent = label; + stackSelect.appendChild(opt); +}); + +const render = () => { + verseBox.textContent = samples[ayahSelect.value] || ""; + const preset = fontPresets[stackSelect.value]; + if (!preset) return; + + verseBox.style.fontFamily = preset.family; + verseBox.style.fontSize = preset.fontSize; + verseBox.style.letterSpacing = preset.letterSpacing; + verseBox.style.borderColor = preset.accent; + verseBox.style.boxShadow = `inset 0 0 0 1px ${preset.accent}22`; +}; + +ayahSelect.addEventListener("change", render); +stackSelect.addEventListener("change", render); +stackSelect.value = "qpc-hafs (with fallbacks)"; +render(); +``` + +## 7) Common Mistakes to Avoid + +- Using a Quran script package without its intended font family. +- Defining only one font and no fallback stack. +- Skipping mobile rendering checks. +- Assuming glyph-style and Unicode-style fonts behave identically. + +## 8) When to Request Updates or Changes + +Open an issue if you find: + +- Broken font download links +- Missing glyph support for expected text +- Incorrect font metadata or unclear usage instructions + +Issue tracker: + +- [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Related Docs + +- [Tutorials Index](tutorials.md) +- [Quran Fonts Guide](resource-fonts.md) +- [Quran Script Guide](resource-quran-script.md) +- [Mushaf Layouts Guide](resource-mushaf-layouts.md) diff --git a/docs/tutorial-morphology-end-to-end.md b/docs/tutorial-morphology-end-to-end.md new file mode 100644 index 00000000..1e8da3c3 --- /dev/null +++ b/docs/tutorial-morphology-end-to-end.md @@ -0,0 +1,268 @@ +# Tutorial 11: Morphology End-to-End + +This tutorial is for users who want to integrate word-level Quran morphology (roots, lemmas, grammar tags) into reading/study tools. + +## 1) What This Resource Is + +Morphology resources provide word-level linguistic annotations for Quran words. + +Typical fields include: + +- Word location keys (for example `surah:ayah:word`) +- Root/lemma/stem fields +- Part-of-speech and grammar tags + +Primary category: + +- [https://qul.tarteel.ai/resources/morphology](https://qul.tarteel.ai/resources/morphology) + +## 2) When to Use It + +Use morphology data when building: + +- Tap-word grammar insights +- Root/lemma based search +- Arabic linguistic study tools + +## 3) How to Get Your First Example Resource + +1. Open [https://qul.tarteel.ai/resources/morphology](https://qul.tarteel.ai/resources/morphology). +2. Keep default listing order and open the first published card. +3. Confirm the detail page includes: + - `Preview` tab (resource-specific title such as `Word root Preview`) + - `Help` tab +4. Confirm available download formats (commonly `sqlite`). + +This keeps onboarding concrete without hardcoded IDs. + +## 4) What the Preview Shows (Website-Aligned) + +On morphology detail pages: + +- `Preview` tab: + - `Jump to Ayah` + - Word-level rows for selected ayah (`Word stem`, `Word root`, `Word lemma`) + - Ayah-level aggregates (`Ayah Stem`, `Ayah Root`, `Ayah Lemma`) +- `Help` tab: + - Field definitions (including word location key) + - Integration notes about joining with word-by-word script + +Practical meaning: + +- Morphology rows must be joined at word-level, not only ayah-level. +- Word order and location keys are mandatory for accurate overlays. + +## 5) Download and Use (Step-by-Step) + +1. Download morphology package (commonly `sqlite`). +2. Import rows with `word_location`/equivalent keys. +3. Join with Quran Script word data using the same key. +4. Index by root/lemma/POS for search features. +5. Render per-word analysis in UI. + +Starter integration snippet (JavaScript): + +```javascript +const buildMorphologyIndex = (rows) => + rows.reduce((index, row) => { + index[row.word_location] = row; + return index; + }, {}); + +const enrichWordsWithMorphology = (wordRows, morphologyIndex) => + wordRows.map((word) => ({ + ...word, + morphology: morphologyIndex[word.location] || null + })); +``` + +## 6) Real-World Example: Tap Word for Grammar + +Goal: + +- User taps a Quran word and sees stem/root/lemma details. + +Inputs: + +- Morphology package +- Quran Script word-by-word package + +Processing: + +1. Render words with location keys. +2. User taps one word. +3. App resolves morphology row by location key. +4. UI shows stem/root/lemma and ayah-level morphology context. + +Expected output: + +- Morphology panel reflects the correct tapped word and the ayah-level summary. + +Interactive preview (temporary sandbox): + +You can edit this code for testing. Edits are not saved and may not persist after refresh. +Tip: scroll down in the preview area to view Ayah Stem/Root/Lemma sections. + +```playground-js +// Source-aligned sample for Surah Al-Fatihah (1:1). +// Shows word stem/root/lemma and ayah stem/root/lemma in one preview. +const words = [ + { location: "1:1:1", text: "بِسۡمِ", stem: "سم", root: "س م و", lemma: "اسم" }, + { location: "1:1:2", text: "ٱللَّهِ", stem: "الله", root: "ا ل ه", lemma: "الله" }, + { location: "1:1:3", text: "ٱلرَّحۡمَٰنِ", stem: "رحمان", root: "ر ح م", lemma: "رحمان" }, + { location: "1:1:4", text: "ٱلرَّحِيمِ", stem: "رحيم", root: "ر ح م", lemma: "رحيم" } +]; + +const ayah = { + text_with_number: "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ ١", + text_without_number: "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ", + stem: "سْمِ اللَّهِ رَّحْمَٰنِ رَّحِيمِ", + root: "سمو اله رحم رحم", + lemma: "اسْم اللَّه رَّحْمَٰن رَّحِيم" +}; + +const app = document.getElementById("app"); +const dropdownStyle = [ + "margin-bottom:12px", + "padding:8px", + "border:1px solid #cbd5e1", + "border-radius:8px", + "background:#fff", + "color:#0f172a", + "font-size:0.95rem", + "line-height:1.3", + "min-width:220px" +].join(";"); + +app.innerHTML = ` +

    Morphology Preview (Word Level)

    +

    Surah Al-Fatihah — Ayah 1 (word stem/root/lemma + ayah summary)

    +
    + Continue scrolling to see Ayah Stem, Ayah Root, and Ayah Lemma. +
    + + + +
    + + + + + + + + +
    WordValue
    +
    +
    +
    +`; + +const fieldSelect = app.querySelector("#field"); +const jumpAyahButton = app.querySelector("#jump-ayah"); +const rowsBox = app.querySelector("#rows"); +const infoBox = app.querySelector("#info"); +const ayahBox = app.querySelector("#ayah"); +let selectedLocation = "1:1:1"; + +const renderRows = () => { + const field = fieldSelect.value; + rowsBox.innerHTML = ""; + words.forEach((word) => { + const tr = document.createElement("tr"); + tr.innerHTML = ` + + + + ${word[field]} + `; + rowsBox.appendChild(tr); + }); + + rowsBox.querySelectorAll("button[data-location]").forEach((btn) => { + btn.addEventListener("click", () => { + selectedLocation = btn.getAttribute("data-location"); + renderInfo(); + }); + }); +}; + +const renderInfo = () => { + const word = words.find((item) => item.location === selectedLocation); + if (!word) { + infoBox.textContent = "No morphology found for selected word."; + return; + } + + infoBox.innerHTML = ` +
    Location: ${word.location}
    +
    Word: ${word.text}
    +
    Stem: ${word.stem}
    +
    Root: ${word.root}
    +
    Lemma: ${word.lemma}
    + `; +}; + +const renderAyah = () => { + ayahBox.innerHTML = ` +
    +
    Ayah Stem for Surah Al-Fatihah — Ayah 1
    +
    ${ayah.text_with_number}
    +
    Stem
    +
    ${ayah.stem}
    +
    +
    +
    Ayah Root for Surah Al-Fatihah — Ayah 1
    +
    ${ayah.text_with_number}
    +
    Root
    +
    ${ayah.root}
    +
    +
    +
    Ayah Lemma for Surah Al-Fatihah — Ayah 1
    +
    ${ayah.text_without_number}
    +
    Lemma
    +
    ${ayah.lemma}
    +
    + `; +}; + +fieldSelect.addEventListener("change", renderRows); +jumpAyahButton.addEventListener("click", () => { + ayahBox.scrollIntoView({ behavior: "smooth", block: "start" }); +}); +renderRows(); +renderInfo(); +renderAyah(); +``` + +## 7) Common Mistakes to Avoid + +- Joining morphology to verse-only keys instead of word location keys. +- Ignoring word order and position. +- Treating morphology labels as stable across different source datasets. + +## 8) When to Request Updates or Changes + +Open an issue if you find: + +- Incorrect location-key mappings +- Missing stem/root/lemma values +- Broken download links + +Issue tracker: + +- [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Related Docs + +- [Tutorials Index](tutorials.md) +- [Morphology Guide](resource-morphology.md) +- [Quran Script Guide](resource-quran-script.md) diff --git a/docs/tutorial-mushaf-layout-end-to-end.md b/docs/tutorial-mushaf-layout-end-to-end.md new file mode 100644 index 00000000..f30ac623 --- /dev/null +++ b/docs/tutorial-mushaf-layout-end-to-end.md @@ -0,0 +1,271 @@ +# Tutorial 2: Mushaf Layout End-to-End + +This tutorial is for users who want to render page-accurate mushaf layouts from downloaded QUL data. + +## 1) What This Resource Is + +Mushaf Layout resources provide page-structured layout data for Quran pages. + +In practice, this includes page lines and word ranges you can use to render page-faithful mushaf views. + +Primary category: + +- [https://qul.tarteel.ai/resources/mushaf-layout](https://qul.tarteel.ai/resources/mushaf-layout) + +## 2) When to Use It + +Use mushaf layout data when you are building: + +- Page-by-page mushaf readers +- Navigation by page number (instead of only surah/ayah) +- Memorization or classroom tools that depend on printed page structure + +## Why Mushaf Layout Is Different (and Helpful) + +The Help sample code and preview on a detail page (for example [https://qul.tarteel.ai/resources/mushaf-layout/12](https://qul.tarteel.ai/resources/mushaf-layout/12)) show a key difference: + +- `mushaf-layout` is a rendering-structure resource, not only a content resource. +- It tells your app *how* to render a page (`line_number`, `line_type`, `is_centered`, `first_word_id`, `last_word_id`). +- Most other resources (recitation, translation, tafsir, etc.) mostly give content keyed by ayah/word, but not page geometry. + +Why this is useful: + +- You can recreate a printed mushaf page layout more accurately. +- You can support page-first navigation and memorization workflows. +- You can combine layout + script + font consistently, instead of hardcoding line templates in app code. + +## 3) How to Get Your First Example Resource + +1. Open [https://qul.tarteel.ai/resources/mushaf-layout](https://qul.tarteel.ai/resources/mushaf-layout). +2. Keep the default listing order and open the first published card. +3. Confirm the resource detail page includes: + - `Mushaf Page Preview` tab + - `Help` tab +4. Confirm available download formats shown on the page (commonly `images`, `sqlite`, `docx`). + +This keeps onboarding concrete without depending on a fixed resource ID. + +## 4) What the Preview Shows (Website-Aligned) + +On the mushaf layout detail page, preview helps you validate page rendering behavior before download: + +- `Mushaf Page Preview` tab: + - `Jump to page` selector + - Previous/next page navigation + - Rendered page output with line and word structure +- `Help` tab: + - Required data to render a page (layout + script + font) + - `pages` table schema (for line mapping) + - `words` table schema expectations (for word ranges) + - Sample rendering logic + +Practical meaning: + +- `pages` table controls line structure for each page. +- `first_word_id`/`last_word_id` ranges map into script words for line rendering. + +## 5) Download and Use (Step-by-Step) + +1. Download the selected mushaf layout package (for example `sqlite` and related assets). +2. Inspect layout records: + - `page_number` + - `line_number` + - `line_type` + - `is_centered` + - `first_word_id`, `last_word_id` + - `surah_number` (when relevant) +3. Load matching Quran script word data (word-by-word format). +4. For each page line: + - If `line_type` is `surah_name`, render surah heading line. + - If `line_type` is `ayah`, render words in `first_word_id..last_word_id`. + - If `line_type` is `basmallah`, render basmallah line. +5. Apply alignment using `is_centered`. +6. Validate with at least three pages (start, middle, end) to catch mapping issues. + +Starter integration snippet (JavaScript): + +```javascript +// Select all lines for a single page and keep display order stable. +const linesForPage = (pagesTableRows, pageNumber) => + pagesTableRows + .filter((row) => row.page_number === pageNumber) + .sort((a, b) => a.line_number - b.line_number); + +// Convert a word ID range into one display string. +const getWordsInRange = (wordsByIndex, firstWordId, lastWordId) => { + const result = []; + for (let id = firstWordId; id <= lastWordId; id += 1) { + if (wordsByIndex[id]) result.push(wordsByIndex[id].text); + } + return result.join(" "); +}; + +// Render line text by line type rules. +const renderLineText = (line, wordsByIndex, surahNameByNumber) => { + if (line.line_type === "surah_name") return surahNameByNumber[line.surah_number] || ""; + if (line.line_type === "basmallah") return "﷽"; + if (line.line_type === "ayah") return getWordsInRange(wordsByIndex, line.first_word_id, line.last_word_id); + return ""; +}; +``` + +## 6) Real-World Example: Render One Mushaf Page + +Goal: + +- User opens page 1 and sees line-accurate mushaf rendering. + +Inputs: + +- Mushaf Layout package (line mapping) +- Quran Script package (word text) +- Surah names metadata + +Processing: + +1. User selects page number. +2. App loads all lines for that page from layout data. +3. App resolves words for each ayah line using word ID ranges. +4. App applies centered/justified alignment based on `is_centered`. +5. UI renders all lines in page order. + +Expected output: + +- Page structure matches expected mushaf layout. +- Surah name and ayah lines render in correct order (with basmallah lines when present in layout data). +- Page navigation remains stable across adjacent pages. + +Interactive preview (temporary sandbox): + +You can edit this code for testing. Edits are not saved and may not persist after refresh. + +```playground-js +// This playground follows the Help section sample pattern from: +// https://qul.tarteel.ai/resources/mushaf-layout/12 + +// Surah labels used by line_type = "surah_name". +const SurahNames = { + 1: "الفاتحة" +}; + +// Simulated rows from the layout DB "pages" table for page 1. +const pageData = [ + { line_number: 1, line_type: "surah_name", is_centered: true, first_word_id: null, last_word_id: null, surah_number: 1 }, + { line_number: 2, line_type: "ayah", is_centered: true, first_word_id: 1, last_word_id: 5, surah_number: null }, + { line_number: 3, line_type: "ayah", is_centered: true, first_word_id: 6, last_word_id: 10, surah_number: null }, + { line_number: 4, line_type: "ayah", is_centered: true, first_word_id: 11, last_word_id: 17, surah_number: null }, + { line_number: 5, line_type: "ayah", is_centered: true, first_word_id: 18, last_word_id: 23, surah_number: null }, + { line_number: 6, line_type: "ayah", is_centered: true, first_word_id: 24, last_word_id: 29, surah_number: null }, + { line_number: 7, line_type: "ayah", is_centered: true, first_word_id: 30, last_word_id: 33, surah_number: null }, + { line_number: 8, line_type: "ayah", is_centered: true, first_word_id: 34, last_word_id: 36, surah_number: null } +]; + +// Simulated rows from Quran script DB "words" table keyed by word id. +const wordData = { + 1: "بِسۡمِ", 2: "ٱللَّهِ", 3: "ٱلرَّحۡمَٰنِ", 4: "ٱلرَّحِيمِ", 5: "١", + 6: "ٱلۡحَمۡدُ", 7: "لِلَّهِ", 8: "رَبِّ", 9: "ٱلۡعَٰلَمِينَ", 10: "٢", + 11: "ٱلرَّحۡمَٰنِ", 12: "ٱلرَّحِيمِ", 13: "٣", 14: "مَٰلِكِ", 15: "يَوۡمِ", + 16: "ٱلدِّينِ", 17: "٤", 18: "إِيَّاكَ", 19: "نَعۡبُدُ", 20: "وَإِيَّاكَ", + 21: "نَسۡتَعِينُ", 22: "٥", 23: "ٱهۡدِنَا", 24: "ٱلصِّرَٰطَ", 25: "ٱلۡمُسۡتَقِيمَ", + 26: "٦", 27: "صِرَٰطَ", 28: "ٱلَّذِينَ", 29: "أَنۡعَمۡتَ", 30: "عَلَيۡهِمۡ", + 31: "غَيۡرِ", 32: "ٱلۡمَغۡضُوبِ", 33: "عَلَيۡهِمۡ", 34: "وَلَا", 35: "ٱلضَّآلِّينَ", 36: "٧" +}; + +// Read words between first_word_id..last_word_id (inclusive), sorted by id. +const getWords = (firstWordId, lastWordId) => + Object.entries(wordData) + .map(([key, value]) => ({ id: Number(key), text: value })) + .sort((a, b) => a.id - b.id) + .filter((word) => word.id >= firstWordId && word.id <= lastWordId) + .map((word) => word.text) + .join(" "); + +const getSurahName = (number) => `سورۃ ${SurahNames[number] || ""}`; + +// Build sandbox preview container. +const app = document.getElementById("app"); +app.innerHTML = ` +

    Mushaf Page Preview (Simulated)

    +

    Aligned with Help sample logic: page lines + word ID ranges

    +
    +`; + +const page = app.querySelector("#page"); + +const renderPage = () => { + page.innerHTML = ""; + + // Render in the same stable order as page line numbers. + pageData + .slice() + .sort((a, b) => a.line_number - b.line_number) + .forEach((line) => { + const lineEl = document.createElement("div"); + lineEl.style.padding = "4px 0"; + lineEl.style.minHeight = "34px"; + lineEl.style.borderBottom = "1px dashed #f1f5f9"; + + // Respect is_centered from layout metadata. + if (line.is_centered) { + lineEl.style.textAlign = "center"; + lineEl.style.display = "flex"; + lineEl.style.justifyContent = "center"; + } else { + lineEl.style.textAlign = "justify"; + } + + // Choose content by line_type like the Help sample. + switch (line.line_type) { + case "surah_name": + lineEl.textContent = getSurahName(line.surah_number); + lineEl.style.fontWeight = "700"; + lineEl.style.fontSize = "1.25rem"; + break; + case "ayah": + lineEl.textContent = getWords(line.first_word_id, line.last_word_id); + lineEl.style.fontWeight = "500"; + lineEl.style.fontSize = "1.1rem"; + break; + case "basmallah": + lineEl.textContent = "﷽"; + lineEl.style.fontWeight = "700"; + break; + default: + lineEl.textContent = ""; + } + + page.appendChild(lineEl); + }); +}; + +renderPage(); +``` + +## 7) Common Mistakes to Avoid + +- Joining layout lines to words with wrong word ID field. +- Ignoring `line_type` and rendering every line as ayah text. +- Ignoring `is_centered`, which changes page appearance. +- Validating only page 1 and skipping middle/end page checks. +- Mixing incompatible script/font with selected layout package. + +## 8) When to Request Updates or Changes + +Open an issue if you find: + +- Incorrect line-to-word mapping on a page +- Missing lines or broken page navigation +- Download package inconsistencies (`images`, `sqlite`, `docx`, metadata) +- Layout metadata that conflicts with preview behavior + +Issue tracker: + +- [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Related Docs + +- [Tutorials Index](tutorials.md) +- [Mushaf Layouts Guide](resource-mushaf-layouts.md) +- [Quran Script Guide](resource-quran-script.md) +- [Fonts Guide](resource-fonts.md) +- [Quran Metadata Guide](resource-quran-metadata.md) diff --git a/docs/tutorial-mutashabihat-end-to-end.md b/docs/tutorial-mutashabihat-end-to-end.md new file mode 100644 index 00000000..1e95dd06 --- /dev/null +++ b/docs/tutorial-mutashabihat-end-to-end.md @@ -0,0 +1,339 @@ +# Tutorial 12: Mutashabihat End-to-End + +This tutorial is for users who want to integrate phrase-level similarity aids for memorization and comparison. + +## 1) What This Resource Is + +Mutashabihat resources provide phrase-level similarity mappings across ayahs. + +The help model typically includes files such as: + +- `phrases.json` (shared phrases) +- `phrase_verses.json` (ayah-to-phrase mappings) + +Primary category: + +- [https://qul.tarteel.ai/resources/mutashabihat](https://qul.tarteel.ai/resources/mutashabihat) + +## 2) When to Use It + +Use mutashabihat data when building: + +- Memorization revision tools +- Similar phrase comparison views +- Confusion-reduction aids for close ayah wording + +## 3) How to Get Your First Example Resource + +1. Open [https://qul.tarteel.ai/resources/mutashabihat](https://qul.tarteel.ai/resources/mutashabihat). +2. Keep default listing order and open the first published card. +3. Confirm the detail page includes: + - `Mutashabihat Preview` tab + - `Help` tab +4. Confirm available download file(s) (commonly `json`). + +This keeps onboarding concrete without hardcoded IDs. + +## 4) What the Preview Shows (Website-Aligned) + +On mutashabihat detail pages: + +- `Mutashabihat Preview` tab: + - `Jump to Ayah` + - Displays similar phrase relationships for selected ayah + - Shows phrase words and repeat statistics + - Lists phrase ayahs where the phrase appears +- `Help` tab: + - Explains `phrases.json` and `phrase_verses.json` + - Shows lookup flow to retrieve related phrases + +Practical meaning: + +- You should resolve phrase IDs first, then fetch phrase details. +- Mutashabihat is phrase-level guidance, not full tafsir/translation content. + +Concrete example (mutashabihat resource `73`): + +- Phrase words: + - بِسۡمِ + - ٱللَّهِ + - ٱلرَّحۡمَٰنِ + - ٱلرَّحِيمِ +- Summary: + - This phrase is repeated 2 times in 2 ayahs across 2 surahs. +- Phrase ayahs: + - `1:1`: بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ ١ + - `27:30`: إِنَّهُۥ مِن سُلَيۡمَٰنَ وَإِنَّهُۥ بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ ٣٠ + +## 5) Download and Use (Step-by-Step) + +1. Download mutashabihat dataset (`json`). +2. Load `phrase_verses.json` and `phrases.json`. +3. For selected ayah, fetch phrase IDs from phrase-verses mapping. +4. Resolve phrase IDs to phrase details. +5. Optionally join with script words for visual highlighting. + +Starter integration snippet (JavaScript): + +```javascript +// Required: load phrases.json, phrase_verses.json, and Quran words data. +const phraseIdsForAyah = (phraseVerses, ayahKey) => phraseVerses[ayahKey] || []; + +const phrasesForAyah = (phraseVerses, phrasesById, ayahKey) => + phraseIdsForAyah(phraseVerses, ayahKey) + .map((id) => phrasesById[id]) + .filter(Boolean); + +// In phrases.json, each phrase can include: +// - source: where the phrase is first sourced from +// - ayah: a mapping like { "2:23": [[from, to], ...] } for word ranges +const phraseRangesForAyah = (phrase, ayahKey) => + phrase?.ayah?.[ayahKey] || []; +``` + +## 6) Real-World Example: Similar Phrase Helper + +Goal: + +- User selects an ayah and sees phrase-level similar references. + +Inputs: + +- `phrases.json` +- `phrase_verses.json` + +Processing: + +1. Resolve phrase IDs for selected ayah. +2. Load matching phrase entries. +3. Render phrase and related ayah references. +4. On demand (`View all`), show all known ayahs where phrase appears. + +Expected output: + +- User can compare similar phrase patterns quickly. + +Interactive preview (temporary sandbox): + +You can edit this code for testing. Edits are not saved and may not persist after refresh. + +```playground-js +// This sandbox mirrors the Help flow: ayah -> phrase IDs -> phrase objects. +// It also mirrors Preview behavior: Jump to Ayah -> repeated phrase cards -> View all ayahs. + +const phraseVerses = { + "1:1": [7301], + "27:30": [7301], + "2:112": [7310, 7311, 7312] +}; + +const phrasesById = { + 7301: { + id: 7301, + words: ["بِسۡمِ", "ٱللَّهِ", "ٱلرَّحۡمَٰنِ", "ٱلرَّحِيمِ"], + phrase_text: "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ", + repeated_count: 2, + ayah_count: 2, + surah_count: 2, + phrase_ayahs: [ + { ayah_key: "1:1", text: "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ ١" }, + { ayah_key: "27:30", text: "إِنَّهُۥ مِن سُلَيۡمَٰنَ وَإِنَّهُۥ بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ ٣٠" } + ] + }, + 7310: { + id: 7310, + words: ["فَلَا", "خَوْفٌ", "عَلَيْهِمْ", "وَلَا"], + phrase_text: "فَلَا خَوْفٌ عَلَيْهِمْ وَلَا", + repeated_count: 13, + ayah_count: 13, + surah_count: 7, + phrase_ayahs: [ + { ayah_key: "2:112", text: "فَلَهُۥٓ أَجۡرُهُۥ عِندَ رَبِّهِۦ وَلَا خَوۡفٌ عَلَيۡهِمۡ وَلَا هُمۡ يَحۡزَنُونَ ١١٢" }, + { ayah_key: "2:262", text: "لَّهُمۡ أَجۡرُهُمۡ عِندَ رَبِّهِمۡ وَلَا خَوۡفٌ عَلَيۡهِمۡ وَلَا هُمۡ يَحۡزَنُونَ ٢٦٢" } + ] + }, + 7311: { + id: 7311, + words: ["مَنۡ", "أَسۡلَمَ", "وَجۡهَهُۥ", "لِلَّهِ", "وَهُوَ", "مُحۡسِنٞ"], + phrase_text: "مَنۡ أَسۡلَمَ وَجۡهَهُۥ لِلَّهِ وَهُوَ مُحۡسِنٞ", + repeated_count: 2, + ayah_count: 2, + surah_count: 2, + phrase_ayahs: [ + { ayah_key: "2:112", text: "مَنۡ أَسۡلَمَ وَجۡهَهُۥ لِلَّهِ وَهُوَ مُحۡسِنٞ" }, + { ayah_key: "31:22", text: "وَمَن يُسۡلِمۡ وَجۡهَهُۥٓ إِلَى ٱللَّهِ وَهُوَ مُحۡسِنٞ" } + ] + }, + 7312: { + id: 7312, + words: ["أَجْرُهُمْ", "عِندَ", "رَبِّهِمْ", "وَلَا", "خَوْفٌ", "عَلَيْهِمْ", "وَلَا", "هُمْ", "يَحْزَنُونَ"], + phrase_text: "أَجْرُهُمْ عِندَ رَبِّهِمْ وَلَا خَوْفٌ عَلَيْهِمْ وَلَا هُمْ يَحْزَنُونَ", + repeated_count: 5, + ayah_count: 5, + surah_count: 1, + phrase_ayahs: [ + { ayah_key: "2:112", text: "فَلَهُۥٓ أَجۡرُهُۥ عِندَ رَبِّهِۦ وَلَا خَوۡفٌ عَلَيۡهِمۡ وَلَا هُمۡ يَحۡزَنُونَ ١١٢" }, + { ayah_key: "2:277", text: "لَهُمۡ أَجۡرُهُمۡ عِندَ رَبِّهِمۡ وَلَا خَوۡفٌ عَلَيۡهِمۡ وَلَا هُمۡ يَحۡزَنُونَ ٢٧٧" } + ] + } +}; + +const ayahTextByKey = { + "1:1": "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ ١", + "27:30": "إِنَّهُۥ مِن سُلَيۡمَٰنَ وَإِنَّهُۥ بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ ٣٠", + "2:112": "بَلَىٰۚ مَنۡ أَسۡلَمَ وَجۡهَهُۥ لِلَّهِ وَهُوَ مُحۡسِنٞ فَلَهُۥٓ أَجۡرُهُۥ عِندَ رَبِّهِۦ وَلَا خَوۡفٌ عَلَيۡهِمۡ وَلَا هُمۡ يَحۡزَنُونَ ١١٢" +}; + +const helpSample = { + phrase_verses: { "2:23": [50, 16379] }, + phrases: { + "50": { + surahs: 32, + ayahs: 70, + count: 71, + source: { key: "2:23", from: 15, to: 17 }, + ayah: { + "19:48": [[4, 6]], + "2:23": [[15, 17]] + } + } + } +}; + +const app = document.getElementById("app"); +const dropdownStyle = [ + "margin-bottom:12px", + "padding:8px", + "border:1px solid #cbd5e1", + "border-radius:8px", + "background:#fff", + "color:#0f172a", + "font-size:0.95rem", + "line-height:1.3", + "min-width:220px" +].join(";"); + +app.innerHTML = ` +

    Mutashabihat Preview (Phrase-Level)

    +

    Preview behavior + Help data model on one screen

    +
    + Start with 1:1 (Bismillah). Then switch to 27:30 or 2:112 to see repeated phrase behavior. +
    + + +
    +
    +
    +
    +
    +
    Help sample: phrase_verses.json
    +
    
    +    
    Help sample: phrases.json
    +
    
    +  
    +`; + +const ayahSelect = app.querySelector("#ayah"); +const current = app.querySelector("#current"); +const ayahTextBox = app.querySelector("#ayah-text"); +const lookup = app.querySelector("#lookup"); +const result = app.querySelector("#result"); +const helpPhraseVersesBox = app.querySelector("#help-phrase-verses"); +const helpPhrasesBox = app.querySelector("#help-phrases"); + +helpPhraseVersesBox.textContent = JSON.stringify(helpSample.phrase_verses, null, 2); +helpPhrasesBox.textContent = JSON.stringify(helpSample.phrases, null, 2); + +const render = () => { + const key = ayahSelect.value; + const ids = phraseVerses[key] || []; + const rows = ids.map((id) => phrasesById[id]).filter(Boolean); + current.textContent = `Selected ayah: ${key}`; + ayahTextBox.textContent = ayahTextByKey[key] || ""; + lookup.innerHTML = ` +
    Lookup flow (from Help): ayah -> phrase IDs -> phrase objects
    +
    phrase_verses.json["${key}"]: [${ids.join(", ")}]
    + `; + + if (rows.length === 0) { + result.textContent = "No phrase relations found."; + return; + } + + result.innerHTML = rows + .map((r) => { + const words = r.words + .map( + (word) => + `${word}` + ) + .join(""); + + const ayahRows = r.phrase_ayahs + .map( + (item) => + `
  • + ${item.ayah_key} +
    ${item.text}
    +
  • ` + ) + .join(""); + + return ` +
    +
    ${words}
    +

    This phrase is repeated ${r.repeated_count} times in ${r.ayah_count} ayahs across ${r.surah_count} surahs.

    + + +
    + `; + }) + .join(""); + + result.querySelectorAll("button[data-phrase-id]").forEach((button) => { + button.addEventListener("click", () => { + const phraseId = button.getAttribute("data-phrase-id"); + const panel = result.querySelector(`#ayahs-${phraseId}`); + if (!panel) return; + const isHidden = panel.style.display === "none"; + panel.style.display = isHidden ? "block" : "none"; + button.textContent = isHidden ? "Hide ayahs" : "View all"; + }); + }); +}; + +ayahSelect.addEventListener("change", render); +render(); +``` + +## 7) Common Mistakes to Avoid + +- Treating mutashabihat as ayah-level one-to-one data. +- Ignoring phrase ID indirection. +- Not showing related ayah context alongside phrase matches. + +## 8) When to Request Updates or Changes + +Open an issue if you find: + +- Missing phrase IDs in mappings +- Broken references from phrase ID to phrase object +- Broken json download links + +Issue tracker: + +- [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Related Docs + +- [Tutorials Index](tutorials.md) +- [Mutashabihat Guide](resource-mutashabihat.md) +- [Similar Ayah Guide](resource-similar-ayah.md) +- [Quran Script Guide](resource-quran-script.md) diff --git a/docs/tutorial-quran-metadata-end-to-end.md b/docs/tutorial-quran-metadata-end-to-end.md new file mode 100644 index 00000000..0de10226 --- /dev/null +++ b/docs/tutorial-quran-metadata-end-to-end.md @@ -0,0 +1,203 @@ +# Tutorial 7: Quran Metadata End-to-End + +This tutorial is for users who want to use Quran structural metadata (surah/juz/hizb/manzil and related entities) for navigation and filtering. + +## 1) What This Resource Is + +Quran Metadata resources provide structural reference data for Quran navigation. + +Typical metadata includes: + +- Surah records +- Juz/Hizb/Rub/Manzil structures +- Ayah-range references and numeric indexes + +Primary category: + +- [https://qul.tarteel.ai/resources/quran-metadata](https://qul.tarteel.ai/resources/quran-metadata) + +## 2) When to Use It + +Use metadata resources when you are building: + +- Browse-by-Juz/Hizb flows +- Structural navigation menus +- Section headers and contextual reading controls + +## 3) How to Get Your First Example Resource + +1. Open [https://qul.tarteel.ai/resources/quran-metadata](https://qul.tarteel.ai/resources/quran-metadata). +2. Keep default listing order and open the first published card. +3. Confirm the detail page includes: + - `Preview` tab (resource-specific title like `Surah names Preview`) + - `Help` tab +4. Confirm available downloads (`json`, `sqlite`). + +This keeps onboarding concrete without hardcoded IDs. + +## 4) What the Preview Shows (Website-Aligned) + +On metadata detail pages: + +- `Preview` tab: + - Displays current metadata item examples + - Provides navigation controls where relevant +- `Help` tab: + - Documents field definitions and enum values + - Clarifies how structural references map to Quran sections + +Practical meaning: + +- Metadata is the navigation layer; script/translation/tafsir are content layers. +- Build filters from metadata, then fetch ayah content via shared keys/ranges. + +## 5) Download and Use (Step-by-Step) + +1. Download metadata package (`json` or `sqlite`). +2. Import structural tables/records. +3. Normalize range fields to one format in your app. +4. Build indexes by structure number (e.g., `juz_number`, `hizb_number`). +5. Connect metadata filters to ayah queries. + +Starter integration snippet (JavaScript): + +```javascript +// Build lookup maps for fast structure-based navigation. +const buildMetadataIndexes = ({ surahs, juzRanges }) => { + const surahById = new Map(surahs.map((s) => [s.surah_id, s])); + const juzByNumber = new Map(juzRanges.map((j) => [j.juz_number, j])); + return { surahById, juzByNumber }; +}; + +// Resolve ayah range for selected juz. +const getJuzAyahRange = (juzByNumber, juzNumber) => { + const record = juzByNumber.get(juzNumber); + if (!record) return null; + return { + from: `${record.from_surah}:${record.from_ayah}`, + to: `${record.to_surah}:${record.to_ayah}` + }; +}; +``` + +## 6) Real-World Example: Browse by Juz + +Goal: + +- User selects a Juz and immediately sees its ayah range. + +Inputs: + +- Quran Metadata package +- Quran Script package + +Processing: + +1. User picks `Juz 30`. +2. App resolves range from metadata. +3. App queries and renders ayahs in that range. + +Expected output: + +- Structural navigation works without manual ayah lookups. + +Interactive preview (temporary sandbox): + +You can edit this code for testing. Edits are not saved and may not persist after refresh. + +```playground-js +// This sandbox shows how metadata powers structural browsing. + +const juzRanges = [ + { juz_number: 1, from_surah: 1, from_ayah: 1, to_surah: 2, to_ayah: 141 }, + { juz_number: 2, from_surah: 2, from_ayah: 142, to_surah: 2, to_ayah: 252 }, + { juz_number: 30, from_surah: 78, from_ayah: 1, to_surah: 114, to_ayah: 6 } +]; + +const surahs = [ + { surah_id: 1, name: "Al-Fatihah", revelation_place: "makkah" }, + { surah_id: 2, name: "Al-Baqarah", revelation_place: "madinah" }, + { surah_id: 78, name: "An-Naba", revelation_place: "makkah" }, + { surah_id: 114, name: "An-Nas", revelation_place: "makkah" } +]; + +const surahById = new Map(surahs.map((s) => [s.surah_id, s])); +const juzByNumber = new Map(juzRanges.map((j) => [j.juz_number, j])); + +const app = document.getElementById("app"); +const dropdownStyle = [ + "margin-bottom:12px", + "padding:8px", + "border:1px solid #cbd5e1", + "border-radius:8px", + "background:#fff", + "color:#0f172a", + "font-size:0.95rem", + "line-height:1.3", + "min-width:220px" +].join(";"); + +app.innerHTML = ` +

    Quran Metadata Preview (Browse by Juz)

    +

    Resolve ayah ranges and boundary surahs from metadata

    + + +
    +`; + +const juzSelect = app.querySelector("#juz"); +const result = app.querySelector("#result"); + +[1, 2, 30].forEach((n) => { + const opt = document.createElement("option"); + opt.value = String(n); + opt.textContent = `Juz ${n}`; + juzSelect.appendChild(opt); +}); + +const render = () => { + const selected = Number(juzSelect.value); + const range = juzByNumber.get(selected); + if (!range) { + result.textContent = "Range not found"; + return; + } + + const fromSurah = surahById.get(range.from_surah)?.name || range.from_surah; + const toSurah = surahById.get(range.to_surah)?.name || range.to_surah; + + result.innerHTML = ` +
    From: ${range.from_surah}:${range.from_ayah} (${fromSurah})
    +
    To: ${range.to_surah}:${range.to_ayah} (${toSurah})
    + `; +}; + +juzSelect.addEventListener("change", render); +juzSelect.value = "30"; +render(); +``` + +## 7) Common Mistakes to Avoid + +- Treating metadata as text content instead of navigation data. +- Hardcoding ranges instead of using metadata package values. +- Ignoring enum fields (for example revelation place) during filtering. + +## 8) When to Request Updates or Changes + +Open an issue if you find: + +- Incorrect structural ranges +- Missing metadata records +- Broken json/sqlite links + +Issue tracker: + +- [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Related Docs + +- [Tutorials Index](tutorials.md) +- [Quran Metadata Guide](resource-quran-metadata.md) +- [Quran Script Guide](resource-quran-script.md) +- [Surah Information Guide](resource-surah-information.md) diff --git a/docs/tutorial-quran-script-end-to-end.md b/docs/tutorial-quran-script-end-to-end.md new file mode 100644 index 00000000..c681f04b --- /dev/null +++ b/docs/tutorial-quran-script-end-to-end.md @@ -0,0 +1,281 @@ +# Tutorial 5: Quran Script End-to-End + +This tutorial is for users who want to download Quran Script data and render Arabic text reliably in apps. + +## 1) What This Resource Is + +Quran Script resources provide Arabic Quran text in script-specific formats (for example: word-by-word and ayah-by-ayah variants). + +Depending on the selected package, script data can include: + +- Verse-level text (`verse_key`, `text`) +- Script metadata (`script_type`, `font_family`) +- Word-level arrays (`words[].position`, `words[].text`, `words[].location`) +- Navigation metadata (`page_number`, `juz_number`, `hizb_number`) + +Primary category: + +- [https://qul.tarteel.ai/resources/quran-script](https://qul.tarteel.ai/resources/quran-script) + +## 2) When to Use It + +Use Quran Script data when you are building: + +- Arabic Quran readers +- Word-by-word reading and study interfaces +- Features that require stable ayah or word keys for joins (translation, tafsir, audio sync) + +## 3) How to Get Your First Example Resource + +1. Open [https://qul.tarteel.ai/resources/quran-script](https://qul.tarteel.ai/resources/quran-script). +2. Keep the default listing order and open the first published card. +3. Confirm the detail page includes: + - `Preview` tab + - `Help` tab +4. Confirm available downloads: + - `sqlite` + - `json` +5. Confirm whether the package is marked `Word by word` or `Ayah by ayah`. + +This keeps onboarding concrete without hardcoding a resource ID. + +## 4) What the Preview Shows (Website-Aligned) + +On the script detail page: + +- `Preview` tab: + - `Jump to Ayah` selector + - Previous/next ayah navigation + - Rendered Arabic script output (often word-by-word blocks in word resources) +- `Help` tab: + - Sample JSON structure + - Field descriptions (`verse_key`, `text`, `script_type`, `font_family`, `words`) + - Rendering notes and usage examples (CSS + JS) + +Practical meaning: + +- You should treat `verse_key` and `words[].location` as canonical join keys. +- You should apply the provided `font_family` (or compatible fallback) for accurate rendering. + +## 5) Download and Use (Step-by-Step) + +1. Download selected script package (`json` or `sqlite`). +2. Inspect core fields: + - `verse_key` in `surah:ayah` + - `text` + - `script_type` + - `font_family` + - `words` (if word-by-word package) +3. Normalize keys in your app: + - Ayah key: `surah:ayah` + - Word key: `surah:ayah:word` +4. Store verse and word rows in indexed tables/maps. +5. Join with translation/tafsir/recitation by shared ayah keys. +6. Apply RTL direction + script font in UI. +7. Validate on one full surah and several random ayahs. + +Starter integration snippet (JavaScript): + +```javascript +// Build fast verse lookup by canonical ayah key. +const buildVerseIndex = (rows) => + rows.reduce((index, row) => { + index[row.verse_key] = { + text: row.text, + scriptType: row.script_type, + fontFamily: row.font_family, + words: Array.isArray(row.words) ? row.words : [] + }; + return index; + }, {}); + +// Render one verse with script-aware font + RTL settings. +const renderVerse = (container, verseRecord) => { + container.dir = "rtl"; + container.style.textAlign = "right"; + container.style.fontFamily = `${verseRecord.fontFamily || "serif"}, "Amiri Quran", "Noto Naskh Arabic", serif`; + container.textContent = verseRecord.text; +}; + +// Optional: render word-by-word blocks when word data exists. +const renderWordBlocks = (container, words) => { + container.innerHTML = ""; + words + .slice() + .sort((a, b) => a.position - b.position) + .forEach((word) => { + const chip = document.createElement("span"); + chip.textContent = word.text; + chip.title = word.location; // e.g., 1:1:2 + chip.style.display = "inline-block"; + chip.style.padding = "6px 10px"; + chip.style.margin = "4px"; + chip.style.border = "1px solid #e2e8f0"; + chip.style.borderRadius = "8px"; + container.appendChild(chip); + }); +}; +``` + +## 6) Real-World Example: Render One Ayah (Verse + Words) + +Goal: + +- User selects an ayah and sees both full Arabic verse text and word-by-word chips. + +Inputs: + +- Quran Script package (word-by-word variant) + +Processing: + +1. User selects ayah key (example: `73:4`). +2. App loads verse row by `verse_key`. +3. App renders full verse text using script-aware font. +4. App renders sorted `words[]` as chips. + +Expected output: + +- Correct Arabic script display. +- Word order remains stable by `position`. +- Keys remain compatible with translation/tafsir/audio joins. + +Interactive preview (temporary sandbox): + +You can edit this code for testing. Edits are not saved and may not persist after refresh. + +```playground-js +// This playground mirrors Quran Script help concepts: +// verse_key + text + script_type + font_family + words[] with location keys. + +const scriptRows = [ + { + verse_key: "73:4", + text: "أَوۡ زِدۡ عَلَيۡهِ وَرَتِّلِ ٱلۡقُرۡءَانَ تَرۡتِيلًا", + script_type: "text_qpc_hafs", + font_family: "qpc-hafs", + words: [ + { position: 1, text: "أَوۡ", location: "73:4:1" }, + { position: 2, text: "زِدۡ", location: "73:4:2" }, + { position: 3, text: "عَلَيۡهِ", location: "73:4:3" }, + { position: 4, text: "وَرَتِّلِ", location: "73:4:4" }, + { position: 5, text: "ٱلۡقُرۡءَانَ", location: "73:4:5" }, + { position: 6, text: "تَرۡتِيلًا", location: "73:4:6" } + ] + }, + { + verse_key: "1:1", + text: "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ", + script_type: "text_qpc_hafs", + font_family: "qpc-hafs", + words: [ + { position: 1, text: "بِسۡمِ", location: "1:1:1" }, + { position: 2, text: "ٱللَّهِ", location: "1:1:2" }, + { position: 3, text: "ٱلرَّحۡمَٰنِ", location: "1:1:3" }, + { position: 4, text: "ٱلرَّحِيمِ", location: "1:1:4" } + ] + } +]; + +// Create verse lookup by verse_key. +const verseByKey = scriptRows.reduce((index, row) => { + index[row.verse_key] = row; + return index; +}, {}); + +const app = document.getElementById("app"); +const dropdownStyle = [ + "margin-bottom:12px", + "padding:8px", + "border:1px solid #cbd5e1", + "border-radius:8px", + "background:#fff", + "color:#0f172a", + "font-size:0.95rem", + "line-height:1.3", + "min-width:220px" +].join(";"); + +app.innerHTML = ` +

    Quran Script Preview (Verse + Word by Word)

    +

    Shows how verse_key and words[].location map to rendering + integration keys

    + + +
    +
    +
    +`; + +const ayahSelect = app.querySelector("#ayah"); +const metaBox = app.querySelector("#meta"); +const verseBox = app.querySelector("#verse"); +const wordsBox = app.querySelector("#words"); + +const renderAyah = (ayahKey) => { + const verse = verseByKey[ayahKey]; + if (!verse) { + verseBox.textContent = "(Verse not found)"; + wordsBox.innerHTML = ""; + metaBox.textContent = ""; + return; + } + + // Show script metadata users should carry into app rendering rules. + metaBox.textContent = `verse_key: ${verse.verse_key} | script_type: ${verse.script_type} | font_family: ${verse.font_family}`; + + // Full verse rendering. + verseBox.textContent = verse.text; + + // Word-by-word rendering sorted by position. + wordsBox.innerHTML = ""; + verse.words + .slice() + .sort((a, b) => a.position - b.position) + .forEach((word) => { + const chip = document.createElement("span"); + chip.textContent = word.text; + chip.title = word.location; + chip.style.display = "inline-block"; + chip.style.padding = "6px 10px"; + chip.style.margin = "4px"; + chip.style.border = "1px solid #e2e8f0"; + chip.style.borderRadius = "8px"; + chip.style.fontFamily = "'KFGQPC Uthmanic Script HAFS','Amiri Quran','Noto Naskh Arabic','Scheherazade New',serif"; + wordsBox.appendChild(chip); + }); +}; + +ayahSelect.addEventListener("change", (event) => renderAyah(event.target.value)); +renderAyah(ayahSelect.value); +``` + +## 7) Common Mistakes to Avoid + +- Joining script data to other resources using row order instead of keys. +- Ignoring `font_family` and then assuming text is wrong when rendering is visually off. +- Treating word-by-word and ayah-by-ayah packages as interchangeable shapes. +- Ignoring `words[].position` and rendering words out of order. + +## 8) When to Request Updates or Changes + +Open an issue if you find: + +- Missing ayah rows or mismatched `verse_key` values +- Broken json/sqlite links for script resources +- Incorrect word order/location keys in word-by-word exports +- Metadata inconsistencies in `script_type` or `font_family` + +Issue tracker: + +- [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Related Docs + +- [Tutorials Index](tutorials.md) +- [Quran Script Guide](resource-quran-script.md) +- [Translations Guide](resource-translations.md) +- [Tafsirs Guide](resource-tafsirs.md) +- [Downloading and Using Data](downloading-data.md) diff --git a/docs/tutorial-recitation-end-to-end.md b/docs/tutorial-recitation-end-to-end.md new file mode 100644 index 00000000..07b7ecce --- /dev/null +++ b/docs/tutorial-recitation-end-to-end.md @@ -0,0 +1,207 @@ +# Tutorial 1: Recitation End-to-End + +This tutorial is for users who want to download recitation data and build a minimal synced playback experience. + +## 1) What This Resource Is + +Recitation resources provide Quran audio and related timing metadata. + +Depending on the selected package, you can get: + +- Surah-by-surah audio (gapless playback) +- Ayah-by-ayah audio (gapped playback) +- Segment timing arrays used for synchronized word or ayah highlighting + +Primary category: + +- [https://qul.tarteel.ai/resources/recitation](https://qul.tarteel.ai/resources/recitation) + +## 2) When to Use It + +Use recitation data when you are building: + +- Quran audio players +- Memorization or revision tools +- Reading experiences that sync highlights to playback + +## 3) How to Get Your First Example Resource + +1. Open [https://qul.tarteel.ai/resources/recitation](https://qul.tarteel.ai/resources/recitation). +2. Open the first published card in the default page order. +3. Confirm the resource detail page includes: + - A `Recitation` preview tab + - A `Help` tab with segment format examples +4. Download the available file format (`JSON`, `SQLite`, or both). + +This avoids hardcoding a resource ID while keeping onboarding concrete. + +## 4) What the Preview Shows (Website-Aligned) + +On the recitation detail page, the preview helps you validate usability before download: + +- `Recitation` tab: + - Jump-to-ayah selector + - Previous/next ayah navigation + - Audio player with timing-driven highlighting behavior +- `Help` tab: + - Difference between surah-by-surah and ayah-by-ayah data + - Segment/timestamp sample formats for integration + +Practical meaning: + +- If segment arrays are present, you can build synchronized highlighting. +- If segment arrays are absent, treat the resource as audio-only playback. + +## 5) Download and Use (Step-by-Step) + +1. Download your selected recitation package. +2. Inspect fields before integration: + - Ayah identity fields (`surah`, `ayah`, or `surah:ayah`) + - Timing fields (`segments`, `timestamp_from`, `timestamp_to`) when provided + - Audio pointer field (`audio_url` or equivalent) +3. Normalize to a consistent key in your app. +4. Join recitation rows with Quran text rows using the same ayah key. +5. Build a minimal player loop. +6. Test with at least one full surah. + +Starter integration snippet (JavaScript): + +```javascript +// Convert mixed source fields into one stable key format (e.g., "2:255"). +const normalizeAyahKey = (row) => { + if (row.ayah_key) return row.ayah_key; + return `${row.surah}:${row.ayah}`; +}; + +// Build a quick lookup so playback can find timing/audio by ayah key. +const buildTimingIndex = (recitationRows) => { + return recitationRows.reduce((index, row) => { + const key = normalizeAyahKey(row); + index[key] = { + audioUrl: row.audio_url, + from: row.timestamp_from, + to: row.timestamp_to, + segments: Array.isArray(row.segments) ? row.segments : [] + }; + return index; + }, {}); +}; + +// Merge Quran text rows with recitation timing rows for display + sync. +const joinTextWithTiming = (scriptRows, timingIndex) => { + return scriptRows + .map((row) => { + const ayahKey = `${row.surah}:${row.ayah}`; + return { ...row, ayahKey, timing: timingIndex[ayahKey] || null }; + }) + // Keep only rows we can actually sync. + .filter((row) => row.timing); +}; +``` + +## 6) Real-World Example: Play One Surah with Live Ayah Highlighting + +Goal: + +- User plays a surah and sees the currently recited ayah highlighted in real time. + +Inputs: + +- Recitation resource (audio + timings when available) +- Quran Script resource (ayah text) + +Processing: + +1. User selects a surah. +2. App loads surah text from Quran Script. +3. App loads matching recitation timing map for the same surah. +4. On each player time update, app finds the active ayah time window. +5. UI updates highlight state for that ayah. + +Expected output: + +- Audio playback is smooth. +- Highlight transitions follow recitation timing. +- Ayah text and audio stay mapped by the same ayah key. + +Interactive preview (temporary sandbox): + +You can edit this code for testing. Edits are not saved and may not persist after refresh. + +```playground-js +const words = [ + { wordKey: "1:1:1", text: "بِسۡمِ", from: 0, to: 2 }, + { wordKey: "1:1:2", text: "ٱللَّهِ", from: 2, to: 4 }, + { wordKey: "1:1:3", text: "ٱلرَّحۡمَٰنِ", from: 4, to: 6 }, + { wordKey: "1:1:4", text: "ٱلرَّحِيمِ", from: 6, to: 8 } +]; + +const app = document.getElementById("app"); +app.innerHTML = ` +

    Word Playback Preview (Real Arabic Text)

    +

    Simulated timestamp-driven highlighting for Surah 1:1 words

    +
      +`; + +const list = app.querySelector("#word-list"); +words.forEach((word) => { + const li = document.createElement("li"); + li.dataset.wordKey = word.wordKey; + li.style.padding = "8px 10px"; + li.style.marginBottom = "6px"; + li.style.borderRadius = "8px"; + li.style.border = "1px solid #e2e8f0"; + li.style.transition = "all 120ms ease"; + li.style.textAlign = "right"; + li.style.fontSize = "1.2rem"; + li.textContent = word.text; + list.appendChild(li); +}); + +let currentSecond = 0; +const totalDuration = words[words.length - 1].to + 1; + +const setActiveWord = (second) => { + const active = words.find((word) => second >= word.from && second < word.to); + list.querySelectorAll("li").forEach((li) => { + const isActive = active && li.dataset.wordKey === active.wordKey; + li.style.background = isActive ? "#dcfce7" : "#ffffff"; + li.style.borderColor = isActive ? "#22c55e" : "#e2e8f0"; + li.style.fontWeight = isActive ? "700" : "400"; + }); +}; + +setActiveWord(currentSecond); +setInterval(() => { + currentSecond = (currentSecond + 1) % totalDuration; + setActiveWord(currentSecond); +}, 1000); +``` + +## 7) Common Mistakes to Avoid + +- Assuming all recitations include segments. +- Mixing key formats without normalization. +- Joining recitation to text using row order instead of ayah identity. +- Treating preview success for one ayah as proof that whole-surah mapping is correct. + +## 8) When to Request Updates or Changes + +Open an issue if you find: + +- Broken or inaccessible audio file links +- Segment/timestamp values that do not match audible recitation +- Missing ayah mappings in downloaded files +- Inconsistent reciter or package metadata + +Issue tracker: + +- [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Related Docs + +- [Tutorials Index](tutorials.md) +- [Recitations Guide](resource-recitations.md) +- [Quran Script Guide](resource-quran-script.md) +- [Data Model](data-model.md) +- [Downloading and Using Data](downloading-data.md) diff --git a/docs/tutorial-similar-ayah-end-to-end.md b/docs/tutorial-similar-ayah-end-to-end.md new file mode 100644 index 00000000..73184070 --- /dev/null +++ b/docs/tutorial-similar-ayah-end-to-end.md @@ -0,0 +1,348 @@ +# Tutorial 13: Similar Ayah End-to-End + +This tutorial is for users who want to show ayah-to-ayah similarity results (matched words, coverage, and score). + +## 1) What This Resource Is + +Similar Ayah resources provide ayah-level similarity records. + +Typical fields include: + +- `verse_key` +- `matched_ayah_key` +- `matched_words_count` +- `coverage` +- `score` +- `match_words_range` (word position range in matched ayah) + +Primary category: + +- [https://qul.tarteel.ai/resources/similar-ayah](https://qul.tarteel.ai/resources/similar-ayah) + +## 2) When to Use It + +Use similar-ayah data when building: + +- Compare-verses tools +- Pattern discovery across Quranic wording +- Memorization reinforcement features + +## 3) How to Get Your First Example Resource + +1. Open [https://qul.tarteel.ai/resources/similar-ayah](https://qul.tarteel.ai/resources/similar-ayah). +2. Keep default listing order and open the first published card. +3. Confirm the detail page includes: + - `Similar Ayah Preview` tab + - `Help` tab +4. Confirm available downloads (`json`, `sqlite`). + +This keeps onboarding concrete without hardcoded IDs. + +## 4) What the Preview Shows (Website-Aligned) + +On similar-ayah detail pages: + +- `Similar Ayah Preview` tab: + - `Jump to Ayah` + - Source ayah card for selected ayah + - "X has N similar ayahs" banner + - List of matching ayahs for selected ayah + - Highlighted matched words inside matched ayah text + - Match summaries ("This ayah matches N words with X% coverage and a similarity score of Y") +- `Help` tab: + - Field definitions (`verse_key`, `matched_ayah_key`, `matched_words_count`, `coverage`, `score`, `match_words_range`) + - Export format notes + +Practical meaning: + +- Similarity is scored data, not binary “same/different”. +- You should sort by score/coverage and let users inspect why matches appear. + +## 5) Download and Use (Step-by-Step) + +1. Download selected package (`json` or `sqlite`). +2. Import similarity rows. +3. Group rows by `verse_key`. +4. Sort matches by score and/or coverage. +5. Join with script text for readable display. +6. Use `match_words_range` to highlight matched words in the matched ayah. + +Starter integration snippet (JavaScript): + +```javascript +const matchesForAyah = (rows, ayahKey) => + rows + .filter((row) => row.verse_key === ayahKey) + .sort((a, b) => b.score - a.score || b.coverage - a.coverage); + +const summaryText = (row) => + `This ayah matches ${row.matched_words_count} words with ${row.coverage}% coverage and a similarity score of ${row.score}`; + +const isMatchedWordPosition = (wordIndexOneBased, range) => { + if (!Array.isArray(range) || range.length !== 2) return false; + const [from, to] = range; + return wordIndexOneBased >= from && wordIndexOneBased <= to; +}; +``` + +## 6) Real-World Example: Top Similar Ayahs Panel + +Goal: + +- User opens an ayah and sees top similar ayahs ranked by score. + +Inputs: + +- Similar Ayah package +- Quran Script package + +Processing: + +1. Filter rows by selected `verse_key`. +2. Sort by `score` (and `coverage` as tie-breaker). +3. Join matched ayah text and highlight words by `match_words_range`. +4. Display per-match summary sentence. + +Expected output: + +- Ranked similarity results that are interpretable, with visible highlighted overlap. + +Interactive preview (temporary sandbox): + +You can edit this code for testing. Edits are not saved and may not persist after refresh. + +```playground-js +// Preview-aligned sandbox: +// Jump to Ayah -> source ayah card -> similar ayah cards with highlighted matched words. +// Based on the behavior shown in similar-ayah resource 74. + +const sourceAyahTextByKey = { + "1:1": "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ ١", + "1:3": "ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ ٣" +}; + +const similarRows = [ + { + verse_key: "1:1", + matched_ayah_key: "27:30", + matched_words_count: 4, + coverage: 50, + score: 80, + match_words_range: [5, 8] + }, + { + verse_key: "1:1", + matched_ayah_key: "59:22", + matched_words_count: 2, + coverage: 15, + score: 56, + match_words_range: [12, 13] + }, + { + verse_key: "1:1", + matched_ayah_key: "1:3", + matched_words_count: 2, + coverage: 100, + score: 50, + match_words_range: [1, 2] + }, + { + verse_key: "1:1", + matched_ayah_key: "41:2", + matched_words_count: 2, + coverage: 50, + score: 50, + match_words_range: [3, 4] + }, + { + verse_key: "1:3", + matched_ayah_key: "1:1", + matched_words_count: 2, + coverage: 100, + score: 50, + match_words_range: [3, 4] + } +]; + +// Keep literals as UTF-8 Arabic text. +const matchedAyahTextByKey = { + "27:30": "إِنَّهُۥ مِن سُلَيۡمَٰنَ وَإِنَّهُۥ بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ ٣٠", + "59:22": "هُوَ ٱللَّهُ ٱلَّذِي لَآ إِلَٰهَ إِلَّا هُوَۖ عَٰلِمُ ٱلۡغَيۡبِ وَٱلشَّهَٰدَةِۖ هُوَ ٱلرَّحۡمَٰنُ ٱلرَّحِيمُ ٢٢", + "1:3": "ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ ٣", + "41:2": "تَنزِيلٞ مِّنَ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ ٢", + "1:1": "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ ١" +}; + +// Help-aligned schema and sample block (as shown on the resource Help tab). +const helpSchema = [ + { column: "verse_key", type: "TEXT", description: "The ayah key that similar ayahs belongs to (e.g., 1:1)." }, + { column: "matched_ayah_key", type: "TEXT", description: "Matching ayah key that shares similar wording." }, + { column: "matched_words_count", type: "INTEGER", description: "Number of matching words between the two ayahs." }, + { column: "coverage", type: "INTEGER", description: "Percentage of words in the source ayah that matched." }, + { column: "score", type: "INTEGER", description: "Similarity score between the source and matched ayah (0–100)." }, + { column: "match_words_range", type: "TEXT", description: "Word position range in matching ayah that matched with source ayah." } +]; + +const helpExample = { + verse_key: "1:1", + matched_ayah_key: "27:30", + matched_words_count: 4, + coverage: 100, + score: 100, + match_words_range: [5, 8] +}; + +const highlightMatchedWords = (ayahText, range) => + String(ayahText || "") + .split(/\s+/) + .filter(Boolean) + .map((word, index) => { + const pos = index + 1; + const inRange = + Array.isArray(range) && + range.length === 2 && + pos >= range[0] && + pos <= range[1]; + if (!inRange) return word; + return `${word}`; + }) + .join(" "); + +const app = document.getElementById("app"); +const dropdownStyle = [ + "margin-bottom:12px", + "padding:8px", + "border:1px solid #cbd5e1", + "border-radius:8px", + "background:#fff", + "color:#0f172a", + "font-size:0.95rem", + "line-height:1.3", + "min-width:220px" +].join(";"); + +app.innerHTML = ` +

      Similar Ayah Preview

      +

      Preview + Help on one screen: interactive similar-ayah cards and schema/sample reference

      +
      + Top section mirrors Preview. Bottom section mirrors the Help field/schema explanation. +
      +
      + + +
      Select the source ayah to compare similar matches.
      +
      +
      +
      +
      +
      +`; + +const ayahSelect = app.querySelector("#ayah"); +const source = app.querySelector("#source"); +const summary = app.querySelector("#summary"); +const result = app.querySelector("#result"); +const help = app.querySelector("#help"); + +const render = () => { + const key = ayahSelect.value; + source.innerHTML = ` +
      ${key}
      +
      ${sourceAyahTextByKey[key] || ""}
      + `; + + const rows = similarRows + .filter((r) => r.verse_key === key) + .sort((a, b) => b.score - a.score || b.coverage - a.coverage); + + summary.textContent = `${key} has ${rows.length} similar ayahs`; + + if (rows.length === 0) { + result.textContent = "No similar ayahs found."; + return; + } + + result.innerHTML = rows + .map((r) => { + const ayahText = matchedAyahTextByKey[r.matched_ayah_key] || ""; + return ` +
      +
      ${r.matched_ayah_key}
      +
      + ${highlightMatchedWords(ayahText, r.match_words_range)} +
      + + This ayah matches ${r.matched_words_count} words with ${r.coverage}% coverage and a similarity score of ${r.score} + +
      + `; + }) + .join(""); + + const schemaRows = helpSchema + .map( + (field) => ` + + ${field.column} + ${field.type} + ${field.description} + + ` + ) + .join(""); + + help.innerHTML = ` +

      Help Reference (Schema + Example)

      +

      QUL exports similar ayah data in JSON and SQLite. Core fields:

      +
      + + + + + + + + + ${schemaRows} +
      ColumnTypeDescription
      +
      +
      Help sample (if 1:1 matches 27:30):
      +
      ${JSON.stringify(helpExample, null, 2)}
      +
      + This ayah matches ${helpExample.matched_words_count} words, with ${helpExample.coverage}% coverage and a similarity score of ${helpExample.score}. + Word ${helpExample.match_words_range[0]} to ${helpExample.match_words_range[1]} matched with source ayah. +
      + `; +}; + +ayahSelect.addEventListener("change", render); +render(); +``` + +## 7) Common Mistakes to Avoid + +- Treating similarity score as strict equivalence. +- Ignoring coverage and matched-word context. +- Not sorting results (raw order can be misleading). + +## 8) When to Request Updates or Changes + +Open an issue if you find: + +- Mismatched `verse_key`/`matched_ayah_key` pairs +- Invalid score/coverage values +- Broken json/sqlite downloads + +Issue tracker: + +- [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Related Docs + +- [Tutorials Index](tutorials.md) +- [Similar Ayah Guide](resource-similar-ayah.md) +- [Mutashabihat Guide](resource-mutashabihat.md) +- [Quran Script Guide](resource-quran-script.md) diff --git a/docs/tutorial-surah-information-end-to-end.md b/docs/tutorial-surah-information-end-to-end.md new file mode 100644 index 00000000..0cc9c4eb --- /dev/null +++ b/docs/tutorial-surah-information-end-to-end.md @@ -0,0 +1,297 @@ +# Tutorial 9: Surah Information End-to-End + +This tutorial is for users who want to show chapter-level context (names, summaries, key notes) before ayah reading. + +## 1) What This Resource Is + +Surah Information resources provide chapter metadata and explanatory context. + +Typical fields include: + +- Surah identity (`surah_id` / number) +- Names/titles +- Short summary text +- Detailed long-form content (often with sections such as `Name`, `Period of Revelation`, `Theme`) +- Language/source metadata +- Revelation context and related descriptors + +Primary category: + +- [https://qul.tarteel.ai/resources/surah-info](https://qul.tarteel.ai/resources/surah-info) + +## 2) When to Use It + +Use surah-info data when building: + +- Surah intro cards before ayah list +- Chapter overview pages +- Learning experiences with chapter context + +## 3) How to Get Your First Example Resource + +1. Open [https://qul.tarteel.ai/resources/surah-info](https://qul.tarteel.ai/resources/surah-info). +2. Keep default listing order and open the first published card. +3. Confirm the detail page includes: + - `Surah Info Preview` tab + - `Help` tab +4. Confirm available downloads (`csv`, `json`, `sqlite`). + +This keeps onboarding concrete without hardcoded IDs. + +## 4) What the Preview Shows (Website-Aligned) + +On surah-info detail pages: + +- `Surah Info Preview` tab: + - `Jump to Surah` + - Next/previous surah navigation + - Short summary paragraph + - Detailed content blocks (for example `Name`, `Period of Revelation`, `Theme`) +- `Help` tab: + - Resource purpose and coverage (themes/topics/reasons for revelation/summaries) + - Format availability (`SQLite`, `CSV`, `JSON`) + - Note that some records include both short summary and detailed long-form text + - Note that detailed text may include HTML tags for formatting + +Practical meaning: + +- Surah-info is chapter-level context, not ayah-level text. +- Use it as a companion layer above script/translation views. + +Full Surah 1 example content (from a surah-info detail page, in the same structure users see): + +- Intro summary: + - This Surah is named Al-Fatihah because of its subject matter. Fatihah is that which opens a subject or a book or any other thing. In other words, Al-Fatihah is a sort of preface. +- Name: + - This Surah is named Al-Fatihah because of its subject matter. Fatihah is that which opens a subject or a book or any other thing. In other words, Al-Fatihah is a sort of preface. +- Period of Revelation: + - Surah Al-Fatihah is one of the very earliest Revelations to the Holy Prophet. As a matter of fact, we learn from authentic traditions that it was the first complete Surah that was revealed to Muhammad (Allah's peace be upon him). Before this, only a few miscellaneous verses were revealed which form parts of Alaq, Muzzammil, Muddaththir, etc. +- Theme: + - This Surah is in fact a prayer that Allah has taught to all those who want to make a study of His book. It has been placed at the very beginning of the Quran to teach this lesson to the reader: if you sincerely want to benefit from the Quran, you should offer this prayer to the Lord of the Universe. + - This preface is meant to create a strong desire in the heart of the reader to seek guidance from the Lord of the Universe Who alone can grant it. Thus Al-Fatihah indirectly teaches that the best thing for a man is to pray for guidance to the straight path, to study the Quran with the mental attitude of a seeker searching for the truth, and to recognize the fact that the Lord of the Universe is the source of all knowledge. He should, therefore, begin the study of the Quran with a prayer to Him for guidance. + - From this theme, it becomes clear that the real relation between Al-Fatihah and the Quran is not that of an introduction to a book but that of a prayer and its answer. Al-Fatihah is the prayer from the servant and the Quran is the answer from the Master to the servant's prayer. The servant prays to Allah to show him guidance and the Master places the whole of the Quran before him in answer to his prayer, as if to say, "This is the Guidance you begged from Me." + +## 5) Download and Use (Step-by-Step) + +1. Download selected package (`csv`, `json`, or `sqlite`). +2. Import surah records by surah number. +3. Normalize language/source fields if multiple sources are used. +4. Separate short summary and detailed content fields in your data model. +5. If rendering detailed HTML content, sanitize before output in production. +6. Cache chapter metadata for quick chapter loads. +7. Render intro card before ayah list. + +Starter integration snippet (JavaScript): + +```javascript +const buildSurahInfoIndex = (rows) => + rows.reduce((index, row) => { + index[row.surah_id] = row; + return index; + }, {}); + +// Use a proper HTML sanitizer in production; this is only a minimal placeholder. +const sanitizeHtml = (html) => + String(html || "") + .replace(/[\s\S]*?<\/script>/gi, "") + .replace(/on\w+="[^"]*"/gi, ""); + +const renderDetailedSections = (surahInfo) => { + if (Array.isArray(surahInfo.sections) && surahInfo.sections.length > 0) { + return surahInfo.sections + .map( + (section) => + `

      ${section.title}

      ${section.text}

      ` + ) + .join(""); + } + + // Some sources provide long-form chapter details as HTML. + return sanitizeHtml(surahInfo.detailed_html || ""); +}; + +const renderSurahHeader = (container, surahInfo) => { + if (!surahInfo) { + container.textContent = "Surah info not found"; + return; + } + + container.innerHTML = ` +

      ${surahInfo.name}

      +

      ${surahInfo.short_summary || ""}

      + Revelation: ${surahInfo.revelation_place || "unknown"} +
      ${renderDetailedSections(surahInfo)}
      + `; +}; +``` + +## 6) Real-World Example: Chapter Intro Card + +Goal: + +- Show surah context before rendering ayahs. + +Inputs: + +- Surah Information package +- Quran Script package + +Processing: + +1. User opens a surah. +2. App fetches chapter info by `surah_id`. +3. App renders intro card, then ayah list. + +Expected output: + +- Users get chapter context before reading ayah content. + +Interactive preview (temporary sandbox): + +You can edit this code for testing. Edits are not saved and may not persist after refresh. + +```playground-js +// This sandbox shows short summary + detailed sectioned content rendering. + +const surahInfoById = { + 1: { + name: "Al-Fatihah", + language: "English", + revelation_place: "makkah", + short_summary: + "This Surah is named Al-Fatihah because of its subject matter. Fatihah is that which opens a subject or a book or any other thing. In other words, Al-Fatihah is a sort of preface.", + sections: [ + { + title: "Name", + text: + "This Surah is named Al-Fatihah because of its subject matter. Fatihah is that which opens a subject or a book or any other thing. In other words, Al-Fatihah is a sort of preface." + }, + { + title: "Period of Revelation", + text: + "Surah Al-Fatihah is one of the very earliest Revelations to the Holy Prophet. As a matter of fact, we learn from authentic traditions that it was the first complete Surah that was revealed to Muhammad (Allah's peace be upon him). Before this, only a few miscellaneous verses were revealed which form parts of Alaq, Muzzammil, Muddaththir, etc." + }, + { + title: "Theme", + text: + "This Surah is in fact a prayer that Allah has taught to all those who want to make a study of His book. It has been placed at the very beginning of the Quran to teach this lesson to the reader: if you sincerely want to benefit from the Quran, you should offer this prayer to the Lord of the Universe." + }, + { + title: "Theme (continued)", + text: + "This preface is meant to create a strong desire in the heart of the reader to seek guidance from the Lord of the Universe Who alone can grant it. Thus Al-Fatihah indirectly teaches that the best thing for a man is to pray for guidance to the straight path, to study the Quran with the mental attitude of a seeker searching for the truth, and to recognize the fact that the Lord of the Universe is the source of all knowledge. He should, therefore, begin the study of the Quran with a prayer to Him for guidance." + }, + { + title: "Theme (prayer and answer)", + text: + "From this theme, it becomes clear that the real relation between Al-Fatihah and the Quran is not that of an introduction to a book but that of a prayer and its answer. Al-Fatihah is the prayer from the servant and the Quran is the answer from the Master to the servant's prayer. The servant prays to Allah to show him guidance and the Master places the whole of the Quran before him in answer to his prayer, as if to say, \"This is the Guidance you begged from Me.\"" + } + ] + }, + 36: { + name: "Ya-Sin", + language: "English", + revelation_place: "makkah", + short_summary: "Emphasizes resurrection, signs of Allah, and prophetic warning.", + sections: [ + { + title: "Name", + text: "Named after the disjointed letters Ya-Sin." + }, + { + title: "Period of Revelation", + text: "Makki surah focused on belief and accountability." + }, + { + title: "Theme", + text: "Calls to faith, reflection, and responsibility before Allah." + } + ] + } +}; + +const app = document.getElementById("app"); +const dropdownStyle = [ + "margin-bottom:12px", + "padding:8px", + "border:1px solid #cbd5e1", + "border-radius:8px", + "background:#fff", + "color:#0f172a", + "font-size:0.95rem", + "line-height:1.3", + "min-width:220px" +].join(";"); + +app.innerHTML = ` +

      Surah Info Preview

      +

      Render short + detailed chapter context before ayah content

      + + +
      +
      +`; + +const surahSelect = app.querySelector("#surah"); +const meta = app.querySelector("#meta"); +const card = app.querySelector("#card"); + +const render = () => { + const surahId = Number(surahSelect.value); + const info = surahInfoById[surahId]; + if (!info) { + meta.textContent = ""; + card.textContent = "Surah info not found"; + return; + } + + // Render all detailed sections so users can see complete chapter context. + const detailedSections = (info.sections || []) + .map( + (section) => + `
      +

      ${section.title}

      +

      ${section.text}

      +
      ` + ) + .join(""); + + meta.textContent = `Language: ${info.language} | Revelation: ${info.revelation_place}`; + card.innerHTML = ` +

      ${info.name}

      +

      Short summary: ${info.short_summary}

      +
      ${detailedSections}
      + `; +}; + +surahSelect.addEventListener("change", render); +render(); +``` + +## 7) Common Mistakes to Avoid + +- Treating surah-info as ayah-level content. +- Not handling multiple language/source variants. +- Forgetting to cache chapter metadata for repeated navigations. + +## 8) When to Request Updates or Changes + +Open an issue if you find: + +- Missing/incorrect surah descriptions +- Mismatched surah identifiers +- Broken `csv/json/sqlite` downloads + +Issue tracker: + +- [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Related Docs + +- [Tutorials Index](tutorials.md) +- [Surah Information Guide](resource-surah-information.md) +- [Quran Metadata Guide](resource-quran-metadata.md) +- [Quran Script Guide](resource-quran-script.md) diff --git a/docs/tutorial-tafsir-end-to-end.md b/docs/tutorial-tafsir-end-to-end.md new file mode 100644 index 00000000..65ea8b25 --- /dev/null +++ b/docs/tutorial-tafsir-end-to-end.md @@ -0,0 +1,262 @@ +# Tutorial 4: Tafsir End-to-End + +This tutorial is for users who want to download tafsir data and render ayah-linked commentary reliably. + +## 1) What This Resource Is + +Tafsir resources provide commentary linked to ayah keys, with support for grouped commentary that can cover multiple ayahs. + +Depending on the selected package, tafsir may include: + +- Ayah-linked tafsir text +- Grouped tafsir mapping (one main ayah key shared by multiple ayahs) +- Optional HTML formatting tags inside text (``, ``, etc.) + +Primary category: + +- [https://qul.tarteel.ai/resources/tafsir](https://qul.tarteel.ai/resources/tafsir) + +## 2) When to Use It + +Use tafsir data when you are building: + +- Ayah detail views with commentary +- Study panels below Arabic/translation text +- Research experiences that compare tafsir sources + +## 2A) Tafsir vs Translation + +- `Translation` provides direct meaning transfer of ayah text into another language. +- `Tafsir` provides explanation, interpretation, and context around ayah meaning. +- Translation is usually one ayah-to-text mapping, while tafsir can be grouped across multiple ayahs. +- In integration terms: translation is mostly direct key-based rendering; tafsir needs grouped/pointer resolution. + +## 3) How to Get Your First Example Resource + +1. Open [https://qul.tarteel.ai/resources/tafsir](https://qul.tarteel.ai/resources/tafsir). +2. Keep the default listing order and open the first published card. +3. Confirm the resource detail page includes: + - `Tafsir Preview` tab + - `Help` tab +4. Confirm available downloads on the detail page: + - `json` + - `sqlite` + +This keeps onboarding concrete without hardcoding a resource ID. + +## 4) What the Preview Shows (Website-Aligned) + +On the tafsir detail page: + +- `Tafsir Preview` tab: + - `Jump to Ayah` selector + - Previous/next ayah navigation + - Arabic ayah block + tafsir text block +- `Help` tab: + - JSON export model for grouped tafsir + - SQLite columns for grouped/shared commentary + - Notes about tafsir text including formatting tags + +Practical meaning: + +- Tafsir is not always one-ayah = one-unique-text. +- Your app must resolve grouped/shared tafsir references correctly. + +## 5) Download and Use (Step-by-Step) + +1. Download your selected tafsir package (`json` or `sqlite`). +2. Inspect payload shape: + - JSON keys are ayah keys like `2:3` + - Value may be an object (main tafsir group) or a string (pointer to group ayah key) +3. Normalize ayah keys in one format (`surah:ayah`). +4. Build a tafsir resolver: + - If current ayah maps to an object, use its `text`. + - If current ayah maps to a string, follow that pointer and use the target group text. +5. Join with Quran Script and optionally Translation by ayah key. +6. Render commentary safely (sanitize HTML if rendering tags in production). +7. Validate grouped cases across consecutive ayahs. + +Starter integration snippet (JavaScript): + +```javascript +// Resolve tafsir text for an ayah key. +// JSON model can be: +// - object: { text, ayah_keys } +// - string: pointer to another ayah key where main text is stored +const resolveTafsirText = (tafsirByAyahKey, ayahKey) => { + const value = tafsirByAyahKey[ayahKey]; + if (!value) return null; + + // Main group record. + if (typeof value === "object" && value.text) { + return { + groupAyahKey: ayahKey, + ayahKeys: Array.isArray(value.ayah_keys) ? value.ayah_keys : [ayahKey], + text: value.text + }; + } + + // Pointer record. Example: "2:4": "2:3" + if (typeof value === "string") { + const main = tafsirByAyahKey[value]; + if (main && typeof main === "object" && main.text) { + return { + groupAyahKey: value, + ayahKeys: Array.isArray(main.ayah_keys) ? main.ayah_keys : [value], + text: main.text + }; + } + } + + return null; +}; +``` + +## 6) Real-World Example: One Tafsir for Multiple Ayahs + +Goal: + +- User opens an ayah and sees the correct tafsir even when commentary is shared across a range. + +Inputs: + +- Tafsir package (`json` or `sqlite`) +- Quran Script package + +Processing: + +1. User selects ayah key (example: `2:4`). +2. App looks up tafsir by ayah key. +3. If value is pointer (`"2:3"`), app resolves to main group record. +4. App renders resolved tafsir text and shows covered ayah keys. + +Expected output: + +- Correct commentary appears for both main ayah and grouped ayahs. +- No blank tafsir for grouped/pointer ayahs. + +Interactive preview (temporary sandbox): + +You can edit this code for testing. Edits are not saved and may not persist after refresh. + +```playground-js +// This playground mirrors the Help model shown on tafsir detail pages: +// object = main tafsir group, string = pointer to main group ayah key. + +const arabicByAyah = { + "2:3": "ٱلَّذِينَ يُؤۡمِنُونَ بِٱلۡغَيۡبِ وَيُقِيمُونَ ٱلصَّلَوٰةَ وَمِمَّا رَزَقۡنَٰهُمۡ يُنفِقُونَ", + "2:4": "وَٱلَّذِينَ يُؤۡمِنُونَ بِمَآ أُنزِلَ إِلَيۡكَ وَمَآ أُنزِلَ مِن قَبۡلِكَ وَبِٱلۡأٓخِرَةِ هُمۡ يُوقِنُونَ", + "2:5": "أُولَٰئِكَ عَلَىٰ هُدًى مِنْ رَبِّهِمْ وَأُولَٰئِكَ هُمُ الْمُفْلِحُونَ" +}; + +const tafsirByAyahKey = { + // Main tafsir group record. + "2:3": { + text: "The muttaqeen are those who believe in the unseen and remain steadfast in worship.", + ayah_keys: ["2:3", "2:4"] + }, + // Pointer record: ayah 2:4 uses tafsir text stored at 2:3. + "2:4": "2:3", + // Independent tafsir record. + "2:5": { + text: "These are upon guidance from their Lord, and they are the successful.", + ayah_keys: ["2:5"] + } +}; + +// Resolve current ayah to a main tafsir record. +const resolveTafsir = (ayahKey) => { + const value = tafsirByAyahKey[ayahKey]; + if (!value) return null; + + if (typeof value === "object" && value.text) { + return { groupAyahKey: ayahKey, text: value.text, ayahKeys: value.ayah_keys || [ayahKey] }; + } + + if (typeof value === "string") { + const main = tafsirByAyahKey[value]; + if (main && typeof main === "object" && main.text) { + return { groupAyahKey: value, text: main.text, ayahKeys: main.ayah_keys || [value] }; + } + } + + return null; +}; + +const app = document.getElementById("app"); +const dropdownStyle = [ + "margin-bottom:12px", + "padding:8px", + "border:1px solid #cbd5e1", + "border-radius:8px", + "background:#fff", + "color:#0f172a", + "font-size:0.95rem", + "line-height:1.3", + "min-width:220px" +].join(";"); + +app.innerHTML = ` +

      Tafsir Preview (Grouped Ayah Aware)

      +

      Demonstrates object + pointer tafsir mapping from the Help format

      + + +
      +
      +

      +`; + +const ayahSelect = app.querySelector("#ayah"); +const arabicBox = app.querySelector("#arabic"); +const tafsirBox = app.querySelector("#tafsir"); +const groupInfo = app.querySelector("#group"); + +const renderAyah = (ayahKey) => { + arabicBox.textContent = arabicByAyah[ayahKey] || "(Arabic not found)"; + + const resolved = resolveTafsir(ayahKey); + if (!resolved) { + tafsirBox.textContent = "(Tafsir not found)"; + groupInfo.textContent = ""; + return; + } + + tafsirBox.textContent = resolved.text; + groupInfo.textContent = `Group source: ${resolved.groupAyahKey} | Covers: ${resolved.ayahKeys.join(", ")}`; +}; + +ayahSelect.addEventListener("change", (event) => renderAyah(event.target.value)); +renderAyah(ayahSelect.value); +``` + +## 7) Common Mistakes to Avoid + +- Assuming every ayah key contains direct tafsir text. +- Ignoring string-pointer records in JSON grouped exports. +- Failing to resolve `group_ayah_key` in SQLite exports. +- Rendering tafsir HTML without sanitization in production apps. + +## 8) When to Request Updates or Changes + +Open an issue if you find: + +- Grouped ayah references pointing to missing source keys +- Tafsir text assigned to wrong ayah ranges +- Broken json/sqlite download links +- Missing or inconsistent source metadata + +Issue tracker: + +- [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Related Docs + +- [Tutorials Index](tutorials.md) +- [Tafsirs Guide](resource-tafsirs.md) +- [Quran Script Guide](resource-quran-script.md) +- [Translations Guide](resource-translations.md) +- [Downloading and Using Data](downloading-data.md) diff --git a/docs/tutorial-translation-end-to-end.md b/docs/tutorial-translation-end-to-end.md new file mode 100644 index 00000000..3f36f872 --- /dev/null +++ b/docs/tutorial-translation-end-to-end.md @@ -0,0 +1,304 @@ +# Tutorial 3: Translation End-to-End + +This tutorial is for users who want to download translation data and show Arabic + translated text in a reliable way. + +## 1) What This Resource Is + +Translation resources provide translated Quran text keyed by ayah, with multiple export formats. + +Depending on the selected package, you may get: + +- Simple translation text (`JSON` / `SQLite`) +- Footnote-tagged translation (``) +- Inline-footnote translation (`[[...]]`) +- Text chunk format for structured rendering + +Primary category: + +- [https://qul.tarteel.ai/resources/translation](https://qul.tarteel.ai/resources/translation) + +## 2) When to Use It + +Use translation data when you are building: + +- Multilingual Quran readers +- Arabic + translation views for learning apps +- Search and discovery experiences in non-Arabic languages + +## 3) How to Get Your First Example Resource + +1. Open [https://qul.tarteel.ai/resources/translation](https://qul.tarteel.ai/resources/translation). +2. Keep the default listing order and open the first published card. +3. Confirm the resource detail page includes: + - `Translation Preview` tab + - `Help` tab +4. Confirm available download formats shown on the page: + - `simple.json` + - `simple.sqlite` + - Optional footnote/chunk variants (when provided by that translation) + +This keeps onboarding concrete without hardcoding a resource ID. + +## 4) What the Preview Shows (Website-Aligned) + +On the translation detail page: + +- `Translation Preview` tab: + - `Jump to Ayah` selector + - Previous/next ayah navigation + - Arabic ayah block + translated text block +- `Help` tab: + - Export format examples + - Simple structures (nested array, key-value) + - Footnote structures (tags, inline notes, text chunks) + +Practical meaning: + +- If you only need plain text, use a simple format. +- If you need footnotes or formatting control, use footnote-tag/chunk formats. + +## 5) Download and Use (Step-by-Step) + +1. Download your selected translation package. +2. Inspect what format you received: + - Plain string by ayah key + - Object with translation text + footnotes (`t`, `f`) + - Chunk array with mixed text/objects +3. Normalize to one stable key format in app code (recommended: `surah:ayah`). +4. Load Quran Script data for Arabic text. +5. Join Arabic + translation rows by the same ayah key. +6. Render translations by format-aware rules. +7. Validate at least 5 consecutive ayahs so you catch format edge cases. + +Starter integration snippet (JavaScript): + +```javascript +// Convert source rows to one stable key like "73:4". +const ayahKeyFromRow = (row) => row.ayah_key || `${row.surah}:${row.ayah}`; + +// Build lookup map for fast joins with Arabic script rows. +const buildTranslationIndex = (rows) => + rows.reduce((index, row) => { + index[ayahKeyFromRow(row)] = row.translation; + return index; + }, {}); + +// Normalize the different translation payload shapes into one renderable object. +const normalizeTranslationPayload = (payload) => { + // Simple plain-text translation. + if (typeof payload === "string") return { text: payload, notes: [] }; + + // Footnote-tag format: { t: "...1...", f: { x: "note" } } + if (payload && typeof payload === "object" && payload.t) { + const noteIds = []; + const text = payload.t.replace(/([^<]+)<\/sup>/g, (_, id, label) => { + noteIds.push(id); + return `[${label}]`; + }); + const notes = noteIds.map((id) => ({ id, text: payload.f?.[id] || "" })); + return { text, notes }; + } + + // Text chunk format: ["plain", {type: "i", text: "italic"}, {type: "f", f: "12", text: "1"}] + if (Array.isArray(payload)) { + const textParts = []; + const notes = []; + payload.forEach((chunk) => { + if (typeof chunk === "string") textParts.push(chunk); + else if (chunk?.type === "i") textParts.push(chunk.text); + else if (chunk?.type === "f") { + textParts.push(`[${chunk.text}]`); + notes.push({ id: chunk.f, text: `Footnote ${chunk.f}` }); + } + }); + return { text: textParts.join(""), notes }; + } + + return { text: "", notes: [] }; +}; +``` + +## 6) Real-World Example: Arabic + Translation + Footnotes + +Goal: + +- User picks an ayah and sees Arabic text plus translation, with footnotes when available. + +Inputs: + +- Quran Script package (Arabic text) +- Translation package (simple or footnote/chunk format) + +Processing: + +1. User selects ayah key (example: `73:4`). +2. App loads Arabic text by ayah key. +3. App loads translation payload by same ayah key. +4. App normalizes payload to text + notes. +5. UI renders translation and optional footnote list. + +Expected output: + +- Arabic and translation stay correctly paired. +- Footnotes are visible when provided. +- Format differences do not break rendering. + +Interactive preview (temporary sandbox): + +You can edit this code for testing. Edits are not saved and may not persist after refresh. + +```playground-js +// This playground mirrors the Translation Preview + Help format ideas. +// It demonstrates simple text, footnote-tags, and chunk-based translation data. + +const arabicByAyah = { + "1:1": "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ", + "1:2": "ٱلۡحَمۡدُ لِلَّهِ رَبِّ ٱلۡعَٰلَمِينَ", + "73:4": "أَوۡ زِدۡ عَلَيۡهِ وَرَتِّلِ ٱلۡقُرۡءَانَ تَرۡتِيلًا" +}; + +const translationByAyah = { + // 1) Simple text format. + "1:1": "In the name of Allah, the Most Compassionate, the Most Merciful.", + + // 2) Text chunks format. + "1:2": [ + { type: "i", text: "All praise" }, + " is for Allah, Lord of all worlds." + ], + + // 3) Footnote-tags format from Help concepts: { t, f }. + "73:4": { + t: "and recite the Qur'an in a measured way 1.", + f: { + "77646": "Measured recitation means deliberate and clear pacing." + } + } +}; + +// Convert tagged footnotes into readable inline markers + footnote list. +const parseTaggedTranslation = (payload) => { + const noteIds = []; + const text = payload.t.replace(/([^<]+)<\/sup>/g, (_, id, label) => { + noteIds.push(id); + return `[${label}]`; + }); + + const notes = noteIds.map((id) => ({ + id, + text: payload.f?.[id] || "(missing footnote text)" + })); + + return { text, notes }; +}; + +// Convert chunks format into display text + footnotes. +const parseChunksTranslation = (chunks) => { + const parts = []; + const notes = []; + + chunks.forEach((chunk) => { + if (typeof chunk === "string") { + parts.push(chunk); + return; + } + if (chunk?.type === "i") { + parts.push(chunk.text); + return; + } + if (chunk?.type === "f") { + parts.push(`[${chunk.text}]`); + notes.push({ id: chunk.f, text: `Footnote ${chunk.f}` }); + } + }); + + return { text: parts.join(""), notes }; +}; + +// Normalize all format types to a single output shape for rendering. +const normalizeTranslation = (payload) => { + if (typeof payload === "string") return { text: payload, notes: [] }; + if (Array.isArray(payload)) return parseChunksTranslation(payload); + if (payload?.t && payload?.f) return parseTaggedTranslation(payload); + return { text: "", notes: [] }; +}; + +const app = document.getElementById("app"); +const dropdownStyle = [ + "margin-bottom:12px", + "padding:8px", + "border:1px solid #cbd5e1", + "border-radius:8px", + "background:#fff", + "color:#0f172a", + "font-size:0.95rem", + "line-height:1.3", + "min-width:220px" +].join(";"); + +app.innerHTML = ` +

      Translation Preview (Format-Aware)

      +

      Arabic + translation rendering with simple, chunk, and footnote-tag formats

      + + +
      +
      +
        +`; + +const ayahSelect = app.querySelector("#ayah"); +const arabicBox = app.querySelector("#arabic"); +const translationBox = app.querySelector("#translation"); +const notesList = app.querySelector("#notes"); + +const renderAyah = (ayahKey) => { + const arabic = arabicByAyah[ayahKey] || "(Arabic not found)"; + const rawTranslation = translationByAyah[ayahKey]; + const normalized = normalizeTranslation(rawTranslation); + + arabicBox.textContent = arabic; + translationBox.textContent = normalized.text || "(Translation not found)"; + + notesList.innerHTML = ""; + normalized.notes.forEach((note) => { + const li = document.createElement("li"); + li.textContent = `[${note.id}] ${note.text}`; + notesList.appendChild(li); + }); +}; + +ayahSelect.addEventListener("change", (event) => renderAyah(event.target.value)); +renderAyah(ayahSelect.value); +``` + +## 7) Common Mistakes to Avoid + +- Joining translation rows to Arabic by row order instead of ayah key. +- Assuming every translation package has the same structure. +- Rendering footnote-tag HTML directly without sanitization in production apps. +- Ignoring missing footnote entries or missing ayah keys. + +## 8) When to Request Updates or Changes + +Open an issue if you find: + +- Broken download links or missing format files +- Translation text mapped to the wrong ayah +- Footnote IDs without matching footnote text +- Inconsistent metadata for language/source + +Issue tracker: + +- [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Related Docs + +- [Tutorials Index](tutorials.md) +- [Translations Guide](resource-translations.md) +- [Quran Script Guide](resource-quran-script.md) +- [Downloading and Using Data](downloading-data.md) +- [Data Model](data-model.md) diff --git a/docs/tutorial-transliteration-end-to-end.md b/docs/tutorial-transliteration-end-to-end.md new file mode 100644 index 00000000..cfbd66ee --- /dev/null +++ b/docs/tutorial-transliteration-end-to-end.md @@ -0,0 +1,192 @@ +# Tutorial 8: Transliteration End-to-End + +This tutorial is for users who want to show pronunciation-friendly Quran text in non-Arabic script while preserving ayah mapping. + +## 1) What This Resource Is + +Transliteration resources provide Quran text transliterated into Latin/non-Arabic writing systems. + +Typical entries include: + +- `ayah_key` in `surah:ayah` +- Transliteration text for each ayah +- Optional variants by language/provider + +Primary category: + +- [https://qul.tarteel.ai/resources/transliteration](https://qul.tarteel.ai/resources/transliteration) + +## 2) When to Use It + +Use transliteration data when you are building: + +- Beginner-friendly reading modes +- Pronunciation assistance features +- Arabic + transliteration dual-display UI + +## 3) How to Get Your First Example Resource + +1. Open [https://qul.tarteel.ai/resources/transliteration](https://qul.tarteel.ai/resources/transliteration). +2. Keep default listing order and open the first published card. +3. Confirm the detail page includes: + - `Transliteration Preview` tab + - `Help` tab +4. Confirm downloads (`json`, `sqlite`). + +This keeps onboarding concrete without hardcoded IDs. + +## 4) What the Preview Shows (Website-Aligned) + +On transliteration detail pages: + +- `Transliteration Preview` tab: + - `Jump to Ayah` + - Previous/next ayah navigation + - Arabic line + transliteration line +- `Help` tab: + - Data shape keyed by `ayah_key` + - Field examples for app integration + +Practical meaning: + +- Transliteration must remain keyed exactly like script/translation for safe joins. +- It is a reading aid layer, not a replacement for script text. + +## 5) Download and Use (Step-by-Step) + +1. Download transliteration package (`json` or `sqlite`). +2. Normalize and index by `ayah_key`. +3. Join with Quran Script rows by ayah key. +4. Add display mode toggle (Arabic | Transliteration | Both). +5. Validate ayah order and alignment. + +Starter integration snippet (JavaScript): + +```javascript +const buildTransliterationIndex = (rows) => + rows.reduce((index, row) => { + index[row.ayah_key] = row.text; + return index; + }, {}); + +const joinScriptAndTransliteration = (scriptRows, transliterationIndex) => + scriptRows.map((row) => { + const ayahKey = `${row.surah}:${row.ayah}`; + return { + ayahKey, + arabic: row.text, + transliteration: transliterationIndex[ayahKey] || null + }; + }); +``` + +## 6) Real-World Example: Reading Mode Toggle + +Goal: + +- User toggles transliteration under Arabic text for selected ayah. + +Inputs: + +- Quran Script data +- Transliteration data + +Processing: + +1. App resolves selected ayah key. +2. App loads Arabic and transliteration lines by same key. +3. UI toggles transliteration visibility. + +Expected output: + +- Stable Arabic-transliteration pairing for every ayah. + +Interactive preview (temporary sandbox): + +You can edit this code for testing. Edits are not saved and may not persist after refresh. + +```playground-js +// This sandbox demonstrates ayah_key-based transliteration joins. + +const scriptByAyah = { + "1:1": "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ", + "1:2": "ٱلۡحَمۡدُ لِلَّهِ رَبِّ ٱلۡعَٰلَمِينَ" +}; + +const transliterationByAyah = { + "1:1": "Bismillahi al-rahmani al-rahim", + "1:2": "Al-hamdu lillahi rabbil alamin" +}; + +const app = document.getElementById("app"); +const dropdownStyle = [ + "margin-bottom:12px", + "padding:8px", + "border:1px solid #cbd5e1", + "border-radius:8px", + "background:#fff", + "color:#0f172a", + "font-size:0.95rem", + "line-height:1.3", + "min-width:220px" +].join(";"); + +app.innerHTML = ` +

        Transliteration Preview

        +

        Switch between Arabic-only and Arabic + transliteration

        + + + +
        +
        +`; + +const ayahSelect = app.querySelector("#ayah"); +const showToggle = app.querySelector("#show"); +const arabicBox = app.querySelector("#arabic"); +const translitBox = app.querySelector("#translit"); + +const render = () => { + const key = ayahSelect.value; + arabicBox.textContent = scriptByAyah[key] || "(Arabic not found)"; + + if (showToggle.checked) { + translitBox.style.display = "block"; + translitBox.textContent = transliterationByAyah[key] || "(Transliteration not found)"; + } else { + translitBox.style.display = "none"; + } +}; + +ayahSelect.addEventListener("change", render); +showToggle.addEventListener("change", render); +render(); +``` + +## 7) Common Mistakes to Avoid + +- Joining transliteration to script by row order instead of `ayah_key`. +- Showing transliteration without clear mode/toggle controls. +- Mixing ayah-by-ayah and word-by-word formats without normalization. + +## 8) When to Request Updates or Changes + +Open an issue if you find: + +- Missing transliteration rows for known ayahs +- Broken character mapping in transliteration text +- Broken json/sqlite links + +Issue tracker: + +- [https://github.com/TarteelAI/quranic-universal-library/issues](https://github.com/TarteelAI/quranic-universal-library/issues) + +## Related Docs + +- [Tutorials Index](tutorials.md) +- [Transliteration Guide](resource-transliteration.md) +- [Quran Script Guide](resource-quran-script.md) +- [Translations Guide](resource-translations.md) diff --git a/docs/tutorials.md b/docs/tutorials.md new file mode 100644 index 00000000..c95723cd --- /dev/null +++ b/docs/tutorials.md @@ -0,0 +1,38 @@ +# Tutorials + +This page is the tutorials index for resource users. + +Each end-to-end tutorial is on its own page so you can focus on one resource type at a time. + +This is the canonical implementation path for resource integration docs. + +## Start Here: How to Pick Your First Resource + +Use this repeatable rule for any resource type: + +1. Open the resource category page. +2. Use the default listing order. +3. Open the first published resource card shown. +4. Check the preview and help tabs. +5. Download the format that fits your project. + +## Tutorial Index + +1. [Tutorial 1: Recitation End-to-End](tutorial-recitation-end-to-end.md) +2. [Tutorial 2: Mushaf Layout End-to-End](tutorial-mushaf-layout-end-to-end.md) +3. [Tutorial 3: Translation End-to-End](tutorial-translation-end-to-end.md) +4. [Tutorial 4: Tafsir End-to-End](tutorial-tafsir-end-to-end.md) +5. [Tutorial 5: Quran Script End-to-End](tutorial-quran-script-end-to-end.md) +6. [Tutorial 6: Font End-to-End](tutorial-font-end-to-end.md) +7. [Tutorial 7: Quran Metadata End-to-End](tutorial-quran-metadata-end-to-end.md) +8. [Tutorial 8: Transliteration End-to-End](tutorial-transliteration-end-to-end.md) +9. [Tutorial 9: Surah Information End-to-End](tutorial-surah-information-end-to-end.md) +10. [Tutorial 10: Ayah Topics End-to-End](tutorial-ayah-topics-end-to-end.md) +11. [Tutorial 11: Morphology End-to-End](tutorial-morphology-end-to-end.md) +12. [Tutorial 12: Mutashabihat End-to-End](tutorial-mutashabihat-end-to-end.md) +13. [Tutorial 13: Similar Ayah End-to-End](tutorial-similar-ayah-end-to-end.md) +14. [Tutorial 14: Ayah Theme End-to-End](tutorial-ayah-theme-end-to-end.md) + +## Status + +All currently planned tutorials in this rollout are included. diff --git a/yarn.lock b/yarn.lock index cb7103a0..fc0411e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -84,47 +84,47 @@ "@esbuild/aix-ppc64@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.2.tgz#7ccd2a552dc4eb740f094a46d18a1b1508b8d37c" + resolved "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.2.tgz" integrity sha512-/c7hocx0pm14bHQlqUVKmxwdT/e5/KkyoY1W8F9lk/8CkE037STDDz8PXUP/LE6faj2HqchvDs9GcShxFhI78Q== "@esbuild/aix-ppc64@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + resolved "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz" integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== -"@esbuild/android-arm64@0.21.2": - version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.2.tgz#016abbee9f0c6f646b0c6b43b172a5053fe53aab" - integrity sha512-SGZKngoTWVUriO5bDjI4WDGsNx2VKZoXcds+ita/kVYB+8IkSCKDRDaK+5yu0b5S0eq6B3S7fpiEvpsa2ammlQ== - -"@esbuild/android-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" - integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== - "@esbuild/android-arm@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.2.tgz#99f3a3c90bf8ac37d1881af6b87d404a02007164" + resolved "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.2.tgz" integrity sha512-G1ve3b4FeyJeyCjB4MX1CiWyTaIJwT9wAYE+8+IRA53YoN/reC/Bf2GDRXAzDTnh69Fpl+1uIKg76DiB3U6vwQ== "@esbuild/android-arm@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + resolved "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz" integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== +"@esbuild/android-arm64@0.21.2": + version "0.21.2" + resolved "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.2.tgz" + integrity sha512-SGZKngoTWVUriO5bDjI4WDGsNx2VKZoXcds+ita/kVYB+8IkSCKDRDaK+5yu0b5S0eq6B3S7fpiEvpsa2ammlQ== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + "@esbuild/android-x64@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.2.tgz#5039e8d0b2ed03ca75d77e581ead591b1d87826f" + resolved "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.2.tgz" integrity sha512-1wzzNoj2QtNkAYwIcWJ66UTRA80+RTQ/kuPMtEuP0X6dp5Ar23Dn566q3aV61h4EYrrgGlOgl/HdcqN/2S/2vg== "@esbuild/android-x64@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + resolved "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz" integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== "@esbuild/darwin-arm64@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.2.tgz#6f55b81878d2295d7d4ecdbbb5ee418d379fb49a" + resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.2.tgz" integrity sha512-ZyMkPWc5eTROcLOA10lEqdDSTc6ds6nuh3DeHgKip/XJrYjZDfnkCVSty8svWdy+SC1f77ULtVeIqymTzaB6/Q== "@esbuild/darwin-arm64@0.21.5": @@ -144,177 +144,177 @@ "@esbuild/freebsd-arm64@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.2.tgz#f665703471824e67ff5f62e6c9ed298f3c363b1b" + resolved "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.2.tgz" integrity sha512-4kbOGdpA61CXqadD+Gb/Pw3YXamQGiz9mal/h93rFVSjr5cgMnmJd/gbfPRm+3BMifvnaOfS1gNWaIDxkE2A3A== "@esbuild/freebsd-arm64@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + resolved "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz" integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== "@esbuild/freebsd-x64@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.2.tgz#6493aa56760521125badd41f78369f18c49e367e" + resolved "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.2.tgz" integrity sha512-ShS+R09nuHzDBfPeMUliKZX27Wrmr8UFp93aFf/S8p+++x5BZ+D344CLKXxmY6qzgTL3mILSImPCNJOzD6+RRg== "@esbuild/freebsd-x64@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + resolved "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz" integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== -"@esbuild/linux-arm64@0.21.2": - version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.2.tgz#beb96b83bfe32630d34eedc09b8e0722819f1a5b" - integrity sha512-Hdu8BL+AmO+eCDvvT6kz/fPQhvuHL8YK4ExKZfANWsNe1kFGOHw7VJvS/FKSLFqheXmB3rTF3xFQIgUWPYsGnA== - -"@esbuild/linux-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" - integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== - "@esbuild/linux-arm@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.2.tgz#b1c5176479397b34c36334218063e223b4e588dd" + resolved "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.2.tgz" integrity sha512-nnGXjOAv+7cM3LYRx4tJsYdgy8dGDGkAzF06oIDGppWbUkUKN9SmgQA8H0KukpU0Pjrj9XmgbWqMVSX/U7eeTA== "@esbuild/linux-arm@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + resolved "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz" integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== +"@esbuild/linux-arm64@0.21.2": + version "0.21.2" + resolved "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.2.tgz" + integrity sha512-Hdu8BL+AmO+eCDvvT6kz/fPQhvuHL8YK4ExKZfANWsNe1kFGOHw7VJvS/FKSLFqheXmB3rTF3xFQIgUWPYsGnA== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + "@esbuild/linux-ia32@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.2.tgz#8ce387793eccdc28f5964e19f4dcbdb901099be4" + resolved "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.2.tgz" integrity sha512-m73BOCW2V9lcj7RtEMi+gBfHC6n3+VHpwQXP5offtQMPLDkpVolYn1YGXxOZ9hp4h3UPRKuezL7WkBsw+3EB3Q== "@esbuild/linux-ia32@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + resolved "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz" integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== "@esbuild/linux-loong64@0.14.54": version "0.14.54" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028" + resolved "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz" integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw== "@esbuild/linux-loong64@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.2.tgz#c7360523a8e5e04e0b76b6e9a89a91ba573ac613" + resolved "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.2.tgz" integrity sha512-84eYHwwWHq3myIY/6ikALMcnwkf6Qo7NIq++xH0x+cJuUNpdwh8mlpUtRY+JiGUc60yu7ElWBbVHGWTABTclGw== "@esbuild/linux-loong64@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + resolved "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz" integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== "@esbuild/linux-mips64el@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.2.tgz#0adac2cc3451c25817b0c93bf160cd19008ed03a" + resolved "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.2.tgz" integrity sha512-9siSZngT0/ZKG+AH+/agwKF29LdCxw4ODi/PiE0F52B2rtLozlDP92umf8G2GPoVV611LN4pZ+nSTckebOscUA== "@esbuild/linux-mips64el@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + resolved "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz" integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== "@esbuild/linux-ppc64@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.2.tgz#d9e79563999288d367eeba2b8194874bef0e8a35" + resolved "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.2.tgz" integrity sha512-y0T4aV2CA+ic04ULya1A/8M2RDpDSK2ckgTj6jzHKFJvCq0jQg8afQQIn4EM0G8u2neyOiNHgSF9YKPfuqKOVw== "@esbuild/linux-ppc64@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + resolved "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz" integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== "@esbuild/linux-riscv64@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.2.tgz#34227910d843b399447a48180381425529eae7d6" + resolved "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.2.tgz" integrity sha512-x5ssCdXmZC86L2Li1qQPF/VaC4VP20u/Zm8jlAu9IiVOVi79YsSz6cpPDYZl1rfKSHYCJW9XBfFCo66S5gVPSA== "@esbuild/linux-riscv64@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + resolved "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz" integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== "@esbuild/linux-s390x@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.2.tgz#2835f5b9b4c961baf6d6f03a870ab2d5bc3fbfcc" + resolved "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.2.tgz" integrity sha512-NP7fTpGSFWdXyvp8iAFU04uFh9ARoplFVM/m+8lTRpaYG+2ytHPZWyscSsMM6cvObSIK2KoPHXiZD4l99WaxbQ== "@esbuild/linux-s390x@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + resolved "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz" integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== "@esbuild/linux-x64@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.2.tgz#756282185a936e752a3a80b227a950813fe62ee7" + resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.2.tgz" integrity sha512-giZ/uOxWDKda44ZuyfKbykeXznfuVNkTgXOUOPJIjbayJV6FRpQ4zxUy9JMBPLaK9IJcdWtaoeQrYBMh3Rr4vQ== "@esbuild/linux-x64@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz" integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== "@esbuild/netbsd-x64@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.2.tgz#e1dde3694f5f8fbf2f7696d021c026e601579167" + resolved "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.2.tgz" integrity sha512-IeFMfGFSQfIj1d4XU+6lkbFzMR+mFELUUVYrZ+jvWzG4NGvs6o53ReEHLHpYkjRbdEjJy2W3lTekTxrFHW7YJg== "@esbuild/netbsd-x64@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + resolved "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz" integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== "@esbuild/openbsd-x64@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.2.tgz#b0a8c1ce0077a5b24c5e4cf1c4417128ae5b6489" + resolved "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.2.tgz" integrity sha512-48QhWD6WxcebNNaE4FCwgvQVUnAycuTd+BdvA/oZu+/MmbpU8pY2dMEYlYzj5uNHWIG5jvdDmFXu0naQeOWUoA== "@esbuild/openbsd-x64@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + resolved "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz" integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== "@esbuild/sunos-x64@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.2.tgz#fc7dd917ffcb2ebab4f22728a23ece3dd36c2979" + resolved "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.2.tgz" integrity sha512-90r3nTBLgdIgD4FCVV9+cR6Hq2Dzs319icVsln+NTmTVwffWcCqXGml8rAoocHuJ85kZK36DCteii96ba/PX8g== "@esbuild/sunos-x64@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + resolved "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz" integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== "@esbuild/win32-arm64@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.2.tgz#251e4cdafae688d54a43ac8544cb8c71e8fcdf15" + resolved "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.2.tgz" integrity sha512-sNndlsBT8OeE/MZDSGpRDJlWuhjuUz/dn80nH0EP4ZzDUYvMDVa7G87DVpweBrn4xdJYyXS/y4CQNrf7R2ODXg== "@esbuild/win32-arm64@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + resolved "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz" integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== "@esbuild/win32-ia32@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.2.tgz#1e3a818791b7e93ed353901c83d7cdc901ffcc8a" + resolved "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.2.tgz" integrity sha512-Ti2QChGNFzWhUNNVuU4w21YkYTErsNh3h+CzvlEhzgRbwsJ7TrWQqRzW3bllLKKvTppuF3DJ3XP1GEg11AfrEQ== "@esbuild/win32-ia32@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + resolved "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz" integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== "@esbuild/win32-x64@0.21.2": version "0.21.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.2.tgz#825b4e7c89b7e7ec64c450ed494a8af7e405a84d" + resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.2.tgz" integrity sha512-VEfTCZicoZnZ6sGkjFPGRFFJuL2fZn2bLhsekZl1CJslflp2cJS/VoKs1jMk+3pDfsGW6CfQVUckP707HwbXeQ== "@esbuild/win32-x64@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz" integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": @@ -451,7 +451,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -710,7 +710,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.9.0: +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.9.0: version "8.12.1" resolved "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== @@ -839,7 +839,7 @@ asn1@~0.2.3: dependencies: safer-buffer "~2.1.0" -assert-plus@1.0.0, assert-plus@^1.0.0: +assert-plus@^1.0.0, assert-plus@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== @@ -896,7 +896,7 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -bare-events@^2.2.0, bare-events@^2.5.4: +bare-events@*, bare-events@^2.2.0, bare-events@^2.5.4: version "2.5.4" resolved "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz" integrity sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA== @@ -1002,7 +1002,23 @@ buffer-crc32@~0.2.3: resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== -buffer@^5.2.1, buffer@^5.5.0, buffer@^5.7.1: +buffer@^5.2.1: + version "5.7.1" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +buffer@^5.7.1: version "5.7.1" resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -1091,15 +1107,16 @@ caseless@~0.12.0: resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== -chalk@4.1.1, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz" - integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== +chalk@^2.4.1: + version "2.4.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" -chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1108,12 +1125,20 @@ chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chardet@^0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -chart.js@4, chart.js@^4.4.9: +chart.js@^4.4.9, chart.js@>=2.8.0, chart.js@4: version "4.5.0" resolved "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz" integrity sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ== @@ -1139,7 +1164,7 @@ check-more-types@^2.24.0: resolved "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz" integrity sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA== -"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3, chokidar@^3.6.0: +chokidar@^3.5.3, chokidar@^3.6.0, "chokidar@>=3.0.0 <4.0.0": version "3.6.0" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== @@ -1240,16 +1265,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - color-name@~1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + colorette@^2.0.16: version "2.0.20" resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" @@ -1289,6 +1314,16 @@ core-util-is@1.0.2: resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== +cosmiconfig@^9.0.0: + version "9.0.0" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz" + integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== + dependencies: + env-paths "^2.2.1" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + cosmiconfig@7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz" @@ -1300,16 +1335,6 @@ cosmiconfig@7.0.0: path-type "^4.0.0" yaml "^1.10.0" -cosmiconfig@^9.0.0: - version "9.0.0" - resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz" - integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== - dependencies: - env-paths "^2.2.1" - import-fresh "^3.3.0" - js-yaml "^4.1.0" - parse-json "^5.2.0" - cross-spawn@^6.0.5: version "6.0.6" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz" @@ -1401,7 +1426,7 @@ data-uri-to-buffer@^6.0.2: resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz" integrity sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw== -date-fns@>=2: +date-fns@>=2, date-fns@>=2.0.0: version "4.1.0" resolved "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz" integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== @@ -1411,13 +1436,6 @@ dayjs@^1.10.4: resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz" integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== -debug@4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.5" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz" - integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== - dependencies: - ms "2.1.2" - debug@^3.1.0: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" @@ -1425,6 +1443,13 @@ debug@^3.1.0: dependencies: ms "^2.1.1" +debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@4: + version "4.3.5" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + debug@^4.3.6: version "4.3.7" resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" @@ -1517,7 +1542,7 @@ delayed-stream@~1.0.0: resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -devtools-protocol@0.0.1330662: +devtools-protocol@*, devtools-protocol@0.0.1330662: version "0.0.1330662" resolved "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1330662.tgz" integrity sha512-pzh6YQ8zZfz3iKlCvgzVCu22NdpZ8hNmwU6WnQjNVquh0A9iVosPtNLWDwaWVGyrntQlltPFztTMK5Cg6lfCuw== @@ -1552,7 +1577,7 @@ dom-serializer@^1.0.1: dom-to-image-more@^3.7.2: version "3.7.2" - resolved "https://registry.yarnpkg.com/dom-to-image-more/-/dom-to-image-more-3.7.2.tgz#15afae48b771c63f0b066f7c0c48900506fb5a7e" + resolved "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-3.7.2.tgz" integrity sha512-uQf+pHv6eQhgfI8t2bFuinV0KsPyT8TZgCLwcSU8uBVgN9v6leb0mMpvp6HQAlAcplP3NCcGjxbdqef6pTzvmw== domelementtype@^2.0.1, domelementtype@^2.2.0: @@ -1612,7 +1637,7 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enquirer@^2.3.6: +enquirer@^2.3.6, "enquirer@>= 2.3.0 < 3": version "2.4.1" resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz" integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== @@ -1729,12 +1754,12 @@ es-to-primitive@^1.2.1: esbuild-android-64@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be" + resolved "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz" integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ== esbuild-android-arm64@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771" + resolved "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz" integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg== esbuild-darwin-64@0.14.54: @@ -1744,67 +1769,67 @@ esbuild-darwin-64@0.14.54: esbuild-darwin-arm64@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73" + resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz" integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw== esbuild-freebsd-64@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d" + resolved "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz" integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg== esbuild-freebsd-arm64@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48" + resolved "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz" integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q== esbuild-linux-32@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5" + resolved "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz" integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw== esbuild-linux-64@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652" + resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz" integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg== -esbuild-linux-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b" - integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig== - esbuild-linux-arm@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59" + resolved "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz" integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw== +esbuild-linux-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz" + integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig== + esbuild-linux-mips64le@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34" + resolved "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz" integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw== esbuild-linux-ppc64le@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e" + resolved "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz" integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ== esbuild-linux-riscv64@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8" + resolved "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz" integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg== esbuild-linux-s390x@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6" + resolved "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz" integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA== esbuild-netbsd-64@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81" + resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz" integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w== esbuild-openbsd-64@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b" + resolved "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz" integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw== esbuild-plugin-vue3@^0.4.0: @@ -1824,7 +1849,7 @@ esbuild-rails@^1.0.7: esbuild-sunos-64@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da" + resolved "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz" integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw== esbuild-visualizer@^0.6.0: @@ -1838,20 +1863,20 @@ esbuild-visualizer@^0.6.0: esbuild-windows-32@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31" + resolved "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz" integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w== esbuild-windows-64@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4" + resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz" integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ== esbuild-windows-arm64@0.14.54: version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982" + resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz" integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg== -esbuild@0.21.2: +esbuild@*, esbuild@0.21.2: version "0.21.2" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.21.2.tgz" integrity sha512-LmHPAa5h4tSxz+g/D8IHY6wCjtIiFx8I7/Q0Aq+NmvtoYvyMnJU0KQJcqB6QH30X9x/W4CemgUtPgQDZFca5SA== @@ -1989,7 +2014,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.55.0: +"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", eslint@^8.55.0, eslint@>=6.0.0: version "8.57.0" resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz" integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== @@ -2127,7 +2152,7 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" -extract-zip@2.0.1, extract-zip@^2.0.1: +extract-zip@^2.0.1, extract-zip@2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz" integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== @@ -2138,7 +2163,7 @@ extract-zip@2.0.1, extract-zip@^2.0.1: optionalDependencies: "@types/yauzl" "^2.9.1" -extsprintf@1.3.0, extsprintf@^1.2.0: +extsprintf@^1.2.0, extsprintf@1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== @@ -2459,7 +2484,19 @@ globby@^11.0.2: merge2 "^1.4.1" slash "^3.0.0" -globby@^14.0.0, globby@^14.0.1: +globby@^14.0.0: + version "14.0.2" + resolved "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz" + integrity sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw== + dependencies: + "@sindresorhus/merge-streams" "^2.1.0" + fast-glob "^3.3.2" + ignore "^5.2.4" + path-type "^5.0.0" + slash "^5.1.0" + unicorn-magic "^0.1.0" + +globby@^14.0.1: version "14.0.2" resolved "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz" integrity sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw== @@ -2647,21 +2684,21 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3, inherits@^2.0.4: +inherits@^2.0.3, inherits@^2.0.4, inherits@2: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - ini@^1.3.5: version "1.3.8" resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +ini@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + inquirer@^8.1.1: version "8.2.6" resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz" @@ -2933,7 +2970,7 @@ jquery-ujs@^1.2.3: resolved "https://registry.npmjs.org/jquery-ujs/-/jquery-ujs-1.2.3.tgz" integrity sha512-59wvfx5vcCTHMeQT1/OwFiAj+UffLIwjRIoXdpO7Z7BCFGepzq9T9oLVeoItjTqjoXfUrHJvV7QU6pUR+UzOoA== -jquery@3.7.1, jquery@>=1.12.0, "jquery@>=1.8.0 <4.0.0": +jquery@>=1.12.0, jquery@>=1.8.0, "jquery@>=1.8.0 <4.0.0", jquery@3.7.1: version "3.7.1" resolved "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz" integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg== @@ -2958,16 +2995,16 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsbn@1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz" - integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== - jsbn@~0.1.0: version "0.1.1" resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + json-buffer@3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" @@ -3276,7 +3313,7 @@ mitt@3.0.1: resolved "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz" integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== -ms@2.1.2, ms@^2.1.1: +ms@^2.1.1, ms@2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== @@ -3323,7 +3360,17 @@ nice-try@^1.0.4: resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -3692,16 +3739,16 @@ proxy-agent@^6.4.0: proxy-from-env "^1.1.0" socks-proxy-agent "^8.0.2" -proxy-from-env@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz" - integrity sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A== - proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +proxy-from-env@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz" + integrity sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A== + pump@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" @@ -3938,7 +3985,7 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +safer-buffer@^2.0.2, safer-buffer@^2.1.0, "safer-buffer@>= 2.1.2 < 3", safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -3948,7 +3995,7 @@ sass-migrator@^1.7.3: resolved "https://registry.npmjs.org/sass-migrator/-/sass-migrator-1.8.1.tgz" integrity sha512-7F7+Dx2rzNN6vhqBjGo7Bl43NCnvPS5hP7YCMRCxW8FwRpcoM6rOsNFjwy8g329HH6ebKjdMnnYgtfRxtnz2kg== -sass@^1.44.0: +sass@^1.35.2, sass@^1.44.0: version "1.69.4" resolved "https://registry.npmjs.org/sass/-/sass-1.69.4.tgz" integrity sha512-+qEreVhqAy8o++aQfCJwp0sklr2xyEzkm9Pp/Igu9wNPoe7EZEQ8X/MBvvXggI2ql607cxKg/RKOwDj6pp2XDA== @@ -3967,7 +4014,7 @@ semver-compare@^1.0.0: resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz" integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== -"semver@2 || 3 || 4 || 5", semver@^5.5.0: +semver@^5.5.0: version "5.7.2" resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== @@ -3977,6 +4024,11 @@ semver@^7.3.4, semver@^7.3.6, semver@^7.5.3, semver@^7.6.0, semver@^7.6.3: resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +"semver@2 || 3 || 4 || 5": + version "5.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + set-function-length@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz" @@ -4096,7 +4148,7 @@ size-limit@^10.0.2: nanospinner "^1.1.0" picocolors "^1.0.0" -size-limit@^11.0.0: +size-limit@^11.0.0, size-limit@11.1.4: version "11.1.4" resolved "https://registry.npmjs.org/size-limit/-/size-limit-11.1.4.tgz" integrity sha512-V2JAI/Z7h8sEuxU3V+Ig3XKA5FcYbI4CZ7sh6s7wvuy+TUwDZYqw7sAqrHhQ4cgcNfPKIAHAaH8VaqOdbcwJDA== @@ -4171,7 +4223,7 @@ sortablejs@^1.15.2: resolved "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz" integrity sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA== -"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.0, source-map-js@^1.2.1: +source-map-js@^1.2.0, source-map-js@^1.2.1, "source-map-js@>=0.6.2 <2.0.0": version "1.2.1" resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== @@ -4247,6 +4299,13 @@ streamx@^2.15.0, streamx@^2.21.0: optionalDependencies: bare-events "^2.2.0" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" @@ -4292,13 +4351,6 @@ string.prototype.trimstart@^1.0.7: define-properties "^1.2.0" es-abstract "^1.22.1" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -4401,7 +4453,7 @@ text-table@^0.2.0: resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -three@^0.166.1: +three@^0.166.1, three@>=0.133.0: version "0.166.1" resolved "https://registry.npmjs.org/three/-/three-0.166.1.tgz" integrity sha512-LtuafkKHHzm61AQA1be2MAYIw1IjmhOUxhBa0prrLpEMWbV7ijvxCRHjSgHPGp2493wLBzwKV46tA9nivLEgKg== @@ -4481,7 +4533,7 @@ trim-newlines@^3.0.0: resolved "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== -trix@^2.1.13: +trix@^2.0.0, trix@^2.1.13: version "2.1.13" resolved "https://registry.npmjs.org/trix/-/trix-2.1.13.tgz" integrity sha512-LTwj6HPo/CDL6KSclIwn41J+EwcqXHiUE43BBbg8Q/whaZcoUaODypHYoGIDd3M32KVXw98M5vC6zNCod9KB2w== @@ -4581,7 +4633,7 @@ typed-query-selector@^2.12.0: resolved "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz" integrity sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg== -typescript@^4.7.4: +typescript@*, typescript@^4.7.4, typescript@>=4.9.5: version "4.9.5" resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== @@ -4702,7 +4754,7 @@ vue-eslint-parser@^9.4.3: lodash "^4.17.21" semver "^7.3.6" -vue@^3.2.24: +vue@^3.2.0, vue@^3.2.24, vue@^3.4.15, vue@3.5.13: version "3.5.13" resolved "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz" integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ== @@ -4749,7 +4801,14 @@ which-typed-array@^1.1.11, which-typed-array@^1.1.13: gopd "^1.0.1" has-tostringtag "^1.0.0" -which@^1.2.9, which@^1.3.1: +which@^1.2.9: + version "1.3.1" + resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^1.3.1: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==