import copy import logging import logging.handlers import os import pathlib import random import re import subprocess import urllib import urllib.request from collections import defaultdict from collections.abc import Iterable from enum import Enum from functools import lru_cache from queue import Queue from typing import Any, Callable, Dict, List, Optional, Tuple, Union import bs4 import requests import sphinx import yaml from docutils import nodes from pygments import highlight from pygments.formatters import HtmlFormatter from pygments.lexers import PythonLexer from sphinx.util import logging as sphinx_logging from sphinx.util.console import red # type: ignore from sphinx.util.nodes import make_refnode from preprocess_github_markdown import preprocess_github_markdown_file import json from packaging.version import Version logger = logging.getLogger(__name__) __all__ = [ "DownloadAndPreprocessEcosystemDocs", "update_context", "LinkcheckSummarizer", "setup_context", "collect_example_orphans", ] # Taken from https://github.com/edx/edx-documentation FEEDBACK_FORM_FMT = ( "https://github.com/ray-project/ray/issues/new?" "title={title}&labels=docs&body={body}" ) EXAMPLE_GALLERY_CONFIGS = [ "source/data/examples.yml", "source/serve/examples.yml", "source/train/examples.yml", ] # Grab a list of all files tracked by git; this is used to determine # what pages should show an "Edit on GitHub" link (autogenerated pages # shouldn't show this link) TRACKED_FILES = ( subprocess.run( ["git", "ls-files", pathlib.Path(__file__).parent], capture_output=True ) .stdout.decode("utf8") .strip() .split("\n") ) _EXTERNAL_HREF_RE = re.compile(r"^[a-z][a-z0-9+.-]*:|^//", re.IGNORECASE) def canonicalize_collection_href( href: str, known_docs: Optional[Iterable[str]] = None ) -> str: """Map moved collection doc links to their generated _collections pages.""" if not known_docs or _EXTERNAL_HREF_RE.match(href) or href.startswith("#"): return href path, sep, fragment = href.partition("#") if not path.endswith(".html"): return href docname = path[: -len(".html")] if docname in known_docs or docname.startswith("_collections/"): return href collection_docname = f"_collections/{docname}" if collection_docname in known_docs: return f"{collection_docname}.html{sep}{fragment}" return href def feedback_form_url(project, page): """Create a URL for feedback on a particular page in a project.""" return FEEDBACK_FORM_FMT.format( title=urllib.parse.quote("[docs] Issue on `{page}.rst`".format(page=page)), body=urllib.parse.quote( "# Documentation Problem/Question/Comment\n" "\n" "\n" "\n\n\n\n" "(Created directly from the docs)\n" ), ) def update_context(app, pagename, templatename, context, doctree): """Update the page rendering context to include ``feedback_form_url``.""" context["feedback_form_url"] = feedback_form_url(app.config.project, pagename) # Add doc files from external repositories to be downloaded during build here # (repo, ref, path to get, path to save on disk) EXTERNAL_MARKDOWN_FILES = [] class DownloadAndPreprocessEcosystemDocs: """ This class downloads markdown readme files for various ecosystem libraries, saves them in specified locations and preprocesses them before sphinx build starts. If you have ecosystem libraries that live in a separate repo from Ray, adding them here will allow for their docs to be present in Ray docs without the need for duplicate files. For more details, see ``doc/README.md``. """ def __init__(self, base_path: str) -> None: self.base_path = pathlib.Path(base_path).absolute() assert self.base_path.is_dir() self.original_docs = {} @staticmethod def get_latest_release_tag(repo: str) -> str: """repo is just the repo name, eg. ray-project/ray""" response = requests.get(f"https://api.github.com/repos/{repo}/releases/latest") return response.json()["tag_name"] @staticmethod def get_file_from_github( repo: str, ref: str, path_to_get_from_repo: str, path_to_save_on_disk: str ) -> None: """If ``ref == "latest"``, use latest release""" if ref == "latest": ref = DownloadAndPreprocessEcosystemDocs.get_latest_release_tag(repo) urllib.request.urlretrieve( f"https://raw.githubusercontent.com/{repo}/{ref}/{path_to_get_from_repo}", path_to_save_on_disk, ) def save_original_doc(self, path: str): with open(path, "r") as f: self.original_docs[path] = f.read() def write_new_docs(self, *args, **kwargs): for ( repo, ref, path_to_get_from_repo, path_to_save_on_disk, ) in EXTERNAL_MARKDOWN_FILES: path_to_save_on_disk = self.base_path.joinpath(path_to_save_on_disk) self.save_original_doc(path_to_save_on_disk) self.get_file_from_github( repo, ref, path_to_get_from_repo, path_to_save_on_disk ) preprocess_github_markdown_file(path_to_save_on_disk) def write_original_docs(self, *args, **kwargs): for path, content in self.original_docs.items(): with open(path, "w") as f: f.write(content) def __call__(self): self.write_new_docs() class _BrokenLinksQueue(Queue): """Queue that discards messages about non-broken links.""" def __init__(self, maxsize: int = 0) -> None: self._last_line_no = None self.used = False super().__init__(maxsize) def put(self, item: logging.LogRecord, block=True, timeout=None): self.used = True message = item.getMessage() # line nos are separate records if ": line" in message: self._last_line_no = item # same formatting as in sphinx.builders.linkcheck # to avoid false positives if "broken" is in url if red("broken ") in message or "broken link:" in message: if self._last_line_no: super().put(self._last_line_no, block=block, timeout=timeout) self._last_line_no = None return super().put(item, block=block, timeout=timeout) class _QueueHandler(logging.handlers.QueueHandler): """QueueHandler without modifying the record.""" def prepare(self, record: logging.LogRecord) -> logging.LogRecord: return record class LinkcheckSummarizer: """Hook into the logger used by linkcheck to display a summary at the end.""" def __init__(self) -> None: self.logger = None self.queue_handler = None self.log_queue = _BrokenLinksQueue() def add_handler_to_linkcheck(self, *args, **kwargs): """Adds a handler to the linkcheck logger.""" self.logger = sphinx_logging.getLogger("sphinx.builders.linkcheck") self.queue_handler = _QueueHandler(self.log_queue) if not self.logger.hasHandlers(): # If there are no handlers, add the one that would # be used anyway. self.logger.logger.addHandler(logging.lastResort) self.logger.logger.addHandler(self.queue_handler) def summarize(self, *args, **kwargs): """Summarizes broken links.""" if not self.log_queue.used: return self.logger.logger.removeHandler(self.queue_handler) self.logger.info("\nBROKEN LINKS SUMMARY:\n") has_broken_links = False while self.log_queue.qsize() > 0: has_broken_links = True record: logging.LogRecord = self.log_queue.get() self.logger.handle(record) if not has_broken_links: self.logger.info("No broken links found!") def parse_navbar_config(app: sphinx.application.Sphinx, config: sphinx.config.Config): """Parse the navbar config file into a set of links to show in the navbar. Parameters ---------- app : sphinx.application.Sphinx Application instance passed when the `config-inited` event is emitted config : sphinx.config.Config Initialized configuration to be modified """ if "navbar_content_path" in config: filename = app.config["navbar_content_path"] else: filename = "" if filename: with open(pathlib.Path(__file__).parent / filename, "r") as f: config.navbar_content = yaml.safe_load(f) else: config.navbar_content = None NavEntry = Dict[str, Union[str, List["NavEntry"]]] def add_collapse_checkboxes(soup: bs4.BeautifulSoup) -> None: """Add checkboxes to collapse children in a toctree. Vendored from ``pydata_sphinx_theme.toctree.add_collapse_checkboxes`` so the sidebar build does not reach into a private theme API. That symbol is not part of the theme's public interface (the module has no ``__all__``) and the sibling ``generate_toctree_html`` that ``preload_sidebar_nav`` below is forked from has already been removed upstream, so depending on it makes theme upgrades fragile. """ # based on https://github.com/pradyunsg/furo for element in soup.find_all("li", recursive=True): # We check all "li" elements, to add a "current-page" to the correct li. classes = element.get("class", []) # expanding the parent part explicitly, if present if "current" in classes: parentli = element.find_parent("li", class_="toctree-l0") if parentli: parentli.find("details")["open"] = None # Nothing more to do, unless this has "children" if not element.find("ul"): continue # Add a class to indicate that this has children. element["class"] = [*classes, "has-children"] if soup.new_tag is None: continue # For table of contents nodes that have subtrees, we modify the HTML so # that the subtree can be expanded or collapsed in the browser. # # The HTML markup tree at the parent node starts with this structure: # # - li.has-children # - a.reference or p.caption # - ul # # Note the first child of li.has-children is p.caption only if this node # is a section heading. (This only happens when show_nav_level is set to # 0.) # # Now we modify the tree structure in one of two ways. # # (1) If the node holds a section heading, the HTML tree will be # modified like so: # # - li.has-children # - details # - summary # - p.caption # - .toctree-toggle # - ul # # (2) Otherwise, if the node holds a link to a page in the docs: # # - li.has-children # - a.reference # - details # - summary # - .toctree-toggle # - ul # # Why the difference? In the first case, the TOC section heading is not # a link, but in the second case it is. So in the first case it makes # sense to put the (non-link) text inside the summary tag so that the # user can click either the text or the .toctree-toggle chevron icon to # expand/collapse the TOC subtree. But in the second case, putting the # link in the summary tag would make it unclear whether clicking on the # link should expand the subtree or take you to the link. # Create
and put the entire subtree into it details = soup.new_tag("details") details.extend(element.contents) element.append(details) # Hoist the link to the top if there is one toc_link = element.select_one("details > a.reference") if toc_link: element.insert(0, toc_link) # Create with chevron icon summary = soup.new_tag("summary") span = soup.new_tag( "span", attrs={ "class": "toctree-toggle", # This element and the chevron it contains are purely decorative; # the actual expand/collapse functionality is delegated to the # tag "role": "presentation", }, ) span.append(soup.new_tag("i", attrs={"class": "fa-solid fa-chevron-down"})) summary.append(span) # Prepend section heading (if there is one) to collapsible_section_heading = element.select_one("details > p.caption") if collapsible_section_heading: # Put heading inside summary so that the heading text (and chevron) are both # clickable summary.insert(0, collapsible_section_heading) # Prepend to
details.insert(0, summary) # If this TOC node has a "current" class, be expanded by default # (by opening the details/summary disclosure widget) if "current" in classes: details["open"] = "open" def preload_sidebar_nav( get_toctree: Callable[[Any], str], pathto: Callable[[str], str], root_doc: str, pagename: str, known_docs: Optional[Iterable[str]] = None, ) -> bs4.BeautifulSoup: """Return the navigation link structure in HTML. This function is modified from the pydata_sphinx_theme function `generate_toctree_html`. However, for ray we only produce one sidebar for all pages. We therefore can call this function just once (per worker), cache the result, and reuse it. The result of this function is equivalent to calling pydata_sphinx_theme.toctree.generate_toctree_html( "sidebar", startdepth=0, show_nav_level=0, collapse=False, includehidden=True, maxdepth=4, titles_only=True ) Here we cache the result on this function itself because the `get_toctree` function is not the same instance for each `html-page-context` event, even if the toctree content is identical. Parameters ---------- get_toctree : Callable[[Any], str] The function defined in context["toctree"] when html-page-context is triggered known_docs : Optional[Iterable[str]] Sphinx docnames known to the build environment. Returns ------- bs4.BeautifulSoup Soup to display in the side navbar """ if hasattr(preload_sidebar_nav, "cached_toctree"): # Need to retrieve a copy of the cached toctree HTML so as not to modify # the cached version when we set the "checked" state of the inputs soup = copy.copy(preload_sidebar_nav.cached_toctree) else: toctree = get_toctree( collapse=False, includehidden=True, maxdepth=4, titles_only=True ) soup = bs4.BeautifulSoup(toctree, "html.parser") # Remove "current" class since this toctree HTML is being reused # from some other page for li in soup("li", {"class": "current"}): li["class"].remove("current") # Remove sidebar links to sub-headers on the page for li in soup.select("li"): # Remove if li.find("a"): href = li.find("a")["href"] if "#" in href and href != "#": li.decompose() # Add bootstrap classes for first `ul` items for ul in soup("ul", recursive=False): ul.attrs["class"] = ul.attrs.get("class", []) + ["nav", "bd-sidenav"] # Add collapse boxes for parts/captions. # Wraps the TOC part in an extra
    to behave like chapters with toggles # show_nav_level: 0 means make parts collapsible. partcaptions = soup.find_all("p", attrs={"class": "caption"}) if len(partcaptions): new_soup = bs4.BeautifulSoup( "
      ", "html.parser" ) for caption in partcaptions: # Assume that the next
        element is the TOC list # for this part for sibling in caption.next_siblings: if sibling.name == "ul": toclist = sibling break li = soup.new_tag("li", attrs={"class": "toctree-l0"}) li.extend([caption, toclist]) new_soup.ul.append(li) soup = new_soup # Add icons and labels for collapsible nested sections add_collapse_checkboxes(soup) # Cache a fresh copy of the toctree HTML preload_sidebar_nav.cached_toctree = copy.copy(soup) # All the URIs are relative to the current document location, e.g. # "../../". There's no need for this, and it messes up the build if # we reuse the same sidebar (with different relative URIs for various pages) on # different pages. Replace the leading "../" sequences in the hrefs with the correct # number of "../" for the given rootdoc if pagename == root_doc: to_root_prefix = "./" else: to_root_prefix = re.sub(f"{root_doc}.html", "", pathto(root_doc)) for a in soup.select("a"): href = a["href"] if _EXTERNAL_HREF_RE.match(href): continue absolute_href = re.sub(r"^(\.\.\/)*", "", href) absolute_href = canonicalize_collection_href(absolute_href, known_docs) a["href"] = to_root_prefix + absolute_href if absolute_href == f"{pagename}.html": # Add a current-page class to the parent li element for styling parent_li = a.find_parent("li") parent_li["class"] = parent_li.get("class", []) + ["current-page"] # Open the dropdowns of every parent li for the active page. # pydata-sphinx-theme 0.17 replaced the # toggle with HTML5
        ; keep as a fallback for 0.14. for parent_li in a.find_parents("li", attrs={"class": "has-children"}): details = parent_li.find("details", recursive=False) if details is not None: details.attrs["open"] = "" continue el = parent_li.find("input") if el: el.attrs["checked"] = True return soup class ExampleEnum(Enum): """Enum which allows easier enumeration of members for example metadata.""" @classmethod def items(cls: type) -> Iterable[Tuple["ExampleEnum", str]]: """Return an iterable mapping between the enum type and the corresponding value. Returns ------- Dict['ExampleEnum', str] Dictionary of enum type, enum value for the enum class """ yield from {entry: entry.value for entry in cls}.items() @classmethod def values(cls: type) -> List[str]: """Return a list of the values of the enum. Returns ------- List[str] List of values for the enum class """ return [entry.value for entry in cls] @classmethod def _missing_(cls: type, value: str) -> "ExampleEnum": """Allow case-insensitive lookup of enum values. This shouldn't be called directly. Instead this is called when e.g., "SkillLevel('beginner')" is called. Parameters ---------- value : str Value for which the associated enum class instance is to be returned. Spaces are stripped from the beginning and end of the string, and matching is case-insensitive. Returns ------- ExampleEnum Enum class instance for the requested value """ value = value.lstrip().rstrip().lower() for member in cls: if member.value.lstrip().rstrip().lower() == value: return member return None @classmethod def formatted_name(cls: type) -> str: """Return the formatted name for the class.""" raise NotImplementedError @classmethod def key(cls: type) -> str: raise NotImplementedError class Contributor(ExampleEnum): RAY_TEAM = "Maintained by the Ray Team" COMMUNITY = "Contributed by the Ray Community" @property def tag(self): if self == Contributor.RAY_TEAM: return "ray-team" return "community" @classmethod def formatted_name(cls): return "All Examples" @classmethod def key(cls: type) -> str: return "contributor" class UseCase(ExampleEnum): """Use case type for example metadata.""" LARGE_LANGUAGE_MODELS = "Large Language Models" GENERATIVE_AI = "Generative AI" COMPUTER_VISION = "Computer Vision" NATURAL_LANGUAGE_PROCESSING = "Natural Language Processing" TIME_SERIES_FORECASTING = "Time Series Forecasting" RECOMMENDATION_SYSTEMS = "Recommendation Systems" ETL = "ETL" DATA_INGESTION = "Data Ingestion" DATA_WAREHOUSING = "Data Warehousing" DOCUMENT_PROCESSING = "Document Processing" REINFORCEMENT_LEARNING = "Reinforcement Learning" @classmethod def formatted_name(cls): return "Use Case" @classmethod def key(cls: type) -> str: return "use_case" class SkillLevel(ExampleEnum): """Skill level type for example metadata.""" BEGINNER = "Beginner" INTERMEDIATE = "Intermediate" ADVANCED = "Advanced" @classmethod def formatted_name(cls): return "Skill Level" @classmethod def key(cls: type) -> str: return "skill_level" class Framework(ExampleEnum): """Framework type for example metadata.""" AWSNEURON = "AWS Neuron" PYTORCH = "PyTorch" JAX = "JAX" FLAX = "Flax" LIGHTNING = "Lightning" TRANSFORMERS = "Transformers" ACCELERATE = "Accelerate" DEEPSPEED = "DeepSpeed" TENSORFLOW = "TensorFlow" HOROVOD = "Horovod" XGBOOST = "XGBoost" HUGGINGFACE = "Hugging Face" DATAJUICER = "Data-Juicer" VLLM = "vLLM" PANDAS = "Pandas" TRL = "TRL" ANY = "Any" UNSTRUCTURED = "Unstructured" @classmethod def formatted_name(cls): return "Framework" @classmethod def key(cls: type) -> str: return "framework" class RelatedTechnology(ExampleEnum): ML_APPLICATIONS = "ML Applications" LLM_APPLICATIONS = "LLM Applications" INTEGRATIONS = "Integrations" AI_ACCELERATORS = "AI Accelerators" DEPLOYMENT_PATTERNS = "Deployment Patterns" @classmethod def formatted_name(cls): return "Related Technology" @classmethod def key(cls: type) -> str: return "related_technology" class Library(ExampleEnum): """Library type for example metadata.""" DATA = "Data" SERVE = "Serve" TRAIN = "Train" @classmethod def formatted_name(cls): return "Library" @classmethod def key(cls: type) -> str: return "library" @classmethod def from_path(cls, path: Union[pathlib.Path, str]) -> "Library": """Instantiate a Library instance from a path. This function works its way up the file tree until it finds a directory that matches one of the enum types; if none is found, raise an exception. Parameters ---------- path : Union[pathlib.Path, str] Path containing a directory named the same as one of the enum types. Returns ------- Library A new Library instance for the given path. """ if isinstance(path, str): path = pathlib.Path(path) for part in path.parts: try: return Library(part) except ValueError: continue raise ValueError(f"Cannot find library name from example config path {path}") class Example: """Class containing metadata about an example to be shown in the example gallery.""" def __init__( self, config: Dict[str, str], library: Library, config_dir: pathlib.Path ): self.config_dir = config_dir self.library = library self.frameworks = [] for framework in config.get("frameworks", []): self.frameworks.append(Framework(framework.strip())) self.use_cases = [] for use_case in config.get("use_cases", []): self.use_cases.append(UseCase(use_case.strip())) contributor = config.get("contributor", "").strip() if contributor: if contributor == "community": self.contributor = Contributor.COMMUNITY elif contributor == "ray team": self.contributor = Contributor.RAY_TEAM else: raise ValueError( f"Invalid contributor type specified: {contributor}. Must be " " either 'ray team' or 'community'." ) else: self.contributor = Contributor.RAY_TEAM related_technology = config.get("related_technology", "").strip() if related_technology: self.related_technology = RelatedTechnology(related_technology) self.skill_level = SkillLevel(config.get("skill_level")) self.title = config.get("title") if "link" not in config: raise ValueError( "All examples must provide a link to either an external resource " "(starting with http://...) or a relative path to an internal page " "that is part of the Ray docs." ) link = config["link"] if link.startswith("http"): self.link = link else: self.link = str(config_dir / link) if self.title is None: raise ValueError(f"Title of an example {config} must be set.") if self.link is None: raise ValueError( f"An internal or external link must be specified: {config}" ) class ExampleConfig: """Object which holds configuration data for a set of library examples.""" def __init__( self, path: Union[pathlib.Path, str], src_dir: Union[pathlib.Path, str] ): """Parse a config file containing examples to display in the example gallery. Parameters ---------- path : Union[pathlib.Path, str] Path to the `examples.yml` to be parsed root_dir : Union[pathlib.Path, str] Path to the root of the docs directory; `app.srcdir` """ if isinstance(path, str): path = pathlib.Path(path) if not path.exists(): raise ValueError(f"No configuration file found at {path}.") with open(path, "r") as f: config = yaml.safe_load(f) self.config_path = path self.library = Library.from_path(path) self.examples = self.parse_examples(config.get("examples", []), src_dir) self.text = config.get("text", "") self.columns_to_show = self.parse_columns_to_show( config.get("columns_to_show", []) ) groupby = config.get("groupby", "skill_level") for cls in ExampleEnum.__subclasses__(): if cls.key() == groupby: self.groupby = cls break else: valid_classes = [cls.key() for cls in ExampleEnum.__subclasses__()] raise ValueError( f"Unable to find class to group example entries by {groupby}. " f"Valid choices are {valid_classes}.", ) def parse_columns_to_show(self, columns: str) -> Dict[str, type]: """Parse the columns to show in the library example page for the config. Note that a link to the example is always shown, and cannot be hidden. Parameters ---------- columns : str Column names to show; valid names are any subclass of ExampleEnum, e.g. UseCase, Framework, etc. Returns ------- List[type] A list of the ExampleEnum classes for which columns are to be shown """ cols = {} for col in columns: if col == "use_cases": cols["use_cases"] = UseCase elif col == "frameworks": cols["frameworks"] = Framework else: raise ValueError( f"Invalid column name {col} specified in {self.config_path}" ) return cols def parse_examples( self, example_config: List[Dict[str, str]], src_dir: Union[pathlib.Path, str] ) -> List[Example]: """Parse the examples in the given configuration. Raise an exception if duplicate examples are found in the configuration file. Parameters ---------- path : pathlib.Path Path to the example file to parse Returns ------- List[Example] List of examples from the parsed configuration file """ links = set() examples = [] for entry in example_config: example = Example( entry, self.library, self.config_path.relative_to(src_dir).parent ) if example.link in links: raise ValueError( f"A duplicate example {example.link} was specified in " f"{self.config_path}. Please remove duplicates and rebuild." ) links.add(example.link) examples.append(example) return examples def __iter__(self): yield from self.examples def setup_context(app, pagename, templatename, context, doctree): def render_library_examples(config: pathlib.Path = None) -> bs4.BeautifulSoup: """Render a table of links to examples for a given Ray library. Duplicate examples will result in an error. Parameters ---------- config : pathlib.Path Path to the examples.yml file for the Ray library Returns ------- bs4.BeautifulSoup Table of links to examples for the library, rendered as HTML """ if config is None: config = (pathlib.Path(app.confdir) / pagename).parent / "examples.yml" # Keep track of whether any of the examples have frameworks metadata; the # column will not be shown if no frameworks metadata exists on any example. # Group the examples by the ExampleConfig.groupby value: examples = defaultdict(list) example_config = ExampleConfig(config, app.srcdir) for example in example_config: try: group = getattr(example, example_config.groupby.key()) except AttributeError as e: raise AttributeError( f"Example {example.link} has no {example_config.groupby.key()} " "key, but needs one because the examples for library " f"{example_config.library.value} are configured to be grouped " f"by {example_config.groupby.key()}." ) from e examples[group].append(example) # Construct a table of examples soup = bs4.BeautifulSoup() # Add the main heading to the page and include the page text page_title = soup.new_tag("h1") page_title.append(f"{example_config.library.value} Examples") soup.append(page_title) page_text = soup.new_tag("p") page_text.append(example_config.text) soup.append(page_text) container = soup.new_tag("div", attrs={"class": "example-index"}) for group, group_examples in examples.items(): if not group_examples: continue header = soup.new_tag("h2", attrs={"class": "example-header"}) header.append(group.value) container.append(header) table = soup.new_tag("table", attrs={"class": ["table", "example-table"]}) # If there are additional columns to show besides just the example link, # include column titles in the table header if len(example_config.columns_to_show) > 0: thead = soup.new_tag("thead") thead_row = soup.new_tag("tr") for example_enum in example_config.columns_to_show.values(): col_header = soup.new_tag("th") col_label = soup.new_tag("p") col_label.append(example_enum.formatted_name()) col_header.append(col_label) thead_row.append(col_header) link_col = soup.new_tag("th") link_label = soup.new_tag("p") link_label.append("Example") link_col.append(link_label) thead_row.append(link_col) thead.append(thead_row) table.append(thead) tbody = soup.new_tag("tbody") for example in group_examples: tr = soup.new_tag("tr") # The columns specify which attributes of each example to show; # for each attribute, a new cell value is added with the attribute from # the example if len(example_config.columns_to_show) > 0: for attribute in example_config.columns_to_show: col_td = soup.new_tag("td") col_p = soup.new_tag("p") attribute_value = getattr(example, attribute, "") if isinstance(attribute_value, str): col_p.append(attribute_value) elif isinstance(attribute_value, list): col_p.append( ", ".join(item.value for item in attribute_value) ) col_td.append(col_p) tr.append(col_td) link_td = soup.new_tag("td") link_p = soup.new_tag("p") if example.link.startswith("http"): link_href = soup.new_tag("a", attrs={"href": example.link}) else: link_href = soup.new_tag( "a", attrs={"href": context["pathto"](example.link)} ) link_span = soup.new_tag("span") link_span.append(example.title) link_href.append(link_span) link_p.append(link_href) link_td.append(link_p) tr.append(link_td) tbody.append(tr) table.append(tbody) container.append(table) soup.append(container) return soup def render_example_gallery(configs: Iterable[pathlib.Path] = None) -> str: """Load and render examples for the example gallery. This function grabs examples from the various example gallery indexes. Each Ray library team maintains its own yml file which a standardized set of metadata with each example to ensure consistency across the code base. Duplicate examples will not raise an error as long as their fields match exactly. Parameters ---------- configs : Iterable[pathlib.Path] Paths to example gallery files to ingest Returns ------- str Example gallery examples rendered as HTML """ if configs is None: configs = EXAMPLE_GALLERY_CONFIGS examples = {} for config in configs: config_path = pathlib.Path(config).relative_to("source") example_config = ExampleConfig( pathlib.Path(app.confdir) / config_path, app.srcdir ) for example in example_config: # Check the ingested examples for duplicates. If there are duplicates, # check that they have the same field values, and keep only one. if example.link in examples: existing_example_fields = vars(examples[example.link]) for key, value in vars(example): if existing_example_fields.get(key) != value: raise ValueError( "One example was specified twice with different " f"attributes: {vars(example)}\n" f"{existing_example_fields}" ) else: examples[example.link] = example soup = bs4.BeautifulSoup() list_area = soup.new_tag("div", attrs={"class": ["example-list-area"]}) for example in examples.values(): example_div = soup.new_tag("div", attrs={"class": "example"}) if example.link.startswith("http"): link = example.link else: link = context["pathto"](example.link) example_link = soup.new_tag( "a", attrs={ "class": "example-link", "href": link, }, ) example_icon_area = soup.new_tag( "div", attrs={"class": "example-icon-area"} ) icon = soup.new_tag( "img", attrs={ "class": "example-icon", "src": f"../_static/img/icon_bg_{random.randint(1, 5)}.jpg", }, ) remix_icon = soup.new_tag( "i", attrs={"class": f"{random.choice(REMIX_ICONS)} remix-icon"} ) icon.append(remix_icon) example_icon_area.append(icon) example_link.append(example_icon_area) example_text_area = soup.new_tag( "div", attrs={"class": "example-text-area"} ) example_title = soup.new_tag("b", attrs={"class": "example-title"}) example_title.append(example.title) example_text_area.append(example_title) example_tags = soup.new_tag("span", attrs={"class": "example-tags"}) frameworks = [item.value for item in example.frameworks] example_tags.append( ". ".join( [example.skill_level.value, example.library.value, *frameworks] ) + "." ) example_text_area.append(example_tags) other_keywords = soup.new_tag( "span", attrs={"class": "example-other-keywords"} ) other_keywords.append( " ".join(use_case.value for use_case in example.use_cases) ) other_keywords.append(f" {example.contributor.tag}") example_text_area.append(other_keywords) # Add the appropriate text if the example comes from the community if example.contributor == Contributor.COMMUNITY: community_text_area = soup.new_tag( "div", attrs={"class": "community-text-area"} ) community_text = soup.new_tag("i", attrs={"class": "community-text"}) community_text.append("*Contributed by the Ray Community") community_text_area.append(community_text) # Add emojis separately; they're not italicized in the mockups emojis = soup.new_tag("span", attrs={"class": "community-emojis"}) emojis.append("💪 ✨") community_text_area.append(emojis) example_text_area.append(community_text_area) example_link.append(example_text_area) example_div.append(example_link) list_area.append(example_div) soup.append(list_area) return soup @lru_cache(maxsize=None) def render_header_nav_links() -> bs4.BeautifulSoup: """Render external header links into the top nav bar. The structure rendered here is defined in an external yaml file. Returns ------- str Raw HTML to be rendered in the top nav bar """ if not hasattr(app.config, "navbar_content"): raise ValueError( "A template is attempting to call render_header_nav_links(); a " "navbar configuration must be specified." ) node = nodes.container(classes=["navbar-content"]) node.append(render_header_nodes(app.config.navbar_content)) header_soup = bs4.BeautifulSoup( app.builder.render_partial(node)["fragment"], "html.parser" ) return add_nav_chevrons(header_soup) def render_header_nodes( obj: List[NavEntry], is_top_level: bool = True ) -> nodes.Node: """Generate a set of header nav links with docutils nodes. Parameters ---------- is_top_level : bool True if the call to this function is rendering the top level nodes, False otherwise (non-top level nodes are displayed as submenus of the top level nodes) obj : List[NavEntry] List of yaml config entries to render as docutils nodes Returns ------- nodes.Node Bullet list which will be turned into header nav HTML by the sphinx builder """ bullet_list = nodes.bullet_list( bullet="-", classes=["navbar-toplevel" if is_top_level else "navbar-sublevel"], ) for item in obj: if "file" in item: ref_node = make_refnode( app.builder, context["current_page_name"], item["file"], None, nodes.inline(classes=["navbar-link-title"], text=item.get("title")), item.get("title"), ) elif "link" in item: ref_node = nodes.reference("", "", internal=False) ref_node["refuri"] = item.get("link") ref_node["reftitle"] = item.get("title") ref_node.append( nodes.inline(classes=["navbar-link-title"], text=item.get("title")) ) if "caption" in item: caption = nodes.Text(item.get("caption")) ref_node.append(caption) paragraph = nodes.paragraph() paragraph.append(ref_node) container = nodes.container(classes=["ref-container"]) container.append(paragraph) list_item = nodes.list_item( classes=["active-link"] if item.get("file") == pagename else [] ) list_item.append(container) if "sections" in item: wrapper = nodes.container(classes=["navbar-dropdown"]) wrapper.append( render_header_nodes(item["sections"], is_top_level=False) ) list_item.append(wrapper) bullet_list.append(list_item) return bullet_list def render_use_cases_dropdown() -> bs4.BeautifulSoup: return render_example_gallery_dropdown(UseCase) def render_libraries_dropdown() -> bs4.BeautifulSoup: return render_example_gallery_dropdown(Library) def render_frameworks_dropdown() -> bs4.BeautifulSoup: return render_example_gallery_dropdown(Framework) def render_contributor_dropdown() -> bs4.BeautifulSoup: return render_example_gallery_dropdown(Contributor) def show_edit_button() -> bool: """Check whether to show the "Edit on GitHub" button on the current page. Returns ------- bool True if the page is tracked by git, and therefore should show an "Edit on GitHub" button; False otherwise (which happens e.g. when the page is generated during the docs build process) """ if "page_source_suffix" in context: file_name = f"{pagename}{context['page_source_suffix']}" return file_name in TRACKED_FILES return False context["show_edit_button"] = show_edit_button() context["cached_toctree"] = preload_sidebar_nav( context["toctree"], context["pathto"], context["root_doc"], pagename, getattr(app.env, "found_docs", None), ) context["render_header_nav_links"] = render_header_nav_links context["render_library_examples"] = render_library_examples context["render_example_gallery"] = render_example_gallery context["render_use_cases_dropdown"] = render_use_cases_dropdown context["render_libraries_dropdown"] = render_libraries_dropdown context["render_frameworks_dropdown"] = render_frameworks_dropdown context["render_contributor_dropdown"] = render_contributor_dropdown # Update the HTML page context with a few extra utilities. context["pygments_highlight_python"] = lambda code: highlight( code, PythonLexer(), HtmlFormatter() ) def update_hrefs(input_soup: bs4.BeautifulSoup, n_levels_deep=0): soup = copy.copy(input_soup) for a in soup.select("a"): a["href"] = "../" * n_levels_deep + a["href"] return soup def add_nav_chevrons(input_soup: bs4.BeautifulSoup) -> bs4.BeautifulSoup: """Add dropdown chevron icons to the header nav bar. Parameters ---------- input_soup : bs4.BeautifulSoup Soup containing rendered HTML which will be inserted into the header nav bar Returns ------- bs4.BeautifulSoup A new BeautifulSoup instance containing chevrons on the list items that are meant to be dropdowns. """ soup = copy.copy(input_soup) for li in soup.find_all("li", recursive=True): divs = li.find_all("div", {"class": "navbar-dropdown"}, recursive=False) if divs: ref = li.find("div", {"class": "ref-container"}) ref.append(soup.new_tag("i", attrs={"class": "fa-solid fa-chevron-down"})) return soup def render_example_gallery_dropdown(cls: type) -> bs4.BeautifulSoup: """Render a dropdown menu selector for the example gallery. Parameters ---------- cls : type ExampleEnum class type to use to populate the dropdown Returns ------- bs4.BeautifulSoup Soup containing the dropdown element """ soup = bs4.BeautifulSoup() dropdown_name = cls.formatted_name().lower().replace(" ", "-") dropdown_container = soup.new_tag( "div", attrs={"class": "filter-dropdown", "id": f"{dropdown_name}-dropdown"} ) dropdown_show_checkbox = soup.new_tag( "input", attrs={ "class": "dropdown-checkbox", "id": f"{dropdown_name}-checkbox", "type": "checkbox", }, ) dropdown_container.append(dropdown_show_checkbox) dropdown_label = soup.new_tag( "label", attrs={"class": "dropdown-label", "for": f"{dropdown_name}-checkbox"} ) dropdown_label.append(cls.formatted_name()) chevron = soup.new_tag("i", attrs={"class": "fa-solid fa-chevron-down"}) dropdown_label.append(chevron) dropdown_container.append(dropdown_label) if cls.values(): dropdown_options = soup.new_tag("div", attrs={"class": "dropdown-content"}) for member in list(cls): label = soup.new_tag("label", attrs={"class": "checkbox-container"}) label.append(member.value) tag = getattr(member, "tag", member.value) checkbox = soup.new_tag( "input", attrs={ "id": f"{tag}-checkbox", "class": "filter-checkbox", "type": "checkbox", }, ) label.append(checkbox) checkmark = soup.new_tag("span", attrs={"class": "checkmark"}) label.append(checkmark) dropdown_options.append(label) dropdown_container.append(dropdown_options) soup.append(dropdown_container) return soup def collect_example_orphans( confdir: os.PathLike, srcdir: os.PathLike, example_configs: Optional[List[str]] = None, ) -> set: """Collect document paths from examples.yml files to mark as orphans. This function parses example configuration files and returns a set of document paths that should be marked as orphan documents during the build. Parameters ---------- confdir : os.PathLike Path to the Sphinx configuration directory (app.confdir) srcdir : os.PathLike Path to the Sphinx source directory (app.srcdir) example_configs : Optional[List[str]] Configuration files to parse. Defaults to EXAMPLE_GALLERY_CONFIGS. Returns ------- set Set of document paths (without file extensions) to mark as orphans """ if example_configs is None: example_configs = EXAMPLE_GALLERY_CONFIGS example_orphan_documents = set() for config in example_configs: config_path = pathlib.Path(confdir) / pathlib.Path(config).relative_to( "source" ) example_config = ExampleConfig(config_path, srcdir) for example in example_config: if not example.link.startswith("http"): # Normalize path and remove file extension to get docname normalized = pathlib.PurePath(os.path.normpath(example.link)) docname = str(normalized.with_suffix('')) example_orphan_documents.add(docname) return example_orphan_documents def pregenerate_example_rsts( app: sphinx.application.Sphinx, *example_configs: Optional[List[str]] ): """Pregenerate RST files for the example page configuration files. This generates RST files for displaying example pages for Ray libraries. See `add_custom_assets` for more information about the custom template that gets rendered from these configuration files. Parameters ---------- *example_configs : Optional[List[str]] Configuration files for which example pages are to be generated """ if not example_configs: example_configs = EXAMPLE_GALLERY_CONFIGS for config in example_configs: # Depending on where the sphinx build command is run from, the path to the # target config file can change. Handle these paths manually to ensure it # works on RTD and locally. config_path = pathlib.Path(app.confdir) / pathlib.Path(config).relative_to( "source" ) page_title = "Examples" title_decoration = "=" * len(page_title) with open(config_path.with_suffix(".rst"), "w") as f: f.write( f"{page_title}\n{title_decoration}\n\n" " .. this file is pregenerated; please edit ./examples.yml to " "modify examples for this library." ) def generate_version_url(version): return f"https://docs.ray.io/en/{version}/" def generate_versions_json(): """Gets the releases from the remote repo, sorts them in semver order, and generates the JSON needed for the version switcher """ ray_prefix = "ray-" min_version = "1.11.0" repo_url = "https://github.com/ray-project/ray.git" static_dir_name = "_static" version_json_filename = "versions.json" dereference_suffix = "^{}" version_json_data = [] # Versions that should always appear at the top for version in ["latest", "master"]: version_json_data.append( {"version": version, "url": generate_version_url(version)} ) git_versions = [] # Fetch release tags from repo output = subprocess.check_output(["git", "ls-remote", "--tags", repo_url]).decode( "utf-8" ) # Extract release versions from tags tags = re.findall(r"refs/tags/(.+)", output) for tag in tags: if ray_prefix in tag and dereference_suffix not in tag: version = tag.split(ray_prefix)[1] if version not in git_versions and Version(version) >= Version(min_version): git_versions.append(version) git_versions.sort(key=Version, reverse=True) for version in git_versions: version_json_data.append( { "version": f"releases-{version}", "url": generate_version_url(f"releases-{version}"), } ) # Ensure static path exists static_dir = os.path.join(os.path.dirname(__file__), static_dir_name) if not os.path.exists(static_dir): os.makedirs(static_dir) # Write JSON output output_path = os.path.join(static_dir, version_json_filename) with open(output_path, "w") as f: json.dump(version_json_data, f, indent=4) REMIX_ICONS = [ "ri-24-hours-fill", "ri-24-hours-line", "ri-4k-fill", "ri-4k-line", "ri-a-b", "ri-account-box-fill", "ri-account-box-line", "ri-account-circle-fill", "ri-account-circle-line", "ri-account-pin-box-fill", "ri-account-pin-box-line", "ri-account-pin-circle-fill", "ri-account-pin-circle-line", "ri-add-box-fill", "ri-add-box-line", "ri-add-circle-fill", "ri-add-circle-line", "ri-add-fill", "ri-add-line", "ri-admin-fill", "ri-admin-line", "ri-advertisement-fill", "ri-advertisement-line", "ri-airplay-fill", "ri-airplay-line", "ri-alarm-fill", "ri-alarm-line", "ri-alarm-warning-fill", "ri-alarm-warning-line", "ri-album-fill", "ri-album-line", "ri-alert-fill", "ri-alert-line", "ri-aliens-fill", "ri-aliens-line", "ri-align-bottom", "ri-align-center", "ri-align-justify", "ri-align-left", "ri-align-right", "ri-align-top", "ri-align-vertically", "ri-alipay-fill", "ri-alipay-line", "ri-amazon-fill", "ri-amazon-line", "ri-anchor-fill", "ri-anchor-line", "ri-ancient-gate-fill", "ri-ancient-gate-line", "ri-ancient-pavilion-fill", "ri-ancient-pavilion-line", "ri-android-fill", "ri-android-line", "ri-angularjs-fill", "ri-angularjs-line", "ri-anticlockwise-2-fill", "ri-anticlockwise-2-line", "ri-anticlockwise-fill", "ri-anticlockwise-line", "ri-app-store-fill", "ri-app-store-line", "ri-apple-fill", "ri-apple-line", "ri-apps-2-fill", "ri-apps-2-line", "ri-apps-fill", "ri-apps-line", "ri-archive-drawer-fill", "ri-archive-drawer-line", "ri-archive-fill", "ri-archive-line", "ri-arrow-down-circle-fill", "ri-arrow-down-circle-line", "ri-arrow-down-fill", "ri-arrow-down-line", "ri-arrow-down-s-fill", "ri-arrow-down-s-line", "ri-arrow-drop-down-fill", "ri-arrow-drop-down-line", "ri-arrow-drop-left-fill", "ri-arrow-drop-left-line", "ri-arrow-drop-right-fill", "ri-arrow-drop-right-line", "ri-arrow-drop-up-fill", "ri-arrow-drop-up-line", "ri-arrow-go-back-fill", "ri-arrow-go-back-line", "ri-arrow-go-forward-fill", "ri-arrow-go-forward-line", "ri-arrow-left-circle-fill", "ri-arrow-left-circle-line", "ri-arrow-left-down-fill", "ri-arrow-left-down-line", "ri-arrow-left-fill", "ri-arrow-left-line", "ri-arrow-left-right-fill", "ri-arrow-left-right-line", "ri-arrow-left-s-fill", "ri-arrow-left-s-line", "ri-arrow-left-up-fill", "ri-arrow-left-up-line", "ri-arrow-right-circle-fill", "ri-arrow-right-circle-line", "ri-arrow-right-down-fill", "ri-arrow-right-down-line", "ri-arrow-right-fill", "ri-arrow-right-line", "ri-arrow-right-s-fill", "ri-arrow-right-s-line", "ri-arrow-right-up-fill", "ri-arrow-right-up-line", "ri-arrow-up-circle-fill", "ri-arrow-up-circle-line", "ri-arrow-up-down-fill", "ri-arrow-up-down-line", "ri-arrow-up-fill", "ri-arrow-up-line", "ri-arrow-up-s-fill", "ri-arrow-up-s-line", "ri-artboard-2-fill", "ri-artboard-2-line", "ri-artboard-fill", "ri-artboard-line", "ri-article-fill", "ri-article-line", "ri-aspect-ratio-fill", "ri-aspect-ratio-line", "ri-asterisk", "ri-at-fill", "ri-at-line", "ri-attachment-2", "ri-attachment-fill", "ri-attachment-line", "ri-auction-fill", "ri-auction-line", "ri-award-fill", "ri-award-line", "ri-baidu-fill", "ri-baidu-line", "ri-ball-pen-fill", "ri-ball-pen-line", "ri-bank-card-2-fill", "ri-bank-card-2-line", "ri-bank-card-fill", "ri-bank-card-line", "ri-bank-fill", "ri-bank-line", "ri-bar-chart-2-fill", "ri-bar-chart-2-line", "ri-bar-chart-box-fill", "ri-bar-chart-box-line", "ri-bar-chart-fill", "ri-bar-chart-grouped-fill", "ri-bar-chart-grouped-line", "ri-bar-chart-horizontal-fill", "ri-bar-chart-horizontal-line", "ri-bar-chart-line", "ri-barcode-box-fill", "ri-barcode-box-line", "ri-barcode-fill", "ri-barcode-line", "ri-barricade-fill", "ri-barricade-line", "ri-base-station-fill", "ri-base-station-line", "ri-basketball-fill", "ri-basketball-line", "ri-battery-2-charge-fill", "ri-battery-2-charge-line", "ri-battery-2-fill", "ri-battery-2-line", "ri-battery-charge-fill", "ri-battery-charge-line", "ri-battery-fill", "ri-battery-line", "ri-battery-low-fill", "ri-battery-low-line", "ri-battery-saver-fill", "ri-battery-saver-line", "ri-battery-share-fill", "ri-battery-share-line", "ri-bear-smile-fill", "ri-bear-smile-line", "ri-behance-fill", "ri-behance-line", "ri-bell-fill", "ri-bell-line", "ri-bike-fill", "ri-bike-line", "ri-bilibili-fill", "ri-bilibili-line", "ri-bill-fill", "ri-bill-line", "ri-billiards-fill", "ri-billiards-line", "ri-bit-coin-fill", "ri-bit-coin-line", "ri-blaze-fill", "ri-blaze-line", "ri-bluetooth-connect-fill", "ri-bluetooth-connect-line", "ri-bluetooth-fill", "ri-bluetooth-line", "ri-blur-off-fill", "ri-blur-off-line", "ri-body-scan-fill", "ri-body-scan-line", "ri-bold", "ri-book-2-fill", "ri-book-2-line", "ri-book-3-fill", "ri-book-3-line", "ri-book-fill", "ri-book-line", "ri-book-marked-fill", "ri-book-marked-line", "ri-book-open-fill", "ri-book-open-line", "ri-book-read-fill", "ri-book-read-line", "ri-booklet-fill", "ri-booklet-line", "ri-bookmark-2-fill", "ri-bookmark-2-line", "ri-bookmark-3-fill", "ri-bookmark-3-line", "ri-bookmark-fill", "ri-bookmark-line", "ri-boxing-fill", "ri-boxing-line", "ri-braces-fill", "ri-braces-line", "ri-brackets-fill", "ri-brackets-line", "ri-briefcase-2-fill", "ri-briefcase-2-line", "ri-briefcase-3-fill", "ri-briefcase-3-line", "ri-briefcase-4-fill", "ri-briefcase-4-line", "ri-briefcase-5-fill", "ri-briefcase-5-line", "ri-briefcase-fill", "ri-briefcase-line", "ri-bring-forward", "ri-bring-to-front", "ri-broadcast-fill", "ri-broadcast-line", "ri-brush-2-fill", "ri-brush-2-line", "ri-brush-3-fill", "ri-brush-3-line", "ri-brush-4-fill", "ri-brush-4-line", "ri-brush-fill", "ri-brush-line", "ri-bubble-chart-fill", "ri-bubble-chart-line", "ri-bug-2-fill", "ri-bug-2-line", "ri-bug-fill", "ri-bug-line", "ri-building-2-fill", "ri-building-2-line", "ri-building-3-fill", "ri-building-3-line", "ri-building-4-fill", "ri-building-4-line", "ri-building-fill", "ri-building-line", "ri-bus-2-fill", "ri-bus-2-line", "ri-bus-fill", "ri-bus-line", "ri-bus-wifi-fill", "ri-bus-wifi-line", "ri-cactus-fill", "ri-cactus-line", "ri-cake-2-fill", "ri-cake-2-line", "ri-cake-3-fill", "ri-cake-3-line", "ri-cake-fill", "ri-cake-line", "ri-calculator-fill", "ri-calculator-line", "ri-calendar-2-fill", "ri-calendar-2-line", "ri-calendar-check-fill", "ri-calendar-check-line", "ri-calendar-event-fill", "ri-calendar-event-line", "ri-calendar-fill", "ri-calendar-line", "ri-calendar-todo-fill", "ri-calendar-todo-line", "ri-camera-2-fill", "ri-camera-2-line", "ri-camera-3-fill", "ri-camera-3-line", "ri-camera-fill", "ri-camera-lens-fill", "ri-camera-lens-line", "ri-camera-line", "ri-camera-off-fill", "ri-camera-off-line", "ri-camera-switch-fill", "ri-camera-switch-line", "ri-capsule-fill", "ri-capsule-line", "ri-car-fill", "ri-car-line", "ri-car-washing-fill", "ri-car-washing-line", "ri-caravan-fill", "ri-caravan-line", "ri-cast-fill", "ri-cast-line", "ri-cellphone-fill", "ri-cellphone-line", "ri-celsius-fill", "ri-celsius-line", "ri-centos-fill", "ri-centos-line", "ri-character-recognition-fill", "ri-character-recognition-line", "ri-charging-pile-2-fill", "ri-charging-pile-2-line", "ri-charging-pile-fill", "ri-charging-pile-line", "ri-chat-1-fill", "ri-chat-1-line", "ri-chat-2-fill", "ri-chat-2-line", "ri-chat-3-fill", "ri-chat-3-line", "ri-chat-4-fill", "ri-chat-4-line", "ri-chat-check-fill", "ri-chat-check-line", "ri-chat-delete-fill", "ri-chat-delete-line", "ri-chat-download-fill", "ri-chat-download-line", "ri-chat-follow-up-fill", "ri-chat-follow-up-line", "ri-chat-forward-fill", "ri-chat-forward-line", "ri-chat-heart-fill", "ri-chat-heart-line", "ri-chat-history-fill", "ri-chat-history-line", "ri-chat-new-fill", "ri-chat-new-line", "ri-chat-off-fill", "ri-chat-off-line", "ri-chat-poll-fill", "ri-chat-poll-line", "ri-chat-private-fill", "ri-chat-private-line", "ri-chat-quote-fill", "ri-chat-quote-line", "ri-chat-settings-fill", "ri-chat-settings-line", "ri-chat-smile-2-fill", "ri-chat-smile-2-line", "ri-chat-smile-3-fill", "ri-chat-smile-3-line", "ri-chat-smile-fill", "ri-chat-smile-line", "ri-chat-upload-fill", "ri-chat-upload-line", "ri-chat-voice-fill", "ri-chat-voice-line", "ri-check-double-fill", "ri-check-double-line", "ri-check-fill", "ri-check-line", "ri-checkbox-blank-circle-fill", "ri-checkbox-blank-circle-line", "ri-checkbox-blank-fill", "ri-checkbox-blank-line", "ri-checkbox-circle-fill", "ri-checkbox-circle-line", "ri-checkbox-fill", "ri-checkbox-indeterminate-fill", "ri-checkbox-indeterminate-line", "ri-checkbox-line", "ri-checkbox-multiple-blank-fill", "ri-checkbox-multiple-blank-line", "ri-checkbox-multiple-fill", "ri-checkbox-multiple-line", "ri-china-railway-fill", "ri-china-railway-line", "ri-chrome-fill", "ri-chrome-line", "ri-clapperboard-fill", "ri-clapperboard-line", "ri-clipboard-fill", "ri-clipboard-line", "ri-clockwise-2-fill", "ri-clockwise-2-line", "ri-clockwise-fill", "ri-clockwise-line", "ri-close-circle-fill", "ri-close-circle-line", "ri-close-fill", "ri-close-line", "ri-closed-captioning-fill", "ri-closed-captioning-line", "ri-cloud-fill", "ri-cloud-line", "ri-cloud-off-fill", "ri-cloud-off-line", "ri-cloud-windy-fill", "ri-cloud-windy-line", "ri-cloudy-2-fill", "ri-cloudy-2-line", "ri-cloudy-fill", "ri-cloudy-line", "ri-code-box-fill", "ri-code-box-line", "ri-code-fill", "ri-code-line", "ri-code-s-fill", "ri-code-s-line", "ri-code-s-slash-fill", "ri-code-s-slash-line", "ri-code-view", "ri-codepen-fill", "ri-codepen-line", "ri-coin-fill", "ri-coin-line", "ri-coins-fill", "ri-coins-line", "ri-collage-fill", "ri-collage-line", "ri-command-fill", "ri-command-line", "ri-community-fill", "ri-community-line", "ri-compass-2-fill", "ri-compass-2-line", "ri-compass-3-fill", "ri-compass-3-line", "ri-compass-4-fill", "ri-compass-4-line", "ri-compass-discover-fill", "ri-compass-discover-line", "ri-compass-fill", "ri-compass-line", "ri-compasses-2-fill", "ri-compasses-2-line", "ri-compasses-fill", "ri-compasses-line", "ri-computer-fill", "ri-computer-line", "ri-contacts-book-2-fill", "ri-contacts-book-2-line", "ri-contacts-book-fill", "ri-contacts-book-line", "ri-contacts-book-upload-fill", "ri-contacts-book-upload-line", "ri-contacts-fill", "ri-contacts-line", "ri-contrast-2-fill", "ri-contrast-2-line", "ri-contrast-drop-2-fill", "ri-contrast-drop-2-line", "ri-contrast-drop-fill", "ri-contrast-drop-line", "ri-contrast-fill", "ri-contrast-line", "ri-copper-coin-fill", "ri-copper-coin-line", "ri-copper-diamond-fill", "ri-copper-diamond-line", "ri-copyleft-fill", "ri-copyleft-line", "ri-copyright-fill", "ri-copyright-line", "ri-coreos-fill", "ri-coreos-line", "ri-coupon-2-fill", "ri-coupon-2-line", "ri-coupon-3-fill", "ri-coupon-3-line", "ri-coupon-4-fill", "ri-coupon-4-line", "ri-coupon-5-fill", "ri-coupon-5-line", "ri-coupon-fill", "ri-coupon-line", "ri-cpu-fill", "ri-cpu-line", "ri-creative-commons-by-fill", "ri-creative-commons-by-line", "ri-creative-commons-fill", "ri-creative-commons-line", "ri-creative-commons-nc-fill", "ri-creative-commons-nc-line", "ri-creative-commons-nd-fill", "ri-creative-commons-nd-line", "ri-creative-commons-sa-fill", "ri-creative-commons-sa-line", "ri-creative-commons-zero-fill", "ri-creative-commons-zero-line", "ri-criminal-fill", "ri-criminal-line", "ri-crop-2-fill", "ri-crop-2-line", "ri-crop-fill", "ri-crop-line", "ri-css3-fill", "ri-css3-line", "ri-cup-fill", "ri-cup-line", "ri-currency-fill", "ri-currency-line", "ri-cursor-fill", "ri-cursor-line", "ri-customer-service-2-fill", "ri-customer-service-2-line", "ri-customer-service-fill", "ri-customer-service-line", "ri-dashboard-2-fill", "ri-dashboard-2-line", "ri-dashboard-3-fill", "ri-dashboard-3-line", "ri-dashboard-fill", "ri-dashboard-line", "ri-database-2-fill", "ri-database-2-line", "ri-database-fill", "ri-database-line", "ri-delete-back-2-fill", "ri-delete-back-2-line", "ri-delete-back-fill", "ri-delete-back-line", "ri-delete-bin-2-fill", "ri-delete-bin-2-line", "ri-delete-bin-3-fill", "ri-delete-bin-3-line", "ri-delete-bin-4-fill", "ri-delete-bin-4-line", "ri-delete-bin-5-fill", "ri-delete-bin-5-line", "ri-delete-bin-6-fill", "ri-delete-bin-6-line", "ri-delete-bin-7-fill", "ri-delete-bin-7-line", "ri-delete-bin-fill", "ri-delete-bin-line", "ri-delete-column", "ri-delete-row", "ri-device-fill", "ri-device-line", "ri-device-recover-fill", "ri-device-recover-line", "ri-dingding-fill", "ri-dingding-line", "ri-direction-fill", "ri-direction-line", "ri-disc-fill", "ri-disc-line", "ri-discord-fill", "ri-discord-line", "ri-discuss-fill", "ri-discuss-line", "ri-dislike-fill", "ri-dislike-line", "ri-disqus-fill", "ri-disqus-line", "ri-divide-fill", "ri-divide-line", "ri-donut-chart-fill", "ri-donut-chart-line", "ri-door-closed-fill", "ri-door-closed-line", "ri-door-fill", "ri-door-line", "ri-door-lock-box-fill", "ri-door-lock-box-line", "ri-door-lock-fill", "ri-door-lock-line", "ri-door-open-fill", "ri-door-open-line", "ri-dossier-fill", "ri-dossier-line", "ri-douban-fill", "ri-douban-line", "ri-double-quotes-l", "ri-double-quotes-r", "ri-download-2-fill", "ri-download-2-line", "ri-download-cloud-2-fill", "ri-download-cloud-2-line", "ri-download-cloud-fill", "ri-download-cloud-line", "ri-download-fill", "ri-download-line", "ri-draft-fill", "ri-draft-line", "ri-drag-drop-fill", "ri-drag-drop-line", "ri-drag-move-2-fill", "ri-drag-move-2-line", "ri-drag-move-fill", "ri-drag-move-line", "ri-dribbble-fill", "ri-dribbble-line", "ri-drive-fill", "ri-drive-line", "ri-drizzle-fill", "ri-drizzle-line", "ri-drop-fill", "ri-drop-line", "ri-dropbox-fill", "ri-dropbox-line", "ri-dual-sim-1-fill", "ri-dual-sim-1-line", "ri-dual-sim-2-fill", "ri-dual-sim-2-line", "ri-dv-fill", "ri-dv-line", "ri-dvd-fill", "ri-dvd-line", "ri-e-bike-2-fill", "ri-e-bike-2-line", "ri-e-bike-fill", "ri-e-bike-line", "ri-earth-fill", "ri-earth-line", "ri-earthquake-fill", "ri-earthquake-line", "ri-edge-fill", "ri-edge-line", "ri-edit-2-fill", "ri-edit-2-line", "ri-edit-box-fill", "ri-edit-box-line", "ri-edit-circle-fill", "ri-edit-circle-line", "ri-edit-fill", "ri-edit-line", "ri-eject-fill", "ri-eject-line", "ri-emotion-2-fill", "ri-emotion-2-line", "ri-emotion-fill", "ri-emotion-happy-fill", "ri-emotion-happy-line", "ri-emotion-laugh-fill", "ri-emotion-laugh-line", "ri-emotion-line", "ri-emotion-normal-fill", "ri-emotion-normal-line", "ri-emotion-sad-fill", "ri-emotion-sad-line", "ri-emotion-unhappy-fill", "ri-emotion-unhappy-line", "ri-empathize-fill", "ri-empathize-line", "ri-emphasis-cn", "ri-emphasis", "ri-english-input", "ri-equalizer-fill", "ri-equalizer-line", "ri-eraser-fill", "ri-eraser-line", "ri-error-warning-fill", "ri-error-warning-line", "ri-evernote-fill", "ri-evernote-line", "ri-exchange-box-fill", "ri-exchange-box-line", "ri-exchange-cny-fill", "ri-exchange-cny-line", "ri-exchange-dollar-fill", "ri-exchange-dollar-line", "ri-exchange-fill", "ri-exchange-funds-fill", "ri-exchange-funds-line", "ri-exchange-line", "ri-external-link-fill", "ri-external-link-line", "ri-eye-2-fill", "ri-eye-2-line", "ri-eye-close-fill", "ri-eye-close-line", "ri-eye-fill", "ri-eye-line", "ri-eye-off-fill", "ri-eye-off-line", "ri-facebook-box-fill", "ri-facebook-box-line", "ri-facebook-circle-fill", "ri-facebook-circle-line", "ri-facebook-fill", "ri-facebook-line", "ri-fahrenheit-fill", "ri-fahrenheit-line", "ri-feedback-fill", "ri-feedback-line", "ri-file-2-fill", "ri-file-2-line", "ri-file-3-fill", "ri-file-3-line", "ri-file-4-fill", "ri-file-4-line", "ri-file-add-fill", "ri-file-add-line", "ri-file-chart-2-fill", "ri-file-chart-2-line", "ri-file-chart-fill", "ri-file-chart-line", "ri-file-cloud-fill", "ri-file-cloud-line", "ri-file-code-fill", "ri-file-code-line", "ri-file-copy-2-fill", "ri-file-copy-2-line", "ri-file-copy-fill", "ri-file-copy-line", "ri-file-damage-fill", "ri-file-damage-line", "ri-file-download-fill", "ri-file-download-line", "ri-file-edit-fill", "ri-file-edit-line", "ri-file-excel-2-fill", "ri-file-excel-2-line", "ri-file-excel-fill", "ri-file-excel-line", "ri-file-fill", "ri-file-forbid-fill", "ri-file-forbid-line", "ri-file-gif-fill", "ri-file-gif-line", "ri-file-history-fill", "ri-file-history-line", "ri-file-hwp-fill", "ri-file-hwp-line", "ri-file-info-fill", "ri-file-info-line", "ri-file-line", "ri-file-list-2-fill", "ri-file-list-2-line", "ri-file-list-3-fill", "ri-file-list-3-line", "ri-file-list-fill", "ri-file-list-line", "ri-file-lock-fill", "ri-file-lock-line", "ri-file-marked-fill", "ri-file-marked-line", "ri-file-music-fill", "ri-file-music-line", "ri-file-paper-2-fill", "ri-file-paper-2-line", "ri-file-paper-fill", "ri-file-paper-line", "ri-file-pdf-fill", "ri-file-pdf-line", "ri-file-ppt-2-fill", "ri-file-ppt-2-line", "ri-file-ppt-fill", "ri-file-ppt-line", "ri-file-reduce-fill", "ri-file-reduce-line", "ri-file-search-fill", "ri-file-search-line", "ri-file-settings-fill", "ri-file-settings-line", "ri-file-shield-2-fill", "ri-file-shield-2-line", "ri-file-shield-fill", "ri-file-shield-line", "ri-file-shred-fill", "ri-file-shred-line", "ri-file-text-fill", "ri-file-text-line", "ri-file-transfer-fill", "ri-file-transfer-line", "ri-file-unknow-fill", "ri-file-unknow-line", "ri-file-upload-fill", "ri-file-upload-line", "ri-file-user-fill", "ri-file-user-line", "ri-file-warning-fill", "ri-file-warning-line", "ri-file-word-2-fill", "ri-file-word-2-line", "ri-file-word-fill", "ri-file-word-line", "ri-file-zip-fill", "ri-file-zip-line", "ri-film-fill", "ri-film-line", "ri-filter-2-fill", "ri-filter-2-line", "ri-filter-3-fill", "ri-filter-3-line", "ri-filter-fill", "ri-filter-line", "ri-filter-off-fill", "ri-filter-off-line", "ri-find-replace-fill", "ri-find-replace-line", "ri-finder-fill", "ri-finder-line", "ri-fingerprint-2-fill", "ri-fingerprint-2-line", "ri-fingerprint-fill", "ri-fingerprint-line", "ri-fire-fill", "ri-fire-line", "ri-firefox-fill", "ri-firefox-line", "ri-first-aid-kit-fill", "ri-first-aid-kit-line", "ri-flag-2-fill", "ri-flag-2-line", "ri-flag-fill", "ri-flag-line", "ri-flashlight-fill", "ri-flashlight-line", "ri-flask-fill", "ri-flask-line", "ri-flight-land-fill", "ri-flight-land-line", "ri-flight-takeoff-fill", "ri-flight-takeoff-line", "ri-flood-fill", "ri-flood-line", "ri-flow-chart", "ri-flutter-fill", "ri-flutter-line", "ri-focus-2-fill", "ri-focus-2-line", "ri-focus-3-fill", "ri-focus-3-line", "ri-focus-fill", "ri-focus-line", "ri-foggy-fill", "ri-foggy-line", "ri-folder-2-fill", "ri-folder-2-line", "ri-folder-3-fill", "ri-folder-3-line", "ri-folder-4-fill", "ri-folder-4-line", "ri-folder-5-fill", "ri-folder-5-line", "ri-folder-add-fill", "ri-folder-add-line", "ri-folder-chart-2-fill", "ri-folder-chart-2-line", "ri-folder-chart-fill", "ri-folder-chart-line", "ri-folder-download-fill", "ri-folder-download-line", "ri-folder-fill", "ri-folder-forbid-fill", "ri-folder-forbid-line", "ri-folder-history-fill", "ri-folder-history-line", "ri-folder-info-fill", "ri-folder-info-line", "ri-folder-keyhole-fill", "ri-folder-keyhole-line", "ri-folder-line", "ri-folder-lock-fill", "ri-folder-lock-line", "ri-folder-music-fill", "ri-folder-music-line", "ri-folder-open-fill", "ri-folder-open-line", "ri-folder-received-fill", "ri-folder-received-line", "ri-folder-reduce-fill", "ri-folder-reduce-line", "ri-folder-settings-fill", "ri-folder-settings-line", "ri-folder-shared-fill", "ri-folder-shared-line", "ri-folder-shield-2-fill", "ri-folder-shield-2-line", "ri-folder-shield-fill", "ri-folder-shield-line", "ri-folder-transfer-fill", "ri-folder-transfer-line", "ri-folder-unknow-fill", "ri-folder-unknow-line", "ri-folder-upload-fill", "ri-folder-upload-line", "ri-folder-user-fill", "ri-folder-user-line", "ri-folder-warning-fill", "ri-folder-warning-line", "ri-folder-zip-fill", "ri-folder-zip-line", "ri-folders-fill", "ri-folders-line", "ri-font-color", "ri-font-size-2", "ri-font-size", "ri-football-fill", "ri-football-line", "ri-footprint-fill", "ri-footprint-line", "ri-forbid-2-fill", "ri-forbid-2-line", "ri-forbid-fill", "ri-forbid-line", "ri-format-clear", "ri-fridge-fill", "ri-fridge-line", "ri-fullscreen-exit-fill", "ri-fullscreen-exit-line", "ri-fullscreen-fill", "ri-fullscreen-line", "ri-function-fill", "ri-function-line", "ri-functions", "ri-funds-box-fill", "ri-funds-box-line", "ri-funds-fill", "ri-funds-line", "ri-gallery-fill", "ri-gallery-line", "ri-gallery-upload-fill", "ri-gallery-upload-line", "ri-game-fill", "ri-game-line", "ri-gamepad-fill", "ri-gamepad-line", "ri-gas-station-fill", "ri-gas-station-line", "ri-gatsby-fill", "ri-gatsby-line", "ri-genderless-fill", "ri-genderless-line", "ri-ghost-2-fill", "ri-ghost-2-line", "ri-ghost-fill", "ri-ghost-line", "ri-ghost-smile-fill", "ri-ghost-smile-line", "ri-gift-2-fill", "ri-gift-2-line", "ri-gift-fill", "ri-gift-line", "ri-git-branch-fill", "ri-git-branch-line", "ri-git-commit-fill", "ri-git-commit-line", "ri-git-merge-fill", "ri-git-merge-line", "ri-git-pull-request-fill", "ri-git-pull-request-line", "ri-git-repository-commits-fill", "ri-git-repository-commits-line", "ri-git-repository-fill", "ri-git-repository-line", "ri-git-repository-private-fill", "ri-git-repository-private-line", "ri-github-fill", "ri-github-line", "ri-gitlab-fill", "ri-gitlab-line", "ri-global-fill", "ri-global-line", "ri-globe-fill", "ri-globe-line", "ri-goblet-fill", "ri-goblet-line", "ri-google-fill", "ri-google-line", "ri-google-play-fill", "ri-google-play-line", "ri-government-fill", "ri-government-line", "ri-gps-fill", "ri-gps-line", "ri-gradienter-fill", "ri-gradienter-line", "ri-grid-fill", "ri-grid-line", "ri-group-2-fill", "ri-group-2-line", "ri-group-fill", "ri-group-line", "ri-guide-fill", "ri-guide-line", "ri-h-1", "ri-h-2", "ri-h-3", "ri-h-4", "ri-h-5", "ri-h-6", "ri-hail-fill", "ri-hail-line", "ri-hammer-fill", "ri-hammer-line", "ri-hand-coin-fill", "ri-hand-coin-line", "ri-hand-heart-fill", "ri-hand-heart-line", "ri-hand-sanitizer-fill", "ri-hand-sanitizer-line", "ri-handbag-fill", "ri-handbag-line", "ri-hard-drive-2-fill", "ri-hard-drive-2-line", "ri-hard-drive-fill", "ri-hard-drive-line", "ri-hashtag", "ri-haze-2-fill", "ri-haze-2-line", "ri-haze-fill", "ri-haze-line", "ri-hd-fill", "ri-hd-line", "ri-heading", "ri-headphone-fill", "ri-headphone-line", "ri-health-book-fill", "ri-health-book-line", "ri-heart-2-fill", "ri-heart-2-line", "ri-heart-3-fill", "ri-heart-3-line", "ri-heart-add-fill", "ri-heart-add-line", "ri-heart-fill", "ri-heart-line", "ri-heart-pulse-fill", "ri-heart-pulse-line", "ri-hearts-fill", "ri-hearts-line", "ri-heavy-showers-fill", "ri-heavy-showers-line", "ri-history-fill", "ri-history-line", "ri-home-2-fill", "ri-home-2-line", "ri-home-3-fill", "ri-home-3-line", "ri-home-4-fill", "ri-home-4-line", "ri-home-5-fill", "ri-home-5-line", "ri-home-6-fill", "ri-home-6-line", "ri-home-7-fill", "ri-home-7-line", "ri-home-8-fill", "ri-home-8-line", "ri-home-fill", "ri-home-gear-fill", "ri-home-gear-line", "ri-home-heart-fill", "ri-home-heart-line", "ri-home-line", "ri-home-smile-2-fill", "ri-home-smile-2-line", "ri-home-smile-fill", "ri-home-smile-line", "ri-home-wifi-fill", "ri-home-wifi-line", "ri-honor-of-kings-fill", "ri-honor-of-kings-line", "ri-honour-fill", "ri-honour-line", "ri-hospital-fill", "ri-hospital-line", "ri-hotel-bed-fill", "ri-hotel-bed-line", "ri-hotel-fill", "ri-hotel-line", "ri-hotspot-fill", "ri-hotspot-line", "ri-hq-fill", "ri-hq-line", "ri-html5-fill", "ri-html5-line", "ri-ie-fill", "ri-ie-line", "ri-image-2-fill", "ri-image-2-line", "ri-image-add-fill", "ri-image-add-line", "ri-image-edit-fill", "ri-image-edit-line", "ri-image-fill", "ri-image-line", "ri-inbox-archive-fill", "ri-inbox-archive-line", "ri-inbox-fill", "ri-inbox-line", "ri-inbox-unarchive-fill", "ri-inbox-unarchive-line", "ri-increase-decrease-fill", "ri-increase-decrease-line", "ri-indent-decrease", "ri-indent-increase", "ri-indeterminate-circle-fill", "ri-indeterminate-circle-line", "ri-information-fill", "ri-information-line", "ri-infrared-thermometer-fill", "ri-infrared-thermometer-line", "ri-ink-bottle-fill", "ri-ink-bottle-line", "ri-input-cursor-move", "ri-input-method-fill", "ri-input-method-line", "ri-insert-column-left", "ri-insert-column-right", "ri-insert-row-bottom", "ri-insert-row-top", "ri-instagram-fill", "ri-instagram-line", "ri-install-fill", "ri-install-line", "ri-invision-fill", "ri-invision-line", "ri-italic", "ri-kakao-talk-fill", "ri-kakao-talk-line", "ri-key-2-fill", "ri-key-2-line", "ri-key-fill", "ri-key-line", "ri-keyboard-box-fill", "ri-keyboard-box-line", "ri-keyboard-fill", "ri-keyboard-line", "ri-keynote-fill", "ri-keynote-line", "ri-knife-blood-fill", "ri-knife-blood-line", "ri-knife-fill", "ri-knife-line", "ri-landscape-fill", "ri-landscape-line", "ri-layout-2-fill", "ri-layout-2-line", "ri-layout-3-fill", "ri-layout-3-line", "ri-layout-4-fill", "ri-layout-4-line", "ri-layout-5-fill", "ri-layout-5-line", "ri-layout-6-fill", "ri-layout-6-line", "ri-layout-bottom-2-fill", "ri-layout-bottom-2-line", "ri-layout-bottom-fill", "ri-layout-bottom-line", "ri-layout-column-fill", "ri-layout-column-line", "ri-layout-fill", "ri-layout-grid-fill", "ri-layout-grid-line", "ri-layout-left-2-fill", "ri-layout-left-2-line", "ri-layout-left-fill", "ri-layout-left-line", "ri-layout-line", "ri-layout-masonry-fill", "ri-layout-masonry-line", "ri-layout-right-2-fill", "ri-layout-right-2-line", "ri-layout-right-fill", "ri-layout-right-line", "ri-layout-row-fill", "ri-layout-row-line", "ri-layout-top-2-fill", "ri-layout-top-2-line", "ri-layout-top-fill", "ri-layout-top-line", "ri-leaf-fill", "ri-leaf-line", "ri-lifebuoy-fill", "ri-lifebuoy-line", "ri-lightbulb-fill", "ri-lightbulb-flash-fill", "ri-lightbulb-flash-line", "ri-lightbulb-line", "ri-line-chart-fill", "ri-line-chart-line", "ri-line-fill", "ri-line-height", "ri-line-line", "ri-link-m", "ri-link-unlink-m", "ri-link-unlink", "ri-link", "ri-linkedin-box-fill", "ri-linkedin-box-line", "ri-linkedin-fill", "ri-linkedin-line", "ri-links-fill", "ri-links-line", "ri-list-check-2", "ri-list-check", "ri-list-ordered", "ri-list-settings-fill", "ri-list-settings-line", "ri-list-unordered", "ri-live-fill", "ri-live-line", "ri-loader-2-fill", "ri-loader-2-line", "ri-loader-3-fill", "ri-loader-3-line", "ri-loader-4-fill", "ri-loader-4-line", "ri-loader-5-fill", "ri-loader-5-line", "ri-loader-fill", "ri-loader-line", "ri-lock-2-fill", "ri-lock-2-line", "ri-lock-fill", "ri-lock-line", "ri-lock-password-fill", "ri-lock-password-line", "ri-lock-unlock-fill", "ri-lock-unlock-line", "ri-login-box-fill", "ri-login-box-line", "ri-login-circle-fill", "ri-login-circle-line", "ri-logout-box-fill", "ri-logout-box-line", "ri-logout-box-r-fill", "ri-logout-box-r-line", "ri-logout-circle-fill", "ri-logout-circle-line", "ri-logout-circle-r-fill", "ri-logout-circle-r-line", "ri-luggage-cart-fill", "ri-luggage-cart-line", "ri-luggage-deposit-fill", "ri-luggage-deposit-line", "ri-lungs-fill", "ri-lungs-line", "ri-mac-fill", "ri-mac-line", "ri-macbook-fill", "ri-macbook-line", "ri-magic-fill", "ri-magic-line", "ri-mail-add-fill", "ri-mail-add-line", "ri-mail-check-fill", "ri-mail-check-line", "ri-mail-close-fill", "ri-mail-close-line", "ri-mail-download-fill", "ri-mail-download-line", "ri-mail-fill", "ri-mail-forbid-fill", "ri-mail-forbid-line", "ri-mail-line", "ri-mail-lock-fill", "ri-mail-lock-line", "ri-mail-open-fill", "ri-mail-open-line", "ri-mail-send-fill", "ri-mail-send-line", "ri-mail-settings-fill", "ri-mail-settings-line", "ri-mail-star-fill", "ri-mail-star-line", "ri-mail-unread-fill", "ri-mail-unread-line", "ri-mail-volume-fill", "ri-mail-volume-line", "ri-map-2-fill", "ri-map-2-line", "ri-map-fill", "ri-map-line", "ri-map-pin-2-fill", "ri-map-pin-2-line", "ri-map-pin-3-fill", "ri-map-pin-3-line", "ri-map-pin-4-fill", "ri-map-pin-4-line", "ri-map-pin-5-fill", "ri-map-pin-5-line", "ri-map-pin-add-fill", "ri-map-pin-add-line", "ri-map-pin-fill", "ri-map-pin-line", "ri-map-pin-range-fill", "ri-map-pin-range-line", "ri-map-pin-time-fill", "ri-map-pin-time-line", "ri-map-pin-user-fill", "ri-map-pin-user-line", "ri-mark-pen-fill", "ri-mark-pen-line", "ri-markdown-fill", "ri-markdown-line", "ri-markup-fill", "ri-markup-line", "ri-mastercard-fill", "ri-mastercard-line", "ri-mastodon-fill", "ri-mastodon-line", "ri-medal-2-fill", "ri-medal-2-line", "ri-medal-fill", "ri-medal-line", "ri-medicine-bottle-fill", "ri-medicine-bottle-line", "ri-medium-fill", "ri-medium-line", "ri-men-fill", "ri-men-line", "ri-mental-health-fill", "ri-mental-health-line", "ri-menu-2-fill", "ri-menu-2-line", "ri-menu-3-fill", "ri-menu-3-line", "ri-menu-4-fill", "ri-menu-4-line", "ri-menu-5-fill", "ri-menu-5-line", "ri-menu-add-fill", "ri-menu-add-line", "ri-menu-fill", "ri-menu-fold-fill", "ri-menu-fold-line", "ri-menu-line", "ri-menu-unfold-fill", "ri-menu-unfold-line", "ri-merge-cells-horizontal", "ri-merge-cells-vertical", "ri-message-2-fill", "ri-message-2-line", "ri-message-3-fill", "ri-message-3-line", "ri-message-fill", "ri-message-line", "ri-messenger-fill", "ri-messenger-line", "ri-meteor-fill", "ri-meteor-line", "ri-mic-2-fill", "ri-mic-2-line", "ri-mic-fill", "ri-mic-line", "ri-mic-off-fill", "ri-mic-off-line", "ri-mickey-fill", "ri-mickey-line", "ri-microscope-fill", "ri-microscope-line", "ri-microsoft-fill", "ri-microsoft-line", "ri-mind-map", "ri-mini-program-fill", "ri-mini-program-line", "ri-mist-fill", "ri-mist-line", "ri-money-cny-box-fill", "ri-money-cny-box-line", "ri-money-cny-circle-fill", "ri-money-cny-circle-line", "ri-money-dollar-box-fill", "ri-money-dollar-box-line", "ri-money-dollar-circle-fill", "ri-money-dollar-circle-line", "ri-money-euro-box-fill", "ri-money-euro-box-line", "ri-money-euro-circle-fill", "ri-money-euro-circle-line", "ri-money-pound-box-fill", "ri-money-pound-box-line", "ri-money-pound-circle-fill", "ri-money-pound-circle-line", "ri-moon-clear-fill", "ri-moon-clear-line", "ri-moon-cloudy-fill", "ri-moon-cloudy-line", "ri-moon-fill", "ri-moon-foggy-fill", "ri-moon-foggy-line", "ri-moon-line", "ri-more-2-fill", "ri-more-2-line", "ri-more-fill", "ri-more-line", "ri-motorbike-fill", "ri-motorbike-line", "ri-mouse-fill", "ri-mouse-line", "ri-movie-2-fill", "ri-movie-2-line", "ri-movie-fill", "ri-movie-line", "ri-music-2-fill", "ri-music-2-line", "ri-music-fill", "ri-music-line", "ri-mv-fill", "ri-mv-line", "ri-navigation-fill", "ri-navigation-line", "ri-netease-cloud-music-fill", "ri-netease-cloud-music-line", "ri-netflix-fill", "ri-netflix-line", "ri-newspaper-fill", "ri-newspaper-line", "ri-node-tree", "ri-notification-2-fill", "ri-notification-2-line", "ri-notification-3-fill", "ri-notification-3-line", "ri-notification-4-fill", "ri-notification-4-line", "ri-notification-badge-fill", "ri-notification-badge-line", "ri-notification-fill", "ri-notification-line", "ri-notification-off-fill", "ri-notification-off-line", "ri-npmjs-fill", "ri-npmjs-line", "ri-number-0", "ri-number-1", "ri-number-2", "ri-number-3", "ri-number-4", "ri-number-5", "ri-number-6", "ri-number-7", "ri-number-8", "ri-number-9", "ri-numbers-fill", "ri-numbers-line", "ri-nurse-fill", "ri-nurse-line", "ri-oil-fill", "ri-oil-line", "ri-omega", "ri-open-arm-fill", "ri-open-arm-line", "ri-open-source-fill", "ri-open-source-line", "ri-opera-fill", "ri-opera-line", "ri-order-play-fill", "ri-order-play-line", "ri-organization-chart", "ri-outlet-2-fill", "ri-outlet-2-line", "ri-outlet-fill", "ri-outlet-line", "ri-page-separator", "ri-pages-fill", "ri-pages-line", "ri-paint-brush-fill", "ri-paint-brush-line", "ri-paint-fill", "ri-paint-line", "ri-palette-fill", "ri-palette-line", "ri-pantone-fill", "ri-pantone-line", "ri-paragraph", "ri-parent-fill", "ri-parent-line", "ri-parentheses-fill", "ri-parentheses-line", "ri-parking-box-fill", "ri-parking-box-line", "ri-parking-fill", "ri-parking-line", "ri-passport-fill", "ri-passport-line", "ri-patreon-fill", "ri-patreon-line", "ri-pause-circle-fill", "ri-pause-circle-line", "ri-pause-fill", "ri-pause-line", "ri-pause-mini-fill", "ri-pause-mini-line", "ri-paypal-fill", "ri-paypal-line", "ri-pen-nib-fill", "ri-pen-nib-line", "ri-pencil-fill", "ri-pencil-line", "ri-pencil-ruler-2-fill", "ri-pencil-ruler-2-line", "ri-pencil-ruler-fill", "ri-pencil-ruler-line", "ri-percent-fill", "ri-percent-line", "ri-phone-camera-fill", "ri-phone-camera-line", "ri-phone-fill", "ri-phone-find-fill", "ri-phone-find-line", "ri-phone-line", "ri-phone-lock-fill", "ri-phone-lock-line", "ri-picture-in-picture-2-fill", "ri-picture-in-picture-2-line", "ri-picture-in-picture-exit-fill", "ri-picture-in-picture-exit-line", "ri-picture-in-picture-fill", "ri-picture-in-picture-line", "ri-pie-chart-2-fill", "ri-pie-chart-2-line", "ri-pie-chart-box-fill", "ri-pie-chart-box-line", "ri-pie-chart-fill", "ri-pie-chart-line", "ri-pin-distance-fill", "ri-pin-distance-line", "ri-ping-pong-fill", "ri-ping-pong-line", "ri-pinterest-fill", "ri-pinterest-line", "ri-pinyin-input", "ri-pixelfed-fill", "ri-pixelfed-line", "ri-plane-fill", "ri-plane-line", "ri-plant-fill", "ri-plant-line", "ri-play-circle-fill", "ri-play-circle-line", "ri-play-fill", "ri-play-line", "ri-play-list-2-fill", "ri-play-list-2-line", "ri-play-list-add-fill", "ri-play-list-add-line", "ri-play-list-fill", "ri-play-list-line", "ri-play-mini-fill", "ri-play-mini-line", "ri-playstation-fill", "ri-playstation-line", "ri-plug-2-fill", "ri-plug-2-line", "ri-plug-fill", "ri-plug-line", "ri-polaroid-2-fill", "ri-polaroid-2-line", "ri-polaroid-fill", "ri-polaroid-line", "ri-police-car-fill", "ri-police-car-line", "ri-price-tag-2-fill", "ri-price-tag-2-line", "ri-price-tag-3-fill", "ri-price-tag-3-line", "ri-price-tag-fill", "ri-price-tag-line", "ri-printer-cloud-fill", "ri-printer-cloud-line", "ri-printer-fill", "ri-printer-line", "ri-product-hunt-fill", "ri-product-hunt-line", "ri-profile-fill", "ri-profile-line", "ri-projector-2-fill", "ri-projector-2-line", "ri-projector-fill", "ri-projector-line", "ri-psychotherapy-fill", "ri-psychotherapy-line", "ri-pulse-fill", "ri-pulse-line", "ri-pushpin-2-fill", "ri-pushpin-2-line", "ri-pushpin-fill", "ri-pushpin-line", "ri-qq-fill", "ri-qq-line", "ri-qr-code-fill", "ri-qr-code-line", "ri-qr-scan-2-fill", "ri-qr-scan-2-line", "ri-qr-scan-fill", "ri-qr-scan-line", "ri-question-answer-fill", "ri-question-answer-line", "ri-question-fill", "ri-question-line", "ri-question-mark", "ri-questionnaire-fill", "ri-questionnaire-line", "ri-quill-pen-fill", "ri-quill-pen-line", "ri-radar-fill", "ri-radar-line", "ri-radio-2-fill", "ri-radio-2-line", "ri-radio-button-fill", "ri-radio-button-line", "ri-radio-fill", "ri-radio-line", "ri-rainbow-fill", "ri-rainbow-line", "ri-rainy-fill", "ri-rainy-line", "ri-reactjs-fill", "ri-reactjs-line", "ri-record-circle-fill", "ri-record-circle-line", "ri-record-mail-fill", "ri-record-mail-line", "ri-recycle-fill", "ri-recycle-line", "ri-red-packet-fill", "ri-red-packet-line", "ri-reddit-fill", "ri-reddit-line", "ri-refresh-fill", "ri-refresh-line", "ri-refund-2-fill", "ri-refund-2-line", "ri-refund-fill", "ri-refund-line", "ri-registered-fill", "ri-registered-line", "ri-remixicon-fill", "ri-remixicon-line", "ri-remote-control-2-fill", "ri-remote-control-2-line", "ri-remote-control-fill", "ri-remote-control-line", "ri-repeat-2-fill", "ri-repeat-2-line", "ri-repeat-fill", "ri-repeat-line", "ri-repeat-one-fill", "ri-repeat-one-line", "ri-reply-all-fill", "ri-reply-all-line", "ri-reply-fill", "ri-reply-line", "ri-reserved-fill", "ri-reserved-line", "ri-rest-time-fill", "ri-rest-time-line", "ri-restart-fill", "ri-restart-line", "ri-restaurant-2-fill", "ri-restaurant-2-line", "ri-restaurant-fill", "ri-restaurant-line", "ri-rewind-fill", "ri-rewind-line", "ri-rewind-mini-fill", "ri-rewind-mini-line", "ri-rhythm-fill", "ri-rhythm-line", "ri-riding-fill", "ri-riding-line", "ri-road-map-fill", "ri-road-map-line", "ri-roadster-fill", "ri-roadster-line", "ri-robot-fill", "ri-robot-line", "ri-rocket-2-fill", "ri-rocket-2-line", "ri-rocket-fill", "ri-rocket-line", "ri-rotate-lock-fill", "ri-rotate-lock-line", "ri-rounded-corner", "ri-route-fill", "ri-route-line", "ri-router-fill", "ri-router-line", "ri-rss-fill", "ri-rss-line", "ri-ruler-2-fill", "ri-ruler-2-line", "ri-ruler-fill", "ri-ruler-line", "ri-run-fill", "ri-run-line", "ri-safari-fill", "ri-safari-line", "ri-safe-2-fill", "ri-safe-2-line", "ri-safe-fill", "ri-safe-line", "ri-sailboat-fill", "ri-sailboat-line", "ri-save-2-fill", "ri-save-2-line", "ri-save-3-fill", "ri-save-3-line", "ri-save-fill", "ri-save-line", "ri-scales-2-fill", "ri-scales-2-line", "ri-scales-3-fill", "ri-scales-3-line", "ri-scales-fill", "ri-scales-line", "ri-scan-2-fill", "ri-scan-2-line", "ri-scan-fill", "ri-scan-line", "ri-scissors-2-fill", "ri-scissors-2-line", "ri-scissors-cut-fill", "ri-scissors-cut-line", "ri-scissors-fill", "ri-scissors-line", "ri-screenshot-2-fill", "ri-screenshot-2-line", "ri-screenshot-fill", "ri-screenshot-line", "ri-sd-card-fill", "ri-sd-card-line", "ri-sd-card-mini-fill", "ri-sd-card-mini-line", "ri-search-2-fill", "ri-search-2-line", "ri-search-eye-fill", "ri-search-eye-line", "ri-search-fill", "ri-search-line", "ri-secure-payment-fill", "ri-secure-payment-line", "ri-seedling-fill", "ri-seedling-line", "ri-send-backward", "ri-send-plane-2-fill", "ri-send-plane-2-line", "ri-send-plane-fill", "ri-send-plane-line", "ri-send-to-back", "ri-sensor-fill", "ri-sensor-line", "ri-separator", "ri-server-fill", "ri-server-line", "ri-service-fill", "ri-service-line", "ri-settings-2-fill", "ri-settings-2-line", "ri-settings-3-fill", "ri-settings-3-line", "ri-settings-4-fill", "ri-settings-4-line", "ri-settings-5-fill", "ri-settings-5-line", "ri-settings-6-fill", "ri-settings-6-line", "ri-settings-fill", "ri-settings-line", "ri-shape-2-fill", "ri-shape-2-line", "ri-shape-fill", "ri-shape-line", "ri-share-box-fill", "ri-share-box-line", "ri-share-circle-fill", "ri-share-circle-line", "ri-share-fill", "ri-share-forward-2-fill", "ri-share-forward-2-line", "ri-share-forward-box-fill", "ri-share-forward-box-line", "ri-share-forward-fill", "ri-share-forward-line", "ri-share-line", "ri-shield-check-fill", "ri-shield-check-line", "ri-shield-cross-fill", "ri-shield-cross-line", "ri-shield-fill", "ri-shield-flash-fill", "ri-shield-flash-line", "ri-shield-keyhole-fill", "ri-shield-keyhole-line", "ri-shield-line", "ri-shield-star-fill", "ri-shield-star-line", "ri-shield-user-fill", "ri-shield-user-line", "ri-ship-2-fill", "ri-ship-2-line", "ri-ship-fill", "ri-ship-line", "ri-shirt-fill", "ri-shirt-line", "ri-shopping-bag-2-fill", "ri-shopping-bag-2-line", "ri-shopping-bag-3-fill", "ri-shopping-bag-3-line", "ri-shopping-bag-fill", "ri-shopping-bag-line", "ri-shopping-basket-2-fill", "ri-shopping-basket-2-line", "ri-shopping-basket-fill", "ri-shopping-basket-line", "ri-shopping-cart-2-fill", "ri-shopping-cart-2-line", "ri-shopping-cart-fill", "ri-shopping-cart-line", "ri-showers-fill", "ri-showers-line", "ri-shuffle-fill", "ri-shuffle-line", "ri-shut-down-fill", "ri-shut-down-line", "ri-side-bar-fill", "ri-side-bar-line", "ri-signal-tower-fill", "ri-signal-tower-line", "ri-signal-wifi-1-fill", "ri-signal-wifi-1-line", "ri-signal-wifi-2-fill", "ri-signal-wifi-2-line", "ri-signal-wifi-3-fill", "ri-signal-wifi-3-line", "ri-signal-wifi-error-fill", "ri-signal-wifi-error-line", "ri-signal-wifi-fill", "ri-signal-wifi-line", "ri-signal-wifi-off-fill", "ri-signal-wifi-off-line", "ri-sim-card-2-fill", "ri-sim-card-2-line", "ri-sim-card-fill", "ri-sim-card-line", "ri-single-quotes-l", "ri-single-quotes-r", "ri-sip-fill", "ri-sip-line", "ri-skip-back-fill", "ri-skip-back-line", "ri-skip-back-mini-fill", "ri-skip-back-mini-line", "ri-skip-forward-fill", "ri-skip-forward-line", "ri-skip-forward-mini-fill", "ri-skip-forward-mini-line", "ri-skull-2-fill", "ri-skull-2-line", "ri-skull-fill", "ri-skull-line", "ri-skype-fill", "ri-skype-line", "ri-slack-fill", "ri-slack-line", "ri-slice-fill", "ri-slice-line", "ri-slideshow-2-fill", "ri-slideshow-2-line", "ri-slideshow-3-fill", "ri-slideshow-3-line", "ri-slideshow-4-fill", "ri-slideshow-4-line", "ri-slideshow-fill", "ri-slideshow-line", "ri-smartphone-fill", "ri-smartphone-line", "ri-snapchat-fill", "ri-snapchat-line", "ri-snowy-fill", "ri-snowy-line", "ri-sort-asc", "ri-sort-desc", "ri-sound-module-fill", "ri-sound-module-line", "ri-soundcloud-fill", "ri-soundcloud-line", "ri-space-ship-fill", "ri-space-ship-line", "ri-space", "ri-spam-2-fill", "ri-spam-2-line", "ri-spam-3-fill", "ri-spam-3-line", "ri-spam-fill", "ri-spam-line", "ri-speaker-2-fill", "ri-speaker-2-line", "ri-speaker-3-fill", "ri-speaker-3-line", "ri-speaker-fill", "ri-speaker-line", "ri-spectrum-fill", "ri-spectrum-line", "ri-speed-fill", "ri-speed-line", "ri-speed-mini-fill", "ri-speed-mini-line", "ri-split-cells-horizontal", "ri-split-cells-vertical", "ri-spotify-fill", "ri-spotify-line", "ri-spy-fill", "ri-spy-line", "ri-stack-fill", "ri-stack-line", "ri-stack-overflow-fill", "ri-stack-overflow-line", "ri-stackshare-fill", "ri-stackshare-line", "ri-star-fill", "ri-star-half-fill", "ri-star-half-line", "ri-star-half-s-fill", "ri-star-half-s-line", "ri-star-line", "ri-star-s-fill", "ri-star-s-line", "ri-star-smile-fill", "ri-star-smile-line", "ri-steam-fill", "ri-steam-line", "ri-steering-2-fill", "ri-steering-2-line", "ri-steering-fill", "ri-steering-line", "ri-stethoscope-fill", "ri-stethoscope-line", "ri-sticky-note-2-fill", "ri-sticky-note-2-line", "ri-sticky-note-fill", "ri-sticky-note-line", "ri-stock-fill", "ri-stock-line", "ri-stop-circle-fill", "ri-stop-circle-line", "ri-stop-fill", "ri-stop-line", "ri-stop-mini-fill", "ri-stop-mini-line", "ri-store-2-fill", "ri-store-2-line", "ri-store-3-fill", "ri-store-3-line", "ri-store-fill", "ri-store-line", "ri-strikethrough-2", "ri-strikethrough", "ri-subscript-2", "ri-subscript", "ri-subtract-fill", "ri-subtract-line", "ri-subway-fill", "ri-subway-line", "ri-subway-wifi-fill", "ri-subway-wifi-line", "ri-suitcase-2-fill", "ri-suitcase-2-line", "ri-suitcase-3-fill", "ri-suitcase-3-line", "ri-suitcase-fill", "ri-suitcase-line", "ri-sun-cloudy-fill", "ri-sun-cloudy-line", "ri-sun-fill", "ri-sun-foggy-fill", "ri-sun-foggy-line", "ri-sun-line", "ri-superscript-2", "ri-superscript", "ri-surgical-mask-fill", "ri-surgical-mask-line", "ri-surround-sound-fill", "ri-surround-sound-line", "ri-survey-fill", "ri-survey-line", "ri-swap-box-fill", "ri-swap-box-line", "ri-swap-fill", "ri-swap-line", "ri-switch-fill", "ri-switch-line", "ri-sword-fill", "ri-sword-line", "ri-syringe-fill", "ri-syringe-line", "ri-t-box-fill", "ri-t-box-line", "ri-t-shirt-2-fill", "ri-t-shirt-2-line", "ri-t-shirt-air-fill", "ri-t-shirt-air-line", "ri-t-shirt-fill", "ri-t-shirt-line", "ri-table-2", "ri-table-alt-fill", "ri-table-alt-line", "ri-table-fill", "ri-table-line", "ri-tablet-fill", "ri-tablet-line", "ri-takeaway-fill", "ri-takeaway-line", "ri-taobao-fill", "ri-taobao-line", "ri-tape-fill", "ri-tape-line", "ri-task-fill", "ri-task-line", "ri-taxi-fill", "ri-taxi-line", "ri-taxi-wifi-fill", "ri-taxi-wifi-line", "ri-team-fill", "ri-team-line", "ri-telegram-fill", "ri-telegram-line", "ri-temp-cold-fill", "ri-temp-cold-line", "ri-temp-hot-fill", "ri-temp-hot-line", "ri-terminal-box-fill", "ri-terminal-box-line", "ri-terminal-fill", "ri-terminal-line", "ri-terminal-window-fill", "ri-terminal-window-line", "ri-test-tube-fill", "ri-test-tube-line", "ri-text-direction-l", "ri-text-direction-r", "ri-text-spacing", "ri-text-wrap", "ri-text", "ri-thermometer-fill", "ri-thermometer-line", "ri-thumb-down-fill", "ri-thumb-down-line", "ri-thumb-up-fill", "ri-thumb-up-line", "ri-thunderstorms-fill", "ri-thunderstorms-line", "ri-ticket-2-fill", "ri-ticket-2-line", "ri-ticket-fill", "ri-ticket-line", "ri-time-fill", "ri-time-line", "ri-timer-2-fill", "ri-timer-2-line", "ri-timer-fill", "ri-timer-flash-fill", "ri-timer-flash-line", "ri-timer-line", "ri-todo-fill", "ri-todo-line", "ri-toggle-fill", "ri-toggle-line", "ri-tools-fill", "ri-tools-line", "ri-tornado-fill", "ri-tornado-line", "ri-trademark-fill", "ri-trademark-line", "ri-traffic-light-fill", "ri-traffic-light-line", "ri-train-fill", "ri-train-line", "ri-train-wifi-fill", "ri-train-wifi-line", "ri-translate-2", "ri-translate", "ri-travesti-fill", "ri-travesti-line", "ri-treasure-map-fill", "ri-treasure-map-line", "ri-trello-fill", "ri-trello-line", "ri-trophy-fill", "ri-trophy-line", "ri-truck-fill", "ri-truck-line", "ri-tumblr-fill", "ri-tumblr-line", "ri-tv-2-fill", "ri-tv-2-line", "ri-tv-fill", "ri-tv-line", "ri-twitch-fill", "ri-twitch-line", "ri-twitter-fill", "ri-twitter-line", "ri-typhoon-fill", "ri-typhoon-line", "ri-u-disk-fill", "ri-u-disk-line", "ri-ubuntu-fill", "ri-ubuntu-line", "ri-umbrella-fill", "ri-umbrella-line", "ri-underline", "ri-uninstall-fill", "ri-uninstall-line", "ri-unsplash-fill", "ri-unsplash-line", "ri-upload-2-fill", "ri-upload-2-line", "ri-upload-cloud-2-fill", "ri-upload-cloud-2-line", "ri-upload-cloud-fill", "ri-upload-cloud-line", "ri-upload-fill", "ri-upload-line", "ri-usb-fill", "ri-usb-line", "ri-user-2-fill", "ri-user-2-line", "ri-user-3-fill", "ri-user-3-line", "ri-user-4-fill", "ri-user-4-line", "ri-user-5-fill", "ri-user-5-line", "ri-user-6-fill", "ri-user-6-line", "ri-user-add-fill", "ri-user-add-line", "ri-user-fill", "ri-user-follow-fill", "ri-user-follow-line", "ri-user-heart-fill", "ri-user-heart-line", "ri-user-line", "ri-user-location-fill", "ri-user-location-line", "ri-user-received-2-fill", "ri-user-received-2-line", "ri-user-received-fill", "ri-user-received-line", "ri-user-search-fill", "ri-user-search-line", "ri-user-settings-fill", "ri-user-settings-line", "ri-user-shared-2-fill", "ri-user-shared-2-line", "ri-user-shared-fill", "ri-user-shared-line", "ri-user-smile-fill", "ri-user-smile-line", "ri-user-star-fill", "ri-user-star-line", "ri-user-unfollow-fill", "ri-user-unfollow-line", "ri-user-voice-fill", "ri-user-voice-line", "ri-video-add-fill", "ri-video-add-line", "ri-video-chat-fill", "ri-video-chat-line", "ri-video-download-fill", "ri-video-download-line", "ri-video-fill", "ri-video-line", "ri-video-upload-fill", "ri-video-upload-line", "ri-vidicon-2-fill", "ri-vidicon-2-line", "ri-vidicon-fill", "ri-vidicon-line", "ri-vimeo-fill", "ri-vimeo-line", "ri-vip-crown-2-fill", "ri-vip-crown-2-line", "ri-vip-crown-fill", "ri-vip-crown-line", "ri-vip-diamond-fill", "ri-vip-diamond-line", "ri-vip-fill", "ri-vip-line", "ri-virus-fill", "ri-virus-line", "ri-visa-fill", "ri-visa-line", "ri-voice-recognition-fill", "ri-voice-recognition-line", "ri-voiceprint-fill", "ri-voiceprint-line", "ri-volume-down-fill", "ri-volume-down-line", "ri-volume-mute-fill", "ri-volume-mute-line", "ri-volume-off-vibrate-fill", "ri-volume-off-vibrate-line", "ri-volume-up-fill", "ri-volume-up-line", "ri-volume-vibrate-fill", "ri-volume-vibrate-line", "ri-vuejs-fill", "ri-vuejs-line", "ri-walk-fill", "ri-walk-line", "ri-wallet-2-fill", "ri-wallet-2-line", "ri-wallet-3-fill", "ri-wallet-3-line", "ri-wallet-fill", "ri-wallet-line", "ri-water-flash-fill", "ri-water-flash-line", "ri-webcam-fill", "ri-webcam-line", "ri-wechat-2-fill", "ri-wechat-2-line", "ri-wechat-fill", "ri-wechat-line", "ri-wechat-pay-fill", "ri-wechat-pay-line", "ri-weibo-fill", "ri-weibo-line", "ri-whatsapp-fill", "ri-whatsapp-line", "ri-wheelchair-fill", "ri-wheelchair-line", "ri-wifi-fill", "ri-wifi-line", "ri-wifi-off-fill", "ri-wifi-off-line", "ri-window-2-fill", "ri-window-2-line", "ri-window-fill", "ri-window-line", "ri-windows-fill", "ri-windows-line", "ri-windy-fill", "ri-windy-line", "ri-wireless-charging-fill", "ri-wireless-charging-line", "ri-women-fill", "ri-women-line", "ri-wubi-input", "ri-xbox-fill", "ri-xbox-line", "ri-xing-fill", "ri-xing-line", "ri-youtube-fill", "ri-youtube-line", "ri-zcool-fill", "ri-zcool-line", "ri-zhihu-fill", "ri-zhihu-line", "ri-zoom-in-fill", "ri-zoom-in-line", "ri-zoom-out-fill", "ri-zoom-out-line", "ri-zzz-fill", "ri-zzz-line", "ri-arrow-down-double-fill", "ri-arrow-down-double-line", "ri-arrow-left-double-fill", "ri-arrow-left-double-line", "ri-arrow-right-double-fill", "ri-arrow-right-double-line", "ri-arrow-turn-back-fill", "ri-arrow-turn-back-line", "ri-arrow-turn-forward-fill", "ri-arrow-turn-forward-line", "ri-arrow-up-double-fill", "ri-arrow-up-double-line", "ri-bard-fill", "ri-bard-line", "ri-bootstrap-fill", "ri-bootstrap-line", "ri-box-1-fill", "ri-box-1-line", "ri-box-2-fill", "ri-box-2-line", "ri-box-3-fill", "ri-box-3-line", "ri-brain-fill", "ri-brain-line", "ri-candle-fill", "ri-candle-line", "ri-cash-fill", "ri-cash-line", "ri-contract-left-fill", "ri-contract-left-line", "ri-contract-left-right-fill", "ri-contract-left-right-line", "ri-contract-right-fill", "ri-contract-right-line", "ri-contract-up-down-fill", "ri-contract-up-down-line", "ri-copilot-fill", "ri-copilot-line", "ri-corner-down-left-fill", "ri-corner-down-left-line", "ri-corner-down-right-fill", "ri-corner-down-right-line", "ri-corner-left-down-fill", "ri-corner-left-down-line", "ri-corner-left-up-fill", "ri-corner-left-up-line", "ri-corner-right-down-fill", "ri-corner-right-down-line", "ri-corner-right-up-fill", "ri-corner-right-up-line", "ri-corner-up-left-double-fill", "ri-corner-up-left-double-line", "ri-corner-up-left-fill", "ri-corner-up-left-line", "ri-corner-up-right-double-fill", "ri-corner-up-right-double-line", "ri-corner-up-right-fill", "ri-corner-up-right-line", "ri-cross-fill", "ri-cross-line", "ri-edge-new-fill", "ri-edge-new-line", "ri-equal-fill", "ri-equal-line", "ri-expand-left-fill", "ri-expand-left-line", "ri-expand-left-right-fill", "ri-expand-left-right-line", "ri-expand-right-fill", "ri-expand-right-line", "ri-expand-up-down-fill", "ri-expand-up-down-line", "ri-flickr-fill", "ri-flickr-line", "ri-forward-10-fill", "ri-forward-10-line", "ri-forward-15-fill", "ri-forward-15-line", "ri-forward-30-fill", "ri-forward-30-line", "ri-forward-5-fill", "ri-forward-5-line", "ri-graduation-cap-fill", "ri-graduation-cap-line", "ri-home-office-fill", "ri-home-office-line", "ri-hourglass-2-fill", "ri-hourglass-2-line", "ri-hourglass-fill", "ri-hourglass-line", "ri-javascript-fill", "ri-javascript-line", "ri-loop-left-fill", "ri-loop-left-line", "ri-loop-right-fill", "ri-loop-right-line", "ri-memories-fill", "ri-memories-line", "ri-meta-fill", "ri-meta-line", "ri-microsoft-loop-fill", "ri-microsoft-loop-line", "ri-nft-fill", "ri-nft-line", "ri-notion-fill", "ri-notion-line", "ri-openai-fill", "ri-openai-line", "ri-overline", "ri-p2p-fill", "ri-p2p-line", "ri-presentation-fill", "ri-presentation-line", "ri-replay-10-fill", "ri-replay-10-line", "ri-replay-15-fill", "ri-replay-15-line", "ri-replay-30-fill", "ri-replay-30-line", "ri-replay-5-fill", "ri-replay-5-line", "ri-school-fill", "ri-school-line", "ri-shining-2-fill", "ri-shining-2-line", "ri-shining-fill", "ri-shining-line", "ri-sketching", "ri-skip-down-fill", "ri-skip-down-line", "ri-skip-left-fill", "ri-skip-left-line", "ri-skip-right-fill", "ri-skip-right-line", "ri-skip-up-fill", "ri-skip-up-line", "ri-slow-down-fill", "ri-slow-down-line", "ri-sparkling-2-fill", "ri-sparkling-2-line", "ri-sparkling-fill", "ri-sparkling-line", "ri-speak-fill", "ri-speak-line", "ri-speed-up-fill", "ri-speed-up-line", "ri-tiktok-fill", "ri-tiktok-line", "ri-token-swap-fill", "ri-token-swap-line", "ri-unpin-fill", "ri-unpin-line", "ri-wechat-channels-fill", "ri-wechat-channels-line", "ri-wordpress-fill", "ri-wordpress-line", "ri-blender-fill", "ri-blender-line", "ri-emoji-sticker-fill", "ri-emoji-sticker-line", "ri-git-close-pull-request-fill", "ri-git-close-pull-request-line", "ri-instance-fill", "ri-instance-line", "ri-megaphone-fill", "ri-megaphone-line", "ri-pass-expired-fill", "ri-pass-expired-line", "ri-pass-pending-fill", "ri-pass-pending-line", "ri-pass-valid-fill", "ri-pass-valid-line", "ri-ai-generate", "ri-calendar-close-fill", "ri-calendar-close-line", "ri-draggable", "ri-font-family", "ri-font-mono", "ri-font-sans-serif", "ri-font-sans", "ri-hard-drive-3-fill", "ri-hard-drive-3-line", "ri-kick-fill", "ri-kick-line", "ri-list-check-3", "ri-list-indefinite", "ri-list-ordered-2", "ri-list-radio", "ri-openbase-fill", "ri-openbase-line", "ri-planet-fill", "ri-planet-line", "ri-prohibited-fill", "ri-prohibited-line", "ri-quote-text", "ri-seo-fill", "ri-seo-line", "ri-slash-commands", "ri-archive-2-fill", "ri-archive-2-line", "ri-inbox-2-fill", "ri-inbox-2-line", "ri-shake-hands-fill", "ri-shake-hands-line", "ri-supabase-fill", "ri-supabase-line", "ri-water-percent-fill", "ri-water-percent-line", "ri-yuque-fill", "ri-yuque-line", "ri-crosshair-2-fill", "ri-crosshair-2-line", "ri-crosshair-fill", "ri-crosshair-line", "ri-file-close-fill", "ri-file-close-line", "ri-infinity-fill", "ri-infinity-line", "ri-rfid-fill", "ri-rfid-line", "ri-slash-commands-2", "ri-user-forbid-fill", "ri-user-forbid-line", "ri-beer-fill", "ri-beer-line", "ri-circle-fill", "ri-circle-line", "ri-dropdown-list", "ri-file-image-fill", "ri-file-image-line", "ri-file-pdf-2-fill", "ri-file-pdf-2-line", "ri-file-video-fill", "ri-file-video-line", "ri-folder-image-fill", "ri-folder-image-line", "ri-folder-video-fill", "ri-folder-video-line", "ri-hexagon-fill", "ri-hexagon-line", "ri-menu-search-fill", "ri-menu-search-line", "ri-octagon-fill", "ri-octagon-line", "ri-pentagon-fill", "ri-pentagon-line", "ri-rectangle-fill", "ri-rectangle-line", "ri-robot-2-fill", "ri-robot-2-line", "ri-shapes-fill", "ri-shapes-line", "ri-square-fill", "ri-square-line", "ri-tent-fill", "ri-tent-line", "ri-threads-fill", "ri-threads-line", "ri-tree-fill", "ri-tree-line", "ri-triangle-fill", "ri-triangle-line", "ri-twitter-x-fill", "ri-twitter-x-line", "ri-verified-badge-fill", "ri-verified-badge-line", "ri-armchair-fill", "ri-armchair-line", "ri-bnb-fill", "ri-bnb-line", "ri-bread-fill", "ri-bread-line", "ri-btc-fill", "ri-btc-line", "ri-calendar-schedule-fill", "ri-calendar-schedule-line", "ri-dice-1-fill", "ri-dice-1-line", "ri-dice-2-fill", "ri-dice-2-line", "ri-dice-3-fill", "ri-dice-3-line", "ri-dice-4-fill", "ri-dice-4-line", "ri-dice-5-fill", "ri-dice-5-line", "ri-dice-6-fill", "ri-dice-6-line", "ri-dice-fill", "ri-dice-line", "ri-drinks-fill", "ri-drinks-line", "ri-equalizer-2-fill", "ri-equalizer-2-line", "ri-equalizer-3-fill", "ri-equalizer-3-line", "ri-eth-fill", "ri-eth-line", "ri-flower-fill", "ri-flower-line", "ri-glasses-2-fill", "ri-glasses-2-line", "ri-glasses-fill", "ri-glasses-line", "ri-goggles-fill", "ri-goggles-line", "ri-image-circle-fill", "ri-image-circle-line", "ri-info-i", "ri-money-rupee-circle-fill", "ri-money-rupee-circle-line", "ri-news-fill", "ri-news-line", "ri-robot-3-fill", "ri-robot-3-line", "ri-share-2-fill", "ri-share-2-line", "ri-sofa-fill", "ri-sofa-line", "ri-svelte-fill", "ri-svelte-line", "ri-vk-fill", "ri-vk-line", "ri-xrp-fill", "ri-xrp-line", "ri-xtz-fill", "ri-xtz-line", "ri-archive-stack-fill", "ri-archive-stack-line", "ri-bowl-fill", "ri-bowl-line", "ri-calendar-view", "ri-carousel-view", "ri-code-block", "ri-color-filter-fill", "ri-color-filter-line", "ri-contacts-book-3-fill", "ri-contacts-book-3-line", "ri-contract-fill", "ri-contract-line", "ri-drinks-2-fill", "ri-drinks-2-line", "ri-export-fill", "ri-export-line", "ri-file-check-fill", "ri-file-check-line", "ri-focus-mode", "ri-folder-6-fill", "ri-folder-6-line", "ri-folder-check-fill", "ri-folder-check-line", "ri-folder-close-fill", "ri-folder-close-line", "ri-folder-cloud-fill", "ri-folder-cloud-line", "ri-gallery-view-2", "ri-gallery-view", "ri-hand", "ri-import-fill", "ri-import-line", "ri-information-2-fill", "ri-information-2-line", "ri-kanban-view-2", "ri-kanban-view", "ri-list-view", "ri-lock-star-fill", "ri-lock-star-line", "ri-puzzle-2-fill", "ri-puzzle-2-line", "ri-puzzle-fill", "ri-puzzle-line", "ri-ram-2-fill", "ri-ram-2-line", "ri-ram-fill", "ri-ram-line", "ri-receipt-fill", "ri-receipt-line", "ri-shadow-fill", "ri-shadow-line", "ri-sidebar-fold-fill", "ri-sidebar-fold-line", "ri-sidebar-unfold-fill", "ri-sidebar-unfold-line", "ri-slideshow-view", "ri-sort-alphabet-asc", "ri-sort-alphabet-desc", "ri-sort-number-asc", "ri-sort-number-desc", "ri-stacked-view", "ri-sticky-note-add-fill", "ri-sticky-note-add-line", "ri-swap-2-fill", "ri-swap-2-line", "ri-swap-3-fill", "ri-swap-3-line", "ri-table-3", "ri-table-view", "ri-text-block", "ri-text-snippet", "ri-timeline-view", "ri-blogger-fill", "ri-blogger-line", "ri-chat-thread-fill", "ri-chat-thread-line", "ri-discount-percent-fill", "ri-discount-percent-line", "ri-exchange-2-fill", "ri-exchange-2-line", "ri-git-fork-fill", "ri-git-fork-line", "ri-input-field", "ri-progress-1-fill", "ri-progress-1-line", "ri-progress-2-fill", "ri-progress-2-line", "ri-progress-3-fill", "ri-progress-3-line", "ri-progress-4-fill", "ri-progress-4-line", "ri-progress-5-fill", "ri-progress-5-line", "ri-progress-6-fill", "ri-progress-6-line", "ri-progress-7-fill", "ri-progress-7-line", "ri-progress-8-fill", "ri-progress-8-line", "ri-remix-run-fill", "ri-remix-run-line", "ri-signpost-fill", "ri-signpost-line", "ri-time-zone-fill", "ri-time-zone-line", "ri-arrow-down-wide-fill", "ri-arrow-down-wide-line", "ri-arrow-left-wide-fill", "ri-arrow-left-wide-line", "ri-arrow-right-wide-fill", "ri-arrow-right-wide-line", "ri-arrow-up-wide-fill", "ri-arrow-up-wide-line", "ri-bluesky-fill", "ri-bluesky-line", "ri-expand-height-fill", "ri-expand-height-line", "ri-expand-width-fill", "ri-expand-width-line", "ri-forward-end-fill", "ri-forward-end-line", "ri-forward-end-mini-fill", "ri-forward-end-mini-line", "ri-friendica-fill", "ri-friendica-line", "ri-git-pr-draft-fill", "ri-git-pr-draft-line", "ri-play-reverse-fill", "ri-play-reverse-line", "ri-play-reverse-mini-fill", "ri-play-reverse-mini-line", "ri-rewind-start-fill", "ri-rewind-start-line", "ri-rewind-start-mini-fill", "ri-rewind-start-mini-line", "ri-scroll-to-bottom-fill", "ri-scroll-to-bottom-line", ]