Compare commits

..

30 Commits

Author SHA1 Message Date
github-actions[bot]
9482483b51 Version Packages (#1031)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-10-05 12:16:51 +02:00
Ola Rubaj
8d74fd9844 [core/Footer][xs]: minor style fixes 2023-10-05 12:12:23 +02:00
Ola Rubaj
3ae685253b [core][xs]: minor storybook setup adjstmnts 2023-10-05 12:12:23 +02:00
github-actions[bot]
56cb6e7912 Version Packages (#1028)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-09-26 21:33:55 +02:00
Ola Rubaj
71716ab018 [remark-wiki-link][xs]: add support youtu.be links 2023-09-26 21:23:39 +02:00
Ola Rubaj
06d39779ce [blog][s]: Ola's Jul-Aug updates blog post (#1026) 2023-09-25 18:51:57 +02:00
Rufus Pollock
aec67de35c [site/blog,refactor][xs: move post from howtos to blog so it shows up on blog.
Move a simple catalog of anything using markdown from howtos to blog so it shows up on blog on as not showing up in blog atm (even though has filetype: 'blog').
2023-09-24 10:26:46 +02:00
github-actions[bot]
68fbf2cda6 Version Packages (#1023)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-09-20 09:56:52 -03:00
Luccas Mateus
83fd7727ba [flatuitable][m] - add bytes + parsingConfig (#1022) 2023-09-20 08:13:19 -03:00
Anuar Ustayev (aka Anu)
083d3178cd Merge pull request #999 from igoradamenko/igoradamenko-patch-1
Fix wiki-link regexp to match non-Latin characters
2023-09-12 00:18:23 +06:00
João Demenech
3200dc5ade fix(site,docs): missing mddb command instruction on 'Searching datasets' tutorial 2023-09-11 08:21:35 -03:00
João Demenech
32dce434eb Merge pull request #1017 from datopian/changeset-release/main
Version Packages
2023-08-31 16:13:39 -03:00
github-actions[bot]
37ef29d9a2 Version Packages 2023-08-31 19:09:52 +00:00
João Demenech
98d62532c5 Merge pull request #1018 from datopian/ckan/feat/private-datasets
feat(ckan): makes it possible to search private datasets on the ckan api
2023-08-31 16:06:01 -03:00
João Demenech
50122cd0cb bump: new version of CKAN API 2023-08-30 18:38:35 -03:00
João Demenech
0156e72dd3 feat(ckan): makes it possible to search private datasets on the ckan api 2023-08-30 18:35:40 -03:00
Luccas Mateus
91217f3256 [packanges/ckan][xs] - fix type (#1016) 2023-08-24 16:04:44 -03:00
github-actions[bot]
11f9253709 Version Packages (#1015)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-24 16:03:42 -03:00
João Demenech
c09c78b015 feat(site,seo): disallow the people/* path to be crawled 2023-08-23 16:33:28 -03:00
github-actions[bot]
4a1ccd2f8d Version Packages (#1012)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-21 15:26:25 -03:00
Anuar Ustayev (aka Anu)
728d5b1465 Merge pull request #1014 from datopian/feat/cloud-waitlist-form
feat(site): the newsletter form is now a cloud waitlist form
2023-08-22 00:24:23 +06:00
João Demenech
a43d4a3b86 feat(site): the newsletter form is now a cloud waitlist form
Refs: #432
2023-08-21 14:47:59 -03:00
João Demenech
4bc7ce5ce7 [site,newsletter][s]: fix hero newsletter form (broken after Brevo update) 2023-08-18 09:41:31 -03:00
João Demenech
8c5c6a2112 Merge pull request #1011 from datopian/fix-flatui
Fix flatui
2023-08-15 15:00:04 -03:00
Luccas Mateus
8e896138c6 [@portaljs/components][sm] - fix bug of multiple flatuitable with different urls 2023-08-14 16:46:15 -03:00
github-actions[bot]
b2b4fbdf12 Version Packages (#1010)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-14 12:06:36 -03:00
Luccas Mateus
099f3c5204 [@portaljs/ckan][sm] - fix types (#1009) 2023-08-14 11:16:51 -03:00
github-actions[bot]
17ad9558e1 Version Packages (#1008)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-14 16:07:19 +02:00
Ola Rubaj
6418dbb7e2 [#1005, remark-wiki-link]: Parse note embeds as regular wiki links (#1006) 2023-08-14 15:23:28 +02:00
Igor Adamenko
3efba6578d Fix wiki-link regexp to match non-Latin characters 2023-08-09 19:02:11 +03:00
49 changed files with 2126 additions and 302 deletions

View File

@@ -1,5 +0,0 @@
---
'@portaljs/ckan': patch
---
added package_count to organization type

1572
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,27 @@
# @portaljs/ckan
## 0.1.0
### Minor Changes
- [#1018](https://github.com/datopian/portaljs/pull/1018) [`50122cd0`](https://github.com/datopian/portaljs/commit/50122cd0cbbf68bdadc641341279b30b22538cfd) Thanks [@demenech](https://github.com/demenech)! - package_search method now supports custom headers and include_private parameter
### Patch Changes
- [#1016](https://github.com/datopian/portaljs/pull/1016) [`91217f32`](https://github.com/datopian/portaljs/commit/91217f325657e2f298b0e632793ae9bb8b08e870) Thanks [@luccasmmg](https://github.com/luccasmmg)! - remove optional from id in resource interface
## 0.0.5
### Patch Changes
- [#1009](https://github.com/datopian/portaljs/pull/1009) [`88ccee6f`](https://github.com/datopian/portaljs/commit/88ccee6f0aa05decd3efbe7279925340ae817127) Thanks [@luccasmmg](https://github.com/luccasmmg)! - added package_count to organization type
## 0.0.4
### Patch Changes
- [#1009](https://github.com/datopian/portaljs/pull/1009) [`099f3c52`](https://github.com/datopian/portaljs/commit/099f3c520407a7215b5b41f67dc8ea5ac73d07c4) Thanks [@luccasmmg](https://github.com/luccasmmg)! - added package_count to organization type
## 0.0.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@portaljs/ckan",
"version": "0.0.3",
"version": "0.1.0",
"type": "module",
"description": "https://portaljs.org",
"keywords": [

View File

@@ -41,7 +41,7 @@ export interface Resource {
description?: string;
format?: string;
hash?: string;
id?: string;
id: string;
last_modified?: string;
metadata_modified?: string;
mimetype?: string;
@@ -69,6 +69,7 @@ export interface PackageSearchOptions {
query?: string;
resFormat?: Array<string>;
sort?: string;
include_private?: boolean;
}
export interface Tag {

View File

@@ -31,11 +31,7 @@ export default class CKAN {
async getDatasetsListWithDetails(options: DatasetListQueryOptions) {
const response = await fetchRetry(
`${
this.DMS
}/api/3/action/current_package_list_with_resources?offset=${
options.offset
}&limit=${options.limit}`,
`${this.DMS}/api/3/action/current_package_list_with_resources?offset=${options.offset}&limit=${options.limit}`,
3
);
const responseData = await response.json();
@@ -44,7 +40,8 @@ export default class CKAN {
}
async packageSearch(
options: PackageSearchOptions
options: PackageSearchOptions,
reqOptions: Partial<RequestInit> = {}
): Promise<{ datasets: Dataset[]; count: number }> {
function buildGroupsQuery(groups: Array<string>) {
if (groups.length > 0) {
@@ -99,16 +96,18 @@ export default class CKAN {
options.groups,
options?.resFormat
);
const response = await fetchRetry(
`${
this.DMS
}/api/3/action/package_search?start=${options.offset}&rows=${
options.limit
}${fq ? fq : ''}${options.query ? '&q=' + options.query : ''}${
options.sort ? '&sort=' + options.sort : ''
}`,
3
);
let url = `${this.DMS}/api/3/action/package_search?`;
url += `start=${options.offset}`;
url += `&rows=${options.limit}`;
url += fq ? fq : '';
url += options.query ? '&q=' + options.query : '';
url += options.sort ? '&sort=' + options.sort : '';
url += options.include_private
? '&include_private=' + options.include_private
: '';
const response = await fetchRetry(url, 3, reqOptions);
const responseData = await response.json();
const datasets: Array<Dataset> = responseData.result.results;
return { datasets, count: responseData.result.count };
@@ -116,9 +115,7 @@ export default class CKAN {
async getDatasetDetails(datasetName: string) {
const response = await fetchRetry(
`${
this.DMS
}/api/3/action/package_show?id=${datasetName}`,
`${this.DMS}/api/3/action/package_show?id=${datasetName}`,
1
);
const responseData = await response.json();
@@ -131,9 +128,7 @@ export default class CKAN {
async getDatasetActivityStream(datasetName: string) {
const response = await fetchRetry(
`${
this.DMS
}/api/3/action/package_activity_list?id=${datasetName}`,
`${this.DMS}/api/3/action/package_activity_list?id=${datasetName}`,
3
);
const responseData = await response.json();
@@ -151,9 +146,7 @@ export default class CKAN {
async getUser(userId: string) {
try {
const response = await fetchRetry(
`${
this.DMS
}/api/3/action/user_show?id=${userId}`,
`${this.DMS}/api/3/action/user_show?id=${userId}`,
3
);
const responseData = await response.json();
@@ -166,10 +159,7 @@ export default class CKAN {
}
async getGroupList() {
const response = await fetchRetry(
`${this.DMS}/api/3/action/group_list`,
3
);
const response = await fetchRetry(`${this.DMS}/api/3/action/group_list`, 3);
const responseData = await response.json();
const groups: Array<string> = responseData.result;
return groups;
@@ -177,9 +167,7 @@ export default class CKAN {
async getGroupsWithDetails() {
const response = await fetchRetry(
`${
this.DMS
}/api/3/action/group_list?all_fields=True`,
`${this.DMS}/api/3/action/group_list?all_fields=True`,
3
);
const responseData = await response.json();
@@ -189,9 +177,7 @@ export default class CKAN {
async getGroupDetails(groupName: string) {
const response = await fetchRetry(
`${
this.DMS
}/api/3/action/group_show?id=${groupName}&include_datasets=True`,
`${this.DMS}/api/3/action/group_show?id=${groupName}&include_datasets=True`,
3
);
const responseData = await response.json();
@@ -201,9 +187,7 @@ export default class CKAN {
async getGroupActivityStream(groupName: string) {
const response = await fetchRetry(
`${
this.DMS
}/api/3/action/group_activity_list?id=${groupName}`,
`${this.DMS}/api/3/action/group_activity_list?id=${groupName}`,
3
);
const responseData = await response.json();
@@ -230,9 +214,7 @@ export default class CKAN {
async getOrgsWithDetails(accrossPages?: boolean) {
if (!accrossPages) {
const response = await fetchRetry(
`${
this.DMS
}/api/3/action/organization_list?all_fields=True`,
`${this.DMS}/api/3/action/organization_list?all_fields=True`,
3
);
const responseData = await response.json();
@@ -251,9 +233,7 @@ export default class CKAN {
for (let i = 0; i < pages; i++) {
let allOrgListResponse = await fetchRetry(
`${
this.DMS
}/api/3/action/organization_list?all_fields=True&offset=${
`${this.DMS}/api/3/action/organization_list?all_fields=True&offset=${
i * 25
}&limit=25`,
3
@@ -267,9 +247,7 @@ export default class CKAN {
async getOrgDetails(orgName: string) {
const response = await fetchRetry(
`${
this.DMS
}/api/3/action/organization_show?id=${orgName}&include_datasets=True`,
`${this.DMS}/api/3/action/organization_show?id=${orgName}&include_datasets=True`,
3
);
const responseData = await response.json();
@@ -279,9 +257,7 @@ export default class CKAN {
async getOrgActivityStream(orgName: string) {
const response = await fetchRetry(
`${
this.DMS
}/api/3/action/organization_activity_list?id=${orgName}`,
`${this.DMS}/api/3/action/organization_activity_list?id=${orgName}`,
3
);
const responseData = await response.json();
@@ -297,9 +273,7 @@ export default class CKAN {
async getAllTags() {
const response = await fetchRetry(
`${
this.DMS
}/api/3/action/tag_list?all_fields=True`,
`${this.DMS}/api/3/action/tag_list?all_fields=True`,
3
);
const responseData = await response.json();
@@ -308,49 +282,41 @@ export default class CKAN {
}
async getResourcesWithAliasList() {
const response = await fetch(
`${this.DMS}/api/3/action/datastore_search`,
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: '_table_metadata',
limit: '32000',
}),
}
);
const response = await fetch(`${this.DMS}/api/3/action/datastore_search`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: '_table_metadata',
limit: '32000',
}),
});
const responseData = await response.json();
const tableMetadata: Array<TableMetadata> = responseData.result.records;
return tableMetadata.filter((item) => item.alias_of);
}
async datastoreSearch(resourceId: string) {
const response = await fetch(
`${this.DMS}/api/3/action/datastore_search`,
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: resourceId,
limit: '32000',
}),
}
);
const response = await fetch(`${this.DMS}/api/3/action/datastore_search`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: resourceId,
limit: '32000',
}),
});
const responseData = await response.json();
return responseData.result.records;
}
async getResourceMetadata(resourceId: string) {
const response = await fetchRetry(
`${
this.DMS
}/api/3/action/resource_show?id=${resourceId}`,
`${this.DMS}/api/3/action/resource_show?id=${resourceId}`,
3
);
const responseData = await response.json();
@@ -359,17 +325,14 @@ export default class CKAN {
}
async getResourceInfo(resourceId: string) {
const response = await fetch(
`${this.DMS}/api/3/action/datastore_info`,
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ resource_id: resourceId }),
}
);
const response = await fetch(`${this.DMS}/api/3/action/datastore_info`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ resource_id: resourceId }),
});
const responseData = await response.json();
const resourceInfo: Array<ResourceInfo> = responseData.result;
return resourceInfo;
@@ -377,9 +340,7 @@ export default class CKAN {
async getFacetFields(field: 'res_format' | 'tags') {
const response = await fetchRetry(
`${
this.DMS
}/api/3/action/package_search?facet.field=["${field}"]&rows=0`,
`${this.DMS}/api/3/action/package_search?facet.field=["${field}"]&rows=0`,
3
);
const responseData = await response.json();

View File

@@ -9,10 +9,14 @@ export function getDaysAgo(date: string) {
return (+today - +createdOn) / msInDay;
}
export default async function fetchRetry(url: string, n: number): Promise<any> {
export default async function fetchRetry(
url: string,
n: number,
opts: Partial<RequestInit> = {}
): Promise<any> {
const abortController = new AbortController();
const id = setTimeout(() => abortController.abort(), 30000);
const res = await fetch(url, { signal: abortController.signal });
const res = await fetch(url, { signal: abortController.signal, ...opts });
clearTimeout(id);
if (!res.ok && n && n > 0) {
return await fetchRetry(url, n - 1);
@@ -21,13 +25,13 @@ export default async function fetchRetry(url: string, n: number): Promise<any> {
}
export function removeTag(tag?: string) {
if (tag === "{{description}}" || !tag) {
if (tag === '{{description}}' || !tag) {
return undefined;
}
if (typeof window !== "undefined") {
const div = document.createElement("div");
if (typeof window !== 'undefined') {
const div = document.createElement('div');
div.innerHTML = tag;
return div.textContent || div.innerText || "";
return div.textContent || div.innerText || '';
}
return tag;
}
@@ -38,10 +42,10 @@ export function convertFieldSchema(
) {
function convertToGraphqlString(fieldName: string) {
return fieldName
.replaceAll(" ", "_")
.replaceAll("(", "_")
.replaceAll(")", "_")
.replace(/[^\w\s]|(_)\1/gi, "_");
.replaceAll(' ', '_')
.replaceAll('(', '_')
.replaceAll(')', '_')
.replace(/[^\w\s]|(_)\1/gi, '_');
}
const entries = Object.entries(schema);
return {

View File

@@ -1,5 +1,17 @@
# @portaljs/components
## 0.4.0
### Minor Changes
- [#1022](https://github.com/datopian/portaljs/pull/1022) [`83fd7727`](https://github.com/datopian/portaljs/commit/83fd7727bafb4902218777597e9848a3e3a71d87) Thanks [@luccasmmg](https://github.com/luccasmmg)! - FlatUiTables now accepts a bytes param and a parsingConfig param for CSV links
## 0.3.2
### Patch Changes
- [#1011](https://github.com/datopian/portaljs/pull/1011) [`8e896138`](https://github.com/datopian/portaljs/commit/8e896138c622615d9bd9bd1d4a18de0cf38d85ec) Thanks [@luccasmmg](https://github.com/luccasmmg)! - fix bug when there were multiple flatuitable components at the same time
## 0.3.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@portaljs/components",
"version": "0.3.1",
"version": "0.4.0",
"type": "module",
"description": "https://portaljs.org",
"keywords": [

View File

@@ -5,22 +5,20 @@ import LoadingSpinner from './LoadingSpinner';
const queryClient = new QueryClient();
export async function getCsv(url: string, corsProxy?: string) {
if (corsProxy) {
url = corsProxy + url;
}
export async function getCsv(url: string, bytes) {
const response = await fetch(url, {
headers: {
Range: 'bytes=0-5132288',
Range: `bytes=0-${bytes}`,
},
});
const data = await response.text();
return data;
}
export async function parseCsv(file: string): Promise<any> {
export async function parseCsv(file: string, parsingConfig): Promise<any> {
return new Promise((resolve, reject) => {
Papa.parse(file, {
...parsingConfig,
header: true,
dynamicTyping: true,
skipEmptyLines: true,
@@ -41,18 +39,29 @@ export interface FlatUiTableProps {
url?: string;
data?: { [key: string]: number | string }[];
rawCsv?: string;
corsProxy?: string;
randomId?: number;
bytes: number;
parsingConfig: any;
}
export const FlatUiTable: React.FC<FlatUiTableProps> = ({
url,
data,
rawCsv,
corsProxy,
bytes = 5132288,
parsingConfig = {},
}) => {
const randomId = Math.random();
return (
// Provide the client to your App
<QueryClientProvider client={queryClient}>
<TableInner corsProxy={corsProxy} url={url} data={data} rawCsv={rawCsv} />
<TableInner
bytes={bytes}
url={url}
data={data}
rawCsv={rawCsv}
randomId={randomId}
parsingConfig={parsingConfig}
/>
</QueryClientProvider>
);
};
@@ -61,7 +70,9 @@ const TableInner: React.FC<FlatUiTableProps> = ({
url,
data,
rawCsv,
corsProxy,
randomId,
bytes,
parsingConfig,
}) => {
if (data) {
return (
@@ -71,13 +82,17 @@ const TableInner: React.FC<FlatUiTableProps> = ({
);
}
const { data: csvString, isLoading: isDownloadingCSV } = useQuery(
['dataCsv', url],
() => getCsv(url as string, corsProxy),
['dataCsv', url, randomId],
() => getCsv(url as string, bytes),
{ enabled: !!url }
);
const { data: parsedData, isLoading: isParsing } = useQuery(
['dataPreview', csvString],
() => parseCsv(rawCsv ? (rawCsv as string) : (csvString as string)),
['dataPreview', csvString, randomId],
() =>
parseCsv(
rawCsv ? (rawCsv as string) : (csvString as string),
parsingConfig
),
{ enabled: rawCsv ? true : !!csvString }
);
if (isParsing || isDownloadingCSV)

View File

@@ -9,17 +9,24 @@ const meta: Meta = {
tags: ['autodocs'],
argTypes: {
data: {
description: "Data to be displayed in the table, must be setup as an array of key value pairs"
description:
'Data to be displayed in the table, must be setup as an array of key value pairs',
},
csv: {
description: "CSV data as string.",
description: 'CSV data as string.',
},
url: {
description: "Fetch the data from a CSV file remotely. only the first 5MB of data will be displayed"
description:
'Fetch the data from a CSV file remotely. only the first 5MB of data will be displayed',
},
bytes: {
description:
'Fetch the data from a CSV file remotely. only the first <bytes> of data will be displayed',
},
parsingConfig: {
description:
'Configuration for parsing the CSV data. See https://www.papaparse.com/docs#config for more details',
},
corsProxy: {
description: "Optionally you cant set a CORS Proxy to which all your requests you be redirected"
}
},
};
@@ -29,7 +36,7 @@ type Story = StoryObj<FlatUiTableProps>;
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
export const FromColumnsAndData: Story = {
name: "Table data",
name: 'Table data',
args: {
data: [
{ id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 },
@@ -44,20 +51,19 @@ export const FromColumnsAndData: Story = {
};
export const FromRawCSV: Story = {
name: "Table from raw CSV",
name: 'Table from raw CSV',
args: {
rawCsv: `
Year,Temp Anomaly
1850,-0.418
2020,0.923
`
}
`,
},
};
export const FromURL: Story = {
name: "Table from URL",
name: 'Table from URL',
args: {
url: "https://raw.githubusercontent.com/datasets/finance-vix/main/data/vix-daily.csv"
}
url: 'https://ckan-dev.sse.datopian.com/datastore/dump/601c9cf0-595e-46d8-88fc-d1ab2904e2db',
},
};

View File

@@ -5,6 +5,7 @@ const config: StorybookConfig = {
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-a11y',
'@nrwl/react/plugins/storybook',
'storybook-tailwind-dark-mode'
],
@@ -12,6 +13,9 @@ const config: StorybookConfig = {
name: '@storybook/react-webpack5',
options: {},
},
docs: {
autodocs: 'tag',
},
};
export default config;

View File

@@ -1,6 +1,9 @@
import './tailwind-imports.css';
const preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
},
globalTypes: {
darkMode: {
defaultValue: false, // Enable dark mode by default on all stories

View File

@@ -1,5 +1,11 @@
# @portaljs/core
## 1.0.7
### Patch Changes
- [`8d74fd98`](https://github.com/datopian/portaljs/commit/8d74fd98443bb3c0a28325be27b41281b59f3581) Thanks [@olayway](https://github.com/olayway)! - Minor footer style fixes.
## 1.0.6
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@portaljs/core",
"version": "1.0.6",
"version": "1.0.7",
"description": "Core Portal.JS components, configs and utils.",
"repository": {
"type": "git",
@@ -46,10 +46,13 @@
"prop-types": "^15.8.1"
},
"peerDependencies": {
"mdx-mermaid": "2.0.0-rc7",
"next": "^13.2.1",
"next-themes": "^0.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"mdx-mermaid": "2.0.0-rc7"
"react-dom": "^18.2.0"
},
"devDependencies": {
"@storybook/addon-a11y": "^7.4.6"
}
}

View File

@@ -0,0 +1,47 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Footer } from './Footer';
const meta: Meta<typeof Footer> = {
component: Footer,
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<typeof Footer>;
export const Basic: Story = {
args: {
author: {
name: "John Doe",
}
},
};
export const Links: Story = {
args: {
links: [
{ name: "Link A", href: "#" },
{ name: "Link B", href: "#" }
],
author: {
name: "John Doe",
}
},
};
export const Logo: Story = {
args: {
links: [
{ name: "Link A", href: "#" },
{ name: "Link B", href: "#" }
],
author: {
name: "John Doe",
logo: "https://via.placeholder.com/150"
}
},
};

View File

@@ -3,59 +3,77 @@ import Link from "next/link.js";
import { AuthorConfig, NavLink } from "../types";
interface Props {
links: Array<NavLink>;
author: AuthorConfig;
author: AuthorConfig;
links?: Array<NavLink>;
}
// TODO replace this with some nice tailwindui footer
export const Footer: React.FC<Props> = ({ links, author }) => {
return (
<footer className="bg-background dark:bg-background-dark prose dark:prose-invert max-w-none flex flex-col items-center justify-center w-full h-auto pt-10 pb-20">
<div className="flex w-full flex-wrap justify-center">
{links.map((item) => (
<Link
key={item.href}
href={item.href}
className="inline-flex items-center mx-4 px-1 pt-1 font-regular hover:text-slate-300 no-underline"
>
{/* TODO aria-current={item.current ? "page" : undefined} */}
{item.name}
</Link>
))}
</div>
<p className="flex items-center justify-center">
Created by
<a
href={author.url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center no-underline"
>
{author.logo && (
<img
src={author.logo}
alt={author.name}
className="my-0 mx-1 h-6 block"
/>
)}
{author.name}
</a>
</p>
<p className="flex items-center justify-center">
Made with
<a
href="https://flowershow.app/"
target="_blank"
rel="noopener noreferrer"
className="flex items-center no-underline"
>
<img
src="https://flowershow.app/images/logo.svg"
alt="Flowershow"
className="my-0 mx-1 h-6 block"
/>
Flowershow
</a>
</p>
</footer>
);
return (
<footer className="bg-background dark:bg-background-dark text-primary dark:text-primary-dark pt-16 pb-20 px-14 flex flex-col items-center justify-center gap-3">
{links && (
<div className="flex w-full flex-wrap justify-center mb-2">
{links.map((item) => (
<Link
key={item.href}
href={item.href}
className="inline-flex items-center mx-4 px-1 py-1 text-black hover:text-primary dark:text-white hover:dark:text-primary-dark no-underline font-semibold"
>
{/* TODO aria-current={item.current ? "page" : undefined} */}
{item.name}
</Link>
))}
</div>
)}
<p className="flex items-center justify-center gap-2">
<span>Created by</span>
{author.url ? (
<a
href={author.url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 no-underline font-semibold text-black dark:text-white"
>
{author.logo && (
<img
src={author.logo}
alt="Logo"
className="h-6 block"
/>
)}
<span>{author.name}</span>
</a>
) : (
<span
className="flex items-center gap-1 no-underline font-semibold text-black dark:text-white"
>
{author.logo && (
<img
src={author.logo}
alt={author.name}
className="h-6 block"
/>
)}
<span>{author.name}</span>
</span>
)}
</p>
<p className="flex items-center justify-center gap-1">
<span>Made with</span>
<a
href="https://flowershow.app/"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 no-underline font-semibold text-black dark:text-white"
>
<img
src="https://flowershow.app/images/logo.svg"
alt="Logo"
className="h-6 block"
/>
<span>Flowershow</span>
</a>
</p>
</footer>
);
};

View File

@@ -7,6 +7,7 @@ export {
TocSection,
EditThisPage,
useTableOfContents,
Footer
} from "./Layout";
export { Pre } from "./Pre";
export { CustomLink } from "./Base/CustomLink";

View File

@@ -9,8 +9,8 @@ export interface NavLink {
export interface AuthorConfig {
name: string;
url: string;
logo: string;
url?: string;
logo?: string;
}
// social

View File

@@ -1,5 +1,11 @@
# @portaljs/remark-embed
## 1.0.5
### Patch Changes
- [`71716ab0`](https://github.com/datopian/portaljs/commit/71716ab01875f04f51bc23ba551c7a6a9a24a335) Thanks [@olayway](https://github.com/olayway)! - Add support for shortened yt links (https://youtu.be).
## 1.0.4
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@portaljs/remark-embed",
"version": "1.0.4",
"version": "1.0.5",
"description": "Converts youtube link in mdx to an iframe embed",
"repository": {
"type": "git",

View File

@@ -1,14 +1,29 @@
import { visit } from "unist-util-visit";
// https://youtu.be/vDuh7vDgGIg
// TODO write tests!
function transformer(tree) {
visit(tree, "paragraph", (node) => {
visit(node, "text", (textNode) => {
if (
textNode.value.includes("https://www.youtube.com") &&
(textNode.value.includes("https://www.youtube.com") ||
textNode.value.includes("https://youtu.be")
) &&
!textNode.value.includes("\n")
) {
const urlSplit = textNode.value.split(/[=&]+/);
const iframeUrl = `https://www.youtube.com/embed/${urlSplit[1]}`;
let videoId: string;
if (textNode.value.includes("https://youtu.be")) {
// Extract video ID for short YT URLs
videoId = textNode.value.split("/").pop();
} else {
// Extract video ID for full YT URLs
const urlSplit = textNode.value.split(/[=&]+/);
videoId = urlSplit[1];
}
const iframeUrl = `https://www.youtube.com/embed/${videoId}`;
Object.assign(node, {
...node,
type: "element",

View File

@@ -1,5 +1,11 @@
# @portaljs/remark-wiki-link
## 1.1.0
### Minor Changes
- [#1006](https://github.com/datopian/portaljs/pull/1006) [`6418dbb7`](https://github.com/datopian/portaljs/commit/6418dbb7e246fa17b56840c64daa043112dc9189) Thanks [@olayway](https://github.com/olayway)! - Parse note embeds as regular wiki links (until we add proper support for note embeds).
## 1.0.4
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@portaljs/remark-wiki-link",
"version": "1.0.4",
"version": "1.1.0",
"description": "Parse and render wiki-style links in markdown especially Obsidian style links.",
"repository": {
"type": "git",

View File

@@ -15,9 +15,9 @@ const defaultWikiLinkResolver = (target: string) => {
export interface FromMarkdownOptions {
pathFormat?:
| "raw" // default; use for regular relative or absolute paths
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
| "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
| "raw" // default; use for regular relative or absolute paths
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
| "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
permalinks?: string[]; // list of permalinks to match possible permalinks of a wiki link against
wikiLinkResolver?: (name: string) => string[]; // function to resolve wiki links to an array of possible permalinks
newClassName?: string; // class name to add to links that don't have a matching permalink
@@ -79,7 +79,7 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
data: { isEmbed, target, alias },
} = wikiLink;
// eslint-disable-next-line no-useless-escape
const wikiLinkWithHeadingPattern = /([\w\s\/\.-]*)(#.*)?/;
const wikiLinkWithHeadingPattern = /([\p{Letter}\d\s\/\.-_]*)(#.*)?/u;
const [, path, heading = ""] = target.match(wikiLinkWithHeadingPattern);
const possibleWikiLinkPermalinks = wikiLinkResolver(path);
@@ -125,13 +125,24 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
if (isEmbed) {
const [isSupportedFormat, format] = isSupportedFileFormat(target);
if (!isSupportedFormat) {
wikiLink.data.hName = "p";
wikiLink.data.hChildren = [
{
type: "text",
value: `![[${target}]]`,
},
];
// Temporarily render note transclusion as a regular wiki link
if (!format) {
wikiLink.data.hName = "a";
wikiLink.data.hProperties = {
className: classNames + " " + "transclusion",
href: hrefTemplate(link) + headingId,
};
wikiLink.data.hChildren = [{ type: "text", value: displayName }];
} else {
wikiLink.data.hName = "p";
wikiLink.data.hChildren = [
{
type: "text",
value: `![[${target}]]`,
},
];
}
} else if (format === "pdf") {
wikiLink.data.hName = "iframe";
wikiLink.data.hProperties = {

View File

@@ -15,9 +15,9 @@ const defaultWikiLinkResolver = (target: string) => {
export interface HtmlOptions {
pathFormat?:
| "raw" // default; use for regular relative or absolute paths
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
| "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
| "raw" // default; use for regular relative or absolute paths
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
| "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
permalinks?: string[]; // list of permalinks to match possible permalinks of a wiki link against
wikiLinkResolver?: (name: string) => string[]; // function to resolve wiki links to an array of possible permalinks
newClassName?: string; // class name to add to links that don't have a matching permalink
@@ -108,7 +108,16 @@ function html(opts: HtmlOptions = {}) {
if (isEmbed) {
const [isSupportedFormat, format] = isSupportedFileFormat(target);
if (!isSupportedFormat) {
this.raw(`![[${target}]]`);
// Temporarily render note transclusion as a regular wiki link
if (!format) {
this.tag(
`<a href="${hrefTemplate(link + headingId)}" class="${classNames} transclusion">`
);
this.raw(displayName);
this.tag("</a>");
} else {
this.raw(`![[${target}]]`);
}
} else if (format === "pdf") {
this.tag(
`<iframe width="100%" src="${hrefTemplate(

View File

@@ -309,4 +309,16 @@ describe("micromark-extension-wiki-link", () => {
expect(serialized).toBe('<p><a href="/" class="internal">/index</a></p>');
});
});
describe("transclusions", () => {
test("parsers a transclusion as a regular wiki link", () => {
const serialized = micromark("![[Some Page]]", "ascii", {
extensions: [syntax()],
htmlExtensions: [html() as any], // TODO type fix
});
expect(serialized).toBe(
'<p><a href="Some Page" class="internal new transclusion">Some Page</a></p>'
);
});
});
});

View File

@@ -535,4 +535,28 @@ describe("remark-wiki-link", () => {
});
});
});
describe("transclusions", () => {
test("replaces a transclusion with a regular wiki link", () => {
const processor = unified().use(markdown).use(wikiLinkPlugin);
let ast = processor.parse("![[Some Page]]");
ast = processor.runSync(ast);
expect(select("wikiLink", ast)).not.toEqual(null);
visit(ast, "wikiLink", (node: Node) => {
expect(node.data?.isEmbed).toEqual(true);
expect(node.data?.exists).toEqual(false);
expect(node.data?.permalink).toEqual("Some Page");
expect(node.data?.alias).toEqual(null);
expect(node.data?.hName).toEqual("a");
expect((node.data?.hProperties as any).className).toEqual(
"internal new transclusion"
);
expect((node.data?.hProperties as any).href).toEqual("Some Page");
expect((node.data?.hChildren as any)[0].value).toEqual("Some Page");
});
});
});
});

View File

@@ -3,31 +3,6 @@ import ButtonLink from './ButtonLink';
import NewsletterForm from './NewsletterForm';
import Image from 'next/image';
import DatahubExampleImg from '@/public/images/showcases/datahub.webp';
const codeLanguage = 'javascript';
const code = `export default {
strategy: 'predictive',
engine: {
cpus: 12,
backups: ['./storage/cache.wtf'],
},
}`;
const tabs = [
{ name: 'cache-advance.config.js', isActive: true },
{ name: 'package.json', isActive: false },
];
function TrafficLightsIcon(props) {
return (
<svg aria-hidden="true" viewBox="0 0 42 10" fill="none" {...props}>
<circle cx="5" cy="5" r="4.5" />
<circle cx="21" cy="5" r="4.5" />
<circle cx="37" cy="5" r="4.5" />
</svg>
);
}
/* eslint jsx-a11y/label-has-associated-control: off */
export function Hero() {
const el = useRef(null);
@@ -50,7 +25,7 @@ export function Hero() {
Rapidly build rich data portals using a modern frontend framework.
</p>
<ButtonLink className="mt-8" href="/docs">
<ButtonLink style="secondary" className="mt-8" href="/docs">
Get started
</ButtonLink>

View File

@@ -1,14 +1,37 @@
import { loadScripts } from '@/lib/loadNewsletterScripts';
import Script from 'next/script';
import { useEffect } from 'react';
import { CloudIcon } from '@heroicons/react/solid';
export default function NewsletterForm() {
useEffect(() => {
/*
* The newsletter scripts MUST be loaded after
* the document is loaded, as they depend on
* the presence of some elements
*
*/
if (document.readyState === 'complete') {
const { resetElements } = loadScripts();
return () => {
resetElements();
};
} else {
window.addEventListener('load', loadScripts);
return () => window.removeEventListener('load', loadScripts);
}
}, []);
return (
<div>
<div
id="sib-form-container"
className="mt-8 sm:mx-auto sm:text-center lg:text-left lg:mx-0"
>
<p className="text-base font-medium text-slate-400 dark:text-slate-400">
Sign up to get notified about updates
<p className="text-base font-medium flex items-center">
<CloudIcon className="w-6 h-6 mr-1" />
Join the PortalJS Cloud waitlist and get early access!
</p>
<div id="sib-container" className="!bg-transparent !p-0 !pb-5">
<form
@@ -47,7 +70,7 @@ export default function NewsletterForm() {
>
<path d="M460.116 373.846l-20.823-12.022c-5.541-3.199-7.54-10.159-4.663-15.874 30.137-59.886 28.343-131.652-5.386-189.946-33.641-58.394-94.896-95.833-161.827-99.676C261.028 55.961 256 50.751 256 44.352V20.309c0-6.904 5.808-12.337 12.703-11.982 83.556 4.306 160.163 50.864 202.11 123.677 42.063 72.696 44.079 162.316 6.031 236.832-3.14 6.148-10.75 8.461-16.728 5.01z" />
</svg>
Notify Me
Join now
</button>
<input
type="text"
@@ -88,37 +111,6 @@ export default function NewsletterForm() {
</div>
</div>
</div>
<Script
id="newsletter-form-validation-message"
dangerouslySetInnerHTML={{
__html: `
window.REQUIRED_CODE_ERROR_MESSAGE = 'Please choose a country code';
window.LOCALE = 'en';
window.EMAIL_INVALID_MESSAGE = window.SMS_INVALID_MESSAGE = "The information provided is invalid. Please review the field format and try again.";
window.REQUIRED_ERROR_MESSAGE = "This field cannot be left blank. ";
window.GENERIC_INVALID_MESSAGE = "The information provided is invalid. Please review the field format and try again.";
window.translation = {
common: {
selectedList: '{quantity} list selected',
selectedLists: '{quantity} lists selected'
}
};
var AUTOHIDE = Boolean(0);
`,
}}
/>
<Script
strategy="worker"
id="newsletter-submit-form"
src="https://sibforms.com/forms/end-form/build/main.js"
/>
</div>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -0,0 +1,69 @@
---
title: What We Shipped in Jul-Aug 2023
authors: ['ola-rubaj']
date: 2023-09-2
---
Hey everyone! 👋 Summer has been in full swing, and while I've managed to catch some vacation vibes, I've also been deep into code. I'm super excited to share some of the latest updates and features we've rolled out over the past two months. Let's dive in:
## 🌷 Flowershow
https://flowershow.app/
1. **CLI is old news**: the Flowershow CLI has been deprecated in favor of our new [Flowershow Obsidian plugin](https://github.com/datopian/obsidian-flowershow).
2. **Self-publishing made even easier**: I wrote a [self-publish tutorial](https://flowershow.app/docs/publish-howto) to guide you through using our Obsidian plugin for publishing your digital garden.
3. **Cloud-publishing MVP**: The first version of our Flowershow Cloud which aims to make publishing your digital garden notes a breeze! [Check out the overview](https://flowershow.app#cloud-publish) and stay tuned 📻!
4. **Hydration errors from Obsidian transclusions/note embeddings are fixed**: For now, note transclusions will convert to regular links, but stay tuned — support for note embeds may be on the horizon. [Learn more](https://github.com/datopian/flowershow/issues/545)
5. **New Obsidian tags list format support**: I added support for Obsidian's new tag list format, so now your notes can be even more organized. [Learn more](https://github.com/datopian/flowershow/issues/543)
## 🗂️ MarkdownDB
https://github.com/datopian/markdowndb
1. **Auto-Publishing to npm**: Changesets now trigger auto-publishing, so we're always up to date.
2. **Obsidian-style wiki links**: Extracting wiki links with Obsidian-style shortest paths is supported now.
## 📚 The Guide
https://portaljs.org/guide
Ive sketched overviews for two upcoming tutorials:
1. **Collaborating with others on your website**: Learn how to make your website projects a team effort. [See it here](https://portaljs.org/guide#tutorial-3-collaborating-with-others-on-your-website-project)
2. **Customising your website and previewing your changes locally**: Customize and preview your site changes locally, without headaches. [See it here](https://portaljs.org/guide#tutorial-4-customising-your-website-locally-and-previewing-your-changes-locally)
## 🌐 LifeItself.org
https://lifeitself.org/
LifeItself.org is our website built on the Flowershow template, and it's been getting some extra care too.
1. New blog home page layout with:
- **Team Top Selection**
![[life-itself-top-picks.png]]
- **Latest Blog Posts**
![[life-itself-latest-blogs.png]]
- And the long awaited filtering by category 🎉
![[life-itself-categories.png]]
[👉 Check out the changes!](https://lifeitself.org/blog)
2. **Blog posts layout revamp**: Refreshed design with share options, reading time estimates, and more.
![[life-itself-blog-post.png]]
---
That wraps it up for now! As always, I'm eager to hear your thoughts and feedback. Feel free to report issues or ask for features on our GitHub repositories. Until next time and happy publishing!
— Ola Rubaj, Developer at Datopian

View File

@@ -101,7 +101,7 @@ List of available datasets:
<Catalog datasets={datasets} facets={['group']}/>
```
You now have a filter in your page with all possible values automatically added to it.
Rerun `npm run mddb`. You now have a filter in your page with all possible values automatically added to it.
![Data catalog with facets built with PortalJS](https://i.imgur.com/p2miSdg.png)

View File

@@ -2,4 +2,5 @@
id: anuveyatsu
name: Anuar Ustayev
avatar: https://avatars.githubusercontent.com/anuveyatsu
---
---
<NextSeo noindex={true} nofollow={true} />

View File

@@ -2,4 +2,5 @@
id: joaodemenech
name: João Demenech
avatar: https://avatars.githubusercontent.com/demenech
---
---
<NextSeo noindex={true} nofollow={true} />

View File

@@ -2,4 +2,5 @@
id: luccasmateus
name: Luccas Mateus
avatar: https://avatars.githubusercontent.com/luccasmmg
---
---
<NextSeo noindex={true} nofollow={true} />

View File

@@ -2,4 +2,5 @@
id: mikanebu
name: Meiran Zhiyenbayev
avatar: https://avatars.githubusercontent.com/mikanebu
---
---
<NextSeo noindex={true} nofollow={true} />

View File

@@ -3,3 +3,4 @@ id: ola-rubaj
name: Ola Rubaj
avatar: https://avatars.githubusercontent.com/olayway
---
<NextSeo noindex={true} nofollow={true} />

View File

@@ -2,4 +2,5 @@
id: popovayoana
name: Yoana Popova
avatar: https://avatars.githubusercontent.com/popovayoana
---
---
<NextSeo noindex={true} nofollow={true} />

View File

@@ -2,4 +2,5 @@
id: rufuspollock
name: Rufus Pollock
avatar: https://avatars.githubusercontent.com/rufuspollock
---
---
<NextSeo noindex={true} nofollow={true} />

View File

@@ -0,0 +1,45 @@
import { HTMLProps } from "react";
const loadScript = (
props: HTMLProps<HTMLScriptElement> & { textContent?: string }
): (() => void) => {
const script = document.createElement("script");
Object.assign(script, props);
document.head.appendChild(script);
return () => document.head.removeChild(script);
};
export const loadScripts = () => {
const formValidationScript = loadScript({
id: "newsletter-form-validation-message",
textContent: `
window.LOCALE = 'en';
window.EMAIL_INVALID_MESSAGE = "The information provided is invalid. Please review the field format and try again.";
window.REQUIRED_ERROR_MESSAGE = "This field cannot be left blank. ";
window.GENERIC_INVALID_MESSAGE = "The information provided is invalid. Please review the field format and try again.";
window.translation = {
common: {
selectedList: '{quantity} list selected',
selectedLists: '{quantity} lists selected'
}
};
var AUTOHIDE = Boolean(0);
`,
});
const formSubmitScript = loadScript({
id: "newsletter-submit-form",
src: "https://sibforms.com/forms/end-form/build/main.js",
async: true,
defer: true,
});
return {
resetElements: () => {
formSubmitScript();
formValidationScript();
},
};
};

View File

@@ -4,6 +4,7 @@ module.exports = {
generateRobotsTxt: true,
robotsTxtOptions: {
policies: [
{ userAgent: '*', disallow: '/people/' },
{ userAgent: '*', disallow: '/people' },
{ userAgent: '*', disallow: '/?amp=1' },
],

View File

@@ -9,7 +9,7 @@
"version": "0.1.0",
"dependencies": {
"@headlessui/react": "^1.3.0",
"@heroicons/react": "^1.0.3",
"@heroicons/react": "^1.0.6",
"@mdx-js/loader": "^2.3.0",
"@portaljs/core": "^1.0.6",
"@portaljs/remark-callouts": "^1.0.5",
@@ -367,9 +367,9 @@
}
},
"node_modules/@heroicons/react": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-1.0.3.tgz",
"integrity": "sha512-wdzWbDiFKzeL6xFJsgY2PqvDkx4hFmQDpEFRVj872EA71XOjr8g0DQj5rHWm0y7LwfGOFL0eQmEiMbTyGNOnTQ==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-1.0.6.tgz",
"integrity": "sha512-JJCXydOFWMDpCP4q13iEplA503MQO3xLoZiKum+955ZCtHINWnx26CUxVxxFQu/uLb4LW3ge15ZpzIkXKkJ8oQ==",
"peerDependencies": {
"react": ">= 16"
}

View File

@@ -12,7 +12,7 @@
},
"dependencies": {
"@headlessui/react": "^1.3.0",
"@heroicons/react": "^1.0.3",
"@heroicons/react": "^1.0.6",
"@mdx-js/loader": "^2.3.0",
"@portaljs/core": "^1.0.6",
"@portaljs/remark-callouts": "^1.0.5",