├── .gitignore ├── Dockerfile ├── README.md ├── __pycache__ └── app.cpython-313.pyc ├── app.py ├── docker-compose.yml ├── nginx ├── Dockerfile └── nginx.conf ├── requirements.txt ├── static └── favicon.png ├── templates ├── editor.html └── landing.html └── tests ├── __pycache__ └── test_app.cpython-313-pytest-8.3.5.pyc └── test_app.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official lightweight Python image 2 | FROM python:3.12-slim 3 | 4 | # Set environment variables to keep container clean and responsive 5 | ENV PYTHONDONTWRITEBYTECODE=1 6 | ENV PYTHONUNBUFFERED=1 7 | 8 | # Set working directory 9 | WORKDIR /app 10 | 11 | # Copy requirements first to leverage Docker layer caching 12 | COPY requirements.txt . 13 | 14 | # Install dependencies 15 | RUN pip install --upgrade pip && pip install -r requirements.txt 16 | 17 | # Copy the rest of the application 18 | COPY . . 19 | 20 | # Expose the port Flask runs on 21 | EXPOSE 5000 22 | 23 | # Start the app with eventlet 24 | CMD ["python", "app.py"] 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Collaborative Flask Editor 2 | 3 | This is a collaborative text editor built with Flask and Socket.IO that allows multiple users to edit a document in real-time. Users can join a room via a unique URL, and all changes are synced across all participants in real time. 4 | 5 | ## Features 6 | 7 | - **Real-time Collaboration:** Users can edit the document simultaneously, and the changes are reflected in real-time for all participants. 8 | - **Room-based Editing:** Each room has a unique ID, and users join by visiting a URL containing that ID. 9 | - **Dark/Light Mode:** Users can toggle between dark and light themes for a personalized experience. 10 | - **Shareable Link:** Users can share the room link with others to collaborate. 11 | 12 | ## 🛠️ Tech Stack 13 | 14 | Backend: Flask, Flask-SocketIO 15 | 16 | Frontend: HTML, CSS, JavaScript, Socket.IO (Client) 17 | 18 | Realtime Communication: WebSockets 19 | 20 | ## Installation 21 | 22 | Follow these steps to get the project up and running: 23 | 24 | ### 1. Clone the repository 25 | 26 | ```bash 27 | git clone https://github.com/your-username/collaborative-flask-editor.git 28 | 29 | cd collaborative-flask-editor 30 | ``` 31 | ### Create a Virtual Environment 32 | 33 | ```bash 34 | 35 | python -m venv venv 36 | 37 | venv\Scripts\activate 38 | ``` 39 | ### Install Dependencies 40 | 41 | ```bash 42 | pip install flask flask-socketio eventlet 43 | ``` 44 | ### Run the App 45 | 46 | ```bash 47 | python app.py 48 | 49 | ``` 50 | ### open in your browser 51 | 52 | ```bash 53 | http://localhost:5000/ 54 | 55 | ``` 56 | ### 🗂️ Project Structure 57 | 58 | ```bash 59 | 60 | collaborative-flask-editor/ 61 | │ 62 | ├── Dockerfile # Dockerfile to containerize the app 63 | ├── templates/ 64 | │ ├── editor.html # Collaborative notepad editor UI 65 | │ └── landing.html # Landing page with "Start Collaborating" button 66 | │ 67 | ├── static/ 68 | │ └── favicon.png # Favicon image 69 | │ 70 | ├── app.py # Main Flask server with Socket.IO logic and routing 71 | ├── requirements.txt # Python dependencies 72 | ├── README.md # Project documentation 73 | └── .gitignore # Ignore virtualenv, __pycache__, etc. 74 | 75 | ``` 76 | ### 🐳 Run with Docker 77 | #### If you prefer Docker, the image is available on Docker Hub. 78 | 79 | #### 🧱 Pull the Image 80 | 81 | ```bash 82 | docker pull shinjan7/flask-collaborative-notepad:latest 83 | 84 | ``` 85 | ### ▶️ Run the Container 86 | 87 | ```bash 88 | docker run -d -p 5000:5000 --name collab-notepad shinjan7/flask-collaborative-notepad:latest 89 | 90 | ``` 91 | ### Then visit: 92 | 93 | ```bash 94 | http://localhost:5000/ 95 | 96 | ``` 97 | ### 🔁 To Stop & Remove 98 | 99 | ```bash 100 | docker stop collab-notepad 101 | docker rm collab-notepad 102 | 103 | ``` 104 | -------------------------------------------------------------------------------- /__pycache__/app.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinjansarkar/Collaborative-Notepad/c44afec1214f2f9ec090c620f8334a679cdbf85d/__pycache__/app.cpython-313.pyc -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import eventlet 2 | eventlet.monkey_patch() 3 | 4 | from flask import Flask, render_template, redirect, url_for, request, abort 5 | from flask_socketio import SocketIO, emit, join_room 6 | import uuid 7 | 8 | app = Flask(__name__) 9 | socketio = SocketIO(app, cors_allowed_origins="*", async_mode='eventlet') 10 | 11 | docs = {} # Stores all documents by room ID 12 | 13 | @app.route('/') 14 | def landing(): 15 | return render_template('landing.html') 16 | 17 | @app.route('/start') 18 | def start_collaboration(): 19 | room_id = str(uuid.uuid4())[:8] 20 | docs[room_id] = "" 21 | return redirect(url_for('editor', room_id=room_id)) 22 | 23 | @app.route('/favicon.ico') 24 | def favicon(): 25 | return redirect(url_for('static', filename='favicon.png')) 26 | 27 | @app.route('/room/') 28 | def editor(room_id): 29 | if room_id not in docs: 30 | abort(404) 31 | return render_template('editor.html', room_id=room_id, content=docs[room_id]) 32 | 33 | @socketio.on('join') 34 | def join(data): 35 | room = data['room'] 36 | if room not in docs: 37 | emit('error', {'message': 'Room not found'}, to=request.sid) 38 | return 39 | join_room(room) 40 | emit('load_content', docs.get(room, ""), to=request.sid) 41 | 42 | @socketio.on('update') 43 | def update(data): 44 | room = data['room'] 45 | content = data['content'] 46 | if room not in docs: 47 | emit('error', {'message': 'Room not found'}, to=request.sid) 48 | return 49 | docs[room] = content 50 | emit('update', content, to=room, skip_sid=request.sid) 51 | 52 | if __name__ == '__main__': 53 | socketio.run(app, host='0.0.0.0', port=5000) # Removed debug=True 54 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | flask: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | container_name: collaborative-notepad 9 | ports: 10 | - "5000:5000" 11 | restart: always 12 | 13 | nginx: 14 | build: 15 | context: ./nginx 16 | dockerfile: Dockerfile 17 | container_name: nginx-proxy 18 | ports: 19 | - "80:80" 20 | depends_on: 21 | - flask 22 | restart: always 23 | -------------------------------------------------------------------------------- /nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | # nginx/Dockerfile 2 | 3 | FROM nginx:alpine 4 | 5 | # Remove default config if needed 6 | RUN rm /etc/nginx/conf.d/default.conf 7 | 8 | # Copy your custom nginx.conf 9 | COPY nginx.conf /etc/nginx/nginx.conf 10 | -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | upstream backend_server { 2 | server backend:5000; 3 | } 4 | 5 | server { 6 | listen 80; 7 | 8 | location / { 9 | proxy_pass http://backend_server; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinjansarkar/Collaborative-Notepad/c44afec1214f2f9ec090c620f8334a679cdbf85d/requirements.txt -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinjansarkar/Collaborative-Notepad/c44afec1214f2f9ec090c620f8334a679cdbf85d/static/favicon.png -------------------------------------------------------------------------------- /templates/editor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Collaborative Editor 7 | 8 | 9 | 10 | 11 | 12 | 13 | 120 | 121 | 122 |
123 |
124 |
125 | 126 |
127 |
📝 Collaborative Notepad
128 |
129 | 130 |
131 |
132 | 133 | 134 | 135 | 140 |
141 | 142 | 189 | 190 | -------------------------------------------------------------------------------- /templates/landing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Welcome | Collaborative Notepad 6 | 7 | 8 | 49 | 50 | 51 |

