@paroicms/site-generator-plugin 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gen-front/dist/gen-front.css +1 -1
- package/gen-front/dist/gen-front.mjs +64 -64
- package/package.json +1 -1
- package/gen-backend/dist/commands/actions.js +0 -49
- package/gen-backend/dist/errors.js +0 -20
- package/gen-backend/dist/generator/actions.js +0 -45
- package/gen-backend/dist/generator/fake-content-generator.ts/augment-fields.js +0 -51
- package/gen-backend/dist/generator/generator-session.js +0 -33
- package/gen-backend/dist/generator/generator-types.js +0 -1
- package/gen-backend/dist/generator/lib/token-tracking.js +0 -118
- package/gen-backend/dist/generator/session/generator-session.js +0 -33
- package/gen-backend/dist/generator/session/session-command.js +0 -17
- package/gen-backend/dist/generator/site-generator/theme-scss.js +0 -262
- package/gen-backend/dist/lib/generator-context.js +0 -14
package/package.json
CHANGED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { invokeMessageGuard } from "../generator/llm-queries/invoke-message-guard.js";
|
|
2
|
-
import { invokeNewSiteAnalysis } from "../generator/llm-queries/invoke-new-site-analysis.js";
|
|
3
|
-
import { invokeUpdateSiteSchema } from "../generator/llm-queries/invoke-update-site-schema.js";
|
|
4
|
-
import { generateSite } from "../generator/site-generator/site-generator.js";
|
|
5
|
-
import { newSession, verifySessionToken } from "./generator-session.js";
|
|
6
|
-
export async function executeCommand(commandCtx, input) {
|
|
7
|
-
if (input.command === "newSession") {
|
|
8
|
-
return {
|
|
9
|
-
success: true,
|
|
10
|
-
result: await newSession(commandCtx, input),
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
const { sessionId } = verifySessionToken(commandCtx, input.sessionToken);
|
|
14
|
-
const ctx = {
|
|
15
|
-
...commandCtx,
|
|
16
|
-
sessionId,
|
|
17
|
-
};
|
|
18
|
-
if (input.command === "createSiteSchema") {
|
|
19
|
-
const errorResponse = await invokeMessageGuard(ctx, input);
|
|
20
|
-
if (errorResponse)
|
|
21
|
-
return errorResponse;
|
|
22
|
-
return {
|
|
23
|
-
success: true,
|
|
24
|
-
result: await invokeNewSiteAnalysis(ctx, input),
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
if (input.command === "updateSiteSchema") {
|
|
28
|
-
const errorResponse = await invokeMessageGuard(ctx, input, {
|
|
29
|
-
skipOutOfScopeCheck: true,
|
|
30
|
-
});
|
|
31
|
-
if (errorResponse)
|
|
32
|
-
return errorResponse;
|
|
33
|
-
return {
|
|
34
|
-
success: true,
|
|
35
|
-
result: await invokeUpdateSiteSchema(ctx, input),
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
if (input.command === "generateSite") {
|
|
39
|
-
return {
|
|
40
|
-
success: true,
|
|
41
|
-
result: await generateSite(ctx, input),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
ctx.logger.error(`Invalid command: ${input.command}`);
|
|
45
|
-
return {
|
|
46
|
-
success: false,
|
|
47
|
-
userMessage: "Invalid command",
|
|
48
|
-
};
|
|
49
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Custom API error class for handling API-specific errors
|
|
3
|
-
*/
|
|
4
|
-
export class ApiError extends Error {
|
|
5
|
-
statusCode;
|
|
6
|
-
constructor(message, statusCode = 400) {
|
|
7
|
-
super(message);
|
|
8
|
-
this.name = "ApiError";
|
|
9
|
-
this.statusCode = statusCode;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Create a new API error
|
|
14
|
-
* @param message Error message
|
|
15
|
-
* @param statusCode HTTP status code (default: 400)
|
|
16
|
-
* @returns ApiError instance
|
|
17
|
-
*/
|
|
18
|
-
export function createApiError(message, statusCode = 400) {
|
|
19
|
-
return new ApiError(message, statusCode);
|
|
20
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { invokeMessageGuard } from "./llm-queries/invoke-message-guard.js";
|
|
2
|
-
import { invokeNewSiteAnalysis } from "./llm-queries/invoke-new-site-analysis.js";
|
|
3
|
-
import { invokeUpdateSiteSchema } from "./llm-queries/invoke-update-site-schema.js";
|
|
4
|
-
import { newSession, verifySessionToken } from "./session/generator-session.js";
|
|
5
|
-
import { generateSite } from "./site-generator/site-generator.js";
|
|
6
|
-
export async function executeCommand(ctx, input) {
|
|
7
|
-
if (input.command === "newSession") {
|
|
8
|
-
return {
|
|
9
|
-
success: true,
|
|
10
|
-
result: await newSession(ctx, input),
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
verifySessionToken(input.sessionToken);
|
|
14
|
-
if (input.command === "createSiteSchema") {
|
|
15
|
-
const errorResponse = await invokeMessageGuard(ctx, input);
|
|
16
|
-
if (errorResponse)
|
|
17
|
-
return errorResponse;
|
|
18
|
-
return {
|
|
19
|
-
success: true,
|
|
20
|
-
result: await invokeNewSiteAnalysis(ctx, input),
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
if (input.command === "updateSiteSchema") {
|
|
24
|
-
const errorResponse = await invokeMessageGuard(ctx, input, {
|
|
25
|
-
skipOutOfScopeCheck: true,
|
|
26
|
-
});
|
|
27
|
-
if (errorResponse)
|
|
28
|
-
return errorResponse;
|
|
29
|
-
return {
|
|
30
|
-
success: true,
|
|
31
|
-
result: await invokeUpdateSiteSchema(ctx, input),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
if (input.command === "generateSite") {
|
|
35
|
-
return {
|
|
36
|
-
success: true,
|
|
37
|
-
result: await generateSite(ctx, input),
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
ctx.logger.error(`Invalid command: ${input.command}`);
|
|
41
|
-
return {
|
|
42
|
-
success: false,
|
|
43
|
-
userMessage: "Invalid command",
|
|
44
|
-
};
|
|
45
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { getRandomImagePath } from "../lib/images-lib.js";
|
|
2
|
-
export function augmentWithComputedValues(list, nodeType, language) {
|
|
3
|
-
if (!nodeType.fields)
|
|
4
|
-
return;
|
|
5
|
-
for (const item of list) {
|
|
6
|
-
for (const f of nodeType.fields) {
|
|
7
|
-
const languageKey = f.storedOn === "node" ? "." : language;
|
|
8
|
-
if (f.storedAs === "mediaHandle") {
|
|
9
|
-
const value = getMediaGeneratedFieldContent(f, language);
|
|
10
|
-
if (value) {
|
|
11
|
-
item[f.name] = value;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
else {
|
|
15
|
-
const value = getDefaultValueForField(f.name);
|
|
16
|
-
if (value !== undefined) {
|
|
17
|
-
item[f.name] = { [languageKey]: value };
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
function getMediaGeneratedFieldContent(f, language) {
|
|
24
|
-
const languageKey = f.storedOn === "node" ? "." : language;
|
|
25
|
-
if (f.dataType === "media") {
|
|
26
|
-
return { [languageKey]: { file: getRandomImagePath() } };
|
|
27
|
-
}
|
|
28
|
-
if (f.dataType === "gallery") {
|
|
29
|
-
return {
|
|
30
|
-
[languageKey]: {
|
|
31
|
-
files: [
|
|
32
|
-
getRandomImagePath(),
|
|
33
|
-
getRandomImagePath(),
|
|
34
|
-
getRandomImagePath(),
|
|
35
|
-
getRandomImagePath(),
|
|
36
|
-
getRandomImagePath(),
|
|
37
|
-
getRandomImagePath(),
|
|
38
|
-
],
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function getDefaultValueForField(fieldName) {
|
|
44
|
-
if (fieldName === "phone" || fieldName === "phone2")
|
|
45
|
-
return "0123456789";
|
|
46
|
-
if (fieldName === "phones")
|
|
47
|
-
return JSON.stringify(["0123456789", "9876543210"]);
|
|
48
|
-
if (fieldName === "updateDateTime")
|
|
49
|
-
return new Date().toISOString();
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import jwt from "jsonwebtoken";
|
|
2
|
-
import { createApiError } from "../errors.js";
|
|
3
|
-
const JWT_SECRET = "init"; // Hardcoded JWT secret as specified
|
|
4
|
-
/**
|
|
5
|
-
* Verifies a session token and returns the session ID if valid
|
|
6
|
-
* @param token The JWT token to verify
|
|
7
|
-
* @returns The session ID contained in the token
|
|
8
|
-
* @throws ApiError if the token is invalid or expired
|
|
9
|
-
*/
|
|
10
|
-
export function verifySessionToken(token) {
|
|
11
|
-
try {
|
|
12
|
-
// Verify and decode the token
|
|
13
|
-
const payload = jwt.verify(token, JWT_SECRET);
|
|
14
|
-
if (!payload || !payload.sessionId) {
|
|
15
|
-
throw createApiError("Invalid session token", 401);
|
|
16
|
-
}
|
|
17
|
-
return payload.sessionId;
|
|
18
|
-
}
|
|
19
|
-
catch (error) {
|
|
20
|
-
if (error.name === "TokenExpiredError") {
|
|
21
|
-
throw createApiError("Session token expired", 401);
|
|
22
|
-
}
|
|
23
|
-
else if (error.name === "JsonWebTokenError") {
|
|
24
|
-
throw createApiError("Invalid session token", 401);
|
|
25
|
-
}
|
|
26
|
-
// Re-throw our custom error
|
|
27
|
-
if (error.name === "ApiError") {
|
|
28
|
-
throw error;
|
|
29
|
-
}
|
|
30
|
-
// Unexpected error
|
|
31
|
-
throw createApiError("Session verification failed", 500);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
// Default limits if not specified in configuration
|
|
2
|
-
const DEFAULT_REQUEST_TOKENS = 8000;
|
|
3
|
-
const DEFAULT_SESSION_TOKENS = 32000;
|
|
4
|
-
const DEFAULT_DAILY_TOKENS = 100000;
|
|
5
|
-
/**
|
|
6
|
-
* Initialize token tracking for a session
|
|
7
|
-
*/
|
|
8
|
-
export function initTokenTracking(pluginConf) {
|
|
9
|
-
const requestLimit = pluginConf.maxRequestTokens ?? DEFAULT_REQUEST_TOKENS;
|
|
10
|
-
const sessionLimit = pluginConf.maxSessionTokens ?? DEFAULT_SESSION_TOKENS;
|
|
11
|
-
return {
|
|
12
|
-
requestLimit,
|
|
13
|
-
sessionLimit,
|
|
14
|
-
sessionUsage: 0,
|
|
15
|
-
userLimits: new Map(),
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Roughly estimate token count from text length
|
|
20
|
-
* This is a simple approximation - for production, use a proper tokenizer
|
|
21
|
-
*/
|
|
22
|
-
export function estimateTokenCount(text) {
|
|
23
|
-
// Rough estimate: 1 token ≈ 4 characters for English text
|
|
24
|
-
return Math.ceil(text.length / 4);
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Track token usage for the current user
|
|
28
|
-
*/
|
|
29
|
-
export function trackUserTokens(ctx, tokenCount) {
|
|
30
|
-
const { userKey, tokenUsage, logger } = ctx;
|
|
31
|
-
const dailyLimit = ctx.pluginConf.maxDailyTokens ?? DEFAULT_DAILY_TOKENS;
|
|
32
|
-
// Update session usage
|
|
33
|
-
tokenUsage.sessionUsage += tokenCount;
|
|
34
|
-
// Get or create user usage entry
|
|
35
|
-
let userUsage = tokenUsage.userLimits.get(userKey);
|
|
36
|
-
if (!userUsage) {
|
|
37
|
-
userUsage = {
|
|
38
|
-
dailyUsage: 0,
|
|
39
|
-
lastReset: new Date(),
|
|
40
|
-
totalRequests: 0,
|
|
41
|
-
};
|
|
42
|
-
tokenUsage.userLimits.set(userKey, userUsage);
|
|
43
|
-
}
|
|
44
|
-
// Reset daily usage if it's a new day
|
|
45
|
-
const today = new Date().toDateString();
|
|
46
|
-
if (userUsage.lastReset.toDateString() !== today) {
|
|
47
|
-
logger.info(`Resetting daily token usage for user ${userKey}`);
|
|
48
|
-
userUsage.dailyUsage = 0;
|
|
49
|
-
userUsage.lastReset = new Date();
|
|
50
|
-
}
|
|
51
|
-
// Update daily usage
|
|
52
|
-
userUsage.dailyUsage += tokenCount;
|
|
53
|
-
userUsage.totalRequests++;
|
|
54
|
-
// Log usage
|
|
55
|
-
logger.debug(`Token usage - User: ${userKey}, Request: ${tokenCount}, ` +
|
|
56
|
-
`Session: ${tokenUsage.sessionUsage}/${tokenUsage.sessionLimit}, ` +
|
|
57
|
-
`Daily: ${userUsage.dailyUsage}/${dailyLimit}`);
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Check if token limits have been exceeded
|
|
61
|
-
* @throws Error if any limit is exceeded
|
|
62
|
-
*/
|
|
63
|
-
export function checkTokenLimits(ctx, estimatedTokens) {
|
|
64
|
-
const { tokenUsage, userKey, logger } = ctx;
|
|
65
|
-
const dailyLimit = ctx.pluginConf.maxDailyTokens ?? DEFAULT_DAILY_TOKENS;
|
|
66
|
-
// Check single request limit
|
|
67
|
-
if (estimatedTokens > tokenUsage.requestLimit) {
|
|
68
|
-
logger.warn(`User ${userKey} exceeded request token limit: ${estimatedTokens}/${tokenUsage.requestLimit}`);
|
|
69
|
-
throw new Error(`Prompt exceeds maximum token limit of ${tokenUsage.requestLimit}`);
|
|
70
|
-
}
|
|
71
|
-
// Check session limit
|
|
72
|
-
if (tokenUsage.sessionUsage + estimatedTokens > tokenUsage.sessionLimit) {
|
|
73
|
-
logger.warn(`User ${userKey} exceeded session token limit: ${tokenUsage.sessionUsage}/${tokenUsage.sessionLimit}`);
|
|
74
|
-
throw new Error("Session token limit exceeded. Please start a new session.");
|
|
75
|
-
}
|
|
76
|
-
// Check daily limit
|
|
77
|
-
const userUsage = tokenUsage.userLimits.get(userKey);
|
|
78
|
-
if (userUsage && userUsage.dailyUsage + estimatedTokens > dailyLimit) {
|
|
79
|
-
logger.warn(`User ${userKey} exceeded daily token limit: ${userUsage.dailyUsage}/${dailyLimit}`);
|
|
80
|
-
throw new Error("Daily token limit exceeded. Please try again tomorrow.");
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Safe wrapper for LLM invocation with token tracking and limits
|
|
85
|
-
*/
|
|
86
|
-
export async function invokeWithTokenLimit(ctx, model, prompt, processor) {
|
|
87
|
-
// For text prompts, estimate tokens
|
|
88
|
-
let promptTokens = 0;
|
|
89
|
-
if (typeof prompt === "string") {
|
|
90
|
-
promptTokens = estimateTokenCount(prompt);
|
|
91
|
-
}
|
|
92
|
-
else if (prompt.message && typeof prompt.message === "string") {
|
|
93
|
-
promptTokens = estimateTokenCount(prompt.message);
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
// JSON objects are harder to estimate; use a conservative approach
|
|
97
|
-
promptTokens = estimateTokenCount(JSON.stringify(prompt)) * 1.5;
|
|
98
|
-
}
|
|
99
|
-
// Check limits before making the call
|
|
100
|
-
checkTokenLimits(ctx, promptTokens);
|
|
101
|
-
// Make the LLM call
|
|
102
|
-
const response = await model.invoke(prompt);
|
|
103
|
-
// Estimate response tokens and track total
|
|
104
|
-
let responseContent = "";
|
|
105
|
-
if (typeof response.content === "string") {
|
|
106
|
-
responseContent = response.content;
|
|
107
|
-
}
|
|
108
|
-
else if (response.content && response.content.length > 0) {
|
|
109
|
-
// Handle array response
|
|
110
|
-
responseContent = response.content.join("\n");
|
|
111
|
-
}
|
|
112
|
-
const responseTokens = estimateTokenCount(responseContent);
|
|
113
|
-
const totalTokens = promptTokens + responseTokens;
|
|
114
|
-
// Track the usage
|
|
115
|
-
trackUserTokens(ctx, totalTokens);
|
|
116
|
-
// Process and return the response
|
|
117
|
-
return processor(response);
|
|
118
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { ApiError } from "@paroicms/public-server-lib";
|
|
2
|
-
import { nanoid } from "nanoid";
|
|
3
|
-
const { sign, verify } = (await import("jsonwebtoken")).default;
|
|
4
|
-
const JWT_SECRET = "init"; // FIXME: Hardcoded JWT secret as specified
|
|
5
|
-
export async function newSession(ctx, command) {
|
|
6
|
-
const { service } = ctx;
|
|
7
|
-
const { recaptchaToken } = command;
|
|
8
|
-
const isValid = await service.validateRecaptchaResponse(recaptchaToken);
|
|
9
|
-
if (!isValid)
|
|
10
|
-
throw new ApiError("Invalid reCAPTCHA token", 400);
|
|
11
|
-
const sessionId = nanoid();
|
|
12
|
-
const token = sign({ sessionId }, JWT_SECRET, { expiresIn: "24h" });
|
|
13
|
-
return { token };
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Verifies a session token and returns the session ID if valid
|
|
17
|
-
*/
|
|
18
|
-
export function verifySessionToken(token) {
|
|
19
|
-
let payload;
|
|
20
|
-
try {
|
|
21
|
-
payload = verify(token, JWT_SECRET);
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
if (error.name === "TokenExpiredError")
|
|
25
|
-
throw new ApiError("Session token expired", 401);
|
|
26
|
-
if (error.name === "JsonWebTokenError")
|
|
27
|
-
throw new ApiError("Invalid session token", 401);
|
|
28
|
-
throw error;
|
|
29
|
-
}
|
|
30
|
-
if (!payload || !payload.sessionId)
|
|
31
|
-
throw new ApiError("Invalid session token", 401);
|
|
32
|
-
return payload.sessionId;
|
|
33
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { ApiError } from "@paroicms/public-server-lib";
|
|
2
|
-
import { nanoid } from "nanoid";
|
|
3
|
-
const { sign, verify } = (await import("jsonwebtoken")).default;
|
|
4
|
-
export async function newSession(ctx, command) {
|
|
5
|
-
const { service } = ctx;
|
|
6
|
-
const { recaptchaToken } = command;
|
|
7
|
-
const isValid = await service.validateRecaptchaResponse(recaptchaToken);
|
|
8
|
-
if (!isValid) {
|
|
9
|
-
throw new ApiError("Invalid reCAPTCHA token", {
|
|
10
|
-
status: 400,
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
const sessionId = nanoid();
|
|
14
|
-
const token = sign({ sessionId }, "init", // Hardcoded JWT secret as specified
|
|
15
|
-
{ expiresIn: "1h" });
|
|
16
|
-
return { token };
|
|
17
|
-
}
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
export function getThemeCssContent() {
|
|
2
|
-
return `/* Reset */
|
|
3
|
-
* {
|
|
4
|
-
box-sizing: border-box;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
a,
|
|
8
|
-
a:visited {
|
|
9
|
-
color: inherit;
|
|
10
|
-
text-decoration: none;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/* Elements */
|
|
14
|
-
|
|
15
|
-
body {
|
|
16
|
-
background-color: #aaa;
|
|
17
|
-
margin: 0;
|
|
18
|
-
padding: 0;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
img {
|
|
22
|
-
display: block;
|
|
23
|
-
max-width: 100%;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
picture {
|
|
27
|
-
display: block;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/* Tile */
|
|
31
|
-
|
|
32
|
-
a > article {
|
|
33
|
-
background-color: #fff;
|
|
34
|
-
border-radius: 6px;
|
|
35
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
36
|
-
display: flex;
|
|
37
|
-
max-width: 340px;
|
|
38
|
-
overflow: hidden;
|
|
39
|
-
}
|
|
40
|
-
a > article div:first-child {
|
|
41
|
-
flex: 0 0 120px;
|
|
42
|
-
max-width: 120px;
|
|
43
|
-
}
|
|
44
|
-
a > article img {
|
|
45
|
-
height: 100%;
|
|
46
|
-
object-fit: cover;
|
|
47
|
-
width: 100%;
|
|
48
|
-
}
|
|
49
|
-
a > article div:last-child {
|
|
50
|
-
display: flex;
|
|
51
|
-
flex: 1;
|
|
52
|
-
flex-direction: column;
|
|
53
|
-
padding: 15px;
|
|
54
|
-
}
|
|
55
|
-
a > article h3 {
|
|
56
|
-
margin: 0 0 8px;
|
|
57
|
-
font-size: 16px;
|
|
58
|
-
font-weight: bold;
|
|
59
|
-
color: #333;
|
|
60
|
-
}
|
|
61
|
-
a > article p {
|
|
62
|
-
color: #666;
|
|
63
|
-
font-size: 14px;
|
|
64
|
-
line-height: 1.4;
|
|
65
|
-
margin: 0;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
a:hover > article {
|
|
69
|
-
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/* Classes */
|
|
73
|
-
|
|
74
|
-
._bg2 {
|
|
75
|
-
background-color: #444;
|
|
76
|
-
color: #eee;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.Container {
|
|
80
|
-
max-width: 1200px;
|
|
81
|
-
margin: 0 auto;
|
|
82
|
-
padding: 0 20px;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.Page {
|
|
86
|
-
background-color: #fff;
|
|
87
|
-
color: #333;
|
|
88
|
-
padding: 5px 40px;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.TextWidth {
|
|
92
|
-
max-width: 600px;
|
|
93
|
-
margin: 0 auto;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.List {
|
|
97
|
-
display: flex;
|
|
98
|
-
gap: 20px;
|
|
99
|
-
flex-wrap: wrap;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.Header {
|
|
103
|
-
display: flex;
|
|
104
|
-
align-items: center;
|
|
105
|
-
justify-content: space-between;
|
|
106
|
-
padding-bottom: 15px;
|
|
107
|
-
padding-top: 15px;
|
|
108
|
-
color: #fff;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
@media (max-width: 768px) {
|
|
112
|
-
.Header {
|
|
113
|
-
flex-wrap: wrap;
|
|
114
|
-
padding: 15px 0 5px;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.Header-logo {
|
|
119
|
-
display: flex;
|
|
120
|
-
align-items: center;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.Header-logo img {
|
|
124
|
-
margin-right: 10px;
|
|
125
|
-
border-radius: 50%;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.Header-title {
|
|
129
|
-
font-size: 20px;
|
|
130
|
-
font-weight: bold;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.Header-search {
|
|
134
|
-
padding: 0 10px;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.Text::after {
|
|
138
|
-
clear: both;
|
|
139
|
-
content: "";
|
|
140
|
-
display: block;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
.Text .Img {
|
|
144
|
-
height: auto;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.Text .Img.left {
|
|
148
|
-
float: left;
|
|
149
|
-
margin: 5px 20px 10px 0;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.Text .Img.right {
|
|
153
|
-
float: right;
|
|
154
|
-
margin: 5px 0 10px 20px;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
.Text .Img.left, .Text .Img.right {
|
|
158
|
-
max-width: 50%;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
.Text .Img.center {
|
|
162
|
-
display: block;
|
|
163
|
-
margin: 20px auto;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.Text a,
|
|
167
|
-
.Text a:visited,
|
|
168
|
-
.TextLink,
|
|
169
|
-
.TextLink:visited {
|
|
170
|
-
color: #007bff;
|
|
171
|
-
cursor: pointer;
|
|
172
|
-
text-decoration: underline;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.Hero img {
|
|
176
|
-
height: auto;
|
|
177
|
-
width: 100%;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.InfiniteLoading-actionArea {
|
|
181
|
-
align-items: center;
|
|
182
|
-
display: flex;
|
|
183
|
-
justify-content: center;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.Button,
|
|
187
|
-
.SearchOpenerBtn,
|
|
188
|
-
.InfiniteLoading-btn {
|
|
189
|
-
background-color: #3498db;
|
|
190
|
-
border: 1px solid #3498db;
|
|
191
|
-
border-radius: 5px;
|
|
192
|
-
color: #fff;
|
|
193
|
-
cursor: pointer;
|
|
194
|
-
display: inline-block;
|
|
195
|
-
font-size: 16px;
|
|
196
|
-
margin: 10px 0;
|
|
197
|
-
padding: 10px 20px;
|
|
198
|
-
text-align: center;
|
|
199
|
-
text-decoration: none;
|
|
200
|
-
}
|
|
201
|
-
.Button:hover,
|
|
202
|
-
.SearchOpenerBtn:hover,
|
|
203
|
-
.InfiniteLoading-btn:hover {
|
|
204
|
-
background-color: #2980b9;
|
|
205
|
-
border-color: #2980b9;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.SearchOpenerBtn {
|
|
209
|
-
display: flex;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
.Navbar {
|
|
213
|
-
align-items: center;
|
|
214
|
-
display: flex;
|
|
215
|
-
flex-grow: 1;
|
|
216
|
-
justify-content: center;
|
|
217
|
-
margin: 0 15px;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
@media (max-width: 768px) {
|
|
221
|
-
.Navbar {
|
|
222
|
-
flex-basis: 100%;
|
|
223
|
-
order: 3;
|
|
224
|
-
margin: 10px 0 5px;
|
|
225
|
-
justify-content: flex-start;
|
|
226
|
-
overflow-x: auto;
|
|
227
|
-
padding-bottom: 5px;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
.NavButton {
|
|
232
|
-
color: rgba(255, 255, 255, 0.85);
|
|
233
|
-
padding: 8px;
|
|
234
|
-
margin: 0 4px;
|
|
235
|
-
border-radius: 4px;
|
|
236
|
-
font-size: 17px;
|
|
237
|
-
font-weight: bold;
|
|
238
|
-
position: relative;
|
|
239
|
-
transition: all 0.2s ease;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
.NavButton:hover {
|
|
243
|
-
color: #fff;
|
|
244
|
-
background-color: rgba(255, 255, 255, 0.1);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.NavButton.active {
|
|
248
|
-
color: #fff;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.NavButton.active::after {
|
|
252
|
-
content: "";
|
|
253
|
-
position: absolute;
|
|
254
|
-
bottom: 0;
|
|
255
|
-
left: 16px;
|
|
256
|
-
right: 16px;
|
|
257
|
-
height: 3px;
|
|
258
|
-
background-color: #3498db;
|
|
259
|
-
border-radius: 1.5px;
|
|
260
|
-
}
|
|
261
|
-
`;
|
|
262
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export function createCommandContext(service, pluginConf, rawContext) {
|
|
2
|
-
const packConf = service.connector.getSitePackConf(pluginConf.packName);
|
|
3
|
-
const { sitesDir, packName } = packConf;
|
|
4
|
-
if (!sitesDir) {
|
|
5
|
-
throw new Error(`Site-generator plugin can generate sites only for pack with "sitesDir", but pack "${packName}" doesn't have it`);
|
|
6
|
-
}
|
|
7
|
-
return {
|
|
8
|
-
...rawContext,
|
|
9
|
-
sitesDir,
|
|
10
|
-
packName,
|
|
11
|
-
service,
|
|
12
|
-
logger: service.logger,
|
|
13
|
-
};
|
|
14
|
-
}
|