Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions app/Livewire/Customer/Course/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,13 @@ public function completedCount(): int
}

/**
* Published modules with at least one published lesson.
* Every published module, including those still awaiting their first
* released lesson.
*
* Drives the pre-purchase curriculum outline, which lists every lesson —
* locked or not — so non-purchasers can see the whole course.
* locked or not — so non-purchasers can see the whole course. Modules with
* nothing released yet are listed as coming soon rather than hidden, so the
* outline reflects the full shape of the course.
*
* Keys are preserved so the view can number each module by its real position
* in the course rather than its position within this filtered subset.
Expand All @@ -140,8 +143,17 @@ public function outlineModules(): Collection
}

return $this->course->modules
->filter(fn (CourseModule $module): bool => $module->is_published
&& $module->lessons->contains(fn (CourseLesson $lesson): bool => $lesson->is_published));
->filter(fn (CourseModule $module): bool => $module->is_published);
}

/**
* A published module whose lessons are all still unreleased.
*/
public function isComingSoonModule(CourseModule $module): bool
{
return ! $module->lessons->contains(
fn (CourseLesson $lesson): bool => $lesson->is_published
);
}

#[Computed]
Expand Down
2 changes: 1 addition & 1 deletion app/Notifications/PluginReviewChecksIncomplete.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class PluginReviewChecksIncomplete extends Notification implements ShouldQueue
{
use Queueable;

private const DOCS_BASE = 'https://nativephp.com/docs/mobile/3/plugins';
private const DOCS_BASE = 'https://nativephp.com/docs/mobile/plugins';

/**
* @var array<string, array{label: string, passing_label: string, docs_url: string, docs_label: string}>
Expand Down
30 changes: 28 additions & 2 deletions app/Support/GitHub.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,38 @@ public function releases(): Collection
) ?? collect();
}

public function releasesAfter(string $version): Collection
/**
* Releases strictly after the given version, optionally capped below
* another version and its prereleases — without a cap, a versioned
* changelog would list the next major's releases too (4.0.0-rc.1
* compares greater than any 3.x tag).
*/
public function releasesAfter(string $version, ?string $before = null): Collection
{
$version = ltrim($version, 'v');
$ceiling = $before === null ? null : ltrim($before, 'v').'-dev';

return $this->releases()->filter(function (Release $release) use ($version, $ceiling) {
$tag = ltrim((string) $release->tag_name, 'v');

return version_compare($tag, $version, '>')
&& ($ceiling === null || version_compare($tag, $ceiling, '<'));
})->values();
}

/**
* Releases for the given version and everything later, including that
* prefix version itself and its prereleases. releasesAfter("4.0.0")
* would exclude 4.0.0-rc.1 because version_compare ranks prereleases
* below the release; "dev" ranks below every other prerelease marker,
* so a "-dev" floor admits alphas, betas, RCs and the release itself.
*/
public function releasesFrom(string $version): Collection
{
$floor = ltrim($version, 'v').'-dev';

return $this->releases()->filter(
fn (Release $release) => version_compare(ltrim((string) $release->tag_name, 'v'), $version, '>')
fn (Release $release) => version_compare(ltrim((string) $release->tag_name, 'v'), $floor, '>=')
)->values();
}

Expand Down
16 changes: 11 additions & 5 deletions app/Support/GitHub/Release.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,31 @@ public function getBodyForMarkdown(): string
{
$body = $this->body;

// Convert any URLs to Markdown links
// Convert bare URLs to Markdown links. Release bodies can already
// contain Markdown links, so URLs preceded by "(" or "<" are left
// alone — wrapping those again nests link syntax and the page ends
// up rendering both the text and the URL. Closing ")" and "]" are
// excluded from the URL so a link's own delimiters are never
// swallowed into it.
if ($this->convertLinks) {
$body = preg_replace(
'/https?:\/\/[^\s]+\/pull\/(\d+)/',
'/(?<![(<])https?:\/\/[^\s)\]]+\/pull\/(\d+)/',
'[#$1]($0)',
$body
);

$body = preg_replace(
'/(https?:\/\/(?![^\s]+\/pull\/\d+)[^\s]+)/',
'/(?<![(<])(https?:\/\/(?![^\s]+\/pull\/\d+)[^\s)\]]+)/',
'[$1]($1)',
$body
);
}

// Change any @ tags to markdown links to GitHub
// Change any @ tags to markdown links to GitHub, unless they are
// already the text of a Markdown link or part of an email address.
if ($this->withUserLinks) {
$body = preg_replace(
'/@([a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)/',
'/(?<![\w\[])@([a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)/',
'[@$1](https://github.com/$1)',
$body
);
Expand Down
5 changes: 3 additions & 2 deletions config/docs.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

'latest_versions' => [
'desktop' => 2,
'mobile' => 3,
'mobile' => 4,
],

/*
Expand All @@ -32,7 +32,7 @@

'prerelease_versions' => [
'desktop' => [],
'mobile' => [4],
'mobile' => [],
],

/*
Expand All @@ -53,6 +53,7 @@
4 => [
'the-basics/native-components' => 'the-basics/native-ui',
'the-basics/dialog' => 'the-basics/dialogs',
'the-basics/web-view' => 'edge-components/web-view',
'getting-started/deployment' => 'publishing/introduction',
'plugins/vibe' => 'digging-deeper/websockets',

Expand Down
9 changes: 9 additions & 0 deletions resources/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
@source '../../storage/framework/views/*.php';
@source '../../vendor/filament/**/*.blade.php';

/*
* flux.css pulls in the flux-pro stubs, and the carousel slide stub declares
* `[snap="mandatory"_&]:snap-always` — one bracket short, so it compiles to the
* invalid selector `snap="mandatory" &` and Lightning CSS warns on every build.
* We don't use <flux:carousel>, so skip that stub. Drop this once Flux fixes it
* upstream; remove it first if we ever adopt the carousel.
*/
@source not '../../vendor/livewire/flux-pro/stubs/resources/views/flux/carousel';

/* Safelist classes used in dynamic content (blog posts from database) */
@source inline("grid-cols-1 grid-cols-2 grid-cols-3 grid-cols-4 grid-cols-5 grid-cols-6");

Expand Down
54 changes: 52 additions & 2 deletions resources/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,58 @@ Alpine.data('countdown', (iso) => ({
}, // tidy up
}))

// Which platform path the homepage is showing: 'mobile' | 'desktop'.
// Shared across sections (hero, explainer) and remembered between visits.
// Registered on alpine:init because $persist only exists once Livewire has
// registered Alpine's plugins.
document.addEventListener('alpine:init', () => {
Alpine.store('platform', {
current: Alpine.$persist('mobile').as('nativephpPlatform'),
is(name) {
return this.current === name
},
select(name) {
if (this.current === name) return

this.current = name
this.revealExplainer()
},
// Switching tracks rewrites the explainer, so bring it into view.
// Two frames: one for Alpine to apply the change, one for the
// browser to lay it out, so the target offset is the final one.
revealExplainer() {
requestAnimationFrame(() =>
requestAnimationFrame(() => {
const target = document.getElementById('platform-explainer')
if (!target) return

const nav = document.querySelector('[data-site-nav]')
const offset = (nav?.offsetHeight ?? 0) + 16
const top =
target.getBoundingClientRect().top +
window.scrollY -
offset

window.scrollTo({
top: Math.max(0, top),
behavior: window.matchMedia(
'(prefers-reduced-motion: reduce)',
).matches
? 'auto'
: 'smooth',
})
}),
)
},
})
})

Livewire.start()

// Docsearch
const docsPathMatch = window.location.pathname.match(/^\/docs\/(desktop|mobile)\/(\d+)/)
const docsPathMatch = window.location.pathname.match(
/^\/docs\/(desktop|mobile)\/(\d+)/,
)
const docsearchOptions = {
appId: 'ZNII9QZ8WI',
apiKey: '9be495a1aaf367b47c873d30a8e7ccf5',
Expand Down Expand Up @@ -148,7 +196,9 @@ docsearch({
// pressing Cmd+K only registers one handler (avoiding duplicate modals).
const mobileContainer = document.getElementById('docsearch-mobile')
if (mobileContainer) {
const desktopButton = document.querySelector('#docsearch-desktop .DocSearch-Button')
const desktopButton = document.querySelector(
'#docsearch-desktop .DocSearch-Button',
)
if (desktopButton) {
const mobileButton = desktopButton.cloneNode(true)
mobileContainer.appendChild(mobileButton)
Expand Down
2 changes: 1 addition & 1 deletion resources/views/components/blog/ad-rotation.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class="group relative z-0 grid place-items-center overflow-hidden rounded-2xl bg

{{-- Price hint --}}
<div class="mt-2 text-xs text-sky-100">
Plans from $19/mo
Plans from $10/mo
</div>

{{-- Decorative stars --}}
Expand Down
4 changes: 2 additions & 2 deletions resources/views/components/docs/platform-switcher.blade.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@php
$isMobile = request()->is('docs/mobile/*');
$mobileHref = '/docs/mobile/3';
$desktopHref = '/docs/desktop/2';
$mobileHref = '/docs/mobile/'.config('docs.latest_versions.mobile');
$desktopHref = '/docs/desktop/'.config('docs.latest_versions.desktop');
@endphp

<a
Expand Down
Loading
Loading