Compare commits
25 Commits
fix_typing
...
jul-aug-up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92b12455c9 | ||
|
|
37902bfc71 | ||
|
|
68fbf2cda6 | ||
|
|
83fd7727ba | ||
|
|
083d3178cd | ||
|
|
3200dc5ade | ||
|
|
32dce434eb | ||
|
|
37ef29d9a2 | ||
|
|
98d62532c5 | ||
|
|
50122cd0cb | ||
|
|
0156e72dd3 | ||
|
|
91217f3256 | ||
|
|
11f9253709 | ||
|
|
c09c78b015 | ||
|
|
4a1ccd2f8d | ||
|
|
728d5b1465 | ||
|
|
a43d4a3b86 | ||
|
|
4bc7ce5ce7 | ||
|
|
8c5c6a2112 | ||
|
|
8e896138c6 | ||
|
|
b2b4fbdf12 | ||
|
|
099f3c5204 | ||
|
|
17ad9558e1 | ||
|
|
6418dbb7e2 | ||
|
|
3efba6578d |
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'@portaljs/ckan': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
added package_count to organization type
|
|
||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -46942,7 +46942,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ckan": {
|
"packages/ckan": {
|
||||||
"name": "@portaljs/ckan",
|
"name": "@portaljs/ckan",
|
||||||
"version": "0.0.3",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"swr": "^2.1.5",
|
"swr": "^2.1.5",
|
||||||
@@ -47347,7 +47347,7 @@
|
|||||||
},
|
},
|
||||||
"packages/components": {
|
"packages/components": {
|
||||||
"name": "@portaljs/components",
|
"name": "@portaljs/components",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@githubocto/flat-ui": "^0.14.1",
|
"@githubocto/flat-ui": "^0.14.1",
|
||||||
"@heroicons/react": "^2.0.17",
|
"@heroicons/react": "^2.0.17",
|
||||||
@@ -47828,7 +47828,7 @@
|
|||||||
},
|
},
|
||||||
"packages/remark-wiki-link": {
|
"packages/remark-wiki-link": {
|
||||||
"name": "@portaljs/remark-wiki-link",
|
"name": "@portaljs/remark-wiki-link",
|
||||||
"version": "1.0.4",
|
"version": "1.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mdast-util-to-markdown": "^1.5.0",
|
"mdast-util-to-markdown": "^1.5.0",
|
||||||
|
|||||||
@@ -1,5 +1,27 @@
|
|||||||
# @portaljs/ckan
|
# @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
|
## 0.0.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@portaljs/ckan",
|
"name": "@portaljs/ckan",
|
||||||
"version": "0.0.3",
|
"version": "0.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "https://portaljs.org",
|
"description": "https://portaljs.org",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export interface Resource {
|
|||||||
description?: string;
|
description?: string;
|
||||||
format?: string;
|
format?: string;
|
||||||
hash?: string;
|
hash?: string;
|
||||||
id?: string;
|
id: string;
|
||||||
last_modified?: string;
|
last_modified?: string;
|
||||||
metadata_modified?: string;
|
metadata_modified?: string;
|
||||||
mimetype?: string;
|
mimetype?: string;
|
||||||
@@ -69,6 +69,7 @@ export interface PackageSearchOptions {
|
|||||||
query?: string;
|
query?: string;
|
||||||
resFormat?: Array<string>;
|
resFormat?: Array<string>;
|
||||||
sort?: string;
|
sort?: string;
|
||||||
|
include_private?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Tag {
|
export interface Tag {
|
||||||
|
|||||||
@@ -31,11 +31,7 @@ export default class CKAN {
|
|||||||
|
|
||||||
async getDatasetsListWithDetails(options: DatasetListQueryOptions) {
|
async getDatasetsListWithDetails(options: DatasetListQueryOptions) {
|
||||||
const response = await fetchRetry(
|
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
|
3
|
||||||
);
|
);
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
@@ -44,7 +40,8 @@ export default class CKAN {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async packageSearch(
|
async packageSearch(
|
||||||
options: PackageSearchOptions
|
options: PackageSearchOptions,
|
||||||
|
reqOptions: Partial<RequestInit> = {}
|
||||||
): Promise<{ datasets: Dataset[]; count: number }> {
|
): Promise<{ datasets: Dataset[]; count: number }> {
|
||||||
function buildGroupsQuery(groups: Array<string>) {
|
function buildGroupsQuery(groups: Array<string>) {
|
||||||
if (groups.length > 0) {
|
if (groups.length > 0) {
|
||||||
@@ -99,16 +96,18 @@ export default class CKAN {
|
|||||||
options.groups,
|
options.groups,
|
||||||
options?.resFormat
|
options?.resFormat
|
||||||
);
|
);
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
let url = `${this.DMS}/api/3/action/package_search?`;
|
||||||
this.DMS
|
url += `start=${options.offset}`;
|
||||||
}/api/3/action/package_search?start=${options.offset}&rows=${
|
url += `&rows=${options.limit}`;
|
||||||
options.limit
|
url += fq ? fq : '';
|
||||||
}${fq ? fq : ''}${options.query ? '&q=' + options.query : ''}${
|
url += options.query ? '&q=' + options.query : '';
|
||||||
options.sort ? '&sort=' + options.sort : ''
|
url += options.sort ? '&sort=' + options.sort : '';
|
||||||
}`,
|
url += options.include_private
|
||||||
3
|
? '&include_private=' + options.include_private
|
||||||
);
|
: '';
|
||||||
|
|
||||||
|
const response = await fetchRetry(url, 3, reqOptions);
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
const datasets: Array<Dataset> = responseData.result.results;
|
const datasets: Array<Dataset> = responseData.result.results;
|
||||||
return { datasets, count: responseData.result.count };
|
return { datasets, count: responseData.result.count };
|
||||||
@@ -116,9 +115,7 @@ export default class CKAN {
|
|||||||
|
|
||||||
async getDatasetDetails(datasetName: string) {
|
async getDatasetDetails(datasetName: string) {
|
||||||
const response = await fetchRetry(
|
const response = await fetchRetry(
|
||||||
`${
|
`${this.DMS}/api/3/action/package_show?id=${datasetName}`,
|
||||||
this.DMS
|
|
||||||
}/api/3/action/package_show?id=${datasetName}`,
|
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
@@ -131,9 +128,7 @@ export default class CKAN {
|
|||||||
|
|
||||||
async getDatasetActivityStream(datasetName: string) {
|
async getDatasetActivityStream(datasetName: string) {
|
||||||
const response = await fetchRetry(
|
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
|
3
|
||||||
);
|
);
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
@@ -151,9 +146,7 @@ export default class CKAN {
|
|||||||
async getUser(userId: string) {
|
async getUser(userId: string) {
|
||||||
try {
|
try {
|
||||||
const response = await fetchRetry(
|
const response = await fetchRetry(
|
||||||
`${
|
`${this.DMS}/api/3/action/user_show?id=${userId}`,
|
||||||
this.DMS
|
|
||||||
}/api/3/action/user_show?id=${userId}`,
|
|
||||||
3
|
3
|
||||||
);
|
);
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
@@ -166,10 +159,7 @@ export default class CKAN {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getGroupList() {
|
async getGroupList() {
|
||||||
const response = await fetchRetry(
|
const response = await fetchRetry(`${this.DMS}/api/3/action/group_list`, 3);
|
||||||
`${this.DMS}/api/3/action/group_list`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
const groups: Array<string> = responseData.result;
|
const groups: Array<string> = responseData.result;
|
||||||
return groups;
|
return groups;
|
||||||
@@ -177,9 +167,7 @@ export default class CKAN {
|
|||||||
|
|
||||||
async getGroupsWithDetails() {
|
async getGroupsWithDetails() {
|
||||||
const response = await fetchRetry(
|
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
|
3
|
||||||
);
|
);
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
@@ -189,9 +177,7 @@ export default class CKAN {
|
|||||||
|
|
||||||
async getGroupDetails(groupName: string) {
|
async getGroupDetails(groupName: string) {
|
||||||
const response = await fetchRetry(
|
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
|
3
|
||||||
);
|
);
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
@@ -201,9 +187,7 @@ export default class CKAN {
|
|||||||
|
|
||||||
async getGroupActivityStream(groupName: string) {
|
async getGroupActivityStream(groupName: string) {
|
||||||
const response = await fetchRetry(
|
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
|
3
|
||||||
);
|
);
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
@@ -230,9 +214,7 @@ export default class CKAN {
|
|||||||
async getOrgsWithDetails(accrossPages?: boolean) {
|
async getOrgsWithDetails(accrossPages?: boolean) {
|
||||||
if (!accrossPages) {
|
if (!accrossPages) {
|
||||||
const response = await fetchRetry(
|
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
|
3
|
||||||
);
|
);
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
@@ -251,9 +233,7 @@ export default class CKAN {
|
|||||||
|
|
||||||
for (let i = 0; i < pages; i++) {
|
for (let i = 0; i < pages; i++) {
|
||||||
let allOrgListResponse = await fetchRetry(
|
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
|
i * 25
|
||||||
}&limit=25`,
|
}&limit=25`,
|
||||||
3
|
3
|
||||||
@@ -267,9 +247,7 @@ export default class CKAN {
|
|||||||
|
|
||||||
async getOrgDetails(orgName: string) {
|
async getOrgDetails(orgName: string) {
|
||||||
const response = await fetchRetry(
|
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
|
3
|
||||||
);
|
);
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
@@ -279,9 +257,7 @@ export default class CKAN {
|
|||||||
|
|
||||||
async getOrgActivityStream(orgName: string) {
|
async getOrgActivityStream(orgName: string) {
|
||||||
const response = await fetchRetry(
|
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
|
3
|
||||||
);
|
);
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
@@ -297,9 +273,7 @@ export default class CKAN {
|
|||||||
|
|
||||||
async getAllTags() {
|
async getAllTags() {
|
||||||
const response = await fetchRetry(
|
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
|
3
|
||||||
);
|
);
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
@@ -308,49 +282,41 @@ export default class CKAN {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getResourcesWithAliasList() {
|
async getResourcesWithAliasList() {
|
||||||
const response = await fetch(
|
const response = await fetch(`${this.DMS}/api/3/action/datastore_search`, {
|
||||||
`${this.DMS}/api/3/action/datastore_search`,
|
method: 'POST',
|
||||||
{
|
headers: {
|
||||||
method: 'POST',
|
Accept: 'application/json',
|
||||||
headers: {
|
'Content-Type': 'application/json',
|
||||||
Accept: 'application/json',
|
},
|
||||||
'Content-Type': 'application/json',
|
body: JSON.stringify({
|
||||||
},
|
id: '_table_metadata',
|
||||||
body: JSON.stringify({
|
limit: '32000',
|
||||||
id: '_table_metadata',
|
}),
|
||||||
limit: '32000',
|
});
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
const tableMetadata: Array<TableMetadata> = responseData.result.records;
|
const tableMetadata: Array<TableMetadata> = responseData.result.records;
|
||||||
return tableMetadata.filter((item) => item.alias_of);
|
return tableMetadata.filter((item) => item.alias_of);
|
||||||
}
|
}
|
||||||
|
|
||||||
async datastoreSearch(resourceId: string) {
|
async datastoreSearch(resourceId: string) {
|
||||||
const response = await fetch(
|
const response = await fetch(`${this.DMS}/api/3/action/datastore_search`, {
|
||||||
`${this.DMS}/api/3/action/datastore_search`,
|
method: 'POST',
|
||||||
{
|
headers: {
|
||||||
method: 'POST',
|
Accept: 'application/json',
|
||||||
headers: {
|
'Content-Type': 'application/json',
|
||||||
Accept: 'application/json',
|
},
|
||||||
'Content-Type': 'application/json',
|
body: JSON.stringify({
|
||||||
},
|
id: resourceId,
|
||||||
body: JSON.stringify({
|
limit: '32000',
|
||||||
id: resourceId,
|
}),
|
||||||
limit: '32000',
|
});
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
return responseData.result.records;
|
return responseData.result.records;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getResourceMetadata(resourceId: string) {
|
async getResourceMetadata(resourceId: string) {
|
||||||
const response = await fetchRetry(
|
const response = await fetchRetry(
|
||||||
`${
|
`${this.DMS}/api/3/action/resource_show?id=${resourceId}`,
|
||||||
this.DMS
|
|
||||||
}/api/3/action/resource_show?id=${resourceId}`,
|
|
||||||
3
|
3
|
||||||
);
|
);
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
@@ -359,17 +325,14 @@ export default class CKAN {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getResourceInfo(resourceId: string) {
|
async getResourceInfo(resourceId: string) {
|
||||||
const response = await fetch(
|
const response = await fetch(`${this.DMS}/api/3/action/datastore_info`, {
|
||||||
`${this.DMS}/api/3/action/datastore_info`,
|
method: 'POST',
|
||||||
{
|
headers: {
|
||||||
method: 'POST',
|
Accept: 'application/json',
|
||||||
headers: {
|
'Content-Type': 'application/json',
|
||||||
Accept: 'application/json',
|
},
|
||||||
'Content-Type': 'application/json',
|
body: JSON.stringify({ resource_id: resourceId }),
|
||||||
},
|
});
|
||||||
body: JSON.stringify({ resource_id: resourceId }),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
const resourceInfo: Array<ResourceInfo> = responseData.result;
|
const resourceInfo: Array<ResourceInfo> = responseData.result;
|
||||||
return resourceInfo;
|
return resourceInfo;
|
||||||
@@ -377,9 +340,7 @@ export default class CKAN {
|
|||||||
|
|
||||||
async getFacetFields(field: 'res_format' | 'tags') {
|
async getFacetFields(field: 'res_format' | 'tags') {
|
||||||
const response = await fetchRetry(
|
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
|
3
|
||||||
);
|
);
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
|
|||||||
@@ -9,10 +9,14 @@ export function getDaysAgo(date: string) {
|
|||||||
return (+today - +createdOn) / msInDay;
|
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 abortController = new AbortController();
|
||||||
const id = setTimeout(() => abortController.abort(), 30000);
|
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);
|
clearTimeout(id);
|
||||||
if (!res.ok && n && n > 0) {
|
if (!res.ok && n && n > 0) {
|
||||||
return await fetchRetry(url, n - 1);
|
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) {
|
export function removeTag(tag?: string) {
|
||||||
if (tag === "{{description}}" || !tag) {
|
if (tag === '{{description}}' || !tag) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== 'undefined') {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement('div');
|
||||||
div.innerHTML = tag;
|
div.innerHTML = tag;
|
||||||
return div.textContent || div.innerText || "";
|
return div.textContent || div.innerText || '';
|
||||||
}
|
}
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
@@ -38,10 +42,10 @@ export function convertFieldSchema(
|
|||||||
) {
|
) {
|
||||||
function convertToGraphqlString(fieldName: string) {
|
function convertToGraphqlString(fieldName: string) {
|
||||||
return fieldName
|
return fieldName
|
||||||
.replaceAll(" ", "_")
|
.replaceAll(' ', '_')
|
||||||
.replaceAll("(", "_")
|
.replaceAll('(', '_')
|
||||||
.replaceAll(")", "_")
|
.replaceAll(')', '_')
|
||||||
.replace(/[^\w\s]|(_)\1/gi, "_");
|
.replace(/[^\w\s]|(_)\1/gi, '_');
|
||||||
}
|
}
|
||||||
const entries = Object.entries(schema);
|
const entries = Object.entries(schema);
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
# @portaljs/components
|
# @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
|
## 0.3.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@portaljs/components",
|
"name": "@portaljs/components",
|
||||||
"version": "0.3.1",
|
"version": "0.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "https://portaljs.org",
|
"description": "https://portaljs.org",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
Binary file not shown.
@@ -5,22 +5,20 @@ import LoadingSpinner from './LoadingSpinner';
|
|||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
export async function getCsv(url: string, corsProxy?: string) {
|
export async function getCsv(url: string, bytes) {
|
||||||
if (corsProxy) {
|
|
||||||
url = corsProxy + url;
|
|
||||||
}
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
Range: 'bytes=0-5132288',
|
Range: `bytes=0-${bytes}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const data = await response.text();
|
const data = await response.text();
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function parseCsv(file: string): Promise<any> {
|
export async function parseCsv(file: string, parsingConfig): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Papa.parse(file, {
|
Papa.parse(file, {
|
||||||
|
...parsingConfig,
|
||||||
header: true,
|
header: true,
|
||||||
dynamicTyping: true,
|
dynamicTyping: true,
|
||||||
skipEmptyLines: true,
|
skipEmptyLines: true,
|
||||||
@@ -41,18 +39,29 @@ export interface FlatUiTableProps {
|
|||||||
url?: string;
|
url?: string;
|
||||||
data?: { [key: string]: number | string }[];
|
data?: { [key: string]: number | string }[];
|
||||||
rawCsv?: string;
|
rawCsv?: string;
|
||||||
corsProxy?: string;
|
randomId?: number;
|
||||||
|
bytes: number;
|
||||||
|
parsingConfig: any;
|
||||||
}
|
}
|
||||||
export const FlatUiTable: React.FC<FlatUiTableProps> = ({
|
export const FlatUiTable: React.FC<FlatUiTableProps> = ({
|
||||||
url,
|
url,
|
||||||
data,
|
data,
|
||||||
rawCsv,
|
rawCsv,
|
||||||
corsProxy,
|
bytes = 5132288,
|
||||||
|
parsingConfig = {},
|
||||||
}) => {
|
}) => {
|
||||||
|
const randomId = Math.random();
|
||||||
return (
|
return (
|
||||||
// Provide the client to your App
|
// Provide the client to your App
|
||||||
<QueryClientProvider client={queryClient}>
|
<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>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -61,7 +70,9 @@ const TableInner: React.FC<FlatUiTableProps> = ({
|
|||||||
url,
|
url,
|
||||||
data,
|
data,
|
||||||
rawCsv,
|
rawCsv,
|
||||||
corsProxy,
|
randomId,
|
||||||
|
bytes,
|
||||||
|
parsingConfig,
|
||||||
}) => {
|
}) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
return (
|
return (
|
||||||
@@ -71,13 +82,17 @@ const TableInner: React.FC<FlatUiTableProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const { data: csvString, isLoading: isDownloadingCSV } = useQuery(
|
const { data: csvString, isLoading: isDownloadingCSV } = useQuery(
|
||||||
['dataCsv', url],
|
['dataCsv', url, randomId],
|
||||||
() => getCsv(url as string, corsProxy),
|
() => getCsv(url as string, bytes),
|
||||||
{ enabled: !!url }
|
{ enabled: !!url }
|
||||||
);
|
);
|
||||||
const { data: parsedData, isLoading: isParsing } = useQuery(
|
const { data: parsedData, isLoading: isParsing } = useQuery(
|
||||||
['dataPreview', csvString],
|
['dataPreview', csvString, randomId],
|
||||||
() => parseCsv(rawCsv ? (rawCsv as string) : (csvString as string)),
|
() =>
|
||||||
|
parseCsv(
|
||||||
|
rawCsv ? (rawCsv as string) : (csvString as string),
|
||||||
|
parsingConfig
|
||||||
|
),
|
||||||
{ enabled: rawCsv ? true : !!csvString }
|
{ enabled: rawCsv ? true : !!csvString }
|
||||||
);
|
);
|
||||||
if (isParsing || isDownloadingCSV)
|
if (isParsing || isDownloadingCSV)
|
||||||
|
|||||||
@@ -9,17 +9,24 @@ const meta: Meta = {
|
|||||||
tags: ['autodocs'],
|
tags: ['autodocs'],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
data: {
|
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: {
|
csv: {
|
||||||
description: "CSV data as string.",
|
description: 'CSV data as string.',
|
||||||
},
|
},
|
||||||
url: {
|
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
|
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
|
||||||
export const FromColumnsAndData: Story = {
|
export const FromColumnsAndData: Story = {
|
||||||
name: "Table data",
|
name: 'Table data',
|
||||||
args: {
|
args: {
|
||||||
data: [
|
data: [
|
||||||
{ id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 },
|
{ id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 },
|
||||||
@@ -44,20 +51,19 @@ export const FromColumnsAndData: Story = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const FromRawCSV: Story = {
|
export const FromRawCSV: Story = {
|
||||||
name: "Table from raw CSV",
|
name: 'Table from raw CSV',
|
||||||
args: {
|
args: {
|
||||||
rawCsv: `
|
rawCsv: `
|
||||||
Year,Temp Anomaly
|
Year,Temp Anomaly
|
||||||
1850,-0.418
|
1850,-0.418
|
||||||
2020,0.923
|
2020,0.923
|
||||||
`
|
`,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FromURL: Story = {
|
export const FromURL: Story = {
|
||||||
name: "Table from URL",
|
name: 'Table from URL',
|
||||||
args: {
|
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',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @portaljs/remark-wiki-link
|
# @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
|
## 1.0.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@portaljs/remark-wiki-link",
|
"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.",
|
"description": "Parse and render wiki-style links in markdown especially Obsidian style links.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ const defaultWikiLinkResolver = (target: string) => {
|
|||||||
|
|
||||||
export interface FromMarkdownOptions {
|
export interface FromMarkdownOptions {
|
||||||
pathFormat?:
|
pathFormat?:
|
||||||
| "raw" // default; use for regular relative or absolute paths
|
| "raw" // default; use for regular relative or absolute paths
|
||||||
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
|
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
|
||||||
| "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
|
| "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
|
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
|
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
|
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 },
|
data: { isEmbed, target, alias },
|
||||||
} = wikiLink;
|
} = wikiLink;
|
||||||
// eslint-disable-next-line no-useless-escape
|
// 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 [, path, heading = ""] = target.match(wikiLinkWithHeadingPattern);
|
||||||
|
|
||||||
const possibleWikiLinkPermalinks = wikiLinkResolver(path);
|
const possibleWikiLinkPermalinks = wikiLinkResolver(path);
|
||||||
@@ -125,13 +125,24 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
|
|||||||
if (isEmbed) {
|
if (isEmbed) {
|
||||||
const [isSupportedFormat, format] = isSupportedFileFormat(target);
|
const [isSupportedFormat, format] = isSupportedFileFormat(target);
|
||||||
if (!isSupportedFormat) {
|
if (!isSupportedFormat) {
|
||||||
wikiLink.data.hName = "p";
|
// Temporarily render note transclusion as a regular wiki link
|
||||||
wikiLink.data.hChildren = [
|
if (!format) {
|
||||||
{
|
wikiLink.data.hName = "a";
|
||||||
type: "text",
|
wikiLink.data.hProperties = {
|
||||||
value: `![[${target}]]`,
|
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") {
|
} else if (format === "pdf") {
|
||||||
wikiLink.data.hName = "iframe";
|
wikiLink.data.hName = "iframe";
|
||||||
wikiLink.data.hProperties = {
|
wikiLink.data.hProperties = {
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ const defaultWikiLinkResolver = (target: string) => {
|
|||||||
|
|
||||||
export interface HtmlOptions {
|
export interface HtmlOptions {
|
||||||
pathFormat?:
|
pathFormat?:
|
||||||
| "raw" // default; use for regular relative or absolute paths
|
| "raw" // default; use for regular relative or absolute paths
|
||||||
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
|
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
|
||||||
| "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
|
| "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
|
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
|
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
|
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) {
|
if (isEmbed) {
|
||||||
const [isSupportedFormat, format] = isSupportedFileFormat(target);
|
const [isSupportedFormat, format] = isSupportedFileFormat(target);
|
||||||
if (!isSupportedFormat) {
|
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") {
|
} else if (format === "pdf") {
|
||||||
this.tag(
|
this.tag(
|
||||||
`<iframe width="100%" src="${hrefTemplate(
|
`<iframe width="100%" src="${hrefTemplate(
|
||||||
|
|||||||
@@ -309,4 +309,16 @@ describe("micromark-extension-wiki-link", () => {
|
|||||||
expect(serialized).toBe('<p><a href="/" class="internal">/index</a></p>');
|
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>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,31 +3,6 @@ import ButtonLink from './ButtonLink';
|
|||||||
import NewsletterForm from './NewsletterForm';
|
import NewsletterForm from './NewsletterForm';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import DatahubExampleImg from '@/public/images/showcases/datahub.webp';
|
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 */
|
/* eslint jsx-a11y/label-has-associated-control: off */
|
||||||
export function Hero() {
|
export function Hero() {
|
||||||
const el = useRef(null);
|
const el = useRef(null);
|
||||||
@@ -50,7 +25,7 @@ export function Hero() {
|
|||||||
Rapidly build rich data portals using a modern frontend framework.
|
Rapidly build rich data portals using a modern frontend framework.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ButtonLink className="mt-8" href="/docs">
|
<ButtonLink style="secondary" className="mt-8" href="/docs">
|
||||||
Get started
|
Get started
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,37 @@
|
|||||||
|
import { loadScripts } from '@/lib/loadNewsletterScripts';
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { CloudIcon } from '@heroicons/react/solid';
|
||||||
|
|
||||||
export default function NewsletterForm() {
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
id="sib-form-container"
|
id="sib-form-container"
|
||||||
className="mt-8 sm:mx-auto sm:text-center lg:text-left lg:mx-0"
|
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">
|
<p className="text-base font-medium flex items-center">
|
||||||
Sign up to get notified about updates
|
<CloudIcon className="w-6 h-6 mr-1" />
|
||||||
|
Join the PortalJS Cloud waitlist and get early access!
|
||||||
</p>
|
</p>
|
||||||
<div id="sib-container" className="!bg-transparent !p-0 !pb-5">
|
<div id="sib-container" className="!bg-transparent !p-0 !pb-5">
|
||||||
<form
|
<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" />
|
<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>
|
</svg>
|
||||||
Notify Me
|
Join now
|
||||||
</button>
|
</button>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -88,37 +111,6 @@ export default function NewsletterForm() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
site/content/assets/blog/life-itself-blog-post.png
Normal file
BIN
site/content/assets/blog/life-itself-blog-post.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
BIN
site/content/assets/blog/life-itself-categories.png
Normal file
BIN
site/content/assets/blog/life-itself-categories.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 410 KiB |
BIN
site/content/assets/blog/life-itself-latest-blogs.png
Normal file
BIN
site/content/assets/blog/life-itself-latest-blogs.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 449 KiB |
BIN
site/content/assets/blog/life-itself-top-picks.png
Normal file
BIN
site/content/assets/blog/life-itself-top-picks.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
69
site/content/blog/summer-updates-2023.md
Normal file
69
site/content/blog/summer-updates-2023.md
Normal 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
|
||||||
|
|
||||||
|
I’ve 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
|
||||||
@@ -101,7 +101,7 @@ List of available datasets:
|
|||||||
<Catalog datasets={datasets} facets={['group']}/>
|
<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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ id: anuveyatsu
|
|||||||
name: Anuar Ustayev
|
name: Anuar Ustayev
|
||||||
avatar: https://avatars.githubusercontent.com/anuveyatsu
|
avatar: https://avatars.githubusercontent.com/anuveyatsu
|
||||||
---
|
---
|
||||||
|
<NextSeo noindex={true} nofollow={true} />
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ id: joaodemenech
|
|||||||
name: João Demenech
|
name: João Demenech
|
||||||
avatar: https://avatars.githubusercontent.com/demenech
|
avatar: https://avatars.githubusercontent.com/demenech
|
||||||
---
|
---
|
||||||
|
<NextSeo noindex={true} nofollow={true} />
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ id: luccasmateus
|
|||||||
name: Luccas Mateus
|
name: Luccas Mateus
|
||||||
avatar: https://avatars.githubusercontent.com/luccasmmg
|
avatar: https://avatars.githubusercontent.com/luccasmmg
|
||||||
---
|
---
|
||||||
|
<NextSeo noindex={true} nofollow={true} />
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ id: mikanebu
|
|||||||
name: Meiran Zhiyenbayev
|
name: Meiran Zhiyenbayev
|
||||||
avatar: https://avatars.githubusercontent.com/mikanebu
|
avatar: https://avatars.githubusercontent.com/mikanebu
|
||||||
---
|
---
|
||||||
|
<NextSeo noindex={true} nofollow={true} />
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ id: ola-rubaj
|
|||||||
name: Ola Rubaj
|
name: Ola Rubaj
|
||||||
avatar: https://avatars.githubusercontent.com/olayway
|
avatar: https://avatars.githubusercontent.com/olayway
|
||||||
---
|
---
|
||||||
|
<NextSeo noindex={true} nofollow={true} />
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ id: popovayoana
|
|||||||
name: Yoana Popova
|
name: Yoana Popova
|
||||||
avatar: https://avatars.githubusercontent.com/popovayoana
|
avatar: https://avatars.githubusercontent.com/popovayoana
|
||||||
---
|
---
|
||||||
|
<NextSeo noindex={true} nofollow={true} />
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ id: rufuspollock
|
|||||||
name: Rufus Pollock
|
name: Rufus Pollock
|
||||||
avatar: https://avatars.githubusercontent.com/rufuspollock
|
avatar: https://avatars.githubusercontent.com/rufuspollock
|
||||||
---
|
---
|
||||||
|
<NextSeo noindex={true} nofollow={true} />
|
||||||
|
|||||||
45
site/lib/loadNewsletterScripts.ts
Normal file
45
site/lib/loadNewsletterScripts.ts
Normal 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();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
@@ -4,6 +4,7 @@ module.exports = {
|
|||||||
generateRobotsTxt: true,
|
generateRobotsTxt: true,
|
||||||
robotsTxtOptions: {
|
robotsTxtOptions: {
|
||||||
policies: [
|
policies: [
|
||||||
|
{ userAgent: '*', disallow: '/people/' },
|
||||||
{ userAgent: '*', disallow: '/people' },
|
{ userAgent: '*', disallow: '/people' },
|
||||||
{ userAgent: '*', disallow: '/?amp=1' },
|
{ userAgent: '*', disallow: '/?amp=1' },
|
||||||
],
|
],
|
||||||
|
|||||||
8
site/package-lock.json
generated
8
site/package-lock.json
generated
@@ -9,7 +9,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.3.0",
|
"@headlessui/react": "^1.3.0",
|
||||||
"@heroicons/react": "^1.0.3",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@mdx-js/loader": "^2.3.0",
|
"@mdx-js/loader": "^2.3.0",
|
||||||
"@portaljs/core": "^1.0.6",
|
"@portaljs/core": "^1.0.6",
|
||||||
"@portaljs/remark-callouts": "^1.0.5",
|
"@portaljs/remark-callouts": "^1.0.5",
|
||||||
@@ -367,9 +367,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@heroicons/react": {
|
"node_modules/@heroicons/react": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-1.0.6.tgz",
|
||||||
"integrity": "sha512-wdzWbDiFKzeL6xFJsgY2PqvDkx4hFmQDpEFRVj872EA71XOjr8g0DQj5rHWm0y7LwfGOFL0eQmEiMbTyGNOnTQ==",
|
"integrity": "sha512-JJCXydOFWMDpCP4q13iEplA503MQO3xLoZiKum+955ZCtHINWnx26CUxVxxFQu/uLb4LW3ge15ZpzIkXKkJ8oQ==",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">= 16"
|
"react": ">= 16"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.3.0",
|
"@headlessui/react": "^1.3.0",
|
||||||
"@heroicons/react": "^1.0.3",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@mdx-js/loader": "^2.3.0",
|
"@mdx-js/loader": "^2.3.0",
|
||||||
"@portaljs/core": "^1.0.6",
|
"@portaljs/core": "^1.0.6",
|
||||||
"@portaljs/remark-callouts": "^1.0.5",
|
"@portaljs/remark-callouts": "^1.0.5",
|
||||||
|
|||||||
Reference in New Issue
Block a user