Upload folder using huggingface_hub
Browse files- client/public/rebuild-nonce.txt +1 -1
- client/src/index.css +68 -0
- client/src/pages/TutorialTasks.tsx +42 -84
client/public/rebuild-nonce.txt
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
Rebuild nonce: 2025-10-25T16:
|
|
|
|
| 1 |
+
Rebuild nonce: 2025-10-25T16:35:45Z
|
client/src/index.css
CHANGED
|
@@ -26,6 +26,74 @@
|
|
| 26 |
-webkit-backface-visibility: hidden;
|
| 27 |
}
|
| 28 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
}
|
| 30 |
|
| 31 |
@layer components {
|
|
|
|
| 26 |
-webkit-backface-visibility: hidden;
|
| 27 |
}
|
| 28 |
}
|
| 29 |
+
|
| 30 |
+
/* Chrome-specific improvements */
|
| 31 |
+
html.is-chrome .safari-stable-form {
|
| 32 |
+
background: #ffffff;
|
| 33 |
+
border: 1px solid #e5e7eb;
|
| 34 |
+
border-radius: 12px;
|
| 35 |
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
html.is-chrome .safari-stable-form select {
|
| 39 |
+
background: #ffffff;
|
| 40 |
+
border: 1px solid #d1d5db;
|
| 41 |
+
border-radius: 8px;
|
| 42 |
+
padding: 8px 12px;
|
| 43 |
+
font-size: 14px;
|
| 44 |
+
font-weight: 500;
|
| 45 |
+
color: #374151;
|
| 46 |
+
transition: all 0.2s ease;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
html.is-chrome .safari-stable-form select:focus {
|
| 50 |
+
outline: none;
|
| 51 |
+
border-color: #6366f1;
|
| 52 |
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
html.is-chrome .safari-stable-form textarea {
|
| 56 |
+
background: #ffffff;
|
| 57 |
+
border: 1px solid #d1d5db;
|
| 58 |
+
border-radius: 8px;
|
| 59 |
+
padding: 12px;
|
| 60 |
+
font-size: 14px;
|
| 61 |
+
font-weight: 400;
|
| 62 |
+
color: #374151;
|
| 63 |
+
transition: all 0.2s ease;
|
| 64 |
+
resize: none;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
html.is-chrome .safari-stable-form textarea:focus {
|
| 68 |
+
outline: none;
|
| 69 |
+
border-color: #6366f1;
|
| 70 |
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
html.is-chrome .safari-stable-form button {
|
| 74 |
+
background: #6366f1;
|
| 75 |
+
color: #ffffff;
|
| 76 |
+
border: none;
|
| 77 |
+
border-radius: 8px;
|
| 78 |
+
padding: 8px 16px;
|
| 79 |
+
font-size: 14px;
|
| 80 |
+
font-weight: 500;
|
| 81 |
+
transition: all 0.2s ease;
|
| 82 |
+
cursor: pointer;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
html.is-chrome .safari-stable-form button:hover {
|
| 86 |
+
background: #5856eb;
|
| 87 |
+
transform: translateY(-1px);
|
| 88 |
+
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
html.is-chrome .safari-stable-form button:disabled {
|
| 92 |
+
background: #9ca3af;
|
| 93 |
+
cursor: not-allowed;
|
| 94 |
+
transform: none;
|
| 95 |
+
box-shadow: none;
|
| 96 |
+
}
|
| 97 |
}
|
| 98 |
|
| 99 |
@layer components {
|
client/src/pages/TutorialTasks.tsx
CHANGED
|
@@ -1937,9 +1937,9 @@ const TutorialTasks: React.FC = () => {
|
|
| 1937 |
</div>
|
| 1938 |
</div>
|
| 1939 |
) : (
|
| 1940 |
-
// Regular task layout
|
| 1941 |
-
<div className="
|
| 1942 |
-
<div className="w-full
|
| 1943 |
{task.imageUrl.startsWith('data:') ? (
|
| 1944 |
<div className="inline-block rounded-lg shadow-md overflow-hidden">
|
| 1945 |
<img src={task.imageUrl} alt={task.imageAlt || 'Uploaded image'} className="w-full h-auto" style={{ height: '200px', width: 'auto', objectFit: 'contain' }} onError={(e) => { console.error('Image failed to load:', e); (e.currentTarget as HTMLImageElement).style.display = 'none'; }} />
|
|
@@ -1952,8 +1952,45 @@ const TutorialTasks: React.FC = () => {
|
|
| 1952 |
</div>
|
| 1953 |
)}
|
| 1954 |
</div>
|
| 1955 |
-
<div className="w-full
|
| 1956 |
-
<div className="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1957 |
</div>
|
| 1958 |
</div>
|
| 1959 |
)
|
|
@@ -2053,85 +2090,6 @@ const TutorialTasks: React.FC = () => {
|
|
| 2053 |
</div>
|
| 2054 |
)}
|
| 2055 |
|
| 2056 |
-
{/* Translation Input (always show if user is logged in, but hide for image-only content) */}
|
| 2057 |
-
{localStorage.getItem('token') && task.content !== 'Image-based task' && task.imageAlignment !== 'portrait-split' && (
|
| 2058 |
-
<div className="bg-white rounded-lg p-6 border border-gray-200 shadow-sm safari-stable-form">
|
| 2059 |
-
<div className="flex items-center space-x-3 mb-4">
|
| 2060 |
-
<div className="bg-gray-100 rounded-lg p-2">
|
| 2061 |
-
<DocumentTextIcon className="h-4 w-4 text-gray-600" />
|
| 2062 |
-
</div>
|
| 2063 |
-
<h4 className="text-gray-900 font-semibold text-lg">Group Translation</h4>
|
| 2064 |
-
</div>
|
| 2065 |
-
|
| 2066 |
-
{/* Group Selection */}
|
| 2067 |
-
<div className="mb-4">
|
| 2068 |
-
<label className="block text-sm font-medium text-gray-700 mb-2">
|
| 2069 |
-
Select Your Group *
|
| 2070 |
-
</label>
|
| 2071 |
-
<select
|
| 2072 |
-
value={selectedGroups[task._id] || ''}
|
| 2073 |
-
onChange={(e) => {
|
| 2074 |
-
const value = parseInt(e.target.value);
|
| 2075 |
-
setSelectedGroups({ ...selectedGroups, [task._id]: value });
|
| 2076 |
-
}}
|
| 2077 |
-
className="w-48 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white text-sm"
|
| 2078 |
-
required
|
| 2079 |
-
>
|
| 2080 |
-
<option value="">Choose your group...</option>
|
| 2081 |
-
{[1, 2, 3, 4, 5, 6, 7, 8].map((group) => (
|
| 2082 |
-
<option key={group} value={group}>
|
| 2083 |
-
Group {group}
|
| 2084 |
-
</option>
|
| 2085 |
-
))}
|
| 2086 |
-
</select>
|
| 2087 |
-
</div>
|
| 2088 |
-
|
| 2089 |
-
<div className="mb-4">
|
| 2090 |
-
<label className="block text-sm font-medium text-gray-700 mb-2">
|
| 2091 |
-
Your Group's Translation *
|
| 2092 |
-
</label>
|
| 2093 |
-
<div className="flex items-center justify-end space-x-2 mb-2">
|
| 2094 |
-
<button onClick={() => applyInlineFormat(`tutorial-translation-${task._id}`, translationText[task._id] || '', v => setTranslationText({ ...translationText, [task._id]: v }), '**')} className="px-2 py-1 text-xs bg-indigo-100 text-indigo-900 rounded">B</button>
|
| 2095 |
-
<button onClick={() => applyInlineFormat(`tutorial-translation-${task._id}`, translationText[task._id] || '', v => setTranslationText({ ...translationText, [task._id]: v }), '*')} className="px-2 py-1 text-xs bg-indigo-100 text-indigo-900 rounded italic">I</button>
|
| 2096 |
-
<button onClick={() => applyLinkFormat(`tutorial-translation-${task._id}`, translationText[task._id] || '', v => setTranslationText({ ...translationText, [task._id]: v }))} className="px-2 py-1 text-xs bg-indigo-100 text-indigo-900 rounded">Link</button>
|
| 2097 |
-
</div>
|
| 2098 |
-
<textarea
|
| 2099 |
-
id={`tutorial-translation-${task._id}`}
|
| 2100 |
-
value={translationText[task._id] || ''}
|
| 2101 |
-
onInput={(e) => {
|
| 2102 |
-
const value = (e.target as HTMLTextAreaElement).value;
|
| 2103 |
-
setTranslationText({ ...translationText, [task._id]: value });
|
| 2104 |
-
}}
|
| 2105 |
-
onChange={(e) => {
|
| 2106 |
-
const value = e.target.value;
|
| 2107 |
-
setTranslationText({ ...translationText, [task._id]: value });
|
| 2108 |
-
}}
|
| 2109 |
-
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white"
|
| 2110 |
-
style={{ height: '150px' }}
|
| 2111 |
-
rows={4}
|
| 2112 |
-
placeholder="Enter your group's translation here..."
|
| 2113 |
-
/>
|
| 2114 |
-
</div>
|
| 2115 |
-
|
| 2116 |
-
<button
|
| 2117 |
-
onClick={() => { (withPreservedScroll.current || ((fn)=>fn()))(() => {}); handleSubmitTranslation(task._id); }}
|
| 2118 |
-
disabled={submitting[task._id]}
|
| 2119 |
-
className="relative inline-flex items-center justify-center gap-2 px-4 py-2 rounded-2xl text-sm font-medium text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 shadow-[inset_0_1px_0_rgba(255,255,255,0.6),inset_0_-1px_0_rgba(0,0,0,0.12)] bg-sky-600/70 disabled:bg-gray-400"
|
| 2120 |
-
>
|
| 2121 |
-
{submitting[task._id] ? (
|
| 2122 |
-
<>
|
| 2123 |
-
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
| 2124 |
-
Submitting...
|
| 2125 |
-
</>
|
| 2126 |
-
) : (
|
| 2127 |
-
<>
|
| 2128 |
-
Submit Group Translation
|
| 2129 |
-
<ArrowRightIcon className="h-4 w-4 ml-2" />
|
| 2130 |
-
</>
|
| 2131 |
-
)}
|
| 2132 |
-
</button>
|
| 2133 |
-
</div>
|
| 2134 |
-
)}
|
| 2135 |
|
| 2136 |
{/* Show login message for visitors */}
|
| 2137 |
{!localStorage.getItem('token') && (
|
|
|
|
| 1937 |
</div>
|
| 1938 |
</div>
|
| 1939 |
) : (
|
| 1940 |
+
// Regular task layout - use grid for stability
|
| 1941 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 items-start">
|
| 1942 |
+
<div className="w-full flex justify-center">
|
| 1943 |
{task.imageUrl.startsWith('data:') ? (
|
| 1944 |
<div className="inline-block rounded-lg shadow-md overflow-hidden">
|
| 1945 |
<img src={task.imageUrl} alt={task.imageAlt || 'Uploaded image'} className="w-full h-auto" style={{ height: '200px', width: 'auto', objectFit: 'contain' }} onError={(e) => { console.error('Image failed to load:', e); (e.currentTarget as HTMLImageElement).style.display = 'none'; }} />
|
|
|
|
| 1952 |
</div>
|
| 1953 |
)}
|
| 1954 |
</div>
|
| 1955 |
+
<div className="w-full">
|
| 1956 |
+
<div className="bg-indigo-50 rounded-lg p-4 mb-4 border border-indigo-200">
|
| 1957 |
+
<h5 className="text-indigo-900 font-semibold mb-2">Source Text</h5>
|
| 1958 |
+
<div className="text-blue-800 leading-relaxed text-lg font-source-text whitespace-pre-wrap">{renderFormatted(task.content)}</div>
|
| 1959 |
+
</div>
|
| 1960 |
+
{localStorage.getItem('token') && (
|
| 1961 |
+
<div className="bg-white rounded-lg p-4 border border-gray-200">
|
| 1962 |
+
<h5 className="text-gray-900 font-semibold mb-2">Your Group's Translation</h5>
|
| 1963 |
+
<div className="mb-2">
|
| 1964 |
+
<label className="block text-xs font-medium text-gray-700 mb-1">Select Your Group</label>
|
| 1965 |
+
<select
|
| 1966 |
+
value={selectedGroups[task._id] || ''}
|
| 1967 |
+
onChange={(e) => setSelectedGroups({ ...selectedGroups, [task._id]: parseInt(e.target.value) })}
|
| 1968 |
+
className="w-40 px-2 py-1 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white text-xs"
|
| 1969 |
+
>
|
| 1970 |
+
<option value="">Choose...</option>
|
| 1971 |
+
{[1,2,3,4,5,6,7,8].map((g) => (<option key={g} value={g}>Group {g}</option>))}
|
| 1972 |
+
</select>
|
| 1973 |
+
</div>
|
| 1974 |
+
<div className="flex items-center justify-end space-x-2 mb-2">
|
| 1975 |
+
<button onClick={() => applyInlineFormat(`tutorial-translation-${task._id}`, translationText[task._id] || '', v => setTranslationText({ ...translationText, [task._id]: v }), '**')} className="px-2 py-1 text-xs bg-indigo-100 text-indigo-900 rounded">B</button>
|
| 1976 |
+
<button onClick={() => applyInlineFormat(`tutorial-translation-${task._id}`, translationText[task._id] || '', v => setTranslationText({ ...translationText, [task._id]: v }), '*')} className="px-2 py-1 text-xs bg-indigo-100 text-indigo-900 rounded italic">I</button>
|
| 1977 |
+
<button onClick={() => applyLinkFormat(`tutorial-translation-${task._id}`, translationText[task._id] || '', v => setTranslationText({ ...translationText, [task._id]: v }))} className="px-2 py-1 text-xs bg-indigo-100 text-indigo-900 rounded">Link</button>
|
| 1978 |
+
</div>
|
| 1979 |
+
<textarea
|
| 1980 |
+
id={`tutorial-translation-${task._id}`}
|
| 1981 |
+
value={translationText[task._id] || ''}
|
| 1982 |
+
onInput={(e) => setTranslationText({ ...translationText, [task._id]: (e.target as HTMLTextAreaElement).value })}
|
| 1983 |
+
onChange={(e) => setTranslationText({ ...translationText, [task._id]: e.target.value })}
|
| 1984 |
+
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white"
|
| 1985 |
+
style={{ height: '150px' }}
|
| 1986 |
+
rows={4}
|
| 1987 |
+
placeholder="Enter your group's translation here..."
|
| 1988 |
+
/>
|
| 1989 |
+
<div className="flex justify-end mt-2">
|
| 1990 |
+
<button onClick={() => handleSubmitTranslation(task._id)} disabled={submitting[task._id]} className="btn-primary disabled:bg-gray-400 text-white px-4 py-2 rounded-lg text-sm">{submitting[task._id] ? 'Submitting...' : 'Submit Translation'}</button>
|
| 1991 |
+
</div>
|
| 1992 |
+
</div>
|
| 1993 |
+
)}
|
| 1994 |
</div>
|
| 1995 |
</div>
|
| 1996 |
)
|
|
|
|
| 2090 |
</div>
|
| 2091 |
)}
|
| 2092 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2093 |
|
| 2094 |
{/* Show login message for visitors */}
|
| 2095 |
{!localStorage.getItem('token') && (
|