diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 62e91d36..256a1772 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -21,10 +21,10 @@ jobs: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: pnpm/action-setup@v2 with: - version: 7 + version: 8.11.0 - uses: actions/setup-node@v3 with: node-version: 18 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 849db7d3..0f6c1181 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,10 +10,10 @@ jobs: node-version: [18] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: pnpm/action-setup@v2 with: - version: 7 + version: 8.11.0 - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} diff --git a/.gitignore b/.gitignore index fe7f9e56..b7704c61 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,87 @@ dev-dist Dirt-Samples tidal-drum-machines webaudiofontdata -src-tauri/target \ No newline at end of file +src-tauri/target + +# BEGIN JetBrains -> END JetBrains + +# for JetBrains IDE users, e.g. WebStorm. Source: https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# END JetBrains -> BEGIN JetBrains diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 467a9391..556df3fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,7 +66,7 @@ To get the project up and running for development, make sure you have installed: - [git](https://git-scm.com/) - [node](https://nodejs.org/en/) >= 18 -- [pnpm](https://pnpm.io/) (`npm i pnpm -g`) +- [pnpm](https://pnpm.io/) (`curl -fsSL https://get.pnpm.io/install.sh | env PNPM_VERSION=8.11.0 sh -`) then, do the following: diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 6449969e..d979ff23 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -381,6 +381,19 @@ const generic_params = [ */ ['coarse'], + /** + * Allows you to set the output channels on the interface + * + * @name channels + * @synonyms ch + * + * @param {number | Pattern} channels pattern the output channels + * @example + * note("e a d b g").channels("3:4") + * + */ + ['channels', 'ch'], + ['phaserrate', 'phasr'], // superdirt only /** diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 9a5c456a..f6969a73 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1985,7 +1985,7 @@ Pattern.prototype.hush = function () { * note("c d e g").palindrome() */ export const palindrome = register('palindrome', function (pat) { - return pat.every(2, rev); + return pat.lastOf(2, rev); }); /** diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs index f4a03110..1c849499 100644 --- a/packages/core/test/pattern.test.mjs +++ b/packages/core/test/pattern.test.mjs @@ -20,6 +20,7 @@ import { slowcat, cat, sequence, + palindrome, polymeter, polymeterSteps, polyrhythm, @@ -571,6 +572,18 @@ describe('Pattern', () => { expect(sequence(1, 2, 3).firstCycle()).toStrictEqual(fastcat(1, 2, 3).firstCycle()); }); }); + describe('palindrome()', () => { + it('Can create palindrome', () => { + expect( + fastcat('a', 'b', 'c') + .palindrome() + .fast(2) + .firstCycle() + .sort((a, b) => a.part.begin.sub(b.part.begin)) + .map((a) => a.value), + ).toStrictEqual(['a', 'b', 'c', 'c', 'b', 'a']); + }); + }); describe('polyrhythm()', () => { it('Can layer up cycles', () => { expect(polyrhythm(['a', 'b'], ['c']).firstCycle()).toStrictEqual( diff --git a/packages/react/src/hooks/useStrudel.mjs b/packages/react/src/hooks/useStrudel.mjs index a10998e7..dbf2269e 100644 --- a/packages/react/src/hooks/useStrudel.mjs +++ b/packages/react/src/hooks/useStrudel.mjs @@ -74,8 +74,11 @@ function useStrudel({ } }); const activateCode = useCallback( - async (autostart = true) => { - const res = await evaluate(code, autostart); + async (newCode, autostart = true) => { + if (newCode) { + setCode(code); + } + const res = await evaluate(newCode || code, autostart); broadcast({ type: 'start', from: id }); return res; }, diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index b8f10d5d..6df5a6b6 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -1,4 +1,4 @@ -import { noteToMidi, valueToMidi } from './util.mjs'; +import { noteToMidi, valueToMidi, nanFallback } from './util.mjs'; import { getAudioContext, registerSound } from './index.mjs'; import { getEnvelope } from './helpers.mjs'; import { logger } from './logger.mjs'; @@ -33,6 +33,7 @@ export const getSampleBufferSource = async (s, n, note, speed, freq, bank, resol const ac = getAudioContext(); let sampleUrl; if (Array.isArray(bank)) { + n = nanFallback(n, 0); sampleUrl = bank[n % bank.length]; } else { const midiDiff = (noteA) => noteToMidi(noteA) - midi; diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index ed133366..3be97615 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -7,7 +7,7 @@ This program is free software: you can redistribute it and/or modify it under th import './feedbackdelay.mjs'; import './reverb.mjs'; import './vowel.mjs'; -import { clamp } from './util.mjs'; +import { clamp, nanFallback } from './util.mjs'; import workletsUrl from './worklets.mjs?url'; import { createFilter, gainNode, getCompressor } from './helpers.mjs'; import { map } from 'nanostores'; @@ -27,28 +27,16 @@ export function getSound(s) { export const resetLoadedSounds = () => soundMap.set({}); let audioContext; + export const getAudioContext = () => { if (!audioContext) { audioContext = new AudioContext(); + const maxChannelCount = audioContext.destination.maxChannelCount; + audioContext.destination.channelCount = maxChannelCount; } return audioContext; }; -let destination; -const getDestination = () => { - const ctx = getAudioContext(); - if (!destination) { - destination = ctx.createGain(); - destination.connect(ctx.destination); - } - return destination; -}; - -export const panic = () => { - getDestination().gain.linearRampToValueAtTime(0, getAudioContext().currentTime + 0.01); - destination = null; -}; - let workletsLoading; function loadWorklets() { @@ -95,6 +83,39 @@ export async function initAudioOnFirstClick(options) { let delays = {}; const maxfeedback = 0.98; +let channelMerger, destinationGain; + +// input: AudioNode, channels: ?Array +export const connectToDestination = (input, channels = [0, 1]) => { + const ctx = getAudioContext(); + if (channelMerger == null) { + channelMerger = new ChannelMergerNode(ctx, { numberOfInputs: ctx.destination.channelCount }); + destinationGain = new GainNode(ctx); + channelMerger.connect(destinationGain); + destinationGain.connect(ctx.destination); + } + //This upmix can be removed if correct channel counts are set throughout the app, + // and then strudel could theoretically support surround sound audio files + const stereoMix = new StereoPannerNode(ctx); + input.connect(stereoMix); + + const splitter = new ChannelSplitterNode(ctx, { + numberOfOutputs: stereoMix.channelCount, + }); + stereoMix.connect(splitter); + channels.forEach((ch, i) => { + splitter.connect(channelMerger, i % stereoMix.channelCount, clamp(ch, 0, ctx.destination.channelCount - 1)); + }); +}; + +export const panic = () => { + if (destinationGain == null) { + return; + } + destinationGain.gain.linearRampToValueAtTime(0, getAudioContext().currentTime + 0.01); + destinationGain = null; +}; + function getDelay(orbit, delaytime, delayfeedback, t) { if (delayfeedback > maxfeedback) { //logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`); @@ -104,7 +125,7 @@ function getDelay(orbit, delaytime, delayfeedback, t) { const ac = getAudioContext(); const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback); dly.start?.(t); // for some reason, this throws when audion extension is installed.. - dly.connect(getDestination()); + connectToDestination(dly, [0, 1]); delays[orbit] = dly; } delays[orbit].delayTime.value !== delaytime && delays[orbit].delayTime.setValueAtTime(delaytime, t); @@ -163,7 +184,7 @@ function getReverb(orbit, duration, fade, lp, dim, ir) { if (!reverbs[orbit]) { const ac = getAudioContext(); const reverb = ac.createReverb(duration, fade, lp, dim, ir); - reverb.connect(getDestination()); + connectToDestination(reverb, [0, 1]); reverbs[orbit] = reverb; } if ( @@ -269,7 +290,7 @@ export const superdough = async (value, deadline, hapDuration) => { bpsustain = 1, bprelease = 0.01, bandq = 1, - + channels = [1, 2], //phaser phaser, phaserdepth = 0.75, @@ -301,6 +322,11 @@ export const superdough = async (value, deadline, hapDuration) => { compressorAttack, compressorRelease, } = value; + gain = nanFallback(gain, 1); + + //music programs/audio gear usually increments inputs/outputs from 1, so imitate that behavior + channels = (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1); + gain *= velocity; // legacy fix for velocity let toDisconnect = []; // audio nodes that will be disconnected when the source has ended const onended = () => { @@ -434,9 +460,9 @@ export const superdough = async (value, deadline, hapDuration) => { } // last gain - const post = gainNode(postgain); + const post = new GainNode(ac, { gain: postgain }); chain.push(post); - post.connect(getDestination()); + connectToDestination(post, channels); // delay let delaySend; diff --git a/packages/superdough/util.mjs b/packages/superdough/util.mjs index db056376..d49ffd6b 100644 --- a/packages/superdough/util.mjs +++ b/packages/superdough/util.mjs @@ -1,3 +1,5 @@ +import { logger } from './logger.mjs'; + // currently duplicate with core util.mjs to skip dependency // TODO: add separate util module? @@ -51,3 +53,11 @@ export const valueToMidi = (value, fallbackValue) => { } return fallbackValue; }; + +export function nanFallback(value, fallback) { + if (isNaN(Number(value))) { + logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning'); + return fallback; + } + return value; +} diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 34f7a850..3d23c106 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -1038,6 +1038,31 @@ exports[`runs examples > example "ceil" example index 0 1`] = ` ] `; +exports[`runs examples > example "channels" example index 0 1`] = ` +[ + "[ 0/1 → 1/5 | note:e channels:[3 4] ]", + "[ 1/5 → 2/5 | note:a channels:[3 4] ]", + "[ 2/5 → 3/5 | note:d channels:[3 4] ]", + "[ 3/5 → 4/5 | note:b channels:[3 4] ]", + "[ 4/5 → 1/1 | note:g channels:[3 4] ]", + "[ 1/1 → 6/5 | note:e channels:[3 4] ]", + "[ 6/5 → 7/5 | note:a channels:[3 4] ]", + "[ 7/5 → 8/5 | note:d channels:[3 4] ]", + "[ 8/5 → 9/5 | note:b channels:[3 4] ]", + "[ 9/5 → 2/1 | note:g channels:[3 4] ]", + "[ 2/1 → 11/5 | note:e channels:[3 4] ]", + "[ 11/5 → 12/5 | note:a channels:[3 4] ]", + "[ 12/5 → 13/5 | note:d channels:[3 4] ]", + "[ 13/5 → 14/5 | note:b channels:[3 4] ]", + "[ 14/5 → 3/1 | note:g channels:[3 4] ]", + "[ 3/1 → 16/5 | note:e channels:[3 4] ]", + "[ 16/5 → 17/5 | note:a channels:[3 4] ]", + "[ 17/5 → 18/5 | note:d channels:[3 4] ]", + "[ 18/5 → 19/5 | note:b channels:[3 4] ]", + "[ 19/5 → 4/1 | note:g channels:[3 4] ]", +] +`; + exports[`runs examples > example "chooseCycles" example index 0 1`] = ` [ "[ 0/1 → 1/4 | s:bd ]", @@ -3267,22 +3292,22 @@ exports[`runs examples > example "outside" example index 0 1`] = ` exports[`runs examples > example "palindrome" example index 0 1`] = ` [ - "[ 0/1 → 1/4 | note:g ]", - "[ 1/4 → 1/2 | note:e ]", - "[ 1/2 → 3/4 | note:d ]", - "[ 3/4 → 1/1 | note:c ]", - "[ 1/1 → 5/4 | note:c ]", - "[ 5/4 → 3/2 | note:d ]", - "[ 3/2 → 7/4 | note:e ]", - "[ 7/4 → 2/1 | note:g ]", - "[ 2/1 → 9/4 | note:g ]", - "[ 9/4 → 5/2 | note:e ]", - "[ 5/2 → 11/4 | note:d ]", - "[ 11/4 → 3/1 | note:c ]", - "[ 3/1 → 13/4 | note:c ]", - "[ 13/4 → 7/2 | note:d ]", - "[ 7/2 → 15/4 | note:e ]", - "[ 15/4 → 4/1 | note:g ]", + "[ 0/1 → 1/4 | note:c ]", + "[ 1/4 → 1/2 | note:d ]", + "[ 1/2 → 3/4 | note:e ]", + "[ 3/4 → 1/1 | note:g ]", + "[ 1/1 → 5/4 | note:g ]", + "[ 5/4 → 3/2 | note:e ]", + "[ 3/2 → 7/4 | note:d ]", + "[ 7/4 → 2/1 | note:c ]", + "[ 2/1 → 9/4 | note:c ]", + "[ 9/4 → 5/2 | note:d ]", + "[ 5/2 → 11/4 | note:e ]", + "[ 11/4 → 3/1 | note:g ]", + "[ 3/1 → 13/4 | note:g ]", + "[ 13/4 → 7/2 | note:e ]", + "[ 7/2 → 15/4 | note:d ]", + "[ 15/4 → 4/1 | note:c ]", ] `; diff --git a/test/__snapshots__/tunes.test.mjs.snap b/test/__snapshots__/tunes.test.mjs.snap index 8d696d75..0a341d12 100644 --- a/test/__snapshots__/tunes.test.mjs.snap +++ b/test/__snapshots__/tunes.test.mjs.snap @@ -8372,16 +8372,16 @@ exports[`renders tunes > tune: hyperpop 1`] = ` exports[`renders tunes > tune: juxUndTollerei 1`] = ` [ - "[ 0/1 → 1/4 | note:bb3 s:sawtooth pan:0 cutoff:1188.2154262966046 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ 0/1 → 1/4 | note:c3 s:sawtooth pan:1 cutoff:1188.2154262966046 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ 1/4 → 1/2 | note:g3 s:sawtooth pan:0 cutoff:1361.2562095290161 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ 1/4 → 1/2 | note:eb3 s:sawtooth pan:1 cutoff:1361.2562095290161 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ 1/2 → 3/4 | note:eb3 s:sawtooth pan:0 cutoff:1524.257063143398 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ 1/2 → 3/4 | note:g3 s:sawtooth pan:1 cutoff:1524.257063143398 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ (101/200 → 1/1) ⇝ 201/200 | note:65 s:triangle pan:0 cutoff:1601.4815730092653 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ (101/200 → 1/1) ⇝ 201/200 | note:55 s:triangle pan:1 cutoff:1601.4815730092653 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ 3/4 → 1/1 | note:c3 s:sawtooth pan:0 cutoff:1670.953955747281 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ 3/4 → 1/1 | note:bb3 s:sawtooth pan:1 cutoff:1670.953955747281 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 0/1 → 1/4 | note:c3 s:sawtooth pan:0 cutoff:1188.2154262966046 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 0/1 → 1/4 | note:bb3 s:sawtooth pan:1 cutoff:1188.2154262966046 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 1/4 → 1/2 | note:eb3 s:sawtooth pan:0 cutoff:1361.2562095290161 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 1/4 → 1/2 | note:g3 s:sawtooth pan:1 cutoff:1361.2562095290161 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 1/2 → 3/4 | note:g3 s:sawtooth pan:0 cutoff:1524.257063143398 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 1/2 → 3/4 | note:eb3 s:sawtooth pan:1 cutoff:1524.257063143398 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ (101/200 → 1/1) ⇝ 201/200 | note:55 s:triangle pan:0 cutoff:1601.4815730092653 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ (101/200 → 1/1) ⇝ 201/200 | note:65 s:triangle pan:1 cutoff:1601.4815730092653 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 3/4 → 1/1 | note:bb3 s:sawtooth pan:0 cutoff:1670.953955747281 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 3/4 → 1/1 | note:c3 s:sawtooth pan:1 cutoff:1670.953955747281 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", ] `; diff --git a/website/astro.config.mjs b/website/astro.config.mjs index f8fceaf7..c5ca3317 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -13,24 +13,39 @@ import AstroPWA from '@vite-pwa/astro'; const site = `https://strudel.cc/`; // root url without a path const base = '/'; // base path of the strudel site +const baseNoTrailing = base.endsWith('/') ? base.slice(0, -1) : base; -// this rehype plugin converts relative anchor links to absolute ones -// it wokrs by prepending the absolute page path to anchor links -// example: #gain -> /learn/effects/#gain +// this rehype plugin fixes relative links +// it works by prepending the base + page path to anchor links +// and by prepending the base path to other relative links starting with / // this is necessary when using a base href like -// in this setup, relative anchor links will always link to base, instead of the current page -function absoluteAnchors() { +// examples with base as "mybase": +// #gain -> /mybase/learn/effects/#gain +// /some/page -> /mybase/some/page +function relativeURLFix() { return (tree, file) => { const chunks = file.history[0].split('/src/pages/'); // file.history[0] is the file path const path = chunks[chunks.length - 1].slice(0, -4); // only path inside src/pages, without .mdx return rehypeUrls((url) => { - if (!url.href.startsWith('#')) { + let newHref = baseNoTrailing; + if (url.href.startsWith('#')) { + // special case: a relative anchor link to the current page + newHref += `/${path}/${url.href}`; + } else if (url.href.startsWith('/')) { + // any other relative url starting with / + newHref += url.pathname; + if (url.pathname.indexOf('.') == -1) { + // append trailing slash to resource only if there is no file extension + newHref += url.pathname.endsWith('/') ? '' : '/'; + } + newHref += url.search || ''; + newHref += url.hash || ''; + } else { + // leave this URL alone return; } - const baseWithSlash = base.endsWith('/') ? base : base + '/'; - const absoluteUrl = baseWithSlash + path + url.href; - // console.log(url.href + ' -> ', absoluteUrl); - return absoluteUrl; + // console.log(url.href + ' -> ', newHref); + return newHref; })(tree); }; } @@ -40,7 +55,7 @@ const options = { remarkToc, // E.g. `remark-frontmatter` ], - rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'append' }], absoluteAnchors], + rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'append' }], relativeURLFix], }; // https://astro.build/config diff --git a/website/src/components/HeadCommon.astro b/website/src/components/HeadCommon.astro index 2aa6acfe..ab18500f 100644 --- a/website/src/components/HeadCommon.astro +++ b/website/src/components/HeadCommon.astro @@ -3,7 +3,7 @@ import { pwaInfo } from 'virtual:pwa-info'; import '../styles/index.css'; const { BASE_URL } = import.meta.env; -const base = BASE_URL; +const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL; --- @@ -11,20 +11,20 @@ const base = BASE_URL; - + - - + + - + - + diff --git a/website/src/components/Header/Header.astro b/website/src/components/Header/Header.astro index 4cee4282..6447d9d6 100644 --- a/website/src/components/Header/Header.astro +++ b/website/src/components/Header/Header.astro @@ -16,6 +16,9 @@ const { currentPage } = Astro.props as Props; // const lang = getLanguageFromURL(currentPage); const langCode = 'en'; // getLanguageFromURL(currentPage); const sidebar = SIDEBAR[langCode]; + +const { BASE_URL } = import.meta.env; +const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL; ---