mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
Make builds of the editor and the website serve their own local fonts (#2186)
* WIP * Done? * Install fonts in CI * Use absolute path so minified inlined CSS works * Fix Bezier-rs demo fonts? * Use opsz * Revert removal of text balancer * Pull in the text balancer from our static host
This commit is contained in:
parent
ea59f10b50
commit
e57637aab1
28 changed files with 1305 additions and 755 deletions
7
.github/workflows/website.yml
vendored
7
.github/workflows/website.yml
vendored
|
@ -13,7 +13,7 @@ on:
|
|||
- website/**
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
INDEX_HTML_HEAD_INCLUSION: <script defer data-domain="graphite.rs" data-api="/visit/event" src="/visit/script.js"></script>
|
||||
INDEX_HTML_HEAD_INCLUSION: <script defer data-domain="graphite.rs" data-api="/visit/event" src="/visit/script.hash.js"></script>
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
@ -30,7 +30,7 @@ jobs:
|
|||
- name: 🕸 Install Zola
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: zola@0.19.1
|
||||
tool: zola@0.20.0
|
||||
|
||||
- name: ✂ Replace template in <head> of index.html
|
||||
run: |
|
||||
|
@ -42,7 +42,8 @@ jobs:
|
|||
MODE: prod
|
||||
run: |
|
||||
cd website
|
||||
zola --config config_prod.toml build
|
||||
npm run install-fonts
|
||||
zola --config config.toml build --minify
|
||||
|
||||
- name: 🔍 Check if `website/other` directory changed
|
||||
uses: dorny/paths-filter@v3
|
||||
|
|
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -3362,7 +3362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6635,9 +6635,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.44.0"
|
||||
version = "1.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a"
|
||||
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
|
|
|
@ -734,7 +734,7 @@ impl OverlayContext {
|
|||
);
|
||||
}
|
||||
|
||||
self.render_context.set_font("12px Source Sans Pro, Arial, sans-serif");
|
||||
self.render_context.set_font(r#"12px "Source Sans Pro", Arial, sans-serif"#);
|
||||
self.render_context.set_fill_style_str(font_color);
|
||||
self.render_context.fill_text(text, 0., 0.).expect("Failed to draw the text at the calculated position");
|
||||
self.render_context.reset_transform().expect("Failed to reset the render context transform");
|
||||
|
|
|
@ -22,7 +22,7 @@ Wraps the editor backend codebase (`/editor`) and provides a JS-centric API for
|
|||
|
||||
## ESLint configurations: `.eslintrc.js`
|
||||
|
||||
[ESLint](https://eslint.org/) is the tool which enforces style rules on the JS, TS, and Svelte files in our frontend codebase. As it is set up in this config file, ESLint will complain about bad practices and often help reformat code automatically when (in VS Code) the file is saved or `npm run lint` is executed. (If you don't use VS Code, remember to run this command before committing!) This config file for ESLint sets our style preferences and configures our usage of extensions/plugins for Svelte support, [Airbnb](https://github.com/airbnb/javascript)'s popular catalog of sane defaults, and [Prettier](https://prettier.io/)'s role as a code formatter.
|
||||
[ESLint](https://eslint.org/) is the tool which enforces style rules on the JS, TS, and Svelte files in our frontend codebase. As it is set up in this config file, ESLint will complain about bad practices and often help reformat code automatically when (in VS Code) the file is saved or `npm run lint` is executed. (If you don't use VS Code, remember to run this command before committing!) This config file for ESLint sets our style preferences and configures our usage of extensions/plugins for Svelte support and [Prettier](https://prettier.io/)'s role as a code formatter.
|
||||
|
||||
## npm ecosystem packages: `package.json`
|
||||
|
||||
|
|
|
@ -1,79 +1,54 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Graphite</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?display=block&family=Source+Sans+Pro:ital,wght@0,400;0,700;1,400;1,700&family=Inconsolata:wght@400;700">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="mask-icon" href="safari-pinned-tab.svg" color="#473a3a">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5">
|
||||
<meta name="apple-mobile-web-app-title" content="Graphite">
|
||||
<meta name="application-name" content="Graphite">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="color-scheme" content="dark only">
|
||||
<meta name="darkreader-lock">
|
||||
<!-- INDEX_HTML_HEAD_REPLACEMENT -->
|
||||
</head>
|
||||
<body>
|
||||
<noscript>JavaScript is required</noscript>
|
||||
<style>
|
||||
body {
|
||||
background: #222;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
border: 4px solid #eee;
|
||||
border-color: #eee transparent #eee transparent;
|
||||
animation: spinning-loading-indicator 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spinning-loading-indicator {
|
||||
0% {
|
||||
transform: translate(-30px, -30px) rotate(0deg);
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Graphite</title>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png" />
|
||||
<link rel="manifest" href="site.webmanifest" />
|
||||
<link rel="mask-icon" href="safari-pinned-tab.svg" color="#473a3a" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5" />
|
||||
<meta name="apple-mobile-web-app-title" content="Graphite" />
|
||||
<meta name="application-name" content="Graphite" />
|
||||
<meta name="msapplication-TileColor" content="#ffffff" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta name="color-scheme" content="dark only" />
|
||||
<meta name="darkreader-lock" />
|
||||
<!-- INDEX_HTML_HEAD_REPLACEMENT -->
|
||||
</head>
|
||||
<body>
|
||||
<noscript>JavaScript is required</noscript>
|
||||
<style>
|
||||
body {
|
||||
background: #222;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translate(-30px, -30px) rotate(360deg);
|
||||
|
||||
body::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
border: 4px solid #eee;
|
||||
border-color: #eee transparent #eee transparent;
|
||||
animation: spinning-loading-indicator 1s linear infinite;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
// Confirm the browser is compatible before initializing the application
|
||||
|
||||
// Display an error if the browser is incompatible with a required API
|
||||
// This is run outside the JS code bundle in case unsupported features cause a syntax error when parsing the bundle, preventing the any bundle code from running
|
||||
let incompatibility;
|
||||
if (!("BigUint64Array" in window)) {
|
||||
incompatibility = `
|
||||
<style>
|
||||
body::after { content: none; }
|
||||
h2, p, a { text-align: center; color: #eee; font-family: "Source Sans Pro", Arial, sans-serif; }
|
||||
</style>
|
||||
<h2>This browser is too old to run Graphite</h2>
|
||||
<p>Please upgrade to a modern web browser such as the latest Firefox, Chrome, Edge, or Safari version 15 or newer.</p>
|
||||
<p>(The <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array#browser_compatibility" target="_blank"><code>BigInt64Array</code></a>
|
||||
JavaScript API must be supported by the browser for Graphite to function.)</p>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
// Load the editor application or display the incompatibility message
|
||||
if (incompatibility) {
|
||||
document.body.innerHTML += incompatibility;
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="src/main.ts"></script>
|
||||
</body>
|
||||
@keyframes spinning-loading-indicator {
|
||||
0% {
|
||||
transform: translate(-30px, -30px) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-30px, -30px) rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script type="module" src="src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
28
frontend/package-lock.json
generated
28
frontend/package-lock.json
generated
|
@ -7,7 +7,8 @@
|
|||
"name": "graphite-web-frontend",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.2.0",
|
||||
"@fontsource/inconsolata": "^5.2.5",
|
||||
"@fontsource/source-sans-pro": "^5.2.5",
|
||||
"class-transformer": "^0.5.1",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"reflect-metadata": "^0.2.2"
|
||||
|
@ -555,6 +556,21 @@
|
|||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/inconsolata": {
|
||||
"version": "5.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/inconsolata/-/inconsolata-5.2.5.tgz",
|
||||
"integrity": "sha512-OvzkZY5qYghv/jEV6cfGZzFhdFTvSnU+ExPC7WcZ7w8PdRhtiu/SpcBWOBt+3LXgS0n9qyepgq4zZmxlDTlGGQ==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/source-sans-pro": {
|
||||
"version": "5.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/source-sans-pro/-/source-sans-pro-5.2.5.tgz",
|
||||
"integrity": "sha512-ypendqc4pYUc+EgF7qqPY9iVYEz1t/Qr03VojKxG/2g3dnpHa1B6DOlDxWQjQXDj5QrG6inEqGT0g+edjALZyg==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||
|
@ -1050,16 +1066,6 @@
|
|||
"vite": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/api": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.2.0.tgz",
|
||||
"integrity": "sha512-R8epOeZl1eJEl603aUMIGb4RXlhPjpgxbGVEaqY+0G5JG9vzV/clNlzTeqc+NLYXVqXcn8mb4c5b9pJIUDEyAg==",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/tauri"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
"wasm:watch-production": "cargo watch --postpone --watch-when-idle --workdir=wasm --shell \"wasm-pack build . --release --target=web -- --color=always\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/inconsolata": "^5.2.5",
|
||||
"@fontsource/source-sans-pro": "^5.2.5",
|
||||
"class-transformer": "^0.5.1",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"reflect-metadata": "^0.2.2"
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
// This file is the browser's entry point for the JS bundle
|
||||
|
||||
// reflect-metadata allows for runtime reflection of types in JavaScript.
|
||||
// Fonts
|
||||
import "@fontsource/inconsolata";
|
||||
import "@fontsource/source-sans-pro/400-italic.css";
|
||||
import "@fontsource/source-sans-pro/400.css";
|
||||
import "@fontsource/source-sans-pro/700-italic.css";
|
||||
import "@fontsource/source-sans-pro/700.css";
|
||||
|
||||
// `reflect-metadata` allows for runtime reflection of types in JavaScript.
|
||||
// It is needed for class-transformer to work and is imported as a side effect.
|
||||
// The library replaces the Reflect API on the window to support more features.
|
||||
import "reflect-metadata";
|
||||
|
|
|
@ -9,11 +9,13 @@ module.exports = {
|
|||
ecmaVersion: 2020,
|
||||
},
|
||||
extends: [
|
||||
// JS defaults
|
||||
"airbnb-base",
|
||||
// General Prettier defaults
|
||||
"eslint:recommended",
|
||||
"plugin:import/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:import/typescript",
|
||||
"prettier",
|
||||
],
|
||||
plugins: ["import", "@typescript-eslint", "prettier"],
|
||||
settings: {
|
||||
// https://github.com/import-js/eslint-plugin-import#resolvers
|
||||
"import/resolver": {
|
||||
|
@ -30,7 +32,6 @@ module.exports = {
|
|||
"!.*.js",
|
||||
"!.*.ts",
|
||||
],
|
||||
plugins: ["prettier"],
|
||||
rules: {
|
||||
// Standard ESLint config
|
||||
indent: "off",
|
||||
|
|
2
website/.gitignore
vendored
2
website/.gitignore
vendored
|
@ -1,3 +1,5 @@
|
|||
node_modules/
|
||||
public/
|
||||
static/fonts/
|
||||
static/syntax-highlighting.css
|
||||
static/text-balancer.js
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
title = "Graphite"
|
||||
description = "2D raster & vector editor that melds traditional layers & tools with a modern node-based procedural workflow."
|
||||
base_url = "https://graphite.rs"
|
||||
feed_filenames = ["rss.xml"]
|
||||
|
||||
compile_sass = true
|
||||
minify_html = true
|
||||
|
||||
[markdown]
|
||||
highlight_code = true
|
||||
highlight_theme = "css"
|
||||
highlight_themes_css = [
|
||||
{ theme = "kronuz", filename = "syntax-highlighting.css" },
|
||||
]
|
||||
|
||||
[extra]
|
||||
# Put all your custom variables here
|
|
@ -3,12 +3,14 @@ title = "Free online vector editor & procedural design tool"
|
|||
template = "section.html"
|
||||
|
||||
[extra]
|
||||
css = ["/page/index.css", "/component/carousel.css", "/component/feature-icons.css", "/component/feature-box.css", "/component/youtube-embed.css", "/layout/balance-text.css"]
|
||||
css = ["/page/index.css", "/component/carousel.css", "/component/feature-icons.css", "/component/feature-box.css", "/component/youtube-embed.css"]
|
||||
js = ["/js/carousel.js", "/js/youtube-embed.js", "/js/video-autoplay.js"]
|
||||
linked_js = ["https://static.graphite.rs/text-balancer/text-balancer.js"]
|
||||
linked_js = []
|
||||
meta_description = "Open source free software. A vector graphics creativity suite with a clean, intuitive interface. Opens instantly (no signup) and runs locally in a browser. Exports SVG, PNG, JPG."
|
||||
+++
|
||||
|
||||
<!-- replacements::text_balancer() -->
|
||||
|
||||
<!-- ▛ LOGO ▜ -->
|
||||
<section id="logo">
|
||||
<div class="block">
|
||||
|
|
|
@ -3,7 +3,6 @@ title = "About Graphite"
|
|||
|
||||
[extra]
|
||||
css = ["/page/about.css", "/component/feature-box.css"]
|
||||
linked_css = ["https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&display=swap"]
|
||||
+++
|
||||
|
||||
<section>
|
||||
|
@ -117,7 +116,7 @@ It's easy to learn and teach, yet Graphite's accessible design does not sacrific
|
|||
|
||||
<img src="https://static.graphite.rs/content/about/core-team-photo-keavon-chambers.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Photo of Keavon Chambers" />
|
||||
|
||||
## Keavon Chambers <span class="handle">(@Keavon)</span> <span class="flag" title="American">🇺🇸</span>
|
||||
## Keavon Chambers <span class="handle">(@Keavon)</span> <img src="https://static.graphite.rs/icons/flags/us.png" class="flag" title="American" />
|
||||
|
||||
***Founder, UI & product design, frontend, editor systems***
|
||||
|
||||
|
@ -128,7 +127,7 @@ Keavon is a creative generalist with a love for the fusion of arts and technolog
|
|||
|
||||
<img src="https://static.graphite.rs/content/about/core-team-photo-dennis-kobert.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Photo of Dennis Kobert" />
|
||||
|
||||
## Dennis Kobert <span class="handle">(@TrueDoctor)</span> <span class="flag" title="German">🇩🇪</span>
|
||||
## Dennis Kobert <span class="handle">(@TrueDoctor)</span> <img src="https://static.graphite.rs/icons/flags/de.png" class="flag" title="German" />
|
||||
|
||||
***Graphene node engine, research, architecture***
|
||||
|
||||
|
@ -143,7 +142,7 @@ Dennis is a mix between a mathematician and a mad scientist. While still enjoyin
|
|||
|
||||
<img src="https://static.graphite.rs/content/about/core-team-photo-hypercube__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Photo of Hypercube" />
|
||||
|
||||
## "Hypercube" <span class="handle">(@0Hypercube)</span> <span class="flag" title="British">🇬🇧</span>
|
||||
## "Hypercube" <span class="handle">(@0Hypercube)</span> <img src="https://static.graphite.rs/icons/flags/gb.png" class="flag" title="British" />
|
||||
|
||||
***Editor systems, nodes, tools, architecture***
|
||||
|
||||
|
@ -155,7 +154,7 @@ Dennis is a mix between a mathematician and a mad scientist. While still enjoyin
|
|||
|
||||
<img src="https://static.graphite.rs/content/about/core-team-photo-adam-gerhant.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Photo of Adam Gerhant" />
|
||||
|
||||
## Adam Gerhant <span class="handle">(@pendapia)</span> <span class="flag" title="American">🇺🇸</span>
|
||||
## Adam Gerhant <span class="handle">(@pendapia)</span> <img src="https://static.graphite.rs/icons/flags/us.png" class="flag" title="American" />
|
||||
|
||||
***Editor graph tooling, node data formats***
|
||||
|
||||
|
|
|
@ -145,17 +145,21 @@ Always on the bleeding edge and built to last— Graphite is written on a robust
|
|||
<img class="atlas" style="--atlas-index: 7" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>Desktop app (Windows, Mac, Linux)</span>
|
||||
</div>
|
||||
<div class="feature-icon ongoing">
|
||||
<div class="feature-icon ongoing" title="Development Ongoing">
|
||||
<img class="atlas" style="--atlas-index: 40" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>Simplified main properties panel</span>
|
||||
</div>
|
||||
<div class="feature-icon">
|
||||
<div class="feature-icon ongoing" title="Development Ongoing">
|
||||
<img class="atlas" style="--atlas-index: 12" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>GPU-accelerated raster rendering</span>
|
||||
</div>
|
||||
<div class="feature-icon">
|
||||
<img class="atlas" style="--atlas-index: 14" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>Infinitely zoomable/panable content</span>
|
||||
<span>Infinitely zoomable/panable raster</span>
|
||||
</div>
|
||||
<div class="feature-icon">
|
||||
<img class="atlas" style="--atlas-index: 21" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>Select mode (marquee masking)</span>
|
||||
</div>
|
||||
<div class="feature-icon">
|
||||
<img class="atlas" style="--atlas-index: 41" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
|
@ -171,7 +175,7 @@ Always on the bleeding edge and built to last— Graphite is written on a robust
|
|||
</div>
|
||||
<div class="feature-icon">
|
||||
<img class="atlas" style="--atlas-index: 57" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>Signed distance fields</span>
|
||||
<span>Signed distance field rendering</span>
|
||||
</div>
|
||||
<div class="feature-icon">
|
||||
<img class="atlas" style="--atlas-index: 56" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
|
@ -181,10 +185,6 @@ Always on the bleeding edge and built to last— Graphite is written on a robust
|
|||
<img class="atlas" style="--atlas-index: 52" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>Command palette and context menus</span>
|
||||
</div>
|
||||
<div class="feature-icon">
|
||||
<img class="atlas" style="--atlas-index: 21" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>Select mode (marquee masking)</span>
|
||||
</div>
|
||||
<div class="feature-icon">
|
||||
<img class="atlas" style="--atlas-index: 53" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>Local fonts access</span>
|
||||
|
|
194
website/install-fonts.js
Normal file
194
website/install-fonts.js
Normal file
|
@ -0,0 +1,194 @@
|
|||
const fs = require("fs");
|
||||
const https = require("https");
|
||||
const path = require("path");
|
||||
|
||||
// Define basePath
|
||||
const basePath = path.resolve(__dirname);
|
||||
|
||||
// Define files to copy as [source, destination] pairs
|
||||
// Files with the same destination will be concatenated
|
||||
const FILES_TO_COPY = [
|
||||
["node_modules/@fontsource-variable/inter/opsz.css", "static/fonts/common.css"],
|
||||
["node_modules/@fontsource-variable/inter/opsz-italic.css", "static/fonts/common.css"],
|
||||
["node_modules/@fontsource/bona-nova/700.css", "static/fonts/common.css"],
|
||||
];
|
||||
|
||||
// Define directories to copy recursively as [source, destination] pairs
|
||||
const DIRECTORIES_TO_COPY = [
|
||||
["node_modules/@fontsource-variable/inter/files", "static/fonts/files"],
|
||||
["node_modules/@fontsource/bona-nova/files", "static/fonts/files"],
|
||||
];
|
||||
|
||||
// Track processed destination files and CSS content
|
||||
const processedDestinations = new Set();
|
||||
const cssDestinations = new Set();
|
||||
const allCopiedFiles = new Set();
|
||||
|
||||
// Process each file
|
||||
FILES_TO_COPY.forEach(([source, dest]) => {
|
||||
// Convert relative paths to absolute paths
|
||||
const sourcePath = path.join(basePath, source);
|
||||
const destPath = path.join(basePath, dest);
|
||||
|
||||
// Track CSS destinations for later analysis
|
||||
if (dest.endsWith(".css")) {
|
||||
cssDestinations.add(destPath);
|
||||
}
|
||||
|
||||
// Ensure destination directory exists
|
||||
const destDir = path.dirname(destPath);
|
||||
if (!fs.existsSync(destDir)) {
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
console.log(`Created directory: ${destDir}`);
|
||||
}
|
||||
|
||||
try {
|
||||
// Read source file content
|
||||
const content = fs.readFileSync(sourcePath, "utf8");
|
||||
|
||||
// Check if destination has been processed before
|
||||
if (processedDestinations.has(destPath)) {
|
||||
// Append to existing file
|
||||
fs.appendFileSync(destPath, "\n\n" + content);
|
||||
console.log(`Appended: ${sourcePath} → ${destPath}`);
|
||||
} else {
|
||||
// First time writing to this destination - copy the file
|
||||
fs.writeFileSync(destPath, content);
|
||||
processedDestinations.add(destPath);
|
||||
console.log(`Copied: ${sourcePath} → ${destPath}`);
|
||||
}
|
||||
|
||||
// Replace all occurrences of "./files" with "/fonts" in the destination file
|
||||
let destFileContent = fs.readFileSync(destPath, "utf8");
|
||||
destFileContent = destFileContent.replaceAll("./files/", "/fonts/files/");
|
||||
fs.writeFileSync(destPath, destFileContent);
|
||||
} catch (error) {
|
||||
console.error(`Error processing ${sourcePath} to ${destPath}:`, error);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Function to recursively copy a directory
|
||||
function copyDirectoryRecursive(source, destination) {
|
||||
// Ensure destination directory exists
|
||||
if (!fs.existsSync(destination)) {
|
||||
fs.mkdirSync(destination, { recursive: true });
|
||||
console.log(`Created directory: ${destination}`);
|
||||
}
|
||||
|
||||
// Get all items in the source directory
|
||||
const items = fs.readdirSync(source);
|
||||
|
||||
// Process each item
|
||||
items.forEach((item) => {
|
||||
const sourcePath = path.join(source, item);
|
||||
const destPath = path.join(destination, item);
|
||||
|
||||
// Check if item is a directory or file
|
||||
const stats = fs.statSync(sourcePath);
|
||||
if (stats.isDirectory()) {
|
||||
// Recursively copy subdirectory
|
||||
copyDirectoryRecursive(sourcePath, destPath);
|
||||
} else {
|
||||
// Copy file and track it
|
||||
fs.copyFileSync(sourcePath, destPath);
|
||||
allCopiedFiles.add(destPath);
|
||||
console.log(`Copied: ${sourcePath} → ${destPath}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Process each directory
|
||||
DIRECTORIES_TO_COPY.forEach(([source, dest]) => {
|
||||
// Convert relative paths to absolute paths
|
||||
const sourcePath = path.join(basePath, source);
|
||||
const destPath = path.join(basePath, dest);
|
||||
|
||||
try {
|
||||
copyDirectoryRecursive(sourcePath, destPath);
|
||||
console.log(`Copied directory: ${sourcePath} → ${destPath}`);
|
||||
} catch (error) {
|
||||
console.error(`Error copying directory ${sourcePath} to ${destPath}:`, error);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("All files and directories copied successfully!");
|
||||
|
||||
// Now check which of the copied files are actually referenced in CSS
|
||||
console.log("\nChecking for unused font files...");
|
||||
|
||||
// Read all CSS content and join it
|
||||
let allCssContent = "";
|
||||
cssDestinations.forEach((cssPath) => {
|
||||
try {
|
||||
const content = fs.readFileSync(cssPath, "utf8");
|
||||
allCssContent += content;
|
||||
} catch (error) {
|
||||
console.error(`Error reading CSS file ${cssPath}:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
// Filter files that aren't referenced in CSS
|
||||
const unusedFiles = [];
|
||||
allCopiedFiles.forEach((filePath) => {
|
||||
const fileName = path.basename(filePath);
|
||||
|
||||
// Check if the file name is mentioned in any CSS
|
||||
if (!allCssContent.includes(fileName)) {
|
||||
unusedFiles.push(filePath);
|
||||
}
|
||||
});
|
||||
|
||||
// Delete unused files
|
||||
if (unusedFiles.length > 0) {
|
||||
console.log(`Found ${unusedFiles.length} unused font files to delete:`);
|
||||
unusedFiles.forEach((filePath) => {
|
||||
try {
|
||||
fs.unlinkSync(filePath);
|
||||
console.log(`Deleted unused file: ${filePath}`);
|
||||
} catch (error) {
|
||||
console.error(`Error deleting file ${filePath}:`, error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("No unused font files found.");
|
||||
}
|
||||
|
||||
console.log("\nFont installation complete!");
|
||||
|
||||
// Fetch and save text-balancer.js, which we don't commit to the repo so we're not version controlling dependency code
|
||||
const textBalancerUrl = "https://static.graphite.rs/text-balancer/text-balancer.js";
|
||||
const textBalancerDest = path.join(basePath, "static", "text-balancer.js");
|
||||
console.log("\nDownloading text-balancer.js...");
|
||||
https
|
||||
.get(textBalancerUrl, (res) => {
|
||||
if (res.statusCode !== 200) {
|
||||
console.error(`Failed to download text-balancer.js. Status code: ${res.statusCode}`);
|
||||
res.resume();
|
||||
return;
|
||||
}
|
||||
|
||||
let data = "";
|
||||
res.on("data", (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on("end", () => {
|
||||
try {
|
||||
// Ensure destination directory exists
|
||||
const destDir = path.dirname(textBalancerDest);
|
||||
if (!fs.existsSync(destDir)) {
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
console.log(`Created directory: ${destDir}`);
|
||||
}
|
||||
fs.writeFileSync(textBalancerDest, data, "utf8");
|
||||
console.log(`Downloaded and saved: ${textBalancerDest}`);
|
||||
} catch (error) {
|
||||
console.error(`Error saving text-balancer.js:`, error);
|
||||
}
|
||||
});
|
||||
})
|
||||
.on("error", (err) => {
|
||||
console.error(`Error downloading text-balancer.js:`, err);
|
||||
});
|
|
@ -4,9 +4,7 @@
|
|||
<meta charset="utf-8" />
|
||||
<title>Bezier-rs Interactive Documentation</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Bona+Nova:wght@700&family=Inter:wght@500&display=swap" rel="stylesheet" />
|
||||
<link href="fonts.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<noscript>JavaScript is required</noscript>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
html,
|
||||
body {
|
||||
font-family: "Inter", sans-serif;
|
||||
font-family: "Inter Variable", sans-serif;
|
||||
text-align: center;
|
||||
background-color: white;
|
||||
}
|
||||
|
|
|
@ -29,5 +29,6 @@ mkdir dist/libraries/bezier-rs
|
|||
cd bezier-rs-demos
|
||||
npm ci
|
||||
NODE_ENV=production npm run build
|
||||
cp ../../static/fonts/common.css dist/fonts.css
|
||||
mv dist/* ../dist/libraries/bezier-rs
|
||||
cd ..
|
||||
|
|
1507
website/package-lock.json
generated
1507
website/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -2,7 +2,6 @@
|
|||
"name": "graphite-website",
|
||||
"description": "Graphite's website. This npm package is for dev tooling only, such as eslint.",
|
||||
"private": true,
|
||||
"scripts": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/GraphiteEditor/Graphite.git"
|
||||
|
@ -10,15 +9,22 @@
|
|||
"author": "Graphite Authors <contact@graphite.rs>",
|
||||
"license": "Apache-2.0",
|
||||
"homepage": "https://graphite.rs",
|
||||
"scripts": {
|
||||
"install-fonts": "npm ci && node install-fonts.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^8.7.0",
|
||||
"@typescript-eslint/parser": "^8.7.0",
|
||||
"eslint": "^9.11.1",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.3",
|
||||
"eslint-plugin-import": "^2.30.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"prettier": "^3.3.3",
|
||||
"sass": "^1.78.0"
|
||||
"sass": "1.78.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/inter": "^5.2.5",
|
||||
"@fontsource/bona-nova": "^5.2.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ html,
|
|||
body {
|
||||
color: var(--color-navy);
|
||||
background: white;
|
||||
font-family: "Inter", sans-serif;
|
||||
font-family: "Inter Variable", sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
font-size: 18px;
|
||||
|
@ -157,12 +157,12 @@ body > .page {
|
|||
background-image: url('data:image/svg+xml;utf8,\
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M8,15C5.12471,9.753694 0.5,8.795225 0.5,4.736524 C0.5,-0.507473 7.468734,0 8,4.967381 C8.531266,0 15.5,-0.507473 15.5,4.736524 C15.5,8.795225 10.87529,9.753694 8,15z" fill="%23cc304f" /></svg>\
|
||||
');
|
||||
display: inline-block;
|
||||
width: 0.75em;
|
||||
height: 0.75em;
|
||||
margin-left: 0.25em;
|
||||
margin-bottom: -0.1em;
|
||||
vertical-align: baseline;
|
||||
display: inline-block;
|
||||
width: 0.75em;
|
||||
height: 0.75em;
|
||||
margin-left: 0.25em;
|
||||
margin-bottom: -0.1em;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,7 +323,6 @@ body > .page {
|
|||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
details[open] summary::before {
|
||||
|
@ -436,7 +435,7 @@ h3,
|
|||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: "Inter", sans-serif;
|
||||
font-family: "Inter Variable", sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 800;
|
||||
display: inline-block;
|
||||
|
@ -663,7 +662,7 @@ hr,
|
|||
|
||||
.arrow::after {
|
||||
content: " »";
|
||||
font-family: "Inter", sans-serif;
|
||||
font-family: "Inter Variable", sans-serif;
|
||||
}
|
||||
|
||||
.video-background {
|
||||
|
|
|
@ -38,7 +38,7 @@ pre {
|
|||
content: attr(data-lang);
|
||||
color: rgba(var(--color-seaside-rgb), 0.5);
|
||||
text-transform: lowercase;
|
||||
font-family: "Inter", sans-serif;
|
||||
font-family: "Inter Variable", sans-serif;
|
||||
font-size: 0.75em;
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
h1.feature-box-header.feature-box-header {
|
||||
&,
|
||||
& a {
|
||||
font-family: "Inter", sans-serif;
|
||||
font-family: "Inter Variable", sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
// CSS component of the JS text balancer script.
|
||||
// This must be loaded as an inline stylesheet in the head of the
|
||||
// document to avoid the possibility of a visible layout shift.
|
||||
|
||||
.balance-text {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@media (scripting: none) {
|
||||
.balance-text {
|
||||
visibility: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
@supports (text-wrap: balance) {
|
||||
.balance-text,
|
||||
.balanced-text {
|
||||
text-align: left;
|
||||
text-wrap: balance;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
|
@ -5,8 +5,12 @@
|
|||
animation: fadeInAfterWait 2s ease-in 2s forwards;
|
||||
|
||||
@keyframes fadeInAfterWait {
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,11 +38,9 @@
|
|||
}
|
||||
|
||||
.flag {
|
||||
font-family: "Noto Color Emoji", sans-serif;
|
||||
font-style: normal;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: middle;
|
||||
font-size: 0.8em;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
</div>
|
||||
</section>
|
||||
{%- if not page.summary -%}
|
||||
{{ throw(message = "ARTICLE HAS NO SUMMARY! After the first paragraph (or two short ones), a `<!-- more -->` comment must be inserted in the markdown. Otherwise the blog page would be missing its preview text." | safe) }}
|
||||
{{ throw(message = "------------------------------------------------------------> ARTICLE HAS NO SUMMARY! After the first paragraph (or two short ones), a `<!-- more -->` comment must be inserted in the markdown. Otherwise the blog page would be missing its preview text." | safe) }}
|
||||
{%- endif -%}
|
||||
{%- endblock content -%}
|
||||
|
||||
|
|
|
@ -27,15 +27,19 @@
|
|||
{% block rss -%}
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS" href="{{ get_url(path = 'blog/rss.xml', trailing_slash = false) | safe }}" />
|
||||
{%- endblock %}
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
|
||||
{#- ======================================================================== -#}
|
||||
|
||||
{#- ON EVERY PAGE OF THE SITE: CSS AND JS TO LOAD EITHER AS A LINK OR INLINE -#}
|
||||
{#- ======================================================================== -#}
|
||||
{%- set global_linked_css = ["https://fonts.googleapis.com/css2?family=Bona+Nova:wght@700&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"] -%}
|
||||
{%- set global_linked_js = [] -%}
|
||||
{%- set global_css = ["/base.css"] -%}
|
||||
{%- set global_linked_css = [] -%}
|
||||
{%- set global_js = ["/js/text-justification.js", "/js/navbar.js"] -%}
|
||||
{%- set global_css = ["/base.css", "/fonts/common.css"] -%}
|
||||
{%- set fonts_loaded = load_data(path="static/fonts/common.css", format="plain", required=false) -%}
|
||||
{%- if not fonts_loaded -%}
|
||||
{{ throw(message="------------------------------------------------------------> FONTS ARE NOT INSTALLED! Before running Zola, execute `npm run install-fonts` from the `/website` directory.") }}
|
||||
{%- endif -%}
|
||||
|
||||
{#- RETRIEVE FROM TEMPLATES AND PAGES: CSS AND JS TO LOAD EITHER AS A LINK OR INLINE -#}
|
||||
{#- ================================================================================ -#}
|
||||
|
@ -89,6 +93,7 @@
|
|||
{{ load_data(path = path) | safe }}
|
||||
{{ "</" ~ "script>" | safe }}
|
||||
{%- endfor %}
|
||||
|
||||
{{- get_env(name = "INDEX_HTML_HEAD_INCLUSION", default = "") | safe }}
|
||||
</head>
|
||||
<body>
|
||||
|
@ -119,8 +124,10 @@
|
|||
</header>
|
||||
<main>
|
||||
{%- filter replace(from = "<!-- replacements::blog_posts(count = 2) -->", to = replacements::blog_posts(count = 2)) -%}
|
||||
{%- filter replace(from = "<!-- replacements::text_balancer() -->", to = replacements::text_balancer()) -%}
|
||||
{%- block content -%}{%- endblock -%}
|
||||
{%- endfilter -%}
|
||||
{%- endfilter -%}
|
||||
</main>
|
||||
<footer>
|
||||
<hr />
|
||||
|
|
|
@ -12,3 +12,29 @@
|
|||
</div>
|
||||
{% endfor %}
|
||||
{% endmacro blog_posts %}
|
||||
|
||||
{% macro text_balancer() %}
|
||||
<style>
|
||||
.balance-text {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@media (scripting: none) {
|
||||
.balance-text {
|
||||
visibility: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
@supports (text-wrap: balance) {
|
||||
.balance-text,
|
||||
.balanced-text {
|
||||
text-align: left;
|
||||
text-wrap: balance;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
{{ load_data(path="static/text-balancer.js", format="plain") | safe }}
|
||||
</script>
|
||||
{% endmacro text_balancer %}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue