mirror of
https://github.com/konpa/devicon.git
synced 2025-08-20 13:21:42 +02:00
Merge pull request #268 from devicons/build-integrate
Introducing github action for build automatisation (using python)
This commit is contained in:
19
.github/scripts/build_assets/PathResolverAction.py
vendored
Normal file
19
.github/scripts/build_assets/PathResolverAction.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class PathResolverAction(argparse.Action):
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
path = Path(values).resolve()
|
||||
if not path.exists():
|
||||
raise ValueError(f"{path} doesn't exist.")
|
||||
|
||||
if self.dest == "icons_folder_path":
|
||||
if not path.is_dir():
|
||||
raise ValueError("icons_folder_path must be a directory")
|
||||
|
||||
elif self.dest == "download_path":
|
||||
if not path.is_dir():
|
||||
raise ValueError("download_path must be a directory")
|
||||
|
||||
setattr(namespace, self.dest, str(path))
|
264
.github/scripts/build_assets/SeleniumRunner.py
vendored
Normal file
264
.github/scripts/build_assets/SeleniumRunner.py
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
from typing import List
|
||||
from pathlib import Path
|
||||
import time
|
||||
|
||||
from selenium.webdriver.firefox.webdriver import WebDriver
|
||||
from selenium.webdriver.firefox.options import Options
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as ec
|
||||
from selenium.common.exceptions import TimeoutException as SeleniumTimeoutException
|
||||
|
||||
|
||||
class SeleniumRunner:
|
||||
"""
|
||||
A runner that upload and download Icomoon resources using Selenium.
|
||||
The WebDriver will use Firefox.
|
||||
"""
|
||||
|
||||
"""
|
||||
The long wait time for the driver in seconds.
|
||||
"""
|
||||
LONG_WAIT_IN_SEC = 25
|
||||
|
||||
"""
|
||||
The medium wait time for the driver in seconds.
|
||||
"""
|
||||
MED_WAIT_IN_SEC = 6
|
||||
|
||||
"""
|
||||
The short wait time for the driver in seconds.
|
||||
"""
|
||||
SHORT_WAIT_IN_SEC = 0.6
|
||||
|
||||
"""
|
||||
The Icomoon Url.
|
||||
"""
|
||||
ICOMOON_URL = "https://icomoon.io/app/#/select"
|
||||
|
||||
def __init__(self, icomoon_json_path: str, download_path: str,
|
||||
geckodriver_path: str, headless):
|
||||
"""
|
||||
Create a SeleniumRunner object.
|
||||
:param icomoon_json_path: a path to the iconmoon.json.
|
||||
:param download_path: the location where you want to download
|
||||
the icomoon.zip to.
|
||||
:param geckodriver_path: the path to the firefox executable.
|
||||
:param headless: whether to run browser in headless (no UI) mode.
|
||||
"""
|
||||
self.icomoon_json_path = icomoon_json_path
|
||||
self.download_path = download_path
|
||||
self.driver = None
|
||||
self.set_options(geckodriver_path, headless)
|
||||
|
||||
def set_options(self, geckodriver_path: str, headless: bool):
|
||||
"""
|
||||
Build the WebDriver with Firefox Options allowing downloads and
|
||||
set download to download_path.
|
||||
:param geckodriver_path: the path to the firefox executable.
|
||||
:param headless: whether to run browser in headless (no UI) mode.
|
||||
|
||||
:raises AssertionError: if the page title does not contain
|
||||
"IcoMoon App".
|
||||
"""
|
||||
options = Options()
|
||||
allowed_mime_types = "application/zip, application/gzip, application/octet-stream"
|
||||
# disable prompt to download from Firefox
|
||||
options.set_preference("browser.helperApps.neverAsk.saveToDisk", allowed_mime_types)
|
||||
options.set_preference("browser.helperApps.neverAsk.openFile", allowed_mime_types)
|
||||
|
||||
# set the default download path to downloadPath
|
||||
options.set_preference("browser.download.folderList", 2)
|
||||
options.set_preference("browser.download.dir", self.download_path)
|
||||
options.headless = headless
|
||||
|
||||
self.driver = WebDriver(options=options, executable_path=geckodriver_path)
|
||||
self.driver.get(self.ICOMOON_URL)
|
||||
assert "IcoMoon App" in self.driver.title
|
||||
|
||||
def upload_icomoon(self):
|
||||
"""
|
||||
Upload the icomoon.json to icomoon.io.
|
||||
:raises TimeoutException: happens when elements are not found.
|
||||
"""
|
||||
print("Uploading icomoon.json file...")
|
||||
try:
|
||||
# find the file input and enter the file path
|
||||
import_btn = WebDriverWait(self.driver, SeleniumRunner.LONG_WAIT_IN_SEC).until(
|
||||
ec.presence_of_element_located((By.CSS_SELECTOR, "div#file input"))
|
||||
)
|
||||
import_btn.send_keys(self.icomoon_json_path)
|
||||
except Exception as e:
|
||||
self.close()
|
||||
raise e
|
||||
|
||||
try:
|
||||
confirm_btn = WebDriverWait(self.driver, SeleniumRunner.MED_WAIT_IN_SEC).until(
|
||||
ec.element_to_be_clickable((By.XPATH, "//div[@class='overlay']//button[text()='Yes']"))
|
||||
)
|
||||
confirm_btn.click()
|
||||
except SeleniumTimeoutException as e:
|
||||
print(e.stacktrace)
|
||||
print("Cannot find the confirm button when uploading the icomoon.json",
|
||||
"Ensure that the icomoon.json is in the correct format for Icomoon.io",
|
||||
sep='\n')
|
||||
self.close()
|
||||
|
||||
print("JSON file uploaded.")
|
||||
|
||||
def upload_svgs(self, svgs: List[str]):
|
||||
"""
|
||||
Upload the SVGs provided in folder_info
|
||||
:param svgs: a list of svg Paths that we'll upload to icomoon.
|
||||
"""
|
||||
try:
|
||||
print("Uploading SVGs...")
|
||||
|
||||
edit_mode_btn = self.driver.find_element_by_css_selector(
|
||||
"div.btnBar button i.icon-edit"
|
||||
)
|
||||
edit_mode_btn.click()
|
||||
|
||||
self.click_hamburger_input()
|
||||
|
||||
for svg in svgs:
|
||||
import_btn = self.driver.find_element_by_css_selector(
|
||||
"li.file input[type=file]"
|
||||
)
|
||||
import_btn.send_keys(svg)
|
||||
print(f"Uploaded {svg}")
|
||||
self.test_for_possible_alert(self.SHORT_WAIT_IN_SEC, "Dismiss")
|
||||
self.remove_color_from_icon()
|
||||
|
||||
self.click_hamburger_input()
|
||||
select_all_button = WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until(
|
||||
ec.element_to_be_clickable((By.XPATH, "//button[text()='Select All']"))
|
||||
)
|
||||
select_all_button.click()
|
||||
except Exception as e:
|
||||
self.close()
|
||||
raise e
|
||||
|
||||
def click_hamburger_input(self):
|
||||
"""
|
||||
Click the hamburger input until the pop up menu appears. This
|
||||
method is needed because sometimes, we need to click the hamburger
|
||||
input two times before the menu appears.
|
||||
:return: None.
|
||||
"""
|
||||
try:
|
||||
hamburger_input = self.driver.find_element_by_css_selector(
|
||||
"button.btn5.lh-def.transparent i.icon-menu"
|
||||
)
|
||||
|
||||
menu_appear_callback = ec.element_to_be_clickable(
|
||||
(By.CSS_SELECTOR, "h1#setH2 ul")
|
||||
)
|
||||
|
||||
while not menu_appear_callback(self.driver):
|
||||
hamburger_input.click()
|
||||
except Exception as e:
|
||||
self.close()
|
||||
raise e
|
||||
|
||||
def test_for_possible_alert(self, wait_period: float, btn_text: str):
|
||||
"""
|
||||
Test for the possible alert when we upload the svgs.
|
||||
:param wait_period: the wait period for the possible alert
|
||||
in seconds.
|
||||
:param btn_text: the text that the alert's button will have.
|
||||
:return: None.
|
||||
"""
|
||||
try:
|
||||
dismiss_btn = WebDriverWait(self.driver, wait_period, 0.15).until(
|
||||
ec.element_to_be_clickable(
|
||||
(By.XPATH, f"//div[@class='overlay']//button[text()='{btn_text}']"))
|
||||
)
|
||||
dismiss_btn.click()
|
||||
except SeleniumTimeoutException:
|
||||
pass
|
||||
|
||||
def remove_color_from_icon(self):
|
||||
"""
|
||||
Remove the color from the most recent uploaded icon.
|
||||
:return: None.
|
||||
"""
|
||||
try:
|
||||
recently_uploaded_icon = WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until(
|
||||
ec.element_to_be_clickable((By.XPATH, "//div[@id='set0']//mi-box[1]//div"))
|
||||
)
|
||||
recently_uploaded_icon.click()
|
||||
except Exception as e:
|
||||
self.close()
|
||||
raise e
|
||||
|
||||
try:
|
||||
color_tab = WebDriverWait(self.driver, self.SHORT_WAIT_IN_SEC).until(
|
||||
ec.element_to_be_clickable((By.CSS_SELECTOR, "div.overlayWindow i.icon-droplet"))
|
||||
)
|
||||
color_tab.click()
|
||||
|
||||
remove_color_btn = self.driver \
|
||||
.find_element_by_css_selector("div.overlayWindow i.icon-droplet-cross")
|
||||
remove_color_btn.click()
|
||||
except SeleniumTimeoutException:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.close()
|
||||
raise e
|
||||
|
||||
try:
|
||||
close_btn = self.driver \
|
||||
.find_element_by_css_selector("div.overlayWindow i.icon-close")
|
||||
close_btn.click()
|
||||
except Exception as e:
|
||||
self.close()
|
||||
raise e
|
||||
|
||||
def download_icomoon_fonts(self, zip_path: Path):
|
||||
"""
|
||||
Download the icomoon.zip from icomoon.io.
|
||||
:param zip_path: the path to the zip file after it's downloaded.
|
||||
"""
|
||||
try:
|
||||
print("Downloading Font files...")
|
||||
self.driver.find_element_by_css_selector(
|
||||
"a[href='#/select/font']"
|
||||
).click()
|
||||
|
||||
self.test_for_possible_alert(self.MED_WAIT_IN_SEC, "Continue")
|
||||
download_btn = WebDriverWait(self.driver, SeleniumRunner.LONG_WAIT_IN_SEC).until(
|
||||
ec.presence_of_element_located((By.CSS_SELECTOR, "button.btn4 span"))
|
||||
)
|
||||
download_btn.click()
|
||||
if self.wait_for_zip(zip_path):
|
||||
print("Font files downloaded.")
|
||||
else:
|
||||
raise TimeoutError(f"Couldn't find {zip_path} after download button was clicked.")
|
||||
except Exception as e:
|
||||
self.close()
|
||||
raise e
|
||||
|
||||
def wait_for_zip(self, zip_path: Path) -> bool:
|
||||
"""
|
||||
Wait for the zip file to be downloaded by checking for its existence
|
||||
in the download path. Wait time is self.LONG_WAIT_IN_SEC and check time
|
||||
is 1 sec.
|
||||
:param zip_path: the path to the zip file after it's
|
||||
downloaded.
|
||||
:return: True if the file is found within the allotted time, else
|
||||
False.
|
||||
"""
|
||||
end_time = time.time() + self.LONG_WAIT_IN_SEC
|
||||
while time.time() <= end_time:
|
||||
if zip_path.exists():
|
||||
return True
|
||||
time.sleep(1)
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the SeleniumRunner instance.
|
||||
"""
|
||||
print("Closing down SeleniumRunner...")
|
||||
self.driver.quit()
|
145
.github/scripts/build_assets/filehandler.py
vendored
Normal file
145
.github/scripts/build_assets/filehandler.py
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
import json
|
||||
from zipfile import ZipFile
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
def find_new_icons(devicon_json_path: str, icomoon_json_path: str) -> List[dict]:
|
||||
"""
|
||||
Find the newly added icons by finding the difference between
|
||||
the devicon_test.json and the icomoon_test.json.
|
||||
:param devicon_json_path, the path to the devicon_test.json.
|
||||
:param icomoon_json_path: a path to the iconmoon.json.
|
||||
:return: a list of the new icons as JSON objects.
|
||||
"""
|
||||
with open(devicon_json_path) as json_file:
|
||||
devicon_json = json.load(json_file)
|
||||
|
||||
with open(icomoon_json_path) as json_file:
|
||||
icomoon_json = json.load(json_file)
|
||||
|
||||
new_icons = []
|
||||
for icon in devicon_json:
|
||||
if is_not_in_icomoon_json(icon, icomoon_json):
|
||||
new_icons.append(icon)
|
||||
|
||||
return new_icons
|
||||
|
||||
|
||||
def is_not_in_icomoon_json(icon, icomoon_json) -> bool:
|
||||
"""
|
||||
Checks whether the icon's name is not in the icomoon_json.
|
||||
:param icon: the icon object we are searching for.
|
||||
:param icomoon_json: the icomoon json object parsed from
|
||||
icomoon_test.json.
|
||||
:return: True if icon's name is not in the icomoon_test.json, else False.
|
||||
"""
|
||||
pattern = re.compile(f"^{icon['name']}-")
|
||||
|
||||
for font in icomoon_json["icons"]:
|
||||
if pattern.search(font["properties"]["name"]):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_svgs_paths(new_icons: List[dict], icons_folder_path: str) -> List[str]:
|
||||
"""
|
||||
Get all the suitable svgs file path listed in the devicon_test.json.
|
||||
:param new_icons, a list containing the info on the new icons.
|
||||
:param icons_folder_path, the path where the function can find the
|
||||
listed folders.
|
||||
:return: a list of svg file paths that can be uploaded to Icomoon.
|
||||
"""
|
||||
file_paths = []
|
||||
for icon_info in new_icons:
|
||||
folder_path = Path(icons_folder_path, icon_info['name'])
|
||||
|
||||
if not folder_path.is_dir():
|
||||
raise ValueError(f"Invalid path. This is not a directory: {folder_path}.")
|
||||
|
||||
# TODO: remove the try-except when the devicon.json is upgraded
|
||||
try:
|
||||
aliases = icon_info["aliases"]
|
||||
except KeyError:
|
||||
aliases = [] # create empty list of aliases if not provided in devicon.json
|
||||
|
||||
for font_version in icon_info["versions"]["font"]:
|
||||
if is_alias(font_version, aliases):
|
||||
continue
|
||||
|
||||
file_name = f"{icon_info['name']}-{font_version}.svg"
|
||||
path = Path(folder_path, file_name)
|
||||
|
||||
if path.exists():
|
||||
file_paths.append(str(path))
|
||||
else:
|
||||
raise ValueError(f"This path doesn't exist: {path}")
|
||||
|
||||
return file_paths
|
||||
|
||||
|
||||
def is_alias(font_version: str, aliases: List[dict]):
|
||||
"""
|
||||
Check whether the font version is an alias of another version.
|
||||
:return: True if it is, else False.
|
||||
"""
|
||||
for alias in aliases:
|
||||
if font_version == alias["alias"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def extract_files(zip_path: str, extract_path: str, delete=True):
|
||||
"""
|
||||
Extract the style.css and font files from the devicon.zip
|
||||
folder. Must call the gulp task "get-icomoon-files"
|
||||
before calling this.
|
||||
:param zip_path, path where the zip file returned
|
||||
from the icomoon.io is located.
|
||||
:param extract_path, the location where the function
|
||||
will put the extracted files.
|
||||
:param delete, whether the function should delete the zip file
|
||||
when it's done.
|
||||
"""
|
||||
print("Extracting zipped files...")
|
||||
|
||||
icomoon_zip = ZipFile(zip_path)
|
||||
target_files = ('selection.json', 'fonts/', 'fonts/devicon.ttf',
|
||||
'fonts/devicon.woff', 'fonts/devicon.eot',
|
||||
'fonts/devicon.svg', "style.css")
|
||||
for file in target_files:
|
||||
icomoon_zip.extract(file, extract_path)
|
||||
|
||||
print("Files extracted")
|
||||
|
||||
if delete:
|
||||
print("Deleting devicon zip file...")
|
||||
icomoon_zip.close()
|
||||
os.remove(zip_path)
|
||||
|
||||
|
||||
def rename_extracted_files(extract_path: str):
|
||||
"""
|
||||
Rename the extracted files selection.json and style.css.
|
||||
:param extract_path, the location where the function
|
||||
can find the extracted files.
|
||||
:return: None.
|
||||
"""
|
||||
print("Renaming files")
|
||||
old_to_new_list = [
|
||||
{
|
||||
"old": Path(extract_path, "selection.json"),
|
||||
"new": Path(extract_path, "icomoon.json")
|
||||
},
|
||||
{
|
||||
"old": Path(extract_path, "style.css"),
|
||||
"new": Path(extract_path, "devicon.css")
|
||||
}
|
||||
]
|
||||
|
||||
for dict_ in old_to_new_list:
|
||||
os.replace(dict_["old"], dict_["new"])
|
||||
|
||||
print("Files renamed")
|
6
.github/scripts/build_assets/geckodriver-v0.27.0-win64/README.md
vendored
Normal file
6
.github/scripts/build_assets/geckodriver-v0.27.0-win64/README.md
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
## geckodriver
|
||||
Proxy for using W3C WebDriver compatible clients to interact with Gecko-based browsers.
|
||||
This program provides the HTTP API described by the WebDriver protocol to communicate with Gecko browsers, such as Firefox.
|
||||
It translates calls into the Marionette remote protocol by acting as a proxy between the local- and remote ends.
|
||||
|
||||
This application was taken from: https://github.com/mozilla/geckodriver/releases/tag/v0.27.0
|
BIN
.github/scripts/build_assets/geckodriver-v0.27.0-win64/geckodriver.exe
vendored
Normal file
BIN
.github/scripts/build_assets/geckodriver-v0.27.0-win64/geckodriver.exe
vendored
Normal file
Binary file not shown.
71
.github/scripts/icomoon_upload.py
vendored
Normal file
71
.github/scripts/icomoon_upload.py
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
from pathlib import Path
|
||||
from argparse import ArgumentParser
|
||||
from selenium.common.exceptions import TimeoutException
|
||||
|
||||
# pycharm complains that build_assets is an unresolved ref
|
||||
# don't worry about it, the script still runs
|
||||
from build_assets.SeleniumRunner import SeleniumRunner
|
||||
from build_assets import filehandler
|
||||
from build_assets.PathResolverAction import PathResolverAction
|
||||
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser(description="Upload svgs to Icomoon to create icon files.")
|
||||
|
||||
parser.add_argument("--headless",
|
||||
help="Whether to run the browser in headless/no UI mode",
|
||||
action="store_true")
|
||||
|
||||
parser.add_argument("geckodriver_path",
|
||||
help="The path to the firefox executable file",
|
||||
action=PathResolverAction)
|
||||
|
||||
parser.add_argument("icomoon_json_path",
|
||||
help="The path to the icomoon.json aka the selection.json created by Icomoon",
|
||||
action=PathResolverAction)
|
||||
|
||||
parser.add_argument("devicon_json_path",
|
||||
help="The path to the devicon.json",
|
||||
action=PathResolverAction)
|
||||
|
||||
parser.add_argument("icons_folder_path",
|
||||
help="The path to the icons folder",
|
||||
action=PathResolverAction)
|
||||
|
||||
parser.add_argument("download_path",
|
||||
help="The path where you'd like to download the Icomoon files to",
|
||||
action=PathResolverAction)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
new_icons = filehandler.find_new_icons(args.devicon_json_path, args.icomoon_json_path)
|
||||
if len(new_icons) == 0:
|
||||
print("No files need to be uploaded. Ending script...")
|
||||
return
|
||||
|
||||
# print list of new icons, separated by comma
|
||||
print("List of new icons:")
|
||||
print(*new_icons, sep = "\n")
|
||||
try:
|
||||
runner = SeleniumRunner(args.icomoon_json_path, args.download_path,
|
||||
args.geckodriver_path, args.headless)
|
||||
runner.upload_icomoon()
|
||||
svgs = filehandler.get_svgs_paths(new_icons, args.icons_folder_path)
|
||||
runner.upload_svgs(svgs)
|
||||
|
||||
|
||||
zip_name = "devicon-v1.0.zip"
|
||||
zip_path = Path(args.download_path, zip_name)
|
||||
runner.download_icomoon_fonts(zip_path)
|
||||
filehandler.extract_files(str(zip_path), args.download_path)
|
||||
filehandler.rename_extracted_files(args.download_path)
|
||||
runner.close()
|
||||
print("Task completed.")
|
||||
except TimeoutException as e:
|
||||
print(e)
|
||||
print(e.stacktrace)
|
||||
runner.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
1
.github/scripts/requirements.txt
vendored
Normal file
1
.github/scripts/requirements.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
selenium==3.141.0
|
42
.github/workflows/build_icons.yml
vendored
Normal file
42
.github/workflows/build_icons.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Build Icons
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build:
|
||||
name: Get Fonts From Icomoon
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
- name: Setup Python v3.8
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install dependencies (python, pip, npm)
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r ./.github/scripts/requirements.txt
|
||||
npm install
|
||||
- name: Run icomoon_upload.py
|
||||
run: >
|
||||
python ./.github/scripts/icomoon_upload.py
|
||||
./.github/scripts/build_assets/geckodriver-v0.27.0-win64/geckodriver.exe
|
||||
./icomoon.json ./devicon.json ./icons ./ --headless
|
||||
- name: Upload geckodriver.log for debugging purposes
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: geckodriver-log
|
||||
path: ./geckodriver.log
|
||||
- name: Running gulp default task for building devicon.min.css
|
||||
run: |
|
||||
gulp default
|
||||
- name: Commit changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
commit_message: Build new icons, icomoon.json and devicon.css
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
.idea
|
||||
geckodriver.log
|
||||
__pycache__
|
||||
*.pyc
|
10
devicon.css
10
devicon.css
@@ -1,10 +1,10 @@
|
||||
@font-face {
|
||||
font-family: 'devicon';
|
||||
src: url('fonts/devicon.eot?xdc21g');
|
||||
src: url('fonts/devicon.eot?xdc21g#iefix') format('embedded-opentype'),
|
||||
url('fonts/devicon.ttf?xdc21g') format('truetype'),
|
||||
url('fonts/devicon.woff?xdc21g') format('woff'),
|
||||
url('fonts/devicon.svg?xdc21g#devicon') format('svg');
|
||||
src: url('fonts/devicon.eot?yl3aib');
|
||||
src: url('fonts/devicon.eot?yl3aib#iefix') format('embedded-opentype'),
|
||||
url('fonts/devicon.ttf?yl3aib') format('truetype'),
|
||||
url('fonts/devicon.woff?yl3aib') format('woff'),
|
||||
url('fonts/devicon.svg?yl3aib#devicon') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
|
@@ -620,7 +620,7 @@
|
||||
"tags": ["testing"],
|
||||
"versions": {
|
||||
"svg": ["plain"],
|
||||
"font": ["plain"]
|
||||
"font": []
|
||||
}
|
||||
},
|
||||
{
|
||||
|
2
devicon.min.css
vendored
2
devicon.min.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
4014
package-lock.json
generated
Normal file
4014
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user