Compare commits
74 Commits
abd26c1c77
...
234f6bd2c7
Author | SHA1 | Date |
---|---|---|
mikwee | 234f6bd2c7 | |
mikwee | ac0be2a361 | |
mikwee | 17f2024129 | |
mikwee | 7916580d2a | |
mikwee | 6f169acd0a | |
mikwee | 62470dde02 | |
mikwee | e88288ed56 | |
mikwee | 4c2de13114 | |
mikwee | 4afa0591c3 | |
mikwee | 0bd9b4ec2d | |
mikwee | 25c47899a9 | |
mikwee | 1e47556e62 | |
mikwee | 2839cc08a0 | |
mikwee | b51a31d811 | |
mikwee | b1268279a3 | |
mikwee | 26dc164ed8 | |
mikwee | 89456c8f16 | |
mikwee | 6949054197 | |
mikwee | 45f5d7ba65 | |
mikwee | 3dcfe5cda4 | |
mikwee | e247f43864 | |
mikwee | fc283bac04 | |
mikwee | 4c9665633c | |
mikwee | 58d76a7139 | |
mikwee | adbeaaa8da | |
mikwee | 518af4bac8 | |
mikwee | 17222b2473 | |
mikwee | 39f6e984fc | |
mikwee | 028d8c9c57 | |
mikwee | 17b761ec89 | |
mikwee | 0a51ce7b29 | |
mikwee | fb95c93ba0 | |
mikwee | 39f46548a5 | |
mikwee | 40c696bb4d | |
mikwee | 7d0b17170c | |
mikwee | 4d038df5c1 | |
mikwee | 103010679a | |
mikwee | 06a1a44b31 | |
mikwee | c3182489ba | |
mikwee | 88bbacaeb0 | |
mikwee | 4a1bce6043 | |
mikwee | fb142dad68 | |
mikwee | 1d35630043 | |
mikwee | f0924ae409 | |
mikwee | 37faac24a2 | |
mikwee | c8fa1fd0f7 | |
mikwee | ce98e393bc | |
mikwee | 2171698901 | |
mikwee | 84fb90b422 | |
mikwee | 2a904dff20 | |
mikwee | 8241e67f84 | |
mikwee | 94ba4b8e40 | |
mikwee | 0defc5a625 | |
mikwee | f8f4cdf2a8 | |
mikwee | 292aed3223 | |
mikwee | df3c874ea4 | |
mikwee | 5dded31a31 | |
mikwee | 6bc50393a8 | |
mikwee | 5deebbab2f | |
mikwee | 8358992698 | |
mikwee | 1a516e3bf5 | |
mikwee | bfa21552be | |
mikwee | 6847f6e742 | |
mikwee | a3f00581fb | |
mikwee | c839ee41b1 | |
mikwee | 07d82da3b1 | |
mikwee | f0e3057ecc | |
mikwee | 48030d8db5 | |
mikwee | bb3075c98c | |
mikwee | 6c3f7078f0 | |
mikwee | 1a5df99b75 | |
mikwee | 11a3d6ef23 | |
mikwee | a7d299d59f | |
mikwee | 8063b74bec |
|
@ -0,0 +1,23 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "pwa-chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"runtimeExecutable": "C:/Users/mikwee/AppData/Local/Chromium/Application/chrome.exe"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"svg.preview.background": "black"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
yarn-path ".yarn/releases/yarn-1.22.15.cjs"
|
|
@ -0,0 +1,29 @@
|
|||
# LinkRoots
|
||||
<p align="center"><img src="src\logo.png"/></p>
|
||||
LinkRoots is a libre, lightweight, self-hosted alternative to Linktree.
|
||||
|
||||
## How to use it
|
||||
|
||||
1. Clone the repository.
|
||||
|
||||
2. Replace `background.png` with any background image of your choice.
|
||||
|
||||
3. Edit `roots.json` to modify the page. The example should be enough to help you understand the format, but feel free to ask me questions if you still don't get it!
|
||||
|
||||
4. Host it!
|
||||
|
||||
|
||||
## Button types
|
||||
|
||||
| Type | Description | Example |
|
||||
| --- | --- | --- |
|
||||
| `basic` | A basic button, not associated with any particular site. | ![Basic Button](buttonImages\basic.png) |
|
||||
| `facebook` | A button for Facebook pages. | ![Facebook Button](buttonImages\facebook.png) |
|
||||
| `instagram` | A button for Instagram pages. | ![Instagram Button](buttonImages\instagram.png) |
|
||||
| `twitter` | A button for Twitter pages. | ![Twitter Button](buttonImages\twitter.png) |
|
||||
| `github` | A button for GitHub pages. | ![GitHub Button](buttonImages\github.png) |
|
||||
| `youtube` | A button for YouTube pages. | ![YouTube Button](buttonImages\youtube.png) |
|
||||
| `linkedin` | A button for LinkedIn pages. | ![LinkedIn Button](buttonImages\linkedin.png) |
|
||||
| `pinterest` | A button for Pinterest pages. | ![Pinterest Button](buttonImages\pinterest.png) |
|
||||
| `tumblr` | A button for Tumblr pages. | ![Reddit Button](buttonImages\tumblr.png) |
|
||||
| `reddit` | A button for Reddit pages. | ![Reddit Button](buttonImages\reddit.png) |
|
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 4.4 KiB |
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "linkroots",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^4.5.1",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
"bootstrap": "5.1.3",
|
||||
"css-loader": "^6.5.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"react": "^17.0.2",
|
||||
"react-bootstrap": "^2.0.2",
|
||||
"react-bootstrap-icons": "^1.5.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-scripts": "4.0.3",
|
||||
"sass": "^1.43.4",
|
||||
"svg-jsx-loader": "^1.0.1",
|
||||
"web-vitals": "^1.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 9.4 KiB |
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
|
@ -0,0 +1,34 @@
|
|||
.App {
|
||||
background:
|
||||
linear-gradient(
|
||||
rgba(0, 0, 0, 0.5),
|
||||
rgba(0, 0, 0, 0.5)
|
||||
),
|
||||
url('background.png');
|
||||
position: fixed;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
#title {
|
||||
text-align: center;
|
||||
margin-top: 3%;
|
||||
font-family: "Roboto";
|
||||
color: white;
|
||||
}
|
||||
#container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
margin-top: 3%;
|
||||
}
|
||||
footer {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import './App.css';
|
||||
import {Button} from 'react-bootstrap';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import { IconContext } from 'react-icons/lib';
|
||||
import { BsFacebook, BsInstagram, BsTwitter, BsGithub, BsYoutube, BsLinkedin, BsPinterest, BsReddit } from 'react-icons/bs';
|
||||
import {GrTumblr} from 'react-icons/gr';
|
||||
import './styles/roots.scss';
|
||||
import data from './roots.json'
|
||||
|
||||
function App() {
|
||||
var res;
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<h1 id="title">{data.title}</h1>
|
||||
<div id="container">
|
||||
{data.links.map((rootDetail, index)=>{
|
||||
if(rootDetail.type === "facebook") {
|
||||
res = <Button className="roots" id="facebook" variant="primary" href={rootDetail.url}><BsFacebook/> {rootDetail.text}</Button>
|
||||
}
|
||||
else if(rootDetail.type === "instagram") {
|
||||
res = <Button className="roots" id="instagram" variant="primary" href={rootDetail.url}><BsInstagram/> {rootDetail.text}</Button>
|
||||
}
|
||||
else if (rootDetail.type === "twitter") {
|
||||
res = <Button className="roots" id="twitter" variant="primary" href={rootDetail.url}><BsTwitter/> {rootDetail.text}</Button>
|
||||
}
|
||||
else if (rootDetail.type === "github") {
|
||||
res = <Button className="roots" id="github" variant="primary" href={rootDetail.url}><BsGithub/> {rootDetail.text}</Button>
|
||||
}
|
||||
else if (rootDetail.type === "youtube") {
|
||||
res = <Button className="roots" id="youtube" variant="primary" href={rootDetail.url}><BsYoutube/> {rootDetail.text}</Button>
|
||||
}
|
||||
else if (rootDetail.type === "linkedin") {
|
||||
res = <Button className="roots" id="linkedin" variant="primary" href={rootDetail.url}><BsLinkedin/> {rootDetail.text}</Button>
|
||||
}
|
||||
else if (rootDetail.type === "pinterest") {
|
||||
res = <Button className="roots" id="pinterest" variant="primary" href={rootDetail.url}><BsPinterest/> {rootDetail.text}</Button>
|
||||
}
|
||||
else if (rootDetail.type === "tumblr") {
|
||||
res = <Button className="roots" id="tumblr" variant="primary" href={rootDetail.url}><IconContext.Provider value={{size: "1em"}}><GrTumblr/></IconContext.Provider> <div className="text">{rootDetail.text}</div></Button>
|
||||
}
|
||||
else if (rootDetail.type === "reddit") {
|
||||
res = <Button className="roots" id="reddit" variant="primary" href={rootDetail.url}><BsReddit/> {rootDetail.text}</Button>
|
||||
}
|
||||
else if (rootDetail.type === "basic") {
|
||||
res = <Button className="roots" variant="primary" href={rootDetail.url}>{rootDetail.text}</Button>
|
||||
}
|
||||
return res;
|
||||
})}
|
||||
</div>
|
||||
<footer style={{color: "white"}}>Made with <a href="https://gitdab.com/mikwee/linkroots">LinkRoots</a></footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
|
@ -0,0 +1,8 @@
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
After Width: | Height: | Size: 1.4 MiB |
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
const Tumblr = ({viewBox, width, height}) => {
|
||||
return (
|
||||
<svg id="svg835" version="1.1" viewBox={viewBox} width={width} height={height} xmlns="http://www.w3.org/2000/svg">
|
||||
<defs id="defs839"/>
|
||||
<path id="path833" style={{"fill":"white"}} d="m 416.98373,160.24149 c -23.965,0 -41.823,-12.328 -41.823,-41.823 V 71.182489 h -21.777 v -25.581 c 23.964,-6.219 33.988,-26.841 35.142,-44.69999988 h 24.885 V 41.456489 h 29.032 v 29.726 h -29.032 v 41.130001 c 0,12.329 6.223,16.589 16.129,16.589 h 14.058 v 31.34 z"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Tumblr;
|
|
@ -0,0 +1,13 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import "@fontsource/roboto/500.css";
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,13 @@
|
|||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"title": "LinkRoots Example",
|
||||
"links": [
|
||||
{
|
||||
"type": "basic",
|
||||
"url": "https://example.com",
|
||||
"text": "Basic"
|
||||
},
|
||||
{
|
||||
"type": "facebook",
|
||||
"url": "https://facebook.com",
|
||||
"text": "Facebook"
|
||||
},
|
||||
{
|
||||
"type": "instagram",
|
||||
"url": "https://instagram.com",
|
||||
"text": "Instagram"
|
||||
},
|
||||
{
|
||||
"type": "twitter",
|
||||
"url": "https://twitter.com",
|
||||
"text": "Twitter"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com",
|
||||
"text": "GitHub"
|
||||
},
|
||||
{
|
||||
"type": "youtube",
|
||||
"url": "https://youtube.com",
|
||||
"text": "YouTube"
|
||||
},
|
||||
{
|
||||
"type": "linkedin",
|
||||
"url": "https://instagram.com",
|
||||
"text": "LinkedIn"
|
||||
},
|
||||
{
|
||||
"type": "pinterest",
|
||||
"url": "https://instagram.com",
|
||||
"text": "Pinterest"
|
||||
},
|
||||
{
|
||||
"type": "tumblr",
|
||||
"url": "https://tumblr.com",
|
||||
"text": "Tumblr"
|
||||
},
|
||||
{
|
||||
"type": "reddit",
|
||||
"url": "https://reddit.come",
|
||||
"text": "Reddit"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
|
@ -0,0 +1,45 @@
|
|||
$btn-border-radius: 25px;
|
||||
$btn-border-width: 0px;
|
||||
|
||||
.roots {
|
||||
width: 15%;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 1%;
|
||||
}
|
||||
#facebook {
|
||||
background-color: #1877f2;
|
||||
}
|
||||
#instagram {
|
||||
background: radial-gradient(circle at 30% 107%, #fdf497 0%, #fdf497 5%, #fd5949 45%,#d6249f 60%,#285AEB 90%);
|
||||
color: black;
|
||||
}
|
||||
#twitter {
|
||||
background-color: #1da1f2;
|
||||
}
|
||||
#github {
|
||||
background-color: #fafafa;
|
||||
color: #333;
|
||||
}
|
||||
#youtube {
|
||||
background-color: #ff0000;
|
||||
}
|
||||
#linkedin {
|
||||
background-color: #0077b5;
|
||||
}
|
||||
#pinterest {
|
||||
background-color: #e60023;
|
||||
}
|
||||
#tumblr {
|
||||
background-color: #35465c;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.text {
|
||||
margin-left: 0.5mm;
|
||||
}
|
||||
}
|
||||
#reddit {
|
||||
background-color: #ff4500;
|
||||
}
|
||||
|
||||
@import "./node_modules/bootstrap/scss/bootstrap.scss";
|
|
@ -0,0 +1,5 @@
|
|||
const webpack = require('webpack');
|
||||
|
||||
module.loaders: [
|
||||
{ test: /\.svg$/, loaders: ['babel?presets[]=react', 'svg-jsx'] }
|
||||
]
|