mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'release-3.4.1' into fix-rhel8-vscode-nsfw
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,6 +14,7 @@ node_modules
|
||||
\#*\#
|
||||
.\#*
|
||||
.idea
|
||||
!.idea/icon.svg
|
||||
*.iml
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
21
.idea/icon.svg
generated
Executable file
21
.idea/icon.svg
generated
Executable file
@@ -0,0 +1,21 @@
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" id="svg2" preserveAspectRatio="xMidYMid meet" version="1.1" viewBox="0 0 160.10664 156.98515" height="156.98515" width="160.10664">
|
||||
<metadata id="metadata26">
|
||||
<rdf:rdf xmlns="http://www.w3.org/1999/xhtml">
|
||||
<cc:work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"></dc:type>
|
||||
<dc:title></dc:title>
|
||||
</cc:work>
|
||||
</rdf:rdf>
|
||||
</metadata>
|
||||
<defs id="defs24"/>
|
||||
<g transform="matrix(0.62649123,0,0,0.62649123,-0.27477954,-0.27455194)" id="g6" style="fill:#de4f4f">
|
||||
<path style="fill:#de4f4f; fill-opacity:1; stroke:none" d="M 0.43860078,0.43823749 219.30039,232.26587 c 0,0 7.45622,5.25885 13.15803,-0.87647 5.70181,-6.13533 1.3158,-12.27065 1.3158,-12.27065 L 0.43860078,0.43823749 l 0,0 z" id="path8"/>
|
||||
<path style="fill:#de4f4f; fill-opacity:1; stroke:none" d="M 69.737525,22.350112 236.40582,202.02749 c 0,0 7.45622,5.25884 13.15803,-0.87648 5.70181,-6.13533 1.3158,-12.27065 1.3158,-12.27065 L 69.737525,22.350112 l 0,0 z" id="path10"/>
|
||||
<path style="fill:#de4f4f; fill-opacity:1; stroke:none" d="M 21.052838,69.241524 187.72114,248.9189 c 0,0 7.45621,5.25885 13.15802,-0.87648 5.70181,-6.13532 1.3158,-12.27065 1.3158,-12.27065 L 21.052838,69.241524 l 0,0 z" id="path12"/>
|
||||
<path style="fill:#de4f4f; fill-opacity:1; stroke:none" d="M 128.32077,41.194324 244.76195,166.72418 c 0,0 5.20922,3.67404 9.19273,-0.61234 3.98351,-4.28639 0.91927,-8.57278 0.91927,-8.57278 L 128.32077,41.194324 l 0,0 z" id="path14"/>
|
||||
<path style="fill:#de4f4f; fill-opacity:1; stroke:none" d="M 37.091803,123.58297 153.53299,249.11282 c 0,0 5.20921,3.67405 9.19273,-0.61234 3.98351,-4.28638 0.91927,-8.57277 0.91927,-8.57277 L 37.091803,123.58297 l 0,0 z" id="path16"/>
|
||||
<path style="fill:#de4f4f; fill-opacity:1; stroke:none" d="m 188.15974,68.365049 52.77506,57.067161 c 0,0 2.57683,1.72156 4.54735,-0.28693 1.97051,-2.00849 0.45473,-4.01699 0.45473,-4.01699 l -57.77714,-52.763241 0,0 z" id="path18"/>
|
||||
<path style="fill:#de4f4f; fill-opacity:1; stroke:none" d="m 66.228719,181.43032 52.775071,57.06716 c 0,0 2.57682,1.72156 4.54734,-0.28693 1.97051,-2.00849 0.45473,-4.01698 0.45473,-4.01698 l -57.777141,-52.76325 0,0 z" id="path20"/>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -507,6 +507,7 @@ Meteor.methods({forgotPassword: async options => {
|
||||
const user = await Accounts.findUserByEmail(options.email, { fields: { emails: 1 } });
|
||||
|
||||
if (!user) {
|
||||
if (Accounts._options.ambiguousErrorMessages) return;
|
||||
Accounts._handleError("User not found");
|
||||
}
|
||||
|
||||
|
||||
@@ -1417,9 +1417,8 @@ if (Meteor.isServer) (() => {
|
||||
);
|
||||
|
||||
Accounts._options.ambiguousErrorMessages = true;
|
||||
await test.throwsAsync(
|
||||
async () => await Meteor.callAsync('forgotPassword', wrongOptions),
|
||||
'Something went wrong. Please check your credentials'
|
||||
await test.doesNotThrowsAsync(
|
||||
async () => await Meteor.callAsync("forgotPassword", wrongOptions)
|
||||
);
|
||||
|
||||
Accounts._options.ambiguousErrorMessages = false;
|
||||
|
||||
@@ -272,7 +272,13 @@ EXPERIMENTAL way to compare two strings that results in a nicer display in the t
|
||||
|
||||
### Assertions without optional fail messages
|
||||
|
||||
`test.throws(func, expected);`
|
||||
`test.throws(func, expected[, message]);`
|
||||
|
||||
`test.throwsAsync(func, expected[, message]);`
|
||||
|
||||
`test.doesNotThrows(func[, failureMessage]);`
|
||||
|
||||
`test.doesNotThrowsAsync(func[, failureMessage]);`
|
||||
|
||||
`expected` can be:
|
||||
|
||||
@@ -281,6 +287,8 @@ EXPERIMENTAL way to compare two strings that results in a nicer display in the t
|
||||
- `regexp`: pass if the exception message passes the regexp.
|
||||
- `function`: call the function as a predicate with the exception.
|
||||
|
||||
`doesNotThrows` and `doesNotThrowsAsync` assert that the function does not throw. If the function throws, the assertion fails. The optional `failureMessage` is only used to annotate the failure.
|
||||
|
||||
Note: Node's `assert.throws` also accepts a constructor to test whether the error is of the expected class. But since JavaScript can't distinguish between constructors and plain functions and Node's `assert.throws` also accepts a predicate function, if the error fails the `instanceof` test with the constructor then the constructor is then treated as a predicate and called (!)
|
||||
|
||||
The upshot is, if you want to test whether an error is of a particular class, use a predicate function.
|
||||
|
||||
@@ -244,6 +244,19 @@ export class TestCaseResults {
|
||||
// The upshot is, if you want to test whether an error is of a
|
||||
// particular class, use a predicate function.
|
||||
//
|
||||
/**
|
||||
* Assert that `f` throws.
|
||||
*
|
||||
* `expected` can be:
|
||||
* - undefined: accept any exception.
|
||||
* - string: pass if the string is a substring of the exception message.
|
||||
* - regexp: pass if the exception message passes the regexp.
|
||||
* - function: call the function as a predicate with the exception.
|
||||
*
|
||||
* @param {Function} f
|
||||
* @param {*} expected
|
||||
* @param {String} message
|
||||
*/
|
||||
throws(f, expected, message) {
|
||||
let actual;
|
||||
const predicate = this._guessPredicate(expected);
|
||||
@@ -258,10 +271,34 @@ export class TestCaseResults {
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as throw, but accepts an async function as a parameter.
|
||||
* @param f
|
||||
* @param expected
|
||||
* @param message
|
||||
* Assert that `f` does not throw.
|
||||
* @param {Function} f
|
||||
* @param {String} failureMessage
|
||||
*/
|
||||
doesNotThrows(f, failureMessage) {
|
||||
let actual;
|
||||
|
||||
try {
|
||||
f();
|
||||
} catch (exception) {
|
||||
actual = exception;
|
||||
}
|
||||
|
||||
if (!actual) {
|
||||
this.ok();
|
||||
} else {
|
||||
this.fail({
|
||||
type: "throws",
|
||||
message: ("threw an error unexpectedly: " + actual.message) + (failureMessage ? ": " + failureMessage : ""),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `throws`, but accepts an async function as a parameter.
|
||||
* @param {Function} f
|
||||
* @param {*} expected
|
||||
* @param {String} message
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async throwsAsync(f, expected, message) {
|
||||
@@ -276,6 +313,31 @@ export class TestCaseResults {
|
||||
this._assertActual(actual, predicate, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `doesNotThrows`, but accepts an async function as a parameter.
|
||||
* @param {Function} f
|
||||
* @param {String} failureMessage
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async doesNotThrowsAsync(f, failureMessage) {
|
||||
let actual;
|
||||
|
||||
try {
|
||||
await f();
|
||||
} catch (exception) {
|
||||
actual = exception;
|
||||
}
|
||||
|
||||
if (!actual) {
|
||||
this.ok();
|
||||
} else {
|
||||
this.fail({
|
||||
type: "throws",
|
||||
message: ("threw an error unexpectedly: " + actual.message) + (failureMessage ? ": " + failureMessage : ""),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
isTrue(v, msg) {
|
||||
if (v)
|
||||
this.ok();
|
||||
|
||||
@@ -312,9 +312,13 @@ async function ensureWatchRoot(dirPath: string): Promise<void> {
|
||||
if (/Events were dropped/.test(err.message)) {
|
||||
return;
|
||||
}
|
||||
if (/RootResolveError/.test(err.message) || /failed to resolve root/.test(err.message)) {
|
||||
console.warn(`Parcel watcher root resolve error on ${osDirPath}, ignoring: ${err.message}`);
|
||||
ignoredWatchRoots.add(dirPath);
|
||||
watchRoots.delete(dirPath);
|
||||
return;
|
||||
}
|
||||
console.error(`Parcel watcher error on ${osDirPath}:`, err);
|
||||
// Only disable native watching for critical errors (like ENOSPC).
|
||||
// @ts-ignore
|
||||
if (err.code === "ENOSPC" || err.errno === require("constants").ENOSPC) {
|
||||
fallbackToPolling();
|
||||
}
|
||||
@@ -340,9 +344,11 @@ async function ensureWatchRoot(dirPath: string): Promise<void> {
|
||||
(e.code === "ENOTDIR" ||
|
||||
/Not a directory/.test(e.message) ||
|
||||
e.code === "EBADF" ||
|
||||
/Bad file descriptor/.test(e.message))
|
||||
/Bad file descriptor/.test(e.message) ||
|
||||
/RootResolveError/.test(e.message) ||
|
||||
/failed to resolve root/.test(e.message))
|
||||
) {
|
||||
console.warn(`Skipping watcher for ${osDirPath}: not a directory`);
|
||||
console.warn(`Skipping watcher for ${osDirPath}: ${e.message || 'not watchable'}`);
|
||||
ignoredWatchRoots.add(dirPath);
|
||||
} else {
|
||||
console.error(`Failed to start watcher for ${osDirPath}:`, e);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { App } from '/imports/ui/App';
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import { App } from "/imports/ui/App";
|
||||
import "/imports/ui/styles.css";
|
||||
|
||||
Meteor.startup(() => {
|
||||
const container = document.getElementById('react-target');
|
||||
const container = document.getElementById("react-target");
|
||||
const root = createRoot(container);
|
||||
root.render(<App />);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { Hello } from './Hello.jsx';
|
||||
import { Info } from './Info.jsx';
|
||||
import { Counter } from "./Counter.jsx";
|
||||
import { Header } from "./Header.jsx";
|
||||
import { Info } from "./Info.jsx";
|
||||
|
||||
export const App = () => (
|
||||
<div>
|
||||
<h1>Welcome to Meteor!</h1>
|
||||
<Hello/>
|
||||
<Info/>
|
||||
<div className="page">
|
||||
<Header />
|
||||
<main className="main">
|
||||
<Counter />
|
||||
<Info />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
24
tools/static-assets/skel-react/imports/ui/Counter.jsx
Normal file
24
tools/static-assets/skel-react/imports/ui/Counter.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useState } from "react";
|
||||
|
||||
export const Counter = () => {
|
||||
const [counter, setCounter] = useState(0);
|
||||
|
||||
const increment = () => {
|
||||
setCounter(counter + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card counter-card">
|
||||
<div className="counter-content">
|
||||
<button className="button" onClick={increment}>
|
||||
Click Me
|
||||
</button>
|
||||
<p className="counter-text">
|
||||
You've pressed the button{" "}
|
||||
<span className="counter-value">{counter}</span>{" "}
|
||||
{counter === 1 ? "time" : "times"}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
14
tools/static-assets/skel-react/imports/ui/Header.jsx
Normal file
14
tools/static-assets/skel-react/imports/ui/Header.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import MeteorLogo from "./meteor-logo.svg";
|
||||
|
||||
export const Header = () => {
|
||||
return (
|
||||
<div className="header">
|
||||
<nav className="nav container">
|
||||
<div className="logo-container">
|
||||
<MeteorLogo className="logo" />
|
||||
</div>
|
||||
<h1 className="page-title">Welcome to Meteor!</h1>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
export const Hello = () => {
|
||||
const [counter, setCounter] = useState(0);
|
||||
|
||||
const increment = () => {
|
||||
setCounter(counter + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={increment}>Click Me</button>
|
||||
<p>You've pressed the button {counter} times.</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,22 +1,30 @@
|
||||
import { useFind, useSubscribe } from 'meteor/react-meteor-data';
|
||||
import { LinksCollection } from '../api/links';
|
||||
import { useFind, useSubscribe } from "meteor/react-meteor-data";
|
||||
import { LinksCollection } from "../api/links";
|
||||
|
||||
export const Info = () => {
|
||||
const isLoading = useSubscribe('links');
|
||||
const isLoading = useSubscribe("links");
|
||||
const links = useFind(() => LinksCollection.find());
|
||||
|
||||
if(isLoading()) {
|
||||
if (isLoading()) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Learn Meteor!</h2>
|
||||
<ul>{links.map(
|
||||
link => <li key={link._id}>
|
||||
<a href={link.url} target="_blank">{link.title}</a>
|
||||
</li>
|
||||
)}</ul>
|
||||
</div>
|
||||
<section>
|
||||
<h2 className="section-title">Learn Meteor!</h2>
|
||||
<ul className="resources-grid">
|
||||
{links.map((link) => (
|
||||
<li className="section" key={link._id}>
|
||||
<a href={link.url} className="resource-link" target="_blank">
|
||||
<div className="card resource-card">
|
||||
<div className="resource-content">
|
||||
<span className="resource-title">{link.title}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
18
tools/static-assets/skel-react/imports/ui/meteor-logo.svg
Normal file
18
tools/static-assets/skel-react/imports/ui/meteor-logo.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg id="c1" data-name="c 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 800 800">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: url(#g3);
|
||||
}
|
||||
</style>
|
||||
<linearGradient id="g3" data-name="g3" x1="149.34" y1="168.23" x2="650.29" y2="637.76" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#cb180a"/>
|
||||
<stop offset=".37" stop-color="#cd1909"/>
|
||||
<stop offset=".6" stop-color="#d61d07"/>
|
||||
<stop offset=".79" stop-color="#e52404"/>
|
||||
<stop offset=".97" stop-color="#f92d00"/>
|
||||
<stop offset="1" stop-color="#ff3000"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path class="cls-1" d="m648.34,479.05c-5.33,32.62-17.15,61.33-35.44,86.12-18.31,24.8-41.19,44.1-68.64,57.91-27.46,13.81-57.66,20.72-90.61,20.72-28.29,0-54.17-4.74-77.63-14.23-22.26-9-41.18-21.63-56.84-37.82,10.14,7,21.23,12.96,33.3,17.84,23.46,9.49,49.34,14.23,77.63,14.23,32.95,0,63.15-6.9,90.61-20.72,27.46-13.81,50.34-33.11,68.64-57.91,18.3-24.79,30.12-53.5,35.44-86.12,4.99-28.96,4.07-55.49-2.75-79.63-6.83-24.13-18.31-44.93-34.45-62.4-.87-.95-1.8-1.83-2.7-2.76,9.59,6.63,18.35,14.2,26.24,22.73,16.14,17.47,27.62,38.28,34.45,62.4,6.82,24.13,7.74,50.67,2.75,79.63Zm-100,15.88c.21.22.47.48.69.69,6.51,6.13,16.75,5.82,22.87-.69,6.12-6.51,5.81-16.75-.69-22.87l-273.47-250.6,250.6,273.48m-18.63,48.73c.21.22.47.48.69.69,6.51,6.13,16.75,5.82,22.87-.69,6.12-6.51,5.81-16.75-.69-22.87L164.5,155.58l365.2,388.08m-47.3,18.63c.21.22.47.48.69.69,6.51,6.13,16.75,5.82,22.87-.69,6.12-6.51,5.81-16.75-.69-22.87l-273.47-250.6,250.6,273.48m-91.19,6.46c.1.11.21.22.32.32,2.19,2.01,5.6,1.87,7.62-.32,2.02-2.19,1.87-5.6-.32-7.62l-94.68-87.06,87.06,94.68m178.94-179.78c.1.1.21.22.32.32,2.19,2.01,5.61,1.87,7.62-.32,2.02-2.19,1.87-5.6-.32-7.62l-94.68-87.06,87.06,94.69m-323.6-16.23c4.87,5.85,184.93,198.16,188.58,201.81,3.65,3.65,9.58,3.65,13.23,0,3.65-3.65,3.65-9.58,0-13.23-3.65-3.65-196.06-183.61-201.81-188.58Zm141.5-128.6c4.87,5.85,178.77,192,182.43,195.65,3.65,3.65,9.58,3.65,13.23,0,3.65-3.65,3.65-9.58,0-13.23-3.65-3.65-189.91-177.46-195.65-182.43Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
305
tools/static-assets/skel-react/imports/ui/styles.css
Normal file
305
tools/static-assets/skel-react/imports/ui/styles.css
Normal file
@@ -0,0 +1,305 @@
|
||||
/* this file is imported in client/main.jsx */
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap");
|
||||
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-background: hsl(210, 20%, 98%);
|
||||
--color-foreground: hsl(220, 20%, 15%);
|
||||
--color-card: hsl(0, 0%, 100%);
|
||||
--color-primary: hsl(4, 70%, 55%);
|
||||
--color-primary-hover: hsl(4, 70%, 45%);
|
||||
--color-muted: hsl(220, 10%, 50%);
|
||||
--color-border: hsl(220, 14%, 90%);
|
||||
|
||||
/* Shadows */
|
||||
--shadow-card: 0 1px 3px 0 hsl(220 20% 15% / 0.04),
|
||||
0 1px 2px -1px hsl(220 20% 15% / 0.04);
|
||||
--shadow-card-hover: 0 10px 15px -3px hsl(220 20% 15% / 0.08),
|
||||
0 4px 6px -4px hsl(220 20% 15% / 0.04);
|
||||
|
||||
/* Spacing */
|
||||
--spacing-xs: 0.25rem;
|
||||
--spacing-sm: 0.5rem;
|
||||
--spacing-md: 1rem;
|
||||
--spacing-lg: 1.5rem;
|
||||
--spacing-xl: 2rem;
|
||||
--spacing-2xl: 3rem;
|
||||
|
||||
/* Border radius */
|
||||
--radius: 0.75rem;
|
||||
--radius-sm: 0.5rem;
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms ease;
|
||||
--transition-normal: 200ms ease;
|
||||
--transition-slow: 250ms ease;
|
||||
}
|
||||
|
||||
/* ============ Reset & Base Styles ============ */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
sans-serif;
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-foreground);
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* ============ Layout ============ */
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding-left: var(--spacing-md);
|
||||
padding-right: var(--spacing-md);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
padding-left: var(--spacing-lg);
|
||||
padding-right: var(--spacing-lg);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============ Header / Navigation ============ */
|
||||
.header {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background-color: var(--color-card);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-weight: 600;
|
||||
color: var(--color-foreground);
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.logo-text {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-foreground);
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.page-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-primary);
|
||||
transition: opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* ============ Main Content ============ */
|
||||
.main {
|
||||
padding-top: var(--spacing-xl);
|
||||
padding-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.main {
|
||||
padding-top: var(--spacing-2xl);
|
||||
padding-bottom: var(--spacing-2xl);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============ Card Component ============ */
|
||||
.card {
|
||||
background-color: var(--color-card);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-card);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* ============ Counter Section ============ */
|
||||
.counter-card {
|
||||
padding: var(--spacing-lg);
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.counter-card {
|
||||
padding: var(--spacing-xl);
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
}
|
||||
|
||||
.counter-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.counter-content {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.counter-text {
|
||||
color: var(--color-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.counter-text {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.counter-value {
|
||||
font-weight: 600;
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
/* ============ Button Component ============ */
|
||||
.button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 120px;
|
||||
padding: 0.625rem 1.5rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
color: white;
|
||||
background-color: var(--color-primary);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition-normal),
|
||||
transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.button:focus-visible {
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ============ Resources Section ============ */
|
||||
.section-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-foreground);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.resources-grid {
|
||||
list-style-type: none;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.resources-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============ Resource Card ============ */
|
||||
.resource-link {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.resource-card {
|
||||
padding: 1.25rem;
|
||||
transition: box-shadow var(--transition-slow),
|
||||
transform var(--transition-slow);
|
||||
}
|
||||
|
||||
.resource-card:hover {
|
||||
box-shadow: var(--shadow-card-hover);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.resource-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.resource-title {
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground);
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.resource-link:hover .resource-title {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.resource-icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
color: var(--color-muted);
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition-fast), color var(--transition-fast);
|
||||
}
|
||||
|
||||
.resource-link:hover .resource-icon {
|
||||
opacity: 1;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
"@rspack/cli": "^1.6.5",
|
||||
"@rspack/core": "^1.6.5",
|
||||
"@rspack/plugin-react-refresh": "^1.4.3",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"react-refresh": "^0.17.0"
|
||||
},
|
||||
"meteor": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { defineConfig } = require('@meteorjs/rspack');
|
||||
const { defineConfig } = require("@meteorjs/rspack");
|
||||
|
||||
/**
|
||||
* Rspack configuration for Meteor projects.
|
||||
@@ -10,6 +10,17 @@ const { defineConfig } = require('@meteorjs/rspack');
|
||||
*
|
||||
* Use these flags to adjust your build settings based on environment.
|
||||
*/
|
||||
module.exports = defineConfig(Meteor => {
|
||||
return {};
|
||||
module.exports = defineConfig((Meteor) => {
|
||||
return {
|
||||
module: {
|
||||
rules: [
|
||||
// Add support for importing SVGs as React components
|
||||
{
|
||||
test: /\.svg$/i,
|
||||
issuer: /\.[jt]sx?$/,
|
||||
use: ["@svgr/webpack"],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { LinksCollection } from '/imports/api/links';
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import { LinksCollection } from "/imports/api/links";
|
||||
import { Random } from "meteor/random";
|
||||
|
||||
async function insertLink({ title, url }) {
|
||||
await LinksCollection.insertAsync({ title, url, createdAt: new Date() });
|
||||
@@ -7,25 +8,35 @@ async function insertLink({ title, url }) {
|
||||
|
||||
Meteor.startup(async () => {
|
||||
// If the Links collection is empty, add some data.
|
||||
if (await LinksCollection.find().countAsync() === 0) {
|
||||
if ((await LinksCollection.find().countAsync()) === 0) {
|
||||
await insertLink({
|
||||
title: 'Do the Tutorial',
|
||||
url: 'https://react-tutorial.meteor.com/simple-todos/01-creating-app.html',
|
||||
title: "Do the Tutorial",
|
||||
url: "https://docs.meteor.com/tutorials/react/",
|
||||
});
|
||||
|
||||
await insertLink({
|
||||
title: 'Follow the Guide',
|
||||
url: 'https://guide.meteor.com',
|
||||
title: "Follow the Guide",
|
||||
url: "https://docs.meteor.com/tutorials/application-structure/",
|
||||
});
|
||||
|
||||
await insertLink({
|
||||
title: 'Read the Docs',
|
||||
url: 'https://docs.meteor.com',
|
||||
title: "Read the Docs",
|
||||
url: "https://docs.meteor.com",
|
||||
});
|
||||
|
||||
await insertLink({
|
||||
title: 'Discussions',
|
||||
url: 'https://forums.meteor.com',
|
||||
title: "Discussions",
|
||||
url: "https://forums.meteor.com",
|
||||
});
|
||||
|
||||
await insertLink({
|
||||
title: "Join us on Discord",
|
||||
url: "https://discord.gg/6mS3wHNg",
|
||||
});
|
||||
|
||||
await insertLink({
|
||||
title: "Deploying in Galaxy",
|
||||
url: "https://www.meteor.com/hosting",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,3 +46,9 @@ Meteor.startup(async () => {
|
||||
return LinksCollection.find();
|
||||
});
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
about() {
|
||||
return `This is a Meteor application running React with React Router. this is a generated id: ${Random.id()}`;
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user