文件
mynode/Assets/WebsiteAssets/templates/plans.mustache
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

273 行
7.7 KiB
Plaintext

<div id="plans-section" class="env-{{env}}">
<style>
.toggle-container {
display: flex;
border: 2px solid black;
border-radius: 100px;
overflow: hidden;
cursor: pointer;
margin-top: 20px;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.toggle-option {
flex: 1;
padding: 10px 20px;
text-align: center;
transition: background 0.3s, color 0.3s;
user-select: none;
white-space: nowrap;
}
.active {
background: black;
color: white;
}
.inactive {
background: white;
color: black;
}
/* Replace the Pro plan with the Pro+ plan when requesting increased storage */
body.-increased-storage .plan-group .account-type-2 {
display: none !important;
}
body:not(.-increased-storage) .plan-group .account-type-4 {
display: none !important;
}
@media (max-width: 480px) {
.toggle-container {
flex-direction: column;
width: 100%;
border-radius: 10px;
}
}
</style>
<div class="container">
<div class="row">
<div class="col-12 title-box">
<h1 translate class="text-center">
Our synchronisation and sharing <span class="frame-bg frame-bg-yellow">solutions</span>
</h1>
<p translate class="text-center sub-title">
Synchronise and share your notes with our range of plans.
</p>
</div>
</div>
<div class="toggle-container" id="toggle">
<div class="toggle-option active toggle-button-managed">Managed hosting</div>
<div class="toggle-option inactive toggle-button-self">Self-hosting</div>
</div>
<noscript>
<div class="alert alert-danger alert-env-dev" role="alert" style='text-align: center; margin-top: 10px;'>
To use this page please enable JavaScript!
</div>
</noscript>
<div class="row hosting-type-managed">
<div class="col-12 title-box">
<h1 translate class="text-center">
Joplin Cloud
</h1>
<p translate class="text-center sub-title">
<a href="https://joplincloud.com">Joplin Cloud</a> allows you to synchronise your notes across devices. It also lets you publish notes, and collaborate on notebooks with your friends, family or colleagues.
</p>
</div>
</div>
<div class="row hosting-type-self">
<div class="col-12 title-box">
<h1 translate class="text-center">
Joplin Server Business
</h1>
<p translate class="text-center sub-title">
Joplin Server Business is the self-hosted solution for businesses and organisations. Install it on your own infrastructure, keeping your data private and secure within your organisation while retaining full control over where it is stored.
</p>
</div>
</div>
<div style="display: flex; justify-content: center; margin-top: 1.2em" class="hosting-type-managed">
<div class="form-check form-check-inline">
<input id="pay-monthly-radio" class="form-check-input" type="radio" name="pay-radio" checked value="monthly">
<label translate style="font-weight: bold" class="form-check-label" for="pay-monthly-radio">
Pay Monthly
</label>
</div>
<div class="form-check form-check-inline">
<input id="pay-yearly-radio" class="form-check-input" type="radio" name="pay-radio" value="yearly">
<label translate style="font-weight: bold" class="form-check-label" for="pay-yearly-radio">
Pay Yearly
</label>
</div>
</div>
<div class="row plan-group plan-prices-monthly default-storage">
{{#plans.basic}}
{{> plan}}
{{/plans.basic}}
{{#plans.pro}}
{{> plan}}
{{/plans.pro}}
{{#plans.pro100Gb}}
{{> plan}}
{{/plans.pro100Gb}}
{{#plans.teams}}
{{> plan}}
{{/plans.teams}}
{{#plans.joplinServerBusiness}}
{{> plan}}
{{/plans.joplinServerBusiness}}
<p translate class="joplin-cloud-login-info hosting-type-managed">Already have a Joplin Cloud account? <a href="https://joplincloud.com">Login now</a></p>
</div>
<div class="row">
<div>
<h1>Feature comparison</h1>
<div class="joplin-cloud-feature-list">
{{{featureListHtml}}}
</div>
<p>&nbsp;</p>
</div>
</div>
<div class="row faq">
{{{faqHtml}}}
</div>
</div>
<script src="https://js.stripe.com/v3/"></script>
<script>
const urlQuery = new URLSearchParams(location.search);
let subscriptionPeriod = 'monthly';
const stripe = Stripe('{{{stripeConfig.publishableKey}}}');
let checkoutSessionUser = null;
// Temporary setup to allow Beta users to start their subscription.
function setupBetaHandling(query) {
let accountType = Number(query.get('account_type'));
if (isNaN(accountType)) accountType = 1;
const email = query.get('email');
if (!email) return;
$('.account-type-3').css('display', 'none');
$('.subscribeButton').text('Buy now');
if (accountType === 2) {
$('.account-type-1').css('display', 'none');
}
checkoutSessionUser = { email, accountType };
}
var createCheckoutSession = function(priceId) {
const promotionCode = urlQuery.get('promo') || '';
console.info('Creating Stripe session for price:', priceId, 'Promotion code:', promotionCode);
const source = localStorage.getItem('source');
return fetch("{{{stripeConfig.webhookBaseUrl}}}/stripe/createCheckoutSession", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
priceId,
promotionCode,
email: checkoutSessionUser ? checkoutSessionUser.email : '',
source,
})
}).then(async function(result) {
if (!result.ok) {
console.error('Could not create Stripe checkout session', await result.text());
alert('The checkout session could not be created. Please contact us on the forum for support.');
} else {
return result.json();
}
});
};
const applyPeriod = (period) => {
subscriptionPeriod = period;
$('.plan-group').removeClass(period === 'monthly' ? 'plan-prices-yearly' : 'plan-prices-monthly');
$('.plan-group').addClass('plan-prices-' + period);
$("#pay-" + period + '-radio').prop('checked', true);
}
$(() => {
$("input[name='pay-radio']").change(function() {
const period = $("input[type='radio'][name='pay-radio']:checked").val();
applyPeriod(period);
});
// Keep the ?source= label so it can be sent to Stripe at checkout.
// Only overwrite when the param is present, to preserve an earlier one.
const sourceParam = urlQuery.get('source');
if (sourceParam) localStorage.setItem('source', sourceParam);
setupBetaHandling(urlQuery);
if (urlQuery.get('period') === 'monthly') {
// Nothing - this is the default
} else {
applyPeriod('yearly');
}
$('.feature-description').hide();
$('.feature-title').click((event) => {
event.preventDefault();
const featureId = event.currentTarget.getAttribute('data-id');
$('.feature-description-' + featureId).toggle(200);
});
});
const setHostingType = (type, updateUrl = true) => {
const other = type === 'managed' ? 'self' : 'managed';
$('.toggle-button-' + type).addClass('active');
$('.toggle-button-' + type).removeClass('inactive');
$('.toggle-button-' + other).addClass('inactive');
$('.toggle-button-' + other).removeClass('active');
$('.hosting-type-' + type).show();
$('.hosting-type-' + other).hide();
if (updateUrl) {
const url = new URL(location.href);
url.searchParams.set('hosting', type);
history.replaceState(null, '', url);
}
}
$('.toggle-button-managed').click((event) => {
event.preventDefault();
setHostingType('managed');
});
$('.toggle-button-self').click((event) => {
event.preventDefault();
// Self-hosting is currently yearly-only
applyPeriod('yearly');
setHostingType('self');
});
const initialHostingType = urlQuery.get('hosting') ? urlQuery.get('hosting') : 'managed';
setHostingType(initialHostingType, false);
</script>
</div>