Compare commits

..

8 Commits

Author SHA1 Message Date
Elias Schneider
0a963bfaf1 release: 0.29.0 2024-07-30 08:43:30 +02:00
Elias Schneider
472c93d548 chore: save caddy logs to caddy.log 2024-07-30 08:43:11 +02:00
Elias Schneider
93aacca9b4 refactor: run formatter 2024-07-30 08:39:22 +02:00
Elias Schneider
3505669135 chore(translations): update translations via Crowdin (#540)
* New translations en-us.ts (French)

* New translations en-us.ts (Hungarian)

* New translations en-us.ts (Japanese)

* New translations en-us.ts (Japanese)
2024-07-30 08:27:36 +02:00
Ivan Li
fe735f9704 feat: add more options to reverse shares (#495)
* feat(reverse-share): optional simplified interface for reverse sharing. issue #155.

* chore: Remove useless form validation.

* feat: Share Ready modal adds a prompt that an email has been sent to the reverse share creator.

* fix: Simplified reverse shared interface elements lack spacing when not logged in.

* fix: Share Ready modal prompt contrast is too low in dark mode.

* feat: add public access options to reverse share.

* feat: remember reverse share simplified and publicAccess options in cookies.

* style: npm run format.

* chore(i18n): Improve translation.

Co-authored-by: Elias Schneider <login@eliasschneider.com>

Update frontend/src/i18n/translations/en-US.ts

Co-authored-by: Elias Schneider <login@eliasschneider.com>

Update frontend/src/i18n/translations/en-US.ts

Co-authored-by: Elias Schneider <login@eliasschneider.com>

chore(i18n): Improve translation.

* chore: Improved variable naming.

* chore(i18n): Improve translation. x2.

* fix(backend/shares): Misjudged the permission of the share of the reverse share.
2024-07-30 08:26:56 +02:00
Elias Schneider
3563715f57 chore(frontend): remove unused dependency 2024-07-28 16:09:31 +02:00
Elias Schneider
14c2185e6f Revert "fix: set max age of access token cookie to 15 minutes"
This reverts commit 2dac38560b.
2024-07-27 17:15:20 +02:00
Elias Schneider
27ee9fb6cb feat: sort share files by name by default 2024-07-25 19:32:00 +02:00
37 changed files with 468 additions and 113 deletions

View File

@@ -1,3 +1,16 @@
## [0.29.0](https://github.com/stonith404/pingvin-share/compare/v0.28.0...v0.29.0) (2024-07-30)
### Features
* add more options to reverse shares ([#495](https://github.com/stonith404/pingvin-share/issues/495)) ([fe735f9](https://github.com/stonith404/pingvin-share/commit/fe735f9704c9d96398f3127a559e17848b08d140)), closes [#155](https://github.com/stonith404/pingvin-share/issues/155)
* sort share files by name by default ([27ee9fb](https://github.com/stonith404/pingvin-share/commit/27ee9fb6cb98177661bed20a0baa399b27e70b7e))
### Reverts
* Revert "fix: set max age of access token cookie to 15 minutes" ([14c2185](https://github.com/stonith404/pingvin-share/commit/14c2185e6f1a81d63e25fbeec3e30a54cf6a44c5))
## [0.28.0](https://github.com/stonith404/pingvin-share/compare/v0.27.0...v0.28.0) (2024-07-22)

View File

@@ -57,4 +57,7 @@ EXPOSE 3000
HEALTHCHECK --interval=10s --timeout=3s CMD curl -f http://localhost:3000/api/health || exit 1
# Application startup updated for Caddy
CMD cp -rn /tmp/img/* /opt/app/frontend/public/img && caddy run --config /etc/caddy/Caddyfile & PORT=3333 HOSTNAME=0.0.0.0 node frontend/server.js & cd backend && npm run prod
CMD cp -rn /tmp/img/* /opt/app/frontend/public/img && \
caddy run --config /etc/caddy/Caddyfile 2> caddy.log & \
PORT=3333 HOSTNAME=0.0.0.0 node frontend/server.js & \
cd backend && npm run prod

View File

@@ -1,12 +1,12 @@
{
"name": "pingvin-share-backend",
"version": "0.28.0",
"version": "0.29.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pingvin-share-backend",
"version": "0.28.0",
"version": "0.29.0",
"dependencies": {
"@nestjs/cache-manager": "^2.2.2",
"@nestjs/common": "^10.3.9",

View File

@@ -1,6 +1,6 @@
{
"name": "pingvin-share-backend",
"version": "0.28.0",
"version": "0.29.0",
"scripts": {
"build": "nest build",
"dev": "cross-env NODE_ENV=development nest start --watch",

View File

@@ -0,0 +1,20 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_ReverseShare" (
"id" TEXT NOT NULL PRIMARY KEY,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"token" TEXT NOT NULL,
"shareExpiration" DATETIME NOT NULL,
"maxShareSize" TEXT NOT NULL,
"sendEmailNotification" BOOLEAN NOT NULL,
"remainingUses" INTEGER NOT NULL,
"simplified" BOOLEAN NOT NULL DEFAULT false,
"creatorId" TEXT NOT NULL,
CONSTRAINT "ReverseShare_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO "new_ReverseShare" ("createdAt", "creatorId", "id", "maxShareSize", "remainingUses", "sendEmailNotification", "shareExpiration", "token") SELECT "createdAt", "creatorId", "id", "maxShareSize", "remainingUses", "sendEmailNotification", "shareExpiration", "token" FROM "ReverseShare";
DROP TABLE "ReverseShare";
ALTER TABLE "new_ReverseShare" RENAME TO "ReverseShare";
CREATE UNIQUE INDEX "ReverseShare_token_key" ON "ReverseShare"("token");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -0,0 +1,22 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_ReverseShare" (
"id" TEXT NOT NULL PRIMARY KEY,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"token" TEXT NOT NULL,
"shareExpiration" DATETIME NOT NULL,
"maxShareSize" TEXT NOT NULL,
"sendEmailNotification" BOOLEAN NOT NULL,
"remainingUses" INTEGER NOT NULL,
"simplified" BOOLEAN NOT NULL DEFAULT false,
"publicAccess" BOOLEAN NOT NULL DEFAULT true,
"creatorId" TEXT NOT NULL,
CONSTRAINT "ReverseShare_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO "new_ReverseShare" ("createdAt", "creatorId", "id", "maxShareSize", "remainingUses", "sendEmailNotification", "shareExpiration", "simplified", "token") SELECT "createdAt", "creatorId", "id", "maxShareSize", "remainingUses", "sendEmailNotification", "shareExpiration", "simplified", "token" FROM "ReverseShare";
DROP TABLE "ReverseShare";
ALTER TABLE "new_ReverseShare" RENAME TO "ReverseShare";
CREATE UNIQUE INDEX "ReverseShare_token_key" ON "ReverseShare"("token");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -103,6 +103,8 @@ model ReverseShare {
maxShareSize String
sendEmailNotification Boolean
remainingUses Int
simplified Boolean @default(false)
publicAccess Boolean @default(true)
creatorId String
creator User @relation(fields: [creatorId], references: [id], onDelete: Cascade)

View File

@@ -251,7 +251,7 @@ export class AuthService {
if (accessToken)
response.cookie("access_token", accessToken, {
sameSite: "lax",
maxAge: 1000 * 60 * 15, // 15 minutes
maxAge: 1000 * 60 * 60 * 24 * 30 * 3, // 3 months
});
if (refreshToken)
response.cookie("refresh_token", refreshToken, {

View File

@@ -9,14 +9,16 @@ import * as moment from "moment";
import { PrismaService } from "src/prisma/prisma.service";
import { ShareSecurityGuard } from "src/share/guard/shareSecurity.guard";
import { ShareService } from "src/share/share.service";
import { ConfigService } from "src/config/config.service";
@Injectable()
export class FileSecurityGuard extends ShareSecurityGuard {
constructor(
private _shareService: ShareService,
private _prisma: PrismaService,
_config: ConfigService,
) {
super(_shareService, _prisma);
super(_shareService, _prisma, _config);
}
async canActivate(context: ExecutionContext) {

View File

@@ -133,9 +133,9 @@ export abstract class GenericOidcProvider implements OAuthProvider<OidcToken> {
: idTokenData.preferred_username ||
idTokenData.name ||
idTokenData.nickname;
let isAdmin: boolean;
if (roleConfig?.path) {
// A path to read roles from the token is configured
let roles: string[] | null;
@@ -146,9 +146,14 @@ export abstract class GenericOidcProvider implements OAuthProvider<OidcToken> {
}
if (Array.isArray(roles)) {
// Roles are found in the token
if (roleConfig.generalAccess && !roles.includes(roleConfig.generalAccess)) {
if (
roleConfig.generalAccess &&
!roles.includes(roleConfig.generalAccess)
) {
// Role for general access is configured and the user does not have it
this.logger.error(`User roles ${roles} do not include ${roleConfig.generalAccess}`);
this.logger.error(
`User roles ${roles} do not include ${roleConfig.generalAccess}`,
);
throw new ErrorPageException("user_not_allowed");
}
if (roleConfig.adminAccess) {

View File

@@ -35,8 +35,10 @@ export class OidcProvider extends GenericOidcProvider {
): Promise<OAuthSignInDto> {
const claim = this.config.get("oauth.oidc-usernameClaim") || undefined;
const rolePath = this.config.get("oauth.oidc-rolePath") || undefined;
const roleGeneralAccess = this.config.get("oauth.oidc-roleGeneralAccess") || undefined;
const roleAdminAccess = this.config.get("oauth.oidc-roleAdminAccess") || undefined;
const roleGeneralAccess =
this.config.get("oauth.oidc-roleGeneralAccess") || undefined;
const roleAdminAccess =
this.config.get("oauth.oidc-roleAdminAccess") || undefined;
return super.getUserInfo(token, query, claim, {
path: rolePath,
generalAccess: roleGeneralAccess,

View File

@@ -13,4 +13,10 @@ export class CreateReverseShareDTO {
@Min(1)
@Max(1000)
maxUseCount: number;
@IsBoolean()
simplified: boolean;
@IsBoolean()
publicAccess: boolean;
}

View File

@@ -13,6 +13,9 @@ export class ReverseShareDTO {
@Expose()
token: string;
@Expose()
simplified: boolean;
from(partial: Partial<ReverseShareDTO>) {
return plainToClass(ReverseShareDTO, partial, {
excludeExtraneousValues: true,

View File

@@ -49,6 +49,8 @@ export class ReverseShareService {
remainingUses: data.maxUseCount,
maxShareSize: data.maxShareSize,
sendEmailNotification: data.sendEmailNotification,
simplified: data.simplified,
publicAccess: data.publicAccess,
creatorId,
},
});

View File

@@ -0,0 +1,19 @@
import { Expose, plainToClass } from "class-transformer";
import { ShareDTO } from "./share.dto";
export class CompletedShareDTO extends ShareDTO {
@Expose()
notifyReverseShareCreator?: boolean;
from(partial: Partial<CompletedShareDTO>) {
return plainToClass(CompletedShareDTO, partial, {
excludeExtraneousValues: true,
});
}
fromList(partial: Partial<CompletedShareDTO>[]) {
return partial.map((part) =>
plainToClass(CompletedShareDTO, part, { excludeExtraneousValues: true }),
);
}
}

View File

@@ -1,5 +1,4 @@
import {
CanActivate,
ExecutionContext,
ForbiddenException,
Injectable,
@@ -9,13 +8,19 @@ import { Request } from "express";
import * as moment from "moment";
import { PrismaService } from "src/prisma/prisma.service";
import { ShareService } from "src/share/share.service";
import { ConfigService } from "src/config/config.service";
import { JwtGuard } from "src/auth/guard/jwt.guard";
import { User } from "@prisma/client";
@Injectable()
export class ShareSecurityGuard implements CanActivate {
export class ShareSecurityGuard extends JwtGuard {
constructor(
private shareService: ShareService,
private prisma: PrismaService,
) {}
configService: ConfigService,
) {
super(configService);
}
async canActivate(context: ExecutionContext) {
const request: Request = context.switchToHttp().getRequest();
@@ -31,7 +36,7 @@ export class ShareSecurityGuard implements CanActivate {
const share = await this.prisma.share.findUnique({
where: { id: shareId },
include: { security: true },
include: { security: true, reverseShare: true },
});
if (
@@ -53,6 +58,22 @@ export class ShareSecurityGuard implements CanActivate {
"share_token_required",
);
// Run the JWTGuard to set the user
await super.canActivate(context);
const user = request.user as User;
// Only the creator and reverse share creator can access the reverse share if it's not public
if (
share.reverseShare &&
!share.reverseShare.publicAccess &&
share.creatorId !== user?.id &&
share.reverseShare.creatorId !== user?.id
)
throw new ForbiddenException(
"Only reverse share creator can access this share",
"private_share",
);
return true;
}
}

View File

@@ -29,6 +29,7 @@ import { ShareOwnerGuard } from "./guard/shareOwner.guard";
import { ShareSecurityGuard } from "./guard/shareSecurity.guard";
import { ShareTokenSecurity } from "./guard/shareTokenSecurity.guard";
import { ShareService } from "./share.service";
import { CompletedShareDTO } from "./dto/shareComplete.dto";
@Controller("shares")
export class ShareController {
constructor(
@@ -86,7 +87,7 @@ export class ShareController {
@UseGuards(CreateShareGuard, ShareOwnerGuard)
async complete(@Param("id") id: string, @Req() request: Request) {
const { reverse_share_token } = request.cookies;
return new ShareDTO().from(
return new CompletedShareDTO().from(
await this.shareService.complete(id, reverse_share_token),
);
}

View File

@@ -159,11 +159,12 @@ export class ShareService {
);
}
if (
share.reverseShare &&
this.config.get("smtp.enabled") &&
share.reverseShare.sendEmailNotification
) {
const notifyReverseShareCreator = share.reverseShare
? this.config.get("smtp.enabled") &&
share.reverseShare.sendEmailNotification
: undefined;
if (notifyReverseShareCreator) {
await this.emailService.sendMailToReverseShareCreator(
share.reverseShare.creator.email,
share.id,
@@ -180,10 +181,15 @@ export class ShareService {
});
}
return this.prisma.share.update({
const updatedShare = await this.prisma.share.update({
where: { id },
data: { uploadLocked: true },
});
return {
...updatedShare,
notifyReverseShareCreator,
};
}
async revertComplete(id: string) {
@@ -239,7 +245,11 @@ export class ShareService {
const share = await this.prisma.share.findUnique({
where: { id },
include: {
files: true,
files: {
orderBy: {
name: "asc",
},
},
creator: true,
security: true,
},

View File

@@ -1,12 +1,12 @@
{
"name": "pingvin-share-frontend",
"version": "0.28.0",
"version": "0.29.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pingvin-share-frontend",
"version": "0.28.0",
"version": "0.29.0",
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/server": "^11.11.0",
@@ -26,7 +26,6 @@
"mime-types": "^2.1.35",
"moment": "^2.30.1",
"next": "^14.2.3",
"next-cookies": "^2.0.3",
"next-http-proxy-middleware": "^1.2.6",
"next-pwa": "^5.6.0",
"p-limit": "^4.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "pingvin-share-frontend",
"version": "0.28.0",
"version": "0.29.0",
"scripts": {
"dev": "next dev",
"build": "next build",
@@ -27,7 +27,6 @@
"mime-types": "^2.1.35",
"moment": "^2.30.1",
"next": "^14.2.3",
"next-cookies": "^2.0.3",
"next-http-proxy-middleware": "^1.2.6",
"next-pwa": "^5.6.0",
"p-limit": "^4.0.0",

View File

@@ -39,7 +39,7 @@ const FileList = ({
const t = useTranslate();
const [sort, setSort] = useState<TableSort>({
property: undefined,
property: "name",
direction: "desc",
});

View File

@@ -22,6 +22,7 @@ import { getExpirationPreview } from "../../../utils/date.util";
import toast from "../../../utils/toast.util";
import FileSizeInput from "../FileSizeInput";
import showCompletedReverseShareModal from "./showCompletedReverseShareModal";
import { getCookie, setCookie } from "cookies-next";
const showCreateReverseShareModal = (
modals: ModalsContextProps,
@@ -61,10 +62,16 @@ const Body = ({
sendEmailNotification: false,
expiration_num: 1,
expiration_unit: "-days",
simplified: !!(getCookie("reverse-share.simplified") ?? false),
publicAccess: !!(getCookie("reverse-share.public-access") ?? true),
},
});
const onSubmit = form.onSubmit(async (values) => {
// remember simplified and publicAccess in cookies
setCookie("reverse-share.simplified", values.simplified);
setCookie("reverse-share.public-access", values.publicAccess);
const expirationDate = moment().add(
form.values.expiration_num,
form.values.expiration_unit.replace(
@@ -91,6 +98,8 @@ const Body = ({
values.maxShareSize,
values.maxUseCount,
values.sendEmailNotification,
values.simplified,
values.publicAccess,
)
.then(({ link }) => {
modals.closeAll();
@@ -210,7 +219,28 @@ const Body = ({
})}
/>
)}
<Switch
mt="xs"
labelPosition="left"
label={t("account.reverseShares.modal.simplified")}
description={t(
"account.reverseShares.modal.simplified.description",
)}
{...form.getInputProps("simplified", {
type: "checkbox",
})}
/>
<Switch
mt="xs"
labelPosition="left"
label={t("account.reverseShares.modal.public-access")}
description={t(
"account.reverseShares.modal.public-access.description",
)}
{...form.getInputProps("publicAccess", {
type: "checkbox",
})}
/>
<Button mt="md" type="submit">
<FormattedMessage id="common.button.create" />
</Button>

View File

@@ -7,12 +7,12 @@ import { FormattedMessage } from "react-intl";
import useTranslate, {
translateOutsideContext,
} from "../../../hooks/useTranslate.hook";
import { Share } from "../../../types/share.type";
import { CompletedShare } from "../../../types/share.type";
import CopyTextField from "../CopyTextField";
const showCompletedUploadModal = (
modals: ModalsContextProps,
share: Share,
share: CompletedShare,
appUrl: string,
) => {
const t = translateOutsideContext();
@@ -25,7 +25,7 @@ const showCompletedUploadModal = (
});
};
const Body = ({ share, appUrl }: { share: Share; appUrl: string }) => {
const Body = ({ share, appUrl }: { share: CompletedShare; appUrl: string }) => {
const modals = useModals();
const router = useRouter();
const t = useTranslate();
@@ -35,6 +35,19 @@ const Body = ({ share, appUrl }: { share: Share; appUrl: string }) => {
return (
<Stack align="stretch">
<CopyTextField link={link} />
{share.notifyReverseShareCreator === true && (
<Text
size="sm"
sx={(theme) => ({
color:
theme.colorScheme === "dark"
? theme.colors.gray[3]
: theme.colors.dark[4],
})}
>
{t("upload.modal.completed.notified-reverse-share-creator")}
</Text>
)}
<Text
size="xs"
sx={(theme) => ({

View File

@@ -31,6 +31,7 @@ import { FileUpload } from "../../../types/File.type";
import { CreateShare } from "../../../types/share.type";
import { getExpirationPreview } from "../../../utils/date.util";
import React from "react";
import toast from "../../../utils/toast.util";
const showCreateUploadModal = (
modals: ModalsContextProps,
@@ -41,12 +42,26 @@ const showCreateUploadModal = (
allowUnauthenticatedShares: boolean;
enableEmailRecepients: boolean;
maxExpirationInHours: number;
simplified: boolean;
},
files: FileUpload[],
uploadCallback: (createShare: CreateShare, files: FileUpload[]) => void,
) => {
const t = translateOutsideContext();
if (options.simplified) {
return modals.openModal({
title: t("upload.modal.title"),
children: (
<SimplifiedCreateUploadModalModal
options={options}
files={files}
uploadCallback={uploadCallback}
/>
),
});
}
return modals.openModal({
title: t("upload.modal.title"),
children: (
@@ -59,6 +74,23 @@ const showCreateUploadModal = (
});
};
const generateLink = () =>
Buffer.from(Math.random().toString(), "utf8")
.toString("base64")
.substring(10, 17);
const generateAvailableLink = async (times = 10): Promise<string> => {
if (times <= 0) {
throw new Error("Could not generate available link");
}
const _link = generateLink();
if (!(await shareService.isShareIdAvailable(_link))) {
return await generateAvailableLink(times - 1);
} else {
return _link;
}
};
const CreateUploadModalBody = ({
uploadCallback,
files,
@@ -78,9 +110,7 @@ const CreateUploadModalBody = ({
const modals = useModals();
const t = useTranslate();
const generatedLink = Buffer.from(Math.random().toString(), "utf8")
.toString("base64")
.substr(10, 7);
const generatedLink = generateLink();
const [showNotSignedInAlert, setShowNotSignedInAlert] = useState(true);
@@ -202,14 +232,7 @@ const CreateUploadModalBody = ({
<Button
style={{ flex: "0 0 auto" }}
variant="outline"
onClick={() =>
form.setFieldValue(
"link",
Buffer.from(Math.random().toString(), "utf8")
.toString("base64")
.substr(10, 7),
)
}
onClick={() => form.setFieldValue("link", generateLink())}
>
<FormattedMessage id="common.button.generate" />
</Button>
@@ -429,4 +452,108 @@ const CreateUploadModalBody = ({
);
};
const SimplifiedCreateUploadModalModal = ({
uploadCallback,
files,
options,
}: {
files: FileUpload[];
uploadCallback: (createShare: CreateShare, files: FileUpload[]) => void;
options: {
isUserSignedIn: boolean;
isReverseShare: boolean;
appUrl: string;
allowUnauthenticatedShares: boolean;
enableEmailRecepients: boolean;
maxExpirationInHours: number;
};
}) => {
const modals = useModals();
const t = useTranslate();
const [showNotSignedInAlert, setShowNotSignedInAlert] = useState(true);
const validationSchema = yup.object().shape({
name: yup
.string()
.transform((value) => value || undefined)
.min(3, t("common.error.too-short", { length: 3 }))
.max(30, t("common.error.too-long", { length: 30 })),
});
const form = useForm({
initialValues: {
name: undefined,
description: undefined,
},
validate: yupResolver(validationSchema),
});
const onSubmit = form.onSubmit(async (values) => {
const link = await generateAvailableLink().catch(() => {
toast.error(t("upload.modal.link.error.taken"));
return undefined;
});
if (!link) {
return;
}
uploadCallback(
{
id: link,
name: values.name,
expiration: "never",
recipients: [],
description: values.description,
security: {
password: undefined,
maxViews: undefined,
},
},
files,
);
modals.closeAll();
});
return (
<Stack>
{showNotSignedInAlert && !options.isUserSignedIn && (
<Alert
withCloseButton
onClose={() => setShowNotSignedInAlert(false)}
icon={<TbAlertCircle size={16} />}
title={t("upload.modal.not-signed-in")}
color="yellow"
>
<FormattedMessage id="upload.modal.not-signed-in-description" />
</Alert>
)}
<form onSubmit={onSubmit}>
<Stack align="stretch">
<Stack align="stretch">
<TextInput
variant="filled"
placeholder={t(
"upload.modal.accordion.name-and-description.name.placeholder",
)}
{...form.getInputProps("name")}
/>
<Textarea
variant="filled"
placeholder={t(
"upload.modal.accordion.name-and-description.description.placeholder",
)}
{...form.getInputProps("description")}
/>
</Stack>
<Button type="submit" data-autofocus>
<FormattedMessage id="common.button.share" />
</Button>
</Stack>
</form>
</Stack>
);
};
export default showCreateUploadModal;

View File

@@ -199,6 +199,14 @@ export default {
"account.reverseShares.modal.send-email.description":
"Send an email notification when a share is created with this reverse share link.",
"account.reverseShares.modal.simplified": "Simple mode",
"account.reverseShares.modal.simplified.description":
"Make it easy for the person uploading the file to share it with you. They will be able to customize only the name and description of the share.",
"account.reverseShares.modal.public-access": "Public access",
"account.reverseShares.modal.public-access.description":
"Make the created shares with this reverse share public. If disabled, only you and the creator of the share can view it.",
"account.reverseShares.modal.max-use.label": "Max uses",
"account.reverseShares.modal.max-use.description":
"The maximum amount of times this URL can be used to create a share.",
@@ -342,6 +350,7 @@ export default {
"upload.modal.completed.expires-on":
"This share will expire on {expiration}.",
"upload.modal.completed.share-ready": "Share ready",
"upload.modal.completed.notified-reverse-share-creator": "We have notified the creator of the reverse share. You can also manually share this link with them through other means.",
// END /upload
@@ -355,6 +364,8 @@ export default {
"share.error.not-found.title": "Share not found",
"share.error.not-found.description":
"The share you're looking for doesn't exist.",
"share.error.access-denied.title": "Private share",
"share.error.access-denied.description": "The current account does not have permission to access this share",
"share.modal.password.title": "Password required",
"share.modal.password.description":

View File

@@ -345,8 +345,8 @@ export default {
"admin.config.smtp.password": "Mot de passe",
"admin.config.smtp.password.description": "Mot de passe du serveur SMTP",
"admin.config.smtp.button.test": "Envoyer un courriel de test",
"admin.config.smtp.allow-unauthorized-certificates": "Trust unauthorized SMTP server certificates",
"admin.config.smtp.allow-unauthorized-certificates.description": "Only set this to true if you need to trust self signed certificates.",
"admin.config.smtp.allow-unauthorized-certificates": "Faire confiance aux certificats de serveurs SMTP non autorisés",
"admin.config.smtp.allow-unauthorized-certificates.description": "Ne permettez ceci que si vous avez besoin de faire confiance aux certificats autosignés.",
"admin.config.oauth.allow-registration": "Autoriser linscription",
"admin.config.oauth.allow-registration.description": "Permettre aux utilisateurs de sinscrire via leur identifiant social",
"admin.config.oauth.ignore-totp": "Ignorer TOTP",
@@ -387,12 +387,12 @@ export default {
"admin.config.oauth.oidc-discovery-uri.description": "LURI de découverte de la connexion à l'application OpenID OAuth",
"admin.config.oauth.oidc-username-claim": "Revendication du nom dutilisateur OpenID",
"admin.config.oauth.oidc-username-claim.description": "Le champ contenant la revendication du nom dutilisateur dans le jeton OpenID Connect. Laissez vide si vous ne savez pas quoi indiquer.",
"admin.config.oauth.oidc-role-path": "Path to roles in OpenID Connect token",
"admin.config.oauth.oidc-role-path.description": "Must be a valid JMES path referencing an array of roles. " + "Managing access rights using OpenID Connect roles is only recommended if no other identity provider is configured and password login is disabled. " + "Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-role-general-access": "OpenID Connect role for general access",
"admin.config.oauth.oidc-role-general-access.description": "Role required for general access. Must be present in a users roles for them to log in. " + "Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-role-admin-access": "OpenID Connect role for admin access",
"admin.config.oauth.oidc-role-admin-access.description": "Role required for administrative access. Must be present in a users roles for them to access the admin panel. " + "Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-role-path": "Chemin vers les rôles dans le jeton OpenID Connect",
"admin.config.oauth.oidc-role-path.description": "Doit être un chemin JMES valide référençant un tableau de rôles. " + "La gestion des droits d'accès en utilisant les rôles OpenID Connect n'est recommandée que si aucun autre fournisseur d'identité n'est configuré et que la connexion par mot de passe est désactivée. " + "Laissez vide si vous ne savez pas ce qu'est cette configuration.",
"admin.config.oauth.oidc-role-general-access": "Rôle OpenID Connect pour un accès général",
"admin.config.oauth.oidc-role-general-access.description": "Rôle requis pour un accès général. Doit être présent dans les rôles d'un utilisateur pour qu'il se connecte. " + "Laissez vide si vous ne savez pas ce qu'est cette configuration.",
"admin.config.oauth.oidc-role-admin-access": "Rôle OpenID Connect pour l'accès admin",
"admin.config.oauth.oidc-role-admin-access.description": "Rôle requis pour l'accès administratif. Doit être présent dans les rôles d'un utilisateur pour accéder au panneau d'administration. " + "Laissez vide si vous ne savez pas ce qu'est cette configuration.",
"admin.config.oauth.oidc-client-id": "ID du client OpenID",
"admin.config.oauth.oidc-client-id.description": "LID du client de lapplication OAuth OpenID Connect",
"admin.config.oauth.oidc-client-secret": "Secret du client OpenID",

View File

@@ -34,7 +34,7 @@ export default {
"signIn.notify.totp-required.title": "Kétfaktoros hitelesítésre van szükség",
"signIn.notify.totp-required.description": "Adja meg a másik úton kapott kódját",
"signIn.oauth.or": "VAGY",
"signIn.oauth.signInWith": "Sign in with",
"signIn.oauth.signInWith": "Bejelentkezés a következővel",
"signIn.oauth.github": "GitHub",
"signIn.oauth.google": "Google",
"signIn.oauth.microsoft": "Microsoft",
@@ -345,14 +345,14 @@ export default {
"admin.config.smtp.password": "Jelszó",
"admin.config.smtp.password.description": "Jelszó az SMTP kiszolgálón",
"admin.config.smtp.button.test": "Teszt email küldése",
"admin.config.smtp.allow-unauthorized-certificates": "Trust unauthorized SMTP server certificates",
"admin.config.smtp.allow-unauthorized-certificates.description": "Only set this to true if you need to trust self signed certificates.",
"admin.config.smtp.allow-unauthorized-certificates": "A jogosulatlan SMTP kiszolgáló tanúsítványok is megbízhatók",
"admin.config.smtp.allow-unauthorized-certificates.description": "Csak akkor engedélyezze ha saját aláírású tanúsítványok elfogadása is szükséges.",
"admin.config.oauth.allow-registration": "Regisztráció engedélyezése",
"admin.config.oauth.allow-registration.description": "A felhasználók közösségi bejelentkezésen át is regisztrálhatnak",
"admin.config.oauth.ignore-totp": "TOTP mellőzése",
"admin.config.oauth.ignore-totp.description": "TOTP mellőzése a közösségi bejelentkezést használó felhasználónál",
"admin.config.oauth.disable-password": "Disable password login",
"admin.config.oauth.disable-password.description": "Whether to disable password login\nMake sure that an OAuth provider is properly configured before activating this configuration to avoid being locked out.",
"admin.config.oauth.disable-password": "Jelszavas bejelentkezés letiltása",
"admin.config.oauth.disable-password.description": "A jelszavas bejelentkezés be- és kikapcsolása\nA letiltás előtt a kizáródás elkerülésére mindenképpen ellenőrizendő az OAuth szolgáltató megfelelő konfigurációja.",
"admin.config.oauth.github-enabled": "GitHub",
"admin.config.oauth.github-enabled.description": "GitHub bejelentkezés engedélyezése",
"admin.config.oauth.github-client-id": "GitHub ügyfél ID",
@@ -387,12 +387,12 @@ export default {
"admin.config.oauth.oidc-discovery-uri.description": "Az OpenID Connect OAuth applikáció Discovery URI azonosítója",
"admin.config.oauth.oidc-username-claim": "OpenID Connect felhasználónév igény",
"admin.config.oauth.oidc-username-claim.description": "Az OpenID Connect ID token felhasználónév igénye. Hagyja üresen ha nincs információja a beállításról.",
"admin.config.oauth.oidc-role-path": "Path to roles in OpenID Connect token",
"admin.config.oauth.oidc-role-path.description": "Must be a valid JMES path referencing an array of roles. " + "Managing access rights using OpenID Connect roles is only recommended if no other identity provider is configured and password login is disabled. " + "Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-role-general-access": "OpenID Connect role for general access",
"admin.config.oauth.oidc-role-general-access.description": "Role required for general access. Must be present in a users roles for them to log in. " + "Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-role-admin-access": "OpenID Connect role for admin access",
"admin.config.oauth.oidc-role-admin-access.description": "Role required for administrative access. Must be present in a users roles for them to access the admin panel. " + "Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-role-path": "Az OpenID Connect token szerepeinek elérési útvonala",
"admin.config.oauth.oidc-role-path.description": "Szerepkörökből álló tömbre hivatkozó érvényes JMES-útvonalnak kell lennie. " + "A belépési jogosultságok kezelésére az OpenID Connect szerepkörök csak más azonosító szolgáltatások hiányában és a jelszavas bejelentkezés letiltottsága mellett javasolt. " + "Hagyja üresen, ha nem tudja, mi ez a konfiguráció.",
"admin.config.oauth.oidc-role-general-access": "OpenID Connect szerepkör általános hozzáféréshez",
"admin.config.oauth.oidc-role-general-access.description": "Role required for general access. Must be present in a users roles for them to log in. " + "Hagyja üresen, ha nem tudja, mi ez a konfiguráció.",
"admin.config.oauth.oidc-role-admin-access": "OpenID Connect szerepkör admin hozzáféréshez",
"admin.config.oauth.oidc-role-admin-access.description": "A rendszergazdai hozzáféréshez szükséges szerepkör. Meg kell lennie a felhasználó szerepkörében ahhoz, hogy hozzáférhessen az adminisztrációs panelhez. " + "Hagyja üresen, ha nem tudja, mi ez a konfiguráció.",
"admin.config.oauth.oidc-client-id": "OpenID Connect ügyfél ID azonosító",
"admin.config.oauth.oidc-client-id.description": "Az OpenID Connect OAuth applikáció ügyfél ID azonosítója",
"admin.config.oauth.oidc-client-secret": "OpenID Connect ügyfél titok",

View File

@@ -34,7 +34,7 @@ export default {
"signIn.notify.totp-required.title": "二段階認証が必要です",
"signIn.notify.totp-required.description": "二段階認証コードを入力してください",
"signIn.oauth.or": "または",
"signIn.oauth.signInWith": "Sign in with",
"signIn.oauth.signInWith": "サインインの方法",
"signIn.oauth.github": "GitHub",
"signIn.oauth.google": "Google",
"signIn.oauth.microsoft": "Microsoft",
@@ -58,7 +58,7 @@ export default {
// /auth/reset-password
"resetPassword.title": "パスワードを忘れてしまいましたか?",
"resetPassword.description": "登録しているメールアドレスを入力してください。",
"resetPassword.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.notify.success": "電子メールが存在する場合、パスワードをリセットするためのリンクを含むメッセージが送信されました。",
"resetPassword.button.back": "サインインページに戻る",
"resetPassword.text.resetPassword": "パスワードをリセット",
"resetPassword.text.enterNewPassword": "新規パスワードを入力",
@@ -170,7 +170,7 @@ export default {
// /admin
"admin.title": "管理画面",
"admin.button.users": "ユーザー管理",
"admin.button.shares": "Share management",
"admin.button.shares": "共有管理",
"admin.button.config": "設定",
"admin.version": "バージョン",
// END /admin
@@ -198,13 +198,13 @@ export default {
"admin.users.modal.create.admin.description": "チェックされている場合、ユーザーは管理画面にアクセスできるようになります。",
// END /admin/users
// /admin/shares
"admin.shares.title": "Share management",
"admin.shares.table.id": "Share ID",
"admin.shares.table.username": "Creator",
"admin.shares.table.visitors": "Visitors",
"admin.shares.table.expires": "Expires At",
"admin.shares.edit.delete.title": "Delete share {id}",
"admin.shares.edit.delete.description": "Do you really want to delete this share?",
"admin.shares.title": "共有管理",
"admin.shares.table.id": "共有ID",
"admin.shares.table.username": "作成者",
"admin.shares.table.visitors": "訪問者",
"admin.shares.table.expires": "有効期限",
"admin.shares.edit.delete.title": "共有 {id} を削除",
"admin.shares.edit.delete.description": "この共有を削除してもよろしいですか?",
// END /admin/shares
// /upload
"upload.title": "アップロード",
@@ -240,9 +240,9 @@ export default {
"upload.modal.expires.month-plural": "ヶ月間",
"upload.modal.expires.year-singular": "年間",
"upload.modal.expires.year-plural": "年間",
"upload.modal.accordion.name-and-description.title": "Name and description",
"upload.modal.accordion.name-and-description.name.placeholder": "Name",
"upload.modal.accordion.name-and-description.description.placeholder": "Note for the recipients of this share",
"upload.modal.accordion.name-and-description.title": "名前と説明",
"upload.modal.accordion.name-and-description.name.placeholder": "名前",
"upload.modal.accordion.name-and-description.description.placeholder": "この共有に関する受信者へのメモ",
"upload.modal.accordion.email.title": "メールで受け取る相手",
"upload.modal.accordion.email.placeholder": "メールの宛先を入力",
"upload.modal.accordion.email.invalid-email": "無効なメールアドレスです",
@@ -274,7 +274,7 @@ export default {
"share.table.name": "ファイル名",
"share.table.size": "サイズ",
"share.modal.file-preview.error.not-supported.title": "プレビューに対応していません",
"share.modal.file-preview.error.not-supported.description": "A preview for this file type is unsupported. Please download the file to view it.",
"share.modal.file-preview.error.not-supported.description": "このファイルタイプのプレビューはサポートされていません。ファイルをダウンロードして表示してください。",
// END /share/[id]
// /share/[id]/edit
"share.edit.title": "編集 {shareId}",
@@ -295,8 +295,8 @@ export default {
"admin.config.general.app-url.description": "Pingvin Shareで利用できるURL",
"admin.config.general.show-home-page": "ホームページを表示する",
"admin.config.general.show-home-page.description": "ホームページを表示するかどうか選択",
"admin.config.general.session-duration": "Session Duration",
"admin.config.general.session-duration.description": "Time in hours after which a user must log in again (default: 3 months).",
"admin.config.general.session-duration": "セッション期間",
"admin.config.general.session-duration.description": "ユーザーが再度ログインする必要がある時間(時間単位)(デフォルト: 3 か月)。",
"admin.config.general.logo": "ロゴ",
"admin.config.general.logo.description": "新しい画像をアップロードしてロゴを変更できます。画像は、PNG形式でアスペクト比が1:1である必要があります。",
"admin.config.general.logo.placeholder": "画像を選択",
@@ -328,10 +328,10 @@ export default {
"admin.config.share.max-size.description": "最大ファイルサイズbyte単位",
"admin.config.share.zip-compression-level": "Zip圧縮レベル",
"admin.config.share.zip-compression-level.description": "ファイルサイズと圧縮速度のバランスを取るように、レベルを調整できます。有効な値は09の間で、0が無圧縮、9で最大限の圧縮となります。 ",
"admin.config.share.chunk-size": "Chunk size",
"admin.config.share.chunk-size.description": "Adjust the chunk size (in bytes) for your uploads to balance efficiency and reliability according to your internet connection. Smaller chunks can enhance success rates for unstable connections, while larger chunks speed up uploads for stable connections.",
"admin.config.share.auto-open-share-modal": "Auto open create share modal",
"admin.config.share.auto-open-share-modal.description": "The share creation modal automatically appears when a user selects files, eliminating the need to manually click the button.",
"admin.config.share.chunk-size": "チャンクサイズ",
"admin.config.share.chunk-size.description": "インターネット接続に応じてアップロードのチャンクサイズ(バイト単位)を調整し、効率と信頼性のバランスを取ってください。不安定な接続の場合、チャンクサイズを小さくすることでアップロード成功率を高めることができ、安定した接続の場合、チャンクサイズを大きくすることでアップロード速度を速めることができます。",
"admin.config.share.auto-open-share-modal": "共有モーダルを自動的に開く",
"admin.config.share.auto-open-share-modal.description": "ユーザーがファイルを選択すると、共有作成モーダルが自動的に表示されるため、手動でボタンをクリックする必要がありません。",
"admin.config.smtp.enabled": "有効",
"admin.config.smtp.enabled.description": "SMTPを有効にするかどうかを選択してください。SMTPサーバーのホスト名、ポート番号、電子メールアドレス、ユーザー名、パスワードが入力されている場合にのみ、有効にしてください。",
"admin.config.smtp.host": "ホスト名",
@@ -345,14 +345,14 @@ export default {
"admin.config.smtp.password": "パスワード",
"admin.config.smtp.password.description": "SMTPサーバーのパスワード",
"admin.config.smtp.button.test": "テストメールを送信",
"admin.config.smtp.allow-unauthorized-certificates": "Trust unauthorized SMTP server certificates",
"admin.config.smtp.allow-unauthorized-certificates.description": "Only set this to true if you need to trust self signed certificates.",
"admin.config.smtp.allow-unauthorized-certificates": "許可されていない SMTP サーバー証明書を信頼します",
"admin.config.smtp.allow-unauthorized-certificates.description": "自己署名証明書を信頼する必要がある場合にのみ、これをtrueに設定してください。",
"admin.config.oauth.allow-registration": "登録を許可する",
"admin.config.oauth.allow-registration.description": "ユーザーにソーシャルアカウント経由での登録を許可します",
"admin.config.oauth.ignore-totp": "二段階認証を無視する",
"admin.config.oauth.ignore-totp.description": "ソーシャルログイン時に二段階認証を無視するかどうかを設定します",
"admin.config.oauth.disable-password": "Disable password login",
"admin.config.oauth.disable-password.description": "Whether to disable password login\nMake sure that an OAuth provider is properly configured before activating this configuration to avoid being locked out.",
"admin.config.oauth.disable-password": "パスワードログインを無効にする",
"admin.config.oauth.disable-password.description": "パスワードログインを無効にするかどうか\nロックアウトされないように、この設定を有効にする前にOAuthプロバイダーが適切に設定されていることを確認してください。",
"admin.config.oauth.github-enabled": "GitHub",
"admin.config.oauth.github-enabled.description": "GitHubアカウントを使用したログインを許可するかどうかを設定します",
"admin.config.oauth.github-client-id": "GitHub クライアントID",
@@ -375,28 +375,28 @@ export default {
"admin.config.oauth.microsoft-client-secret.description": "Microsoft OAuthアプリのクライアントシークレット",
"admin.config.oauth.discord-enabled": "Discord",
"admin.config.oauth.discord-enabled.description": "Discordアカウントを使用したログインを許可するかどうかを設定します",
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
"admin.config.oauth.discord-limited-guild": "Discord限定サーバーID",
"admin.config.oauth.discord-limited-guild.description": "特定のサーバーのユーザーにサインインを制限します。無効にするには空白のままにしてください。",
"admin.config.oauth.discord-client-id": "Discord クライアントID",
"admin.config.oauth.discord-client-id.description": "Discord OAuthアプリのクライアントID",
"admin.config.oauth.discord-client-secret": "Discord クライアントシークレット",
"admin.config.oauth.discord-client-secret.description": "Discord OAuthアプリのクライアントシークレット",
"admin.config.oauth.oidc-enabled": "OpenID Connect",
"admin.config.oauth.oidc-enabled.description": "Whether OpenID Connect login is enabled",
"admin.config.oauth.oidc-enabled.description": "OpenID Connect のログインが有効かを設定します",
"admin.config.oauth.oidc-discovery-uri": "OpenID Connect Discovery URI",
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID Connect OAuth app",
"admin.config.oauth.oidc-username-claim": "OpenID Connect username claim",
"admin.config.oauth.oidc-username-claim.description": "Username claim in OpenID Connect ID token. Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-role-path": "Path to roles in OpenID Connect token",
"admin.config.oauth.oidc-role-path.description": "Must be a valid JMES path referencing an array of roles. " + "Managing access rights using OpenID Connect roles is only recommended if no other identity provider is configured and password login is disabled. " + "Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-role-general-access": "OpenID Connect role for general access",
"admin.config.oauth.oidc-role-general-access.description": "Role required for general access. Must be present in a users roles for them to log in. " + "Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-role-admin-access": "OpenID Connect role for admin access",
"admin.config.oauth.oidc-role-admin-access.description": "Role required for administrative access. Must be present in a users roles for them to access the admin panel. " + "Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-client-id": "OpenID Connect Client ID",
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID Connect OAuth app",
"admin.config.oauth.oidc-discovery-uri.description": "OpenID OAuthアプリのDiscovery URI",
"admin.config.oauth.oidc-username-claim": "OpenID Connect ユーザー名の要求",
"admin.config.oauth.oidc-username-claim.description": "OpenID Connect ID トークンのユーザー名要求。この設定が何かわからない場合は空白のままにしてください。",
"admin.config.oauth.oidc-role-path": "OpenID Connectトークンのロールへのパス",
"admin.config.oauth.oidc-role-path.description": "ロールの配列を参照する有効なJMESパスでなければなりません。" + "OpenID Connectのロールを使用してアクセス権を管理することは、他のIDプロバイダが設定されておらず、パスワードログインが無効になっている場合にのみ推奨されます。この構成がわからない場合は空白のままにしてください。" + "この設定が何であるか分からない場合は空白のままにしてください。",
"admin.config.oauth.oidc-role-general-access": "一般的なアクセスのためのOpenID Connectのロール",
"admin.config.oauth.oidc-role-general-access.description": "一般的なアクセスに必要なロール。ログインするユーザーのロールに存在する必要があります。 " + "この設定が何であるか分からない場合は空白のままにしてください。",
"admin.config.oauth.oidc-role-admin-access": "管理者アクセスのための OpenID Connectのロール",
"admin.config.oauth.oidc-role-admin-access.description": "管理者アクセスに必要なロール。管理パネルにアクセスするためには、ユーザのロールに存在する必要があります。" + "この設定が何であるか分からない場合は空白のままにしてください。",
"admin.config.oauth.oidc-client-id": "OpenID Connect クライアントID",
"admin.config.oauth.oidc-client-id.description": "OpenID Connect OAuth アプリのクライアント ID",
"admin.config.oauth.oidc-client-secret": "OpenID Connect Client secret",
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID Connect OAuth app",
"admin.config.oauth.oidc-client-secret.description": "OpenID Connect OAuthアプリのクライアントシークレット",
// 404
"404.description": "ページが見つかりません。",
"404.button.home": "ホームに戻る",
@@ -407,14 +407,14 @@ export default {
"error.msg.default": "問題が発生しました。",
"error.msg.access_denied": "認証処理を中止しました、後で再度お試しください。",
"error.msg.expired_token": "認証処理に時間がかかりすぎています、後で再度お試しください。",
"error.msg.invalid_token": "Internal Error",
"error.msg.invalid_token": "内部エラー",
"error.msg.no_user": "この{0} アカウントにリンクしたユーザーが存在しません。",
"error.msg.no_email": "この{0} アカウントからメールアドレスを取得出来ません。",
"error.msg.already_linked": "この{0} アカウントは、既に別のアカウントにリンクされています。",
"error.msg.not_linked": "この{0} アカウントはどのアカウントにもリンクされていません。",
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
"error.msg.user_not_allowed": "You are not allowed to sign in.",
"error.msg.cannot_get_user_info": "Can not get your user info from this {0} account.",
"error.msg.unverified_account": "この {0} アカウントは認証されていません。認証後にもう一度お試しください。",
"error.msg.user_not_allowed": "サインインできません。",
"error.msg.cannot_get_user_info": "この {0} アカウントからユーザー情報を取得できません。",
"error.param.provider_github": "GitHub",
"error.param.provider_google": "Google",
"error.param.provider_microsoft": "Microsoft",
@@ -432,10 +432,10 @@ export default {
"common.button.generate": "生成",
"common.button.done": "完了",
"common.text.link": "リンク",
"common.text.navigate-to-link": "Go to the link",
"common.text.navigate-to-link": "リンクへ移動",
"common.text.or": "または",
"common.button.go-back": "戻る",
"common.button.go-home": "Go home",
"common.button.go-home": "ホームに戻る",
"common.notify.copied": "リンクをクリップボードにコピーしました",
"common.success": "成功",
"common.error": "エラー",

View File

@@ -152,6 +152,12 @@ export default {
"account.reverseShares.modal.max-size.label": "共享文件上限",
"account.reverseShares.modal.send-email": "发送邮件提醒",
"account.reverseShares.modal.send-email.description": "当这个预留共享链接被用于共享时,发送邮件提醒",
"account.reverseShares.modal.simplified": "简单模式",
"account.reverseShares.modal.simplified.description":
"让上传者更轻松地与你共享文件,他们仅能自定义共享的名称和描述。",
"account.reverseShares.modal.public-access": "公开访问",
"account.reverseShares.modal.public-access.description":
"让通过这个预留共享创建共享能被公开访问。如果禁用,将只有您和创建者能够访问。",
"account.reverseShares.modal.max-use.label": "最大使用次数",
"account.reverseShares.modal.max-use.description": "这个预留共享链接可被用于创建共享的最大使用次数",
"account.reverseShare.never-expires": "这个预留共享永不过期",
@@ -255,6 +261,7 @@ export default {
"upload.modal.completed.never-expires": "这个共享永不过期",
"upload.modal.completed.expires-on": "这个共享将过期于 {expiration}.",
"upload.modal.completed.share-ready": "共享创建完毕",
"upload.modal.completed.notified-reverse-share-creator": "我们已经通知预留共享的创建者。您也可以通过其他方式将该链接手动分享给他们。",
// END /upload
// /share/[id]
"share.title": "共享 {shareId}",
@@ -264,6 +271,8 @@ export default {
"share.error.removed.title": "共享已删除",
"share.error.not-found.title": "共享未找到",
"share.error.not-found.description": "共享文件走丢了",
"share.error.access-denied.title": "私有共享",
"share.error.access-denied.description": "当前账户没有权限访问此共享",
"share.modal.password.title": "需要密码",
"share.modal.password.description": "请输入密码来访问此共享",
"share.modal.password": "密码",

View File

@@ -43,6 +43,12 @@ const Share = ({ shareId }: { shareId: string }) => {
t("share.error.not-found.description"),
);
}
} else if (e.response.status == 403 && error == "share_removed") {
showErrorModal(
modals,
t("share.error.access-denied.title"),
t("share.error.access-denied.description"),
);
} else {
showErrorModal(modals, t("common.error"), t("common.error.unknown"));
}

View File

@@ -69,6 +69,12 @@ const Share = ({ shareId }: { shareId: string }) => {
"go-home",
);
}
} else if (e.response.status == 403 && error == "private_share") {
showErrorModal(
modals,
t("share.error.access-denied.title"),
t("share.error.access-denied.description"),
);
} else if (error == "share_password_required") {
showEnterPasswordModal(modals, getShareToken);
} else if (error == "share_token_required") {

View File

@@ -17,12 +17,14 @@ const Share = ({ reverseShareToken }: { reverseShareToken: string }) => {
const [isLoading, setIsLoading] = useState(true);
const [maxShareSize, setMaxShareSize] = useState(0);
const [simplified, setSimplified] = useState(false);
useEffect(() => {
shareService
.setReverseShare(reverseShareToken)
.then((reverseShareTokenData) => {
setMaxShareSize(parseInt(reverseShareTokenData.maxShareSize));
setSimplified(reverseShareTokenData.simplified);
setIsLoading(false);
})
.catch(() => {
@@ -38,7 +40,13 @@ const Share = ({ reverseShareToken }: { reverseShareToken: string }) => {
if (isLoading) return <LoadingOverlay visible />;
return <Upload isReverseShare maxShareSize={maxShareSize} />;
return (
<Upload
isReverseShare
maxShareSize={maxShareSize}
simplified={simplified}
/>
);
};
export default Share;

View File

@@ -25,9 +25,11 @@ let createdShare: Share;
const Upload = ({
maxShareSize,
isReverseShare = false,
simplified,
}: {
maxShareSize?: number;
isReverseShare: boolean;
simplified: boolean;
}) => {
const modals = useModals();
const t = useTranslate();
@@ -133,6 +135,7 @@ const Upload = ({
),
enableEmailRecepients: config.get("email.enableShareEmailRecipients"),
maxExpirationInHours: config.get("share.maxExpiration"),
simplified,
},
files,
uploadFiles,

View File

@@ -109,6 +109,8 @@ const createReverseShare = async (
maxShareSize: number,
maxUseCount: number,
sendEmailNotification: boolean,
simplified: boolean,
publicAccess: boolean,
) => {
return (
await api.post("reverseShares", {
@@ -116,6 +118,8 @@ const createReverseShare = async (
maxShareSize: maxShareSize.toString(),
maxUseCount,
sendEmailNotification,
simplified,
publicAccess,
})
).data;
};

View File

@@ -11,6 +11,15 @@ export type Share = {
hasPassword: boolean;
};
export type CompletedShare = Share & {
/**
* undefined means is not reverse share
* true means server was send email to reverse share creator
* false means server was not send email to reverse share creator
* */
notifyReverseShareCreator: boolean | undefined;
};
export type CreateShare = {
id: string;
name?: string;

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "pingvin-share",
"version": "0.28.0",
"version": "0.29.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pingvin-share",
"version": "0.28.0",
"version": "0.29.0",
"devDependencies": {
"conventional-changelog-cli": "^3.0.0"
}

View File

@@ -1,6 +1,6 @@
{
"name": "pingvin-share",
"version": "0.28.0",
"version": "0.29.0",
"scripts": {
"format": "cd frontend && npm run format && cd ../backend && npm run format",
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",