1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-07-16 00:14:34 +02:00

Doc: Add Joplin Cloud feature descriptions to Plans page

This commit is contained in:
Laurent Cozic
2023-09-22 15:35:02 +01:00
parent 62c4fd4b7d
commit 89b3c41d65
10 changed files with 128 additions and 21 deletions

View File

@ -667,6 +667,30 @@ footer .bottom-links-row p {
color: #0557ba;
}
.joplin-cloud-feature-list .feature-description {
max-width: 600px;
font-size: .8em;
color: #555555;
margin-top: 5px;
}
.joplin-cloud-feature-list .feature-title {
text-decoration: none;
color: #000000;
margin-left: 10px;
border: 2px solid black;
display: inline-block;
width: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 100px;
height: 20px;
font-weight: bold;
font-size: 0.8em;
opacity: 0.5;
}
/*****************************************************************
WHAT'S NEW PAGE
*****************************************************************/

View File

@ -86,11 +86,7 @@ https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}}
{{> footer}}
</div>
<script
src="{{jsBaseUrl}}/bootstrap5.0.2.bundle.min.js"
rel="preload"
as="script"
></script>
<script src="{{jsBaseUrl}}/bootstrap5.0.2.bundle.min.js" rel="preload" as="script"></script>
<script src="{{{assetUrls.js.script}}}"></script>
{{> analytics}}

View File

