Notes
Notes - notes.io |
import {useHistory} from 'react-router-dom';
import ConfirmModal from '@aosprodsys/brucke-ui-core/dist/es/components/Modal';
import isEmpty from 'lodash-es/isEmpty';
import map from 'lodash-es/map';
import queryString from 'query-string';
import Tree from 'react-d3-tree';
import Close from '@apple/symbols/light/xmark.svg';
import styles from './sass/tech-specs-collection-graph-view.scss';
const getTreeData = (data = [], part, techSpecs = []) => {
const root = {name: part, children: []};
const nodes = data.map(eachData => {
const node = {
name: eachData.optionGroupType,
children: []
};
if (eachData.variantValuesSummaryByVariantKey) {
const cnodes = Object.entries(eachData.variantValuesSummaryByVariantKey).map(([key, value]) => ({
name: key,
children: value.map(each => ({name: each}))
}));
node.children = cnodes;
}
const matchingSpecs = techSpecs.filter(spec => spec.optionGroup === eachData.optionGroupType);
if (matchingSpecs.length) {
const techSpecNode = {name: 'Tech Specs', children: []};
matchingSpecs.forEach(spec => {
const specNode = {name: spec.specLabel, children: []};
if (spec.specSummary?.length) {
specNode.children = spec.specSummary.map(summary => ({
name: summary.specKey,
children: summary.specValues.map(val => ({name: val}))
}));
}
techSpecNode.children.push(specNode);
});
node.children.push(techSpecNode);
}
return node;
});
root.children = nodes;
return root;
};
const getDepthNodes = node => {
if (node?.children && !node?.children?.length) {
return 0;
}
let maxChildDepth = 0;
node.children?.forEach(child => {
const childPath = getDepthNodes(child);
if (childPath > maxChildDepth) {
maxChildDepth = childPath;
}
});
return maxChildDepth + 1;
};
const getDepthGraphViewDropdownOptions = node => {
const depthCount = getDepthNodes(node);
const options = [];
for (let i = 1; i < depthCount; i++) {
options.push({
label: `Level ${i}`,
value: Number(i)
});
}
return options;
};
const wrapText = (text, width) => {
if (!text) return [];
const regex = new RegExp(`.{1,${width}}`, 'g');
if (text.includes('_') || !text.includes(' ')) {
return text.match(regex) || [text];
}
const words = text.split(/s+/);
const lines = [];
let currentLine = [];
let currentLength = 0;
words.forEach(word => {
if (currentLength + word.length > width) {
lines.push(currentLine.join(' '));
currentLine = [word];
currentLength = word.length;
} else {
currentLine.push(word);
currentLength += word.length + 1;
}
});
if (currentLine.length) {
lines.push(currentLine.join(' '));
}
return lines;
};
const normalizeNode = node => {
if (Array.isArray(node?.children)) {
node.children = node.children.map(child => {
if (typeof child === 'string') {
return {name: child, value: null};
} else {
return normalizeNode(child);
}
});
}
return node;
};
const markTechSpecsNodes = (node, isUnderTechSpecs = false) => {
const isTechSpecs = node.name === 'Tech Specs';
const mark = isUnderTechSpecs || isTechSpecs;
return {
...node,
isTechSpecsBranch: mark,
children: node.children?.map(child => markTechSpecsNodes(child, mark))
};
};
function TechSpecsGraphViewWithGroupings({
groups,
optionValuesWithFacetsSummary,
optionValuesMap,
sortTemplateMap,
part,
detailLists,
query,
optionGroupDimensions,
optionGroupVariantValues,
setSelectedNode,
selectedNode,
graphViewInitialDepth,
setDepthGraphViewDropdownOptions,
onOptionGroupTypeClick,
onTechSpecsClick,
CollectionSchemaParts,
optionGroupAttributesMap,
techSpecs
}) {
const {revision, segment, language, geo, channel} = query;
const treeContainer = useRef(null);
const history = useHistory();
const [showImageModal, setShowImageModal] = useState({
show: false,
imageUrl: ''
});
const [showOptionModal, setShowOptionModal] = useState({
show: false,
sapContainerParts: [],
optionValueAttributes: {}
});
const clickTimeoutRef = useRef(null);
const treeRef = useRef();
const [translate, setTranslate] = useState({x: 0, y: 0});
const [rightTreeData, setRightTreeData] = useState({});
const treeData = useMemo(
() => {
const raw = getTreeData(optionValuesWithFacetsSummary, part, techSpecs);
return markTechSpecsNodes(raw);
},
[optionValuesWithFacetsSummary, part, techSpecs]
);
useEffect(
() => {
if (graphViewInitialDepth && treeRef.current) {
treeRef.current?.collapseAll();
treeRef.current?.expandAtDepth(Number(graphViewInitialDepth));
}
},
[graphViewInitialDepth, treeData]
);
useEffect(
() => {
const depthGraphViewDropdownOptions = getDepthGraphViewDropdownOptions(treeData);
setDepthGraphViewDropdownOptions(depthGraphViewDropdownOptions);
if (treeContainer.current) {
const dimensions = treeContainer.current.getBoundingClientRect();
setTranslate({x: dimensions.width / 2, y: dimensions.height / 4});
}
},
[treeContainer]
);
useEffect(
() => {
if (selectedNode) {
const displayAarr = Object.entries(detailLists[selectedNode] ?? {}).map(entry => {
const [key, value] = entry;
const dimensions = key.split('.').slice(2);
const dimensionArr = optionGroupDimensions[selectedNode];
const variants = dimensionArr?.map((dim, ind) => [dim, dimensions[ind]]);
const searchString = queryString.stringify({
optionGroupType: selectedNode,
revision,
geo,
segment,
language,
channel,
variants
});
return {
name: key,
value: value,
isLink: true,
linkTo: `/option-value-display-name-setup?${searchString}`
};
});
const optionAarr =
optionValuesMap[selectedNode]?.map(ov => ({
name: ov.optionPartNumber,
value: ov.optionDisplay,
onClick: () => {
setShowOptionModal({
show: true,
sapContainerParts: ov.sapContainerParts,
optionValueAttributes: ov.optionValueAttributes
});
}
})) ?? [];
const groupValueAarr = map(optionGroupVariantValues[selectedNode], (variantVal, variantType) => {
const variantAarr = variantVal?.map(val => val);
return {
name: `${variantType} (${variantAarr.length})`,
children: variantAarr
};
});
const optionGroupAttributesAarr = !isEmpty(optionGroupAttributesMap[selectedNode])
? Object.entries(optionGroupAttributesMap[selectedNode]).map(([key, value]) => ({
name: key,
value: value
}))
: [];
const imageAarr = CollectionSchemaParts?.imageDetails?.imageInfo.map(imageValue => ({
name: imageValue.imageKey,
value: imageValue.baseImageName,
onClick: () => {
setShowImageModal({
show: true,
imageUrl: imageValue.imageUrl
});
}
}));
const sortTemplateAarr = sortTemplateMap[selectedNode]
? [
{
name: sortTemplateMap[selectedNode].templateName,
isLink: true,
linkTo: `/option-sort-templates/create?templateId=${sortTemplateMap[selectedNode].templateId}`
}
]
: [];
const data = {
name: selectedNode,
children: []
};
if (displayAarr.length > 0) {
const displayNameArr = displayAarr.map(entry => ({
name: entry.name,
value: entry.value,
isLink: entry.isLink,
linkTo: entry.linkTo
}));
data.children.push({
name: `Display Name (${displayNameArr.length})`,
children: displayNameArr
});
}
if (optionAarr.length > 0) {
data.children.push({
name: `Option Values (${optionAarr.length})`,
children: optionAarr
});
}
if (groupValueAarr.length > 0) {
data.children.push({
name: `Variants (${groupValueAarr.length})`,
children: groupValueAarr
});
}
if (optionGroupAttributesAarr.length > 0) {
data.children.push({
name: 'Option Group Attributes',
children: optionGroupAttributesAarr
});
}
if (imageAarr.length > 0 && selectedNode === 'chassis') {
data.children.push({
name: 'Image Keys',
children: imageAarr
});
}
if (sortTemplateAarr.length) {
data.children.push({
name: 'Sort Template',
children: sortTemplateAarr
});
}
if (selectedNode === part) {
const collectionSchemaPartsAarr = [];
if (!isEmpty(CollectionSchemaParts?.btrParts)) {
collectionSchemaPartsAarr.push({
name: 'BTR Parts',
children: CollectionSchemaParts.btrParts.map(btrPart => ({name: btrPart, value: null}))
});
}
if (!isEmpty(CollectionSchemaParts?.containerParts)) {
collectionSchemaPartsAarr.push({
name: 'Container Parts',
children: CollectionSchemaParts.containerParts.map(containerPart => ({name: containerPart, value: null}))
});
}
if (!isEmpty(CollectionSchemaParts?.imageDetails?.imageInfo)) {
collectionSchemaPartsAarr.push({
name: 'Image Keys',
children: CollectionSchemaParts.imageDetails.imageInfo.map(imageValue => ({
name: imageValue.imageKey,
value: imageValue.baseImageName,
onClick: () => {
setShowImageModal({
show: true,
imageUrl: imageValue.imageUrl
});
}
}))
});
}
if (collectionSchemaPartsAarr.length > 0) {
data.children.push(...collectionSchemaPartsAarr);
}
}
setRightTreeData(data);
}
},
[
selectedNode,
detailLists,
optionValuesMap,
optionGroupVariantValues,
sortTemplateMap,
CollectionSchemaParts,
optionGroupDimensions
]
);
const renderTree = (node, depth = 0) => {
const hasChildren = node.children && node.children.length > 0;
const handleClick = () => {
if (node.onClick) {
node.onClick();
return;
}
if (node.isLink && node.linkTo) {
if (node.name.includes('imagekey')) {
node.onClick();
} else {
history.push(node.linkTo);
}
}
};
return (
<div className="tree-node-wrapper">
<div className="tree-node-line-wrapper">{depth > 0 && <div className="vertical-line" />}</div>
<div key={node.name + depth} className="tree-node">
<div>
<span className="node-circle" />
<span
className={`tree-head ${node.isLink || node.onClick ? 'global-link-medium ' : 'global-body-medium'}`}
onClick={handleClick}
>
{node.name}
</span>
<span className="tree-value global-body-medium"> {node.value}</span>
</div>
{hasChildren && (
<div className="tree-children">
{node.children.map((child, idx) => (
<div className="tree-child-connector" key={child.name + idx}>
<div className="horizontal-line" />
{renderTree(child, depth + 1)}
</div>
))}
</div>
)}
</div>
</div>
);
};
const renderCustomNode = ({nodeDatum, toggleNode}) => {
const containsDetail =
!isEmpty(detailLists[nodeDatum.name]) ||
!isEmpty(optionValuesMap[nodeDatum.name]) ||
!isEmpty(optionGroupVariantValues[nodeDatum.name]) ||
!isEmpty(sortTemplateMap[nodeDatum.name]);
const isSelected = selectedNode === nodeDatum.name;
const handleClick = () => {
if (nodeDatum.name === part) {
clickTimeoutRef.current = setTimeout(() => {
setSelectedNode(nodeDatum.name);
clickTimeoutRef.current = null;
}, 250);
} else {
if (clickTimeoutRef.current) {
if (containsDetail) {
setSelectedNode(nodeDatum.name);
}
clearTimeout(clickTimeoutRef.current);
clickTimeoutRef.current = null;
} else {
clickTimeoutRef.current = setTimeout(() => {
toggleNode();
clickTimeoutRef.current = null;
}, 250);
}
}
};
const attachRef = el => {
if (!el) return;
const isTechSpecsNode = techSpecs.some(grp => grp.specLabel === nodeDatum.name);
if (isTechSpecsNode && !el.dataset.hasDblclick) {
el.dataset.hasDblclick = 'true';
el.addEventListener('dblclick', () => {
clearTimeout(clickTimeoutRef.current);
clickTimeoutRef.current = null;
const selectedGroup = techSpecs.find(grp => grp.specLabel === nodeDatum.name);
if (selectedGroup) onTechSpecsClick(selectedGroup);
});
}
};
const isLeafNode = !containsDetail && !techSpecs.some(grp => grp.specLabel === nodeDatum.name);
const cursorStyle = isLeafNode ? 'default' : 'pointer';
let fillColor;
if (nodeDatum.isTechSpecsBranch) {
fillColor = '#ffcc00';
} else if (isSelected) {
fillColor = '#119af0';
} else if (containsDetail) {
fillColor = '#7ecfed';
} else {
fillColor = '#a9cbcf';
}
const wrappedLines = wrapText(nodeDatum.name, 12);
const lineHeight = 16;
const totalHeight = wrappedLines.length * lineHeight;
const startY = -(totalHeight / 2) + lineHeight / 2;
return (
<g ref={attachRef} style={{cursor: cursorStyle}} onClick={handleClick}>
<circle r="30" fill={fillColor} stroke={isSelected ? '#000' : '#ccc'} strokeWidth={isSelected ? 5 : 1} />
<text fill="black" textAnchor="middle" fontSize="12" fontWeight="400">
{wrappedLines.map((line, i) => (
<tspan key={i} x="0" y={startY + i * lineHeight}>
{line}
</tspan>
))}
</text>
</g>
);
};
const summaryNode = () => {
const totalOptionValues = optionValuesMap[selectedNode]?.length || 0;
const totalVariants = Object.keys(optionGroupVariantValues[selectedNode] || {}).length;
const totalVariantValues = Object.values(optionGroupVariantValues[selectedNode] || {}).reduce(
(acc, arr) => arr.length + acc,
0
);
const totalComponentVariations = totalVariants * totalVariantValues;
return (
<ul className="summary-node">
<li className="global-body-medium">
<strong>Total Option Values</strong>: {totalOptionValues}
</li>
<li className="global-body-medium">
<strong>Total Variants</strong>: {totalVariants}
</li>
<li className="global-body-medium">
<strong>Total Variant Values</strong>: {totalVariantValues}
</li>
<li className="global-body-medium">
<strong>Total Component Variations</strong>: {totalComponentVariations}
</li>
</ul>
);
};
const data = normalizeNode(rightTreeData);
return (
<div className={styles.graphView}>
<div className={`${styles.tree} ${!isEmpty(selectedNode) ? 'sel-node' : ''}`} ref={treeContainer}>
<Tree
key={graphViewInitialDepth}
data={treeData}
orientation="vertical"
translate={translate}
pathFunc="diagonal"
nodeSize={{x: 160, y: 200}}
separation={{siblings: 1, nonSiblings: 1}}
renderCustomNodeElement={renderCustomNode}
enableLegacyTransitions={true}
initialDepth={Number(graphViewInitialDepth) || 1}
/>
</div>
{selectedNode && (
<div className={styles.rightPanel}>
<Close
className="close-icon icon"
desiredFontSize={12}
onClick={() => {
setSelectedNode(null);
}}
/>
<div className="data-wrapper">
<button
className="global-header-super"
onClick={() => {
const selectedGroup = groups.find(group => {
return group.optionGroupType === selectedNode;
});
onOptionGroupTypeClick(selectedGroup);
}}
>
{selectedNode}
</button>
{summaryNode()}
<hr />
{data ? renderTree(data) : <div className="global-body-medium">No Results</div>}
</div>
</div>
)}
<ConfirmModal
{...{
show: showImageModal?.show,
onHide: () =>
setShowImageModal({
show: false
}),
onConfirm: () =>
setShowImageModal({
show: false
}),
confirmButtonText: 'Close',
showCancelButton: false
}}
>
{showImageModal.imageUrl && (
<img
className={`${styles.imageViewModal}`}
src={showImageModal.imageUrl}
onError={event => (event.target.style.display = 'none')}
/>
)}
</ConfirmModal>
<ConfirmModal
{...{
show: showOptionModal?.show,
onHide: () =>
setShowOptionModal({
show: false
}),
onConfirm: () =>
setShowOptionModal({
show: false
}),
confirmButtonText: 'Close',
showCancelButton: false
}}
>
<div className={styles.graphViewModal}>
<div className="data-wrapper">
{showOptionModal.sapContainerParts && (
<details className="details-wrapper" open={true}>
<summary className="detail-title">
<span className="section-title global-header-medium">Sap Container Parts</span>
</summary>
<div className="data-detail">
<div className="global-body-medium">{showOptionModal.sapContainerParts.join(' ,')}</div>
</div>
</details>
)}
{showOptionModal.optionValueAttributes && (
<details className="details-wrapper" open={true}>
<summary className="detail-title">
<span className="section-title global-header-medium">Option Value Attributes</span>
</summary>
<div className="data-detail">
{Object.entries(showOptionModal.optionValueAttributes).map(([key, value], index) => (
<div className="option-value-row global-body-medium" key={index}>
<div className="value">{key}</div>
<div className="name">{value}</div>
</div>
))}
</div>
</details>
)}
</div>
</div>
</ConfirmModal>
</div>
);
}
export default TechSpecsGraphViewWithGroupings;
i just provided one reference object , just thik that same way for other optiongrouptypes and show in image like the view
techspecs = [ {
"specId": "battery_and_power",
"specLabel": "Battery And Power",
"optionGroup": "power_adapter",
"specSummary": [
{
"specKey": "tsVideoStreamingBatteryLife",
"specValues": [
"upto24hours",
"upto22hours",
"upto18hours",
"upto21hours"
]
},
{
"specKey": "tsWirelessWebBatteryLife",
"specValues": [
"upto16hours",
"upto14hours",
"upto13hours",
"upto17hours"
]
},
{
"specKey": "tsBatteryPower",
"specValues": [
"72.4-watt-hour",
"100-watt-hour"
]
},
{
"specKey": "tsBatteryMaterial",
"specValues": [
"lithium-polymer"
]
},
{
"specKey": "wattage",
"specValues": [
"70w",
"96w_fast_charge",
"140w_fast_charge"
]
},
{
"specKey": "tsCable",
"specValues": [
"usbc-magsafe3-cable"
]
}
],
"specInfo": [
{
"specVariantsGroup": [
{
"variantKey": "dimensionScreensize",
"variantValue": "14inch"
},
{
"variantKey": "dimensionChip",
"variantValue": "m4"
}
],
"specData": [
{
"specVariants": [
{
"specKey": "tsVideoStreamingBatteryLife",
"specValue": "upto24hours"
}
],
"specText": "Up to 24 hours video streaming"
},
{
"specVariants": [
{
"specKey": "tsWirelessWebBatteryLife",
"specValue": "upto16hours"
}
],
"specText": "Up to 16 hours wireless web"
},
{
"specVariants": [
{
"specKey": "tsBatteryPower",
"specValue": "72.4-watt-hour"
},
{
"specKey": "tsBatteryMaterial",
"specValue": "lithium-polymer"
}
],
"specText": "72.4-watt-hour lithium-polymer battery"
},
{
"specVariants": [
{
"specKey": "wattage",
"specValue": "70w"
}
],
"specText": "70W USB-C Power Adapter"
},
{
"specVariants": [
{
"specKey": "wattage",
"specValue": "96w_fast_charge"
}
],
"specText": "Fast-charge capable with 96W USB-C Power Adapter"
},
{
"specVariants": [
{
"specKey": "tsCable",
"specValue": "usbc-magsafe3-cable"
}
],
"specText": "USB-C to MagSafe 3 Cable"
}
]
},
{
"specVariantsGroup": [
{
"variantKey": "dimensionScreensize",
"variantValue": "14inch"
},
{
"variantKey": "dimensionChip",
"variantValue": "m4pro"
}
],
"specData": [
{
"specVariants": [
{
"specKey": "tsVideoStreamingBatteryLife",
"specValue": "upto22hours"
}
],
"specText": "Up to 22 hours video streaming"
},
{
"specVariants": [
{
"specKey": "tsWirelessWebBatteryLife",
"specValue": "upto14hours"
}
],
"specText": "Up to 14 hours wireless web"
},
{
"specVariants": [
{
"specKey": "tsBatteryPower",
"specValue": "72.4-watt-hour"
},
{
"specKey": "tsBatteryMaterial",
"specValue": "lithium-polymer"
}
],
"specText": "72.4-watt-hour lithium-polymer battery"
},
{
"specVariants": [
{
"specKey": "wattage",
"specValue": "70w"
}
],
"specText": "70W USB-C Power Adapter (included with M4 Pro with 12-core CPU)"
},
{
"specVariants": [
{
"specKey": "wattage",
"specValue": "96w_fast_charge"
}
],
"specText": "96W USB-C Power Adapter (included with M4 Pro with 14-core CPU, configurable with M4 Pro with 12-core CPU). Fast-charge capable with 96W USB-C Power Adapter"
},
{
"specVariants": [
{
"specKey": "tsCable",
"specValue": "usbc-magsafe3-cable"
}
],
"specText": "USB-C to MagSafe 3 Cable"
}
]
},
{
"specVariantsGroup": [
{
"variantKey": "dimensionScreensize",
"variantValue": "14inch"
},
{
"variantKey": "dimensionChip",
"variantValue": "m4max"
}
],
"specData": [
{
"specVariants": [
{
"specKey": "tsVideoStreamingBatteryLife",
"specValue": "upto18hours"
}
],
"specText": "Up to 18 hours video streaming"
},
{
"specVariants": [
{
"specKey": "tsWirelessWebBatteryLife",
"specValue": "upto13hours"
}
],
"specText": "Up to 13 hours wireless web"
},
{
"specVariants": [
{
"specKey": "tsBatteryPower",
"specValue": "72.4-watt-hour"
},
{
"specKey": "tsBatteryMaterial",
"specValue": "lithium-polymer"
}
],
"specText": "72.4-watt-hour lithium-polymer battery"
},
{
"specVariants": [
{
"specKey": "wattage",
"specValue": "96w_fast_charge"
}
],
"specText": "Fast-charge capable with 96W USB-C Power Adapter"
},
{
"specVariants": [
{
"specKey": "tsCable",
"specValue": "usbc-magsafe3-cable"
}
],
"specText": "USB-C to MagSafe 3 Cable"
}
]
},
{
"specVariantsGroup": [
{
"variantKey": "dimensionScreensize",
"variantValue": "16inch"
},
{
"variantKey": "dimensionChip",
"variantValue": "m4pro"
}
],
"specData": [
{
"specVariants": [
{
"specKey": "tsVideoStreamingBatteryLife",
"specValue": "upto24hours"
}
],
"specText": "Up to 24 hours video streaming"
},
{
"specVariants": [
{
"specKey": "tsWirelessWebBatteryLife",
"specValue": "upto17hours"
}
],
"specText": "Up to 17 hours wireless web"
},
{
"specVariants": [
{
"specKey": "tsBatteryPower",
"specValue": "100-watt-hour"
},
{
"specKey": "tsBatteryMaterial",
"specValue": "lithium-polymer"
}
],
"specText": "100-watt-hour lithium-polymer battery"
},
{
"specVariants": [
{
"specKey": "wattage",
"specValue": "140w_fast_charge"
}
],
"specText": "Fast-charge capable with 140W USB-C Power Adapter"
},
{
"specVariants": [
{
"specKey": "tsCable",
"specValue": "usbc-magsafe3-cable"
}
],
"specText": "USB-C to MagSafe 3 Cable"
}
]
},
{
"specVariantsGroup": [
{
"variantKey": "dimensionScreensize",
"variantValue": "16inch"
},
{
"variantKey": "dimensionChip",
"variantValue": "m4max"
}
],
"specData": [
{
"specVariants": [
{
"specKey": "tsVideoStreamingBatteryLife",
"specValue": "upt218hours"
}
],
"specText": "Up to 21 hours video streaming"
},
{
"specVariants": [
{
"specKey": "tsWirelessWebBatteryLife",
"specValue": "upto14hours"
}
],
"specText": "Up to 14 hours wireless web"
},
{
"specVariants": [
{
"specKey": "tsBatteryPower",
"specValue": "100-watt-hour"
},
{
"specKey": "tsBatteryMaterial",
"specValue": "lihium-polymer"
}
],
"specText": "100-watt-hour lithium-polymer battery"
},
{
"specVariants": [
{
"specKey": "wattage",
"specValue": "140w_fast_charge"
}
],
"specText": "Fast-charge capable with 140W USB-C Power Adapter"
},
{
"specVariants": [
{
"specKey": "tsCable",
"specValue": "usbc-magsafe3-cable"
}
],
"specText": "USB-C to MagSafe 3 Cable"
}
]
}
]
}]
![]() |
Notes is a web-based application for online taking notes. You can take your notes and share with others people. If you like taking long notes, notes.io is designed for you. To date, over 8,000,000,000+ notes created and continuing...
With notes.io;
- * You can take a note from anywhere and any device with internet connection.
- * You can share the notes in social platforms (YouTube, Facebook, Twitter, instagram etc.).
- * You can quickly share your contents without website, blog and e-mail.
- * You don't need to create any Account to share a note. As you wish you can use quick, easy and best shortened notes with sms, websites, e-mail, or messaging services (WhatsApp, iMessage, Telegram, Signal).
- * Notes.io has fabulous infrastructure design for a short link and allows you to share the note as an easy and understandable link.
Fast: Notes.io is built for speed and performance. You can take a notes quickly and browse your archive.
Easy: Notes.io doesn’t require installation. Just write and share note!
Short: Notes.io’s url just 8 character. You’ll get shorten link of your note when you want to share. (Ex: notes.io/q )
Free: Notes.io works for 14 years and has been free since the day it was started.
You immediately create your first note and start sharing with the ones you wish. If you want to contact us, you can use the following communication channels;
Email: [email protected]
Twitter: http://twitter.com/notesio
Instagram: http://instagram.com/notes.io
Facebook: http://facebook.com/notesio
Regards;
Notes.io Team
