Compare commits

..

10 Commits

Author SHA1 Message Date
Daniel 07eb242c26 Merge pull request #400 from linkwarden/dev
updated version
2024-01-02 15:15:41 -05:00
daniel31x13 7880551c4d updated version 2024-01-02 15:15:14 -05:00
Daniel f71acd86df Merge pull request #399 from linkwarden/dev
bug fixed + improved docker image
2024-01-02 15:12:56 -05:00
daniel31x13 98fbb5b678 bug fixed 2024-01-02 15:11:38 -05:00
Daniel 0c2c837028 Merge pull request #398 from modem7/yarn-cache
Implement docker cache mount for yarn
2024-01-02 12:41:34 -05:00
modem7 a5b166f41d implement docker cache mount for yarn 2024-01-02 17:39:50 +00:00
Daniel 89de1829c2 Merge pull request #395 from linkwarden/dev
Revert "updated README.md"
2024-01-02 07:16:56 -05:00
Daniel 06ab784441 Merge pull request #394 from linkwarden/dev
updated README.md
2024-01-02 07:12:50 -05:00
Daniel a8f4072f1c Merge pull request #393 from linkwarden/dev
updated SECURITY.md
2024-01-02 07:01:28 -05:00
Daniel ba49946974 Merge pull request #391 from linkwarden/dev
Dev
2024-01-01 17:13:16 -05:00
12 changed files with 473 additions and 619 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ WORKDIR /data
COPY ./package.json ./yarn.lock ./playwright.config.ts ./ COPY ./package.json ./yarn.lock ./playwright.config.ts ./
# Increase timeout to pass github actions arm64 build # Increase timeout to pass github actions arm64 build
RUN yarn install --network-timeout 10000000 RUN --mount=type=cache,sharing=locked,target=/usr/local/share/.cache/yarn yarn install --network-timeout 10000000
RUN npx playwright install-deps && \ RUN npx playwright install-deps && \
apt-get clean && \ apt-get clean && \
@@ -69,11 +69,13 @@ export default function PreservedFormatsModal({ onClose, activeLink }: Props) {
const isReady = () => { const isReady = () => {
return ( return (
collectionOwner.archiveAsScreenshot ===
(link && link.pdf && link.pdf !== "pending") &&
collectionOwner.archiveAsPDF ===
(link && link.pdf && link.pdf !== "pending") &&
link && link &&
(collectionOwner.archiveAsScreenshot === true
? link.pdf && link.pdf !== "pending"
: true) &&
(collectionOwner.archiveAsPDF === true
? link.pdf && link.pdf !== "pending"
: true) &&
link.readable && link.readable &&
link.readable !== "pending" link.readable !== "pending"
); );
+6 -6
View File
@@ -26,13 +26,13 @@ export default function Navbar() {
const { width } = useWindowDimensions(); const { width } = useWindowDimensions();
const handleToggle = () => { const handleToggle = () => {
const [colorTheme, mode] = (settings.theme || "default-light").split('-'); if (settings.theme === "dark") {
const newMode = mode === "dark" ? "light" : "dark"; updateSettings({ theme: "light" });
const newTheme = `${colorTheme}-${newMode}`; } else {
updateSettings({ theme: newTheme }); updateSettings({ theme: "dark" });
}
}; };
useEffect(() => { useEffect(() => {
setSidebar(false); setSidebar(false);
}, [width]); }, [width]);
@@ -143,7 +143,7 @@ export default function Navbar() {
tabIndex={0} tabIndex={0}
role="button" role="button"
> >
Switch to {(settings.theme || "default-light").endsWith("-dark") ? "Light" : "Dark"} Switch to {settings.theme === "light" ? "Dark" : "Light"}
</div> </div>
</li> </li>
<li> <li>
+1 -1
View File
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
export default function SettingsSidebar({ className }: { className?: string }) { export default function SettingsSidebar({ className }: { className?: string }) {
const LINKWARDEN_VERSION = "v2.4.7"; const LINKWARDEN_VERSION = "v2.4.8";
const { collections } = useCollectionStore(); const { collections } = useCollectionStore();
+20 -27
View File
@@ -6,39 +6,32 @@ type Props = {
}; };
export default function ToggleDarkMode({ className }: Props) { export default function ToggleDarkMode({ className }: Props) {
const { updateSettings } = useLocalSettingsStore(); const { settings, updateSettings } = useLocalSettingsStore();
const [theme, setTheme] = useState('default-light');
useEffect(() => { const [theme, setTheme] = useState(localStorage.getItem("theme"));
const storedTheme = localStorage.getItem("theme");
if (storedTheme) {
setTheme(storedTheme);
} else {
// Default theme if not set in localStorage
localStorage.setItem("theme", "default-light");
setTheme("default-light");
}
console.log("Initial theme from localStorage:", storedTheme || "default-light");
}, []);
const handleToggle = () => { const handleToggle = (e: any) => {
const [currentColorTheme, currentMode] = theme.split('-'); setTheme(e.target.checked ? "dark" : "light");
const newMode = currentMode === 'light' ? 'dark' : 'light';
const newTheme = `${currentColorTheme}-${newMode}`;
setTheme(newTheme);
localStorage.setItem("theme", newTheme);
document.documentElement.setAttribute('data-theme', newTheme);
updateSettings({ theme: newTheme });
console.log("New theme set:", newTheme);
}; };
const isDarkMode = theme.endsWith('-dark'); useEffect(() => {
updateSettings({ theme: theme as string });
}, [theme]);
return ( return (
<div className="tooltip tooltip-bottom" data-tip={`Switch to ${isDarkMode ? "Light" : "Dark"}`}> <div
<label className={`swap swap-rotate btn-square text-neutral btn btn-ghost btn-sm ${className}`}> className="tooltip tooltip-bottom"
<input type="checkbox" onChange={handleToggle} className="theme-controller" checked={isDarkMode} /> data-tip={`Switch to ${settings.theme === "light" ? "Dark" : "Light"}`}
>
<label
className={`swap swap-rotate btn-square text-neutral btn btn-ghost btn-sm ${className}`}
>
<input
type="checkbox"
onChange={handleToggle}
className="theme-controller"
checked={localStorage.getItem("theme") === "light" ? false : true}
/>
<i className="bi-sun-fill text-xl swap-on"></i> <i className="bi-sun-fill text-xl swap-on"></i>
<i className="bi-moon-fill text-xl swap-off"></i> <i className="bi-moon-fill text-xl swap-off"></i>
</label> </label>
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "linkwarden", "name": "linkwarden",
"version": "2.4.7", "version": "2.4.8",
"main": "index.js", "main": "index.js",
"repository": "https://github.com/linkwarden/linkwarden.git", "repository": "https://github.com/linkwarden/linkwarden.git",
"author": "Daniel31X13 <daniel31x13@gmail.com>", "author": "Daniel31X13 <daniel31x13@gmail.com>",
+1 -13
View File
@@ -1,4 +1,4 @@
import React, { useEffect } from "react"; import React from "react";
import "@/styles/globals.css"; import "@/styles/globals.css";
import "bootstrap-icons/font/bootstrap-icons.css"; import "bootstrap-icons/font/bootstrap-icons.css";
import { SessionProvider } from "next-auth/react"; import { SessionProvider } from "next-auth/react";
@@ -14,18 +14,6 @@ export default function App({
}: AppProps<{ }: AppProps<{
session: Session; session: Session;
}>) { }>) {
useEffect(() => {
let theme = localStorage.getItem("theme");
if (!theme || !theme.includes("-")) {
theme = "default-dark"; // Default theme
localStorage.setItem("theme", theme);
}
document.documentElement.setAttribute('data-theme', theme);
}, []);
return ( return (
<SessionProvider <SessionProvider
session={pageProps.session} session={pageProps.session}
+2 -4
View File
@@ -104,10 +104,8 @@ export default function Index() {
className="h-[60rem] p-5 flex gap-3 flex-col" className="h-[60rem] p-5 flex gap-3 flex-col"
style={{ style={{
backgroundImage: `linear-gradient(${activeCollection?.color}20 10%, ${ backgroundImage: `linear-gradient(${activeCollection?.color}20 10%, ${
(settings.theme || "default-light").endsWith("-dark") ? "#262626" : "#f3f4f6" settings.theme === "dark" ? "#262626" : "#f3f4f6"
} 13rem, ${ } 13rem, ${settings.theme === "dark" ? "#171717" : "#ffffff"} 100%)`,
(settings.theme || "default-light").endsWith("-dark") ? "#171717" : "#ffffff"
} 100%)`,
}} }}
> >
{activeCollection && ( {activeCollection && (
+2 -2
View File
@@ -155,7 +155,7 @@ export default function Dashboard() {
</div> </div>
<Link <Link
href="/links" href="/links"
className="flex items-center text-sm text-neutral gap-2 cursor-pointer" className="flex items-center text-sm text-black/75 dark:text-white/75 gap-2 cursor-pointer"
> >
View All View All
<i className="bi-chevron-right text-sm"></i> <i className="bi-chevron-right text-sm"></i>
@@ -264,7 +264,7 @@ export default function Dashboard() {
</div> </div>
<Link <Link
href="/links/pinned" href="/links/pinned"
className="flex items-center text-sm text-neutral gap-2 cursor-pointer" className="flex items-center text-sm text-black/75 dark:text-white/75 gap-2 cursor-pointer"
> >
View All View All
<i className="bi-chevron-right text-sm "></i> <i className="bi-chevron-right text-sm "></i>
+39 -58
View File
@@ -1,16 +1,16 @@
import SettingsLayout from "@/layouts/SettingsLayout"; import SettingsLayout from "@/layouts/SettingsLayout";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import useAccountStore from "@/store/account"; import useAccountStore from "@/store/account";
import { AccountSettings } from "@/types/global"; import { AccountSettings } from "@/types/global";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import React from "react";
import useLocalSettingsStore from "@/store/localSettings"; import useLocalSettingsStore from "@/store/localSettings";
export default function Appearance() { export default function Appearance() {
const { updateSettings } = useLocalSettingsStore(); const { updateSettings } = useLocalSettingsStore();
const { account, updateAccount } = useAccountStore();
const submit = async () => { const submit = async () => {
setSubmitLoader(true); setSubmitLoader(true);
const load = toast.loading("Applying..."); const load = toast.loading("Applying...");
const response = await updateAccount({ const response = await updateAccount({
@@ -18,12 +18,17 @@ export default function Appearance() {
}); });
toast.dismiss(load); toast.dismiss(load);
if (response.ok) { if (response.ok) {
toast.success("Settings Applied!"); toast.success("Settings Applied!");
} else toast.error(response.data as string); } else toast.error(response.data as string);
setSubmitLoader(false); setSubmitLoader(false);
}; };
const [submitLoader, setSubmitLoader] = useState(false); const [submitLoader, setSubmitLoader] = useState(false);
const { account, updateAccount } = useAccountStore();
const [user, setUser] = useState<AccountSettings>( const [user, setUser] = useState<AccountSettings>(
!objectIsEmpty(account) !objectIsEmpty(account)
? account ? account
@@ -43,9 +48,6 @@ export default function Appearance() {
} as unknown as AccountSettings) } as unknown as AccountSettings)
); );
// Combine colorTheme and mode into a single state
const [theme, setTheme] = useState(localStorage.getItem("theme") || "default-dark");
function objectIsEmpty(obj: object) { function objectIsEmpty(obj: object) {
return Object.keys(obj).length === 0; return Object.keys(obj).length === 0;
} }
@@ -54,72 +56,51 @@ export default function Appearance() {
if (!objectIsEmpty(account)) setUser({ ...account }); if (!objectIsEmpty(account)) setUser({ ...account });
}, [account]); }, [account]);
const handleThemeChange = (newThemePart: string, isColorTheme: boolean) => {
const currentTheme = localStorage.getItem("theme") || "default-light";
const [currentColorTheme, currentMode] = currentTheme.split('-');
const newTheme = isColorTheme ? `${newThemePart}-${currentMode}` : `${currentColorTheme}-${newThemePart}`;
localStorage.setItem("theme", newTheme);
document.documentElement.setAttribute('data-theme', newTheme);
updateSettings({ theme: newTheme });
// Update the theme state
setTheme(newTheme);
};
return ( return (
<SettingsLayout> <SettingsLayout>
<p className="capitalize text-3xl font-thin inline">Appearance</p> <p className="capitalize text-3xl font-thin inline">Appearance</p>
<div className="divider my-3"></div> <div className="divider my-3"></div>
<div className="flex flex-col gap-5"> <div className="flex flex-col gap-5">
<div> <div>
<p className="mb-3">Select Mode</p> <p className="mb-3">Select Theme</p>
<div className="grid grid-cols-2 gap-3"> <div className="flex gap-3 w-full">
{["light", "dark"].map((modeOption) => ( <div
<button className={`w-full text-center outline-solid outline-neutral-content outline dark:outline-neutral-700 h-36 duration-100 rounded-md flex items-center justify-center cursor-pointer select-none bg-black ${
key={modeOption} localStorage.getItem("theme") === "dark"
onClick={() => handleThemeChange(modeOption, false)} ? "dark:outline-primary text-primary"
className={`w-full text-center h-36 duration-100 rounded-md flex items-center justify-center cursor-pointer select-none ${theme.endsWith(modeOption) ? "ring-2 ring-primary" : "ring-2 ring-neutral"}`} : "text-white"
}`}
onClick={() => updateSettings({ theme: "dark" })}
> >
{modeOption === 'light' ? <i className="bi-moon-fill text-6xl"></i>
<i className={`bi-sun-fill text-6xl ${theme.endsWith(modeOption) ? "text-primary" : ""}`}></i> : <p className="ml-2 text-2xl">Dark</p>
<i className={`bi-moon-fill text-6xl ${theme.endsWith(modeOption) ? "text-primary" : ""}`}></i>}
<p className="ml-2 text-2xl">{modeOption.charAt(0).toUpperCase() + modeOption.slice(1)}</p> {/* <hr className="my-3 outline-1 outline-neutral-content dark:outline-neutral-700" /> */}
</button> </div>
))} <div
className={`w-full text-center outline-solid outline-neutral-content outline dark:outline-neutral-700 h-36 duration-100 rounded-md flex items-center justify-center cursor-pointer select-none bg-white ${
localStorage.getItem("theme") === "light"
? "outline-primary text-primary"
: "text-black"
}`}
onClick={() => updateSettings({ theme: "light" })}
>
<i className="bi-sun-fill text-6xl"></i>
<p className="ml-2 text-2xl">Light</p>
{/* <hr className="my-3 outline-1 outline-neutral-content dark:outline-neutral-700" /> */}
</div>
</div> </div>
</div> </div>
<div> {/* <SubmitButton
<p className="mb-3">Select Color Theme</p>
<div className="grid grid-cols-4 gap-3">
{["default", "red", "green", "orange"].map((colorTheme) => (
<button
key={colorTheme}
onClick={() => handleThemeChange(colorTheme, true)}
className={`w-full text-center h-36 duration-100 rounded-md flex items-center justify-center cursor-pointer select-none ${theme.startsWith(colorTheme) ? "ring-2 ring-primary" : "ring-2 ring-neutral"}`}
>
{colorTheme.charAt(0).toUpperCase() + colorTheme.slice(1)}
</button>
))}
</div>
</div>
<button
onClick={submit} onClick={submit}
disabled={submitLoader} loading={submitLoader}
className="mt-2 mx-auto lg:mx-0 bg-primary text-white rounded-md px-4 py-2" label="Save"
> className="mt-2 mx-auto lg:mx-0"
{submitLoader ? "Saving..." : "Save"} /> */}
</button>
</div> </div>
</SettingsLayout> </SettingsLayout>
); );
} }
+16 -21
View File
@@ -18,39 +18,34 @@ const useLocalSettingsStore = create<LocalSettingsStore>((set) => ({
viewMode: "", viewMode: "",
}, },
updateSettings: async (newSettings) => { updateSettings: async (newSettings) => {
if (newSettings.theme) { if (
newSettings.theme &&
newSettings.theme !== localStorage.getItem("theme")
) {
localStorage.setItem("theme", newSettings.theme); localStorage.setItem("theme", newSettings.theme);
document.documentElement.setAttribute('data-theme', newSettings.theme);
if (newSettings.theme.endsWith("-dark")) { const localTheme = localStorage.getItem("theme") || "";
document.documentElement.classList.add("dark");
} else { document.querySelector("html")?.setAttribute("data-theme", localTheme);
document.documentElement.classList.remove("dark");
}
} }
if (newSettings.viewMode) { if (
newSettings.viewMode &&
newSettings.viewMode !== localStorage.getItem("viewMode")
) {
localStorage.setItem("viewMode", newSettings.viewMode); localStorage.setItem("viewMode", newSettings.viewMode);
// const localTheme = localStorage.getItem("viewMode") || "";
} }
set((state) => ({ settings: { ...state.settings, ...newSettings } })); set((state) => ({ settings: { ...state.settings, ...newSettings } }));
}, },
setSettings: async () => { setSettings: async () => {
let theme = localStorage.getItem("theme"); if (!localStorage.getItem("theme")) {
if (!theme || !theme.includes("-")) { localStorage.setItem("theme", "dark");
theme = "default-dark"; // Default theme
localStorage.setItem("theme", theme);
} }
const localTheme = theme; const localTheme = localStorage.getItem("theme") || "";
document.documentElement.setAttribute('data-theme', localTheme);
if (localTheme.endsWith("-dark")) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
set((state) => ({ set((state) => ({
settings: { ...state.settings, theme: localTheme }, settings: { ...state.settings, theme: localTheme },
+2 -105
View File
@@ -5,7 +5,7 @@ module.exports = {
daisyui: { daisyui: {
themes: [ themes: [
{ {
"default-light": { light: {
primary: "#0369a1", primary: "#0369a1",
secondary: "#0891b2", secondary: "#0891b2",
accent: "#6d28d9", accent: "#6d28d9",
@@ -21,7 +21,7 @@ module.exports = {
}, },
}, },
{ {
"default-dark": { dark: {
primary: "#7dd3fc", primary: "#7dd3fc",
secondary: "#22d3ee", secondary: "#22d3ee",
accent: "#6d28d9", accent: "#6d28d9",
@@ -36,108 +36,6 @@ module.exports = {
error: "#f1293c", error: "#f1293c",
}, },
}, },
// Red Light Theme
{
"red-light": {
primary: "#ef4444",
secondary: "#dc2626",
accent: "#6d28d9",
neutral: "#6b7280",
"neutral-content": "#d1d5db",
"base-100": "#ffffff",
"base-200": "#f3f4f6",
"base-content": "#0a0a0a",
info: "#a5f3fc",
success: "#22c55e",
warning: "#facc15",
error: "#dc2626",
},
},
// Red Dark Theme
{
"red-dark": {
primary: "#ef4444",
secondary: "#dc2626",
accent: "#6d28d9",
neutral: "#9ca3af",
"neutral-content": "#404040",
"base-100": "#171717",
"base-200": "#262626",
"base-content": "#fafafa",
info: "#009ee4",
success: "#00b17d",
warning: "#eac700",
error: "#f1293c",
},
},
// Green Light Theme
{
"green-light": {
primary: "#22c55e",
secondary: "#16a34a",
accent: "#6d28d9",
neutral: "#6b7280",
"neutral-content": "#d1d5db",
"base-100": "#ffffff",
"base-200": "#f3f4f6",
"base-content": "#0a0a0a",
info: "#a5f3fc",
success: "#22c55e",
warning: "#facc15",
error: "#dc2626",
},
},
// Green Dark Theme
{
"green-dark": {
primary: "#22c55e",
secondary: "#16a34a",
accent: "#6d28d9",
neutral: "#9ca3af",
"neutral-content": "#404040",
"base-100": "#171717",
"base-200": "#262626",
"base-content": "#fafafa",
info: "#009ee4",
success: "#00b17d",
warning: "#eac700",
error: "#f1293c",
},
},
// Orange Light Theme
{
"orange-light": {
primary: "#f97316",
secondary: "#ea580c",
accent: "#6d28d9",
neutral: "#9ca3af",
"neutral-content": "#404040",
"base-100": "#171717",
"base-200": "#262626",
"base-content": "#fafafa",
info: "#009ee4",
success: "#00b17d",
warning: "#eac700",
error: "#f1293c",
},
},
// Orange Dark Theme
{
"orange-dark": {
primary: "#f97316",
secondary: "#ea580c",
accent: "#6d28d9",
neutral: "#9ca3af",
"neutral-content": "#404040",
"base-100": "#171717",
"base-200": "#262626",
"base-content": "#fafafa",
info: "#009ee4",
success: "#00b17d",
warning: "#eac700",
error: "#f1293c",
},
},
], ],
}, },
darkMode: ["class", '[data-theme="dark"]'], darkMode: ["class", '[data-theme="dark"]'],
@@ -156,4 +54,3 @@ module.exports = {
}), }),
], ],
}; };