@ -21,7 +21,7 @@
</div>
{{#featureLabelsOn}}
<p><i class="fas fa-check feature feature-on"></i>{{.}}</p>
<p><i class="fas fa-check feature feature-on"></i>{{title}}</p>
{{/featureLabelsOn}}
{{#featureLabelsOff}}

View File

@ -5,6 +5,9 @@
<h1 translate class="text-center">
Joplin Cloud <span class="frame-bg frame-bg-yellow">plans</span>
</h1>
<button id="myButton">My button</button>
<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>
@ -138,6 +141,14 @@
} else {
applyPeriod('yearly');
}
$('.feature-description').hide();
$('.feature-title').click((event) => {
event.preventDefault();
const featureId = event.currentTarget.getAttribute('data-id');
$('.feature-description-' + featureId).show();
});
});
</script>
</div>

View File

@ -19,6 +19,7 @@ export interface MarkdownTableHeader {
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
filter?: Function;
disableEscape?: boolean;
disableHtmlEscape?: boolean;
justify?: MarkdownTableJustify;
}
@ -39,10 +40,12 @@ const markdownUtils = {
return url;
},
escapeTableCell(text: string) {
escapeTableCell(text: string, escapeHtml = true) {
// Disable HTML code
text = text.replace(/</g, '&lt;');
text = text.replace(/>/g, '&gt;');
if (escapeHtml) {
text = text.replace(/</g, '&lt;');
text = text.replace(/>/g, '&gt;');
}
// Table cells can't contain new lines so replace with <br/>
text = text.replace(/\n/g, '<br/>');
// "|" is a reserved characters that should be escaped
@ -173,7 +176,7 @@ const markdownUtils = {
for (let j = 0; j < headers.length; j++) {
const h = headers[j];
const value = (h.filter ? h.filter(row[h.name]) : row[h.name]) || '';
const valueMd = h.disableEscape ? value : markdownUtils.escapeTableCell(value);
const valueMd = h.disableEscape ? value : markdownUtils.escapeTableCell(value, !h.disableHtmlEscape);
rowMd.push(stringPadding(valueMd, minCellWidth, ' ', stringPadding.RIGHT));
}
output.push(`| ${rowMd.join(' | ')} |`);

View File

@ -1,6 +1,7 @@
import * as fs from 'fs-extra';
import markdownUtils, { MarkdownTableHeader, MarkdownTableRow } from '../markdownUtils';
import { _ } from '../locale';
import { htmlentities } from '@joplin/utils/html';
type FeatureId = string;
@ -12,6 +13,7 @@ export enum PlanName {
interface PlanFeature {
title: string;
description?: string;
basic: boolean;
pro: boolean;
teams: boolean;
@ -32,7 +34,7 @@ export interface Plan {
iconName: string;
featuresOn: FeatureId[];
featuresOff: FeatureId[];
featureLabelsOn: string[];
featureLabelsOn: any[];
featureLabelsOff: string[];
cfaLabel: string;
cfaUrl: string;
@ -112,6 +114,8 @@ export function findPrice(prices: StripePublicConfigPrice[], query: FindPriceQue
}
const features = (): Record<FeatureId, PlanFeature> => {
const shareNotebookTitle = _('Share a notebook with others');
return {
maxItemSize: {
title: _('Max note or attachment size'),
@ -139,6 +143,7 @@ const features = (): Record<FeatureId, PlanFeature> => {
},
publishNote: {
title: _('Publish notes to the internet'),
description: 'You can publish a note from the Joplin app. You will get a link that you can share with other users, who can then view the note in their browser.',
basic: true,
pro: true,
teams: true,
@ -157,18 +162,21 @@ const features = (): Record<FeatureId, PlanFeature> => {
},
collaborate: {
title: _('Collaborate on a notebook with others'),
description: _('This allows another user to share a notebook with you, and you can then both collaborate on it. It does not however allow you to share a notebook with someone else, unless you have the feature "%s".', shareNotebookTitle),
basic: true,
pro: true,
teams: true,
},
share: {
title: _('Share a notebook with others'),
title: shareNotebookTitle,
description: 'You can share a notebook with other Joplin Cloud users, who can then view the notes and edit them.',
basic: false,
pro: true,
teams: true,
},
emailToNote: {
title: _('Email to Note'),
description: 'You can save your emails in Joplin Cloud by forwarding your emails to a special email address. The subject of the email will become the note title, and the email body will become the note content.',
basic: false,
pro: true,
teams: true,
@ -185,8 +193,15 @@ const features = (): Record<FeatureId, PlanFeature> => {
pro: false,
teams: true,
},
sharingAccessControl: {
title: _('Sharing access control'),
// sharingAccessControl: {
// title: _('Sharing access control'),
// basic: false,
// pro: false,
// teams: true,
// },
sharePermissions: {
title: _('Share permissions'),
description: 'With this feature you can define whether a notebook you share with someone can be edited or is read-only. It can be useful for example to share documentation that you do not want to be modified.',
basic: false,
pro: false,
teams: true,
@ -235,7 +250,7 @@ export const getFeatureById = (featureId: FeatureId): PlanFeature => {
export const getFeaturesByPlan = (planName: PlanName, featureOn: boolean): PlanFeature[] => {
const output: PlanFeature[] = [];
for (const [, v] of Object.entries(features)) {
for (const [, v] of Object.entries(features())) {
if (v[planName] === featureOn) {
output.push(v);
}
@ -261,6 +276,7 @@ export const createFeatureTableMd = () => {
{
name: 'featureLabel',
label: 'Feature',
disableHtmlEscape: true,
},
{
name: 'basic',
@ -285,9 +301,20 @@ export const createFeatureTableMd = () => {
return '✔️';
};
for (const [, feature] of Object.entries(features())) {
const makeFeatureLabel = (featureId: string, feature: PlanFeature) => {
const output: string[] = [
`${htmlentities(feature.title)}`,
];
if (feature.description) {
output.push(`<a data-id=${htmlentities(featureId)} class="feature-title" name="feature-${htmlentities(featureId)}" href="#feature-${htmlentities(featureId)}">i</a>`);
output.push(`<div class="feature-description feature-description-${htmlentities(featureId)}">${htmlentities(feature.description)}</div>`);
}
return output.join('');
};
for (const [id, feature] of Object.entries(features())) {
const row: MarkdownTableRow = {
featureLabel: feature.title,
featureLabel: makeFeatureLabel(id, feature),
basic: getCellInfo(PlanName.Basic, feature),
pro: getCellInfo(PlanName.Pro, feature),
teams: getCellInfo(PlanName.Teams, feature),
@ -316,7 +343,7 @@ export function getPlans(stripeConfig: StripePublicConfig): Record<PlanName, Pla
iconName: 'basic-icon',
featuresOn: getFeatureIdsByPlan(PlanName.Basic, true),
featuresOff: getFeatureIdsByPlan(PlanName.Basic, false),
featureLabelsOn: getFeatureLabelsByPlan(PlanName.Basic, true),
featureLabelsOn: getFeaturesByPlan(PlanName.Basic, true),
featureLabelsOff: getFeatureLabelsByPlan(PlanName.Basic, false),
cfaLabel: _('Try it now'),
cfaUrl: '',
@ -338,7 +365,7 @@ export function getPlans(stripeConfig: StripePublicConfig): Record<PlanName, Pla
iconName: 'pro-icon',
featuresOn: getFeatureIdsByPlan(PlanName.Pro, true),
featuresOff: getFeatureIdsByPlan(PlanName.Pro, false),
featureLabelsOn: getFeatureLabelsByPlan(PlanName.Pro, true),
featureLabelsOn: getFeaturesByPlan(PlanName.Pro, true),
featureLabelsOff: getFeatureLabelsByPlan(PlanName.Pro, false),
cfaLabel: _('Try it now'),
cfaUrl: '',
@ -360,7 +387,7 @@ export function getPlans(stripeConfig: StripePublicConfig): Record<PlanName, Pla
iconName: 'business-icon',
featuresOn: getFeatureIdsByPlan(PlanName.Teams, true),
featuresOff: getFeatureIdsByPlan(PlanName.Teams, false),
featureLabelsOn: getFeatureLabelsByPlan(PlanName.Teams, true),
featureLabelsOn: getFeaturesByPlan(PlanName.Teams, true),
featureLabelsOff: getFeatureLabelsByPlan(PlanName.Teams, false),
cfaLabel: _('Try it now'),
cfaUrl: '',

View File

@ -46,6 +46,7 @@
"@rmp135/sql-ts": "1.18.0",
"@types/fs-extra": "11.0.1",
"@types/jest": "29.5.4",
"@types/markdown-it": "13.0.1",
"@types/mustache": "4.2.2",
"@types/node": "18.17.14",
"@types/node-fetch": "2.6.4",

View File

@ -15,6 +15,7 @@ import { setLocale } from '@joplin/lib/locale';
import applyTranslations from './utils/applyTranslations';
import { loadSponsors } from '../utils/loadSponsors';
import convertLinksToLocale from './utils/convertLinksToLocale';
import { copyFile } from 'fs/promises';
interface BuildConfig {
env: Env;
@ -89,6 +90,38 @@ const jsBasePath = `${websiteAssetDir}/js`;
const jsBaseUrl = `${baseUrl}/js`;
async function getAssetUrls(): Promise<AssetUrls> {
const scriptsToImport: any[] = [
// {
// id: 'tippy',
// sourcePath: rootDir + '/packages/tools/node_modules/tippy.js/dist/tippy-bundle.umd.min.js',
// md5: '',
// filename: '',
// },
// {
// id: 'popper',
// sourcePath: rootDir + '/packages/tools/node_modules/@popperjs/core/dist/umd/popper.min.js',
// md5: '',
// filename: '',
// },
];
for (const s of scriptsToImport) {
const filename = basename(s.sourcePath);
const sourceMd5 = await md5File(s.sourcePath);
const targetPath = `${websiteAssetDir}/js/${filename}`;
const targetMd5 = await md5File(targetPath);
s.md5 = sourceMd5;
s.filename = filename;
// We check the MD5, otherwise it makes nodemon goes into an infinite building loop
if (sourceMd5 !== targetMd5) await copyFile(s.sourcePath, targetPath);
}
const importedJs: Record<string, string> = {};
for (const s of scriptsToImport) {
importedJs[s.id] = `${jsBaseUrl}/${s.filename}?h=${await md5File(`${websiteAssetDir}/js/${s.filename}`)}`;
}
return {
css: {
fontawesome: `${cssBaseUrl}/fontawesome-all.min.css?h=${await md5File(`${cssBasePath}/fontawesome-all.min.css`)}`,
@ -96,6 +129,7 @@ async function getAssetUrls(): Promise<AssetUrls> {
},
js: {
script: `${jsBaseUrl}/script.js?h=${await md5File(`${jsBasePath}/script.js`)}`,
...importedJs,
},
};
}

View File

@ -3,7 +3,7 @@ import { filename } from '@joplin/lib/path-utils';
import * as fs from 'fs-extra';
import { Partials, TemplateParams } from './types';
import { headerAnchor } from '@joplin/renderer';
const MarkdownIt = require('markdown-it');
import * as MarkdownIt from 'markdown-it';
export async function loadMustachePartials(partialDir: string) {
const output: Partials = {};

View File

@ -5178,6 +5178,7 @@ __metadata:
"@rmp135/sql-ts": 1.18.0
"@types/fs-extra": 11.0.1
"@types/jest": 29.5.4
"@types/markdown-it": 13.0.1
"@types/mustache": 4.2.2
"@types/node": 18.17.14
"@types/node-fetch": 2.6.4
@ -8094,6 +8095,16 @@ __metadata:
languageName: node
linkType: hard
"@types/markdown-it@npm:13.0.1":
version: 13.0.1
resolution: "@types/markdown-it@npm:13.0.1"
dependencies:
"@types/linkify-it": "*"
"@types/mdurl": "*"
checksum: 184d383ac21903a9e6be1639cde2b0cc082d0366b423fd8a69d0f37d9d1d36338f66611226ba4ef1da6148f370a62e08f688e8147ead43d429d6ff213c38c062
languageName: node
linkType: hard
"@types/mdast@npm:^3.0.0":
version: 3.0.12
resolution: "@types/mdast@npm:3.0.12"