📝 Welcome to Collaborative Notepad

52 |

Create and edit notes together in real-time. Click the button below to start a new session.

53 | Start Collaborating 🚀 54 | 55 | -------------------------------------------------------------------------------- /tests/__pycache__/test_app.cpython-313-pytest-8.3.5.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinjansarkar/Collaborative-Notepad/c44afec1214f2f9ec090c620f8334a679cdbf85d/tests/__pycache__/test_app.cpython-313-pytest-8.3.5.pyc -------------------------------------------------------------------------------- /tests/test_app.py: -------------------------------------------------------------------------------- 1 | # tests/test_app.py 2 | import sys 3 | import os 4 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 5 | 6 | from app import app, socketio 7 | 8 | import pytest 9 | from app import app, socketio 10 | 11 | @pytest.fixture 12 | def client(): 13 | app.config['TESTING'] = True 14 | with app.test_client() as client: 15 | yield client 16 | 17 | def test_landing_page(client): 18 | response = client.get('/') 19 | assert response.status_code == 200 20 | 21 | def test_start_collaboration(client): 22 | response = client.get('/start') 23 | assert response.status_code == 302 # Redirect 24 | assert '/room/' in response.location 25 | 26 | def test_editor_page(client): 27 | response = client.get('/room/nonexistent') 28 | assert response.status_code == 404 29 | 30 | def test_favicon(client): 31 | response = client.get('/favicon.ico') 32 | assert response.status_code == 302 # Redirect to static favicon 33 | 34 | def test_socketio_join(): 35 | client = socketio.test_client(app) 36 | client.emit('join', {'room': 'nonexistent'}) 37 | received = client.get_received() 38 | assert received[0]['name'] == 'error' 39 | assert received[0]['args'][0]['message'] == 'Room not found' --------------------------------------------------------------------------------