diff --git a/Dockerfile b/Dockerfile index 148d970..95ddea4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,7 @@ COPY . . RUN mkdir -p ./public ./data \ && cd ./client \ + && npm install --production \ && npm run build \ && cd .. \ && mv ./client/build/* ./public \ diff --git a/Dockerfile.multiarch b/Dockerfile.multiarch index e0827af..808b815 100644 --- a/Dockerfile.multiarch +++ b/Dockerfile.multiarch @@ -13,6 +13,7 @@ COPY . . RUN mkdir -p ./public ./data \ && cd ./client \ + && npm install --production \ && npm run build \ && cd .. \ && mv ./client/build/* ./public \ diff --git a/README.md b/README.md index 6f4927c..9c86503 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,11 @@ Follow instructions from wiki: [Installation without Docker](https://github.com/ - Format: `www.domain.com`, `domain.com`, `sub.domain.com`, `local`, `ip`, `ip:port` - Redirect: `http://{dest}` +### Custom CSS +> This is an experimental feature. Its behaviour might change in the future. +> +Follow instructions from wiki: [Custom CSS](https://github.com/pawelmalak/flame/wiki/Custom-CSS) + ## Support If you want to support development of Flame and my upcoming self-hosted and open source projects you can use the following link: diff --git a/api.js b/api.js index 6ea1ab1..a720fe2 100644 --- a/api.js +++ b/api.js @@ -1,15 +1,17 @@ -const path = require('path'); +const { join } = require('path'); const express = require('express'); const errorHandler = require('./middleware/errorHandler'); const api = express(); // Static files -api.use(express.static(path.join(__dirname, 'public'))); +api.use(express.static(join(__dirname, 'public'))); +api.use('/uploads', express.static(join(__dirname, 'data/uploads'))); api.get(/^\/(?!api)/, (req, res) => { - res.sendFile(path.join(__dirname, 'public/index.html')); + res.sendFile(join(__dirname, 'public/index.html')); }) + // Body parser api.use(express.json()); diff --git a/client/.env b/client/.env index a0df532..601e08c 100644 --- a/client/.env +++ b/client/.env @@ -1 +1 @@ -REACT_APP_VERSION=1.4.2 \ No newline at end of file +REACT_APP_VERSION=1.4.3 \ No newline at end of file diff --git a/client/src/components/Apps/AppCard/AppCard.module.css b/client/src/components/Apps/AppCard/AppCard.module.css index 26a8a69..768ef8e 100644 --- a/client/src/components/Apps/AppCard/AppCard.module.css +++ b/client/src/components/Apps/AppCard/AppCard.module.css @@ -39,4 +39,12 @@ .AppCard:hover { background-color: rgba(0,0,0,0.2); } +} + +.CustomIcon { + width: 90%; + height: 90%; + margin-top: 2px; + margin-left: 2px; + object-fit: contain; } \ No newline at end of file diff --git a/client/src/components/Apps/AppCard/AppCard.tsx b/client/src/components/Apps/AppCard/AppCard.tsx index 50fb979..79e09fe 100644 --- a/client/src/components/Apps/AppCard/AppCard.tsx +++ b/client/src/components/Apps/AppCard/AppCard.tsx @@ -3,6 +3,7 @@ import Icon from '../../UI/Icons/Icon/Icon'; import { iconParser, urlParser } from '../../../utility'; import { App } from '../../../interfaces'; +import { searchConfig } from '../../../utility'; interface ComponentProps { app: App; @@ -15,12 +16,19 @@ const AppCard = (props: ComponentProps): JSX.Element => { return (
- + {(/.(jpeg|jpg|png)$/).test(props.app.icon) + ? {`${props.app.name} + : + }
{props.app.name}
diff --git a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx index 9e8dff9..b0536d9 100644 --- a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx +++ b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx @@ -2,7 +2,7 @@ import { Bookmark, Category } from '../../../interfaces'; import classes from './BookmarkCard.module.css'; import Icon from '../../UI/Icons/Icon/Icon'; -import { iconParser, urlParser } from '../../../utility'; +import { iconParser, urlParser, searchConfig } from '../../../utility'; interface ComponentProps { category: Category; @@ -19,7 +19,7 @@ const BookmarkCard = (props: ComponentProps): JSX.Element => { return (
{bookmark.icon && ( diff --git a/client/src/components/Settings/OtherSettings/OtherSettings.tsx b/client/src/components/Settings/OtherSettings/OtherSettings.tsx index bba197d..3c63d83 100644 --- a/client/src/components/Settings/OtherSettings/OtherSettings.tsx +++ b/client/src/components/Settings/OtherSettings/OtherSettings.tsx @@ -29,7 +29,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { pinAppsByDefault: 1, pinCategoriesByDefault: 1, hideHeader: 0, - useOrdering: 'createdAt' + useOrdering: 'createdAt', + openSameTab: 0 }) // Get config @@ -39,7 +40,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { pinAppsByDefault: searchConfig('pinAppsByDefault', 1), pinCategoriesByDefault: searchConfig('pinCategoriesByDefault', 1), hideHeader: searchConfig('hideHeader', 0), - useOrdering: searchConfig('useOrdering', 'createdAt') + useOrdering: searchConfig('useOrdering', 'createdAt'), + openSameTab: searchConfig('openSameTab', 0) }) }, [props.loading]); @@ -134,6 +136,18 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { + + + + ) diff --git a/client/src/interfaces/Forms.ts b/client/src/interfaces/Forms.ts index 360dae1..948fb42 100644 --- a/client/src/interfaces/Forms.ts +++ b/client/src/interfaces/Forms.ts @@ -11,4 +11,5 @@ export interface SettingsForm { pinCategoriesByDefault: number; hideHeader: number; useOrdering: string; + openSameTab: number; } \ No newline at end of file diff --git a/client/src/setupProxy.js b/client/src/setupProxy.js index 5cafcb1..b45d4fe 100644 --- a/client/src/setupProxy.js +++ b/client/src/setupProxy.js @@ -5,11 +5,16 @@ module.exports = function (app) { target: 'http://localhost:5005' }) + const assetsProxy = createProxyMiddleware('/uploads', { + target: 'http://localhost:5005' + }) + const wsProxy = createProxyMiddleware('/socket', { target: 'http://localhost:5005', ws: true }) app.use(apiProxy); + app.use(assetsProxy); app.use(wsProxy); }; \ No newline at end of file diff --git a/controllers/apps.js b/controllers/apps.js index 4f50f96..267daa3 100644 --- a/controllers/apps.js +++ b/controllers/apps.js @@ -14,11 +14,18 @@ exports.createApp = asyncWrapper(async (req, res, next) => { }); let app; + let _body = { ...req.body }; + + if (req.file) { + console.log(req.file.filename) + _body.icon = req.file.filename; + } + if (pinApps) { if (parseInt(pinApps.value)) { app = await App.create({ - ...req.body, + ..._body, isPinned: true }) } else { diff --git a/middleware/multer.js b/middleware/multer.js new file mode 100644 index 0000000..d1d0556 --- /dev/null +++ b/middleware/multer.js @@ -0,0 +1,27 @@ +const fs = require('fs'); +const { join } = require('path'); +const multer = require('multer'); +const uuid = require('uuid'); + +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + cb(null, './data/uploads'); + }, + filename: (req, file, cb) => { + cb(null, Date.now() + '--' + file.originalname); + } +}) + +const supportedTypes = ['jpg', 'jpeg', 'png']; + +const fileFilter = (req, file, cb) => { + if (supportedTypes.includes(file.mimetype.split('/')[1])) { + cb(null, true); + } else { + cb(null, false); + } +} + +const upload = multer({ storage, fileFilter }); + +module.exports = upload.single('icon'); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6ac480a..21017a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -224,6 +224,11 @@ "picomatch": "^2.0.4" } }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -364,6 +369,43 @@ "fill-range": "^7.0.1" } }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -553,6 +595,17 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "concurrently": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.0.2.tgz", @@ -741,6 +794,38 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -1611,6 +1696,21 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "multer": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz", + "integrity": "sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.1", + "object-assign": "^4.1.1", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } + }, "needle": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", @@ -2411,6 +2511,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, "string-width": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", @@ -2577,6 +2682,11 @@ "mime-types": "~2.1.24" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -2804,6 +2914,11 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "dev": true }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index b958be8..243a5f0 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,11 @@ "concurrently": "^6.0.2", "dotenv": "^9.0.0", "express": "^4.17.1", + "multer": "^1.4.2", "node-schedule": "^2.0.0", "sequelize": "^6.6.2", "sqlite3": "^5.0.2", + "uuid": "^8.3.2", "ws": "^7.4.6" }, "devDependencies": { diff --git a/routes/apps.js b/routes/apps.js index a0b3f47..091550c 100644 --- a/routes/apps.js +++ b/routes/apps.js @@ -1,5 +1,6 @@ const express = require('express'); const router = express.Router(); +const upload = require('../middleware/multer'); const { createApp, @@ -12,7 +13,7 @@ const { router .route('/') - .post(createApp) + .post(upload, createApp) .get(getApps); router diff --git a/utils/Logger.js b/utils/Logger.js index 6648212..1d1deef 100644 --- a/utils/Logger.js +++ b/utils/Logger.js @@ -20,7 +20,7 @@ class Logger { // Timezone const tz = -d.getTimezoneOffset() / 60; - return `${year}-${month}-${day} ${hour}:${minutes}:${seconds}.${miliseconds} UTC${tz > 0 ? '+' + tz : tz}`; + return `${year}-${month}-${day} ${hour}:${minutes}:${seconds}.${miliseconds} UTC${tz >= 0 ? '+' + tz : tz}`; } parseDate(date, ms = false) { diff --git a/utils/initialConfig.json b/utils/initialConfig.json index 09bf4b8..ab0a861 100644 --- a/utils/initialConfig.json +++ b/utils/initialConfig.json @@ -35,6 +35,10 @@ { "key": "useOrdering", "value": "createdAt" + }, + { + "key": "openSameTab", + "value": false } ] } \ No newline at end of file