├── .modified ├── docs ├── logo.png ├── screenshots │ ├── export.png │ ├── ai-editor.png │ └── main-view.png └── blueprint.md ├── favicon.ico.png ├── .vscode ├── extensions.json └── settings.json ├── src ├── app │ ├── favicon.ico │ ├── page.tsx │ ├── [locale] │ │ ├── page.tsx │ │ └── layout.tsx │ ├── layout.tsx │ ├── api │ │ └── ai-generate │ │ │ └── route.ts │ └── globals.css ├── ai │ ├── dev.ts │ ├── genkit.ts │ └── flows │ │ ├── section-suggester-flow.ts │ │ └── pdf-processor-flow.ts ├── lib │ ├── utils.ts │ ├── types.ts │ └── constants.ts ├── locales │ ├── server.ts │ ├── client.ts │ └── i18n.ts ├── components │ ├── ui │ │ ├── skeleton.tsx │ │ ├── textarea.tsx │ │ ├── label.tsx │ │ ├── input.tsx │ │ ├── progress.tsx │ │ ├── separator.tsx │ │ ├── toaster.tsx │ │ ├── checkbox.tsx │ │ ├── slider.tsx │ │ ├── switch.tsx │ │ ├── badge.tsx │ │ ├── tooltip.tsx │ │ ├── popover.tsx │ │ ├── avatar.tsx │ │ ├── radio-group.tsx │ │ ├── alert.tsx │ │ ├── scroll-area.tsx │ │ ├── tabs.tsx │ │ ├── card.tsx │ │ ├── accordion.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── table.tsx │ │ ├── dialog.tsx │ │ ├── sheet.tsx │ │ ├── form.tsx │ │ ├── alert-dialog.tsx │ │ ├── toast.tsx │ │ ├── select.tsx │ │ ├── dropdown-menu.tsx │ │ ├── menubar.tsx │ │ └── chart.tsx │ ├── SectionsManager.tsx │ ├── TagInput.tsx │ ├── WysiwygEditor.tsx │ ├── SectionItemCard.tsx │ ├── ImageUploader.tsx │ ├── GeneralInfoPanel.tsx │ ├── MarkdownEditor.tsx │ ├── ApiKeyConfigModal.tsx │ └── AboutModal.tsx ├── hooks │ ├── useWriteUp.ts │ ├── use-mobile.tsx │ └── use-toast.ts └── middleware.ts ├── ctf_writeup_builder-main.code-workspace ├── postcss.config.mjs ├── components.json ├── .gitignore ├── tsconfig.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT_ES.md ├── .idx └── dev.nix ├── CONTRIBUTING_ES.md ├── SECURITY.md ├── SECURITY_ES.md ├── package.json ├── tailwind.config.ts ├── next.config.mjs ├── USER_GUIDE.md └── GUIA_USUARIO.md /.modified: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilanami/ctf_writeup_builder/HEAD/docs/logo.png -------------------------------------------------------------------------------- /favicon.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilanami/ctf_writeup_builder/HEAD/favicon.ico.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "github.copilot" 4 | ] 5 | } -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilanami/ctf_writeup_builder/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /ctf_writeup_builder-main.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ] 7 | } -------------------------------------------------------------------------------- /docs/screenshots/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilanami/ctf_writeup_builder/HEAD/docs/screenshots/export.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IDX.aI.enableInlineCompletion": true, 3 | "IDX.aI.enableCodebaseIndexing": true 4 | } -------------------------------------------------------------------------------- /docs/screenshots/ai-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilanami/ctf_writeup_builder/HEAD/docs/screenshots/ai-editor.png -------------------------------------------------------------------------------- /docs/screenshots/main-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilanami/ctf_writeup_builder/HEAD/docs/screenshots/main-view.png -------------------------------------------------------------------------------- /src/ai/dev.ts: -------------------------------------------------------------------------------- 1 | // Flows will be imported for their side effects in this file. 2 | import '@/ai/flows/section-suggester-flow'; 3 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /src/ai/genkit.ts: -------------------------------------------------------------------------------- 1 | import {genkit} from 'genkit'; 2 | import {googleAI} from '@genkit-ai/googleai'; 3 | 4 | export const ai = genkit({ 5 | plugins: [googleAI()], 6 | model: 'googleai/gemini-1.5-pro', 7 | }); 8 | -------------------------------------------------------------------------------- /src/locales/server.ts: -------------------------------------------------------------------------------- 1 | // src/locales/server.ts 2 | import { createI18nServer } from 'next-international/server'; 3 | 4 | export const { getI18n, getScopedI18n, getCurrentLocale } = createI18nServer({ 5 | en: () => import('./en'), 6 | es: () => import('./es'), 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | 2 | // This file is no longer the root page. 3 | // Its content has been moved to src/app/[locale]/page.tsx 4 | // This file can be safely removed. 5 | export default function DeprecatedHomePage() { 6 | return
') // Double line breaks = new paragraphs
33 | .replace(/\n/g, '
') // Single line breaks =
tags
34 | .replace(/^/, '
') // Start with paragraph 35 | .replace(/$/, '
'); // End with paragraph 36 | }; 37 | 38 | const editor = useEditor({ 39 | extensions: [ 40 | StarterKit, 41 | Link.configure({ openOnClick: false }), 42 | Underline, 43 | TextAlign.configure({ types: ['heading', 'paragraph'] }), 44 | Placeholder.configure({ placeholder }), 45 | ], 46 | content: processTemplateContent(value), 47 | onUpdate: ({ editor }) => { 48 | onChange(editor.getHTML()); 49 | }, 50 | editorProps: { 51 | attributes: { 52 | class: 'min-h-[200px] p-2 focus:outline-none prose prose-invert bg-background text-foreground rounded-b-md', 53 | id, 54 | }, 55 | }, 56 | immediatelyRender: false, 57 | }); 58 | 59 | useEffect(() => { 60 | if (editor && value !== editor.getHTML()) { 61 | editor.commands.setContent(processTemplateContent(value) || '', false); 62 | } 63 | // eslint-disable-next-line react-hooks/exhaustive-deps 64 | }, [value]); 65 | 66 | if (!editor) return null; 67 | 68 | return ( 69 |163 | {body} 164 |
165 | ) 166 | }) 167 | FormMessage.displayName = "FormMessage" 168 | 169 | export { 170 | useFormField, 171 | Form, 172 | FormItem, 173 | FormLabel, 174 | FormControl, 175 | FormDescription, 176 | FormMessage, 177 | FormField, 178 | } 179 | -------------------------------------------------------------------------------- /src/components/ui/alert-dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" 5 | 6 | import { cn } from "@/lib/utils" 7 | import { buttonVariants } from "@/components/ui/button" 8 | 9 | const AlertDialog = AlertDialogPrimitive.Root 10 | 11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger 12 | 13 | const AlertDialogPortal = AlertDialogPrimitive.Portal 14 | 15 | const AlertDialogOverlay = React.forwardRef< 16 | React.ElementRef{label || t('imageUploader.defaultLabel')}
137 |{t('imageUploader.dragDropOrClick')}
138 |{t('imageUploader.maxFileSize', { maxSize: MAX_FILE_SIZE_MB })}
139 |{error}
} 150 |{error}
} 187 | {showWordCount && !error && ( 188 |189 | Palabras: {wordCount} | Caracteres: {charCount} 190 |
191 | )} 192 |115 | {provider === 'gemini' ? tai('configureApiKeyDescriptionGemini') : tai('configureApiKeyDescriptionOpenAI')} 116 |
117 |118 | {tai('responsibilityNote')} 119 |
120 | {/* Input de API Key */} 121 |