diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b875b91 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,46 @@ +# 更新日誌 + +本專案的所有重要變更都會記錄在此檔案中。 + +格式基於 [Keep a Changelog](https://keepachangelog.com/zh-TW/1.0.0/), +且本專案遵循 [Semantic Versioning](https://semver.org/lang/zh-TW/)。 + +## [未發布] + +### 新增 +- 新功能 A +- 新功能 B + +### 變更 +- 變更功能 A +- 變更功能 B + +### 修復 +- 修復 Bug A +- 修復 Bug B + +## [1.0.0] - 2024-01-01 + +### 新增 +- 初始版本發布 +- 基本圖片上傳功能 +- React 前端介面 +- Flask 後端 API +- 圖片預覽功能 +- 響應式設計 +- Tailwind CSS 樣式 + +### 技術特色 +- Flask 3.0.0 後端框架 +- React 19 前端框架 +- Flask-CORS 跨域支援 +- 現代化 UI/UX 設計 +- 支援多種圖片格式 + +--- + +## 版本說明 + +- **主要版本**:不相容的 API 變更 +- **次要版本**:向後相容的新功能 +- **修補版本**:向後相容的 Bug 修復 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6059e8d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,168 @@ +# 貢獻指南 + +感謝您對 ImageZoom 專案的關注!我們歡迎所有形式的貢獻。 + +## 如何貢獻 + +### 報告 Bug + +如果您發現了 Bug,請: + +1. 檢查 [Issues](https://github.com/your-username/imagezoom/issues) 是否已經有人報告過 +2. 如果沒有,請建立新的 Issue,並包含: + - 詳細的 Bug 描述 + - 重現步驟 + - 預期行為和實際行為 + - 作業系統和瀏覽器版本 + - 錯誤訊息或截圖 + +### 功能請求 + +如果您有新的功能想法: + +1. 檢查現有的 Issues 和 Pull Requests +2. 建立新的 Issue,描述: + - 功能需求 + - 使用場景 + - 預期效果 + +### 程式碼貢獻 + +#### 開發環境設定 + +1. **Fork 專案** + ```bash + git clone https://github.com/your-username/imagezoom.git + cd imagezoom + ``` + +2. **建立虛擬環境** + ```bash + python -m venv venv + source venv/bin/activate # Linux/Mac + # 或 + venv\Scripts\activate # Windows + ``` + +3. **安裝依賴** + ```bash + pip install -r requirements.txt + cd frontend && npm install + ``` + +#### 開發流程 + +1. **建立功能分支** + ```bash + git checkout -b feature/your-feature-name + ``` + +2. **開發功能** + - 遵循現有的程式碼風格 + - 撰寫測試(如果適用) + - 確保所有測試通過 + +3. **提交變更** + ```bash + git add . + git commit -m "feat: 新增功能描述" + ``` + +4. **推送到分支** + ```bash + git push origin feature/your-feature-name + ``` + +5. **建立 Pull Request** + - 前往 GitHub 建立 Pull Request + - 填寫 PR 模板 + - 等待審查 + +#### 提交訊息規範 + +我們使用 [Conventional Commits](https://www.conventionalcommits.org/) 規範: + +- `feat:` 新功能 +- `fix:` Bug 修復 +- `docs:` 文件更新 +- `style:` 程式碼格式調整 +- `refactor:` 重構 +- `test:` 測試相關 +- `chore:` 建置工具或輔助工具的變動 + +範例: +``` +feat: 新增圖片縮放功能 +fix: 修復上傳檔案大小限制問題 +docs: 更新 API 文件 +``` + +#### 程式碼風格 + +**Python (後端)** +- 遵循 PEP 8 規範 +- 使用 4 個空格縮排 +- 行長度限制在 79 字元內 +- 使用有意義的變數和函數名稱 + +**JavaScript (前端)** +- 使用 ESLint 和 Prettier +- 遵循 React 最佳實踐 +- 使用有意義的組件和函數名稱 + +#### 測試 + +**後端測試** +```bash +# 執行測試 +python -m pytest + +# 執行測試並顯示覆蓋率 +python -m pytest --cov=app +``` + +**前端測試** +```bash +cd frontend +npm test +``` + +## Pull Request 審查流程 + +1. **自動檢查** + - CI/CD 流程會自動執行測試 + - 程式碼風格檢查 + - 安全性掃描 + +2. **人工審查** + - 至少需要一位維護者審查 + - 可能需要修改或改進 + +3. **合併** + - 審查通過後會合併到主分支 + - 會自動部署到測試環境 + +## 行為準則 + +我們致力於建立一個友善和包容的社群環境: + +- 尊重所有貢獻者 +- 使用友善和建設性的語言 +- 接受建設性的批評 +- 專注於問題本身,而非個人 + +## 聯絡方式 + +如果您有任何問題或建議: + +- 建立 [Issue](https://github.com/your-username/imagezoom/issues) +- 發送 Email 至 [your-email@example.com] +- 加入我們的 [Discord 社群](https://discord.gg/your-server) + +## 致謝 + +感謝所有為這個專案做出貢獻的開發者! + +--- + +**注意:** 請確保您同意本專案的 [LICENSE](LICENSE) 條款。 \ No newline at end of file diff --git a/accessible-emoji.js b/accessible-emoji.js new file mode 100644 index 0000000..89ac00f --- /dev/null +++ b/accessible-emoji.js @@ -0,0 +1,63 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = void 0; +var _emojiRegex = _interopRequireDefault(require("emoji-regex")); +var _jsxAstUtils = require("jsx-ast-utils"); +var _safeRegexTest = _interopRequireDefault(require("safe-regex-test")); +var _schemas = require("../util/schemas"); +var _getElementType = _interopRequireDefault(require("../util/getElementType")); +var _isHiddenFromScreenReader = _interopRequireDefault(require("../util/isHiddenFromScreenReader")); +function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; } +/** + * @fileoverview Enforce emojis are wrapped in and provide screen reader access. + * @author Ethan Cohen + */ + +// ---------------------------------------------------------------------------- +// Rule Definition +// ---------------------------------------------------------------------------- + +var errorMessage = 'Emojis should be wrapped in , have role="img", and have an accessible description with aria-label or aria-labelledby.'; +var schema = (0, _schemas.generateObjSchema)(); +var _default = exports["default"] = { + meta: { + docs: { + description: 'Enforce emojis are wrapped in `` and provide screen reader access.', + url: 'https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/tree/HEAD/docs/rules/accessible-emoji.md' + }, + deprecated: true, + schema: [schema] + }, + create: function create(context) { + var elementType = (0, _getElementType["default"])(context); + var testEmoji = (0, _safeRegexTest["default"])((0, _emojiRegex["default"])()); + return { + JSXOpeningElement: function JSXOpeningElement(node) { + var literalChildValue = node.parent.children.find(function (child) { + return child.type === 'Literal' || child.type === 'JSXText'; + }); + if (literalChildValue && testEmoji(literalChildValue.value)) { + var elementIsHidden = (0, _isHiddenFromScreenReader["default"])(elementType(node), node.attributes); + if (elementIsHidden) { + return; // emoji is decorative + } + var rolePropValue = (0, _jsxAstUtils.getLiteralPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'role')); + var ariaLabelProp = (0, _jsxAstUtils.getProp)(node.attributes, 'aria-label'); + var arialLabelledByProp = (0, _jsxAstUtils.getProp)(node.attributes, 'aria-labelledby'); + var hasLabel = ariaLabelProp !== undefined || arialLabelledByProp !== undefined; + var isSpan = elementType(node) === 'span'; + if (hasLabel === false || rolePropValue !== 'img' || isSpan === false) { + context.report({ + node, + message: errorMessage + }); + } + } + } + }; + } +}; +module.exports = exports.default; \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..7c0b7b8 --- /dev/null +++ b/app.py @@ -0,0 +1,36 @@ +from flask import Flask, request, jsonify, send_from_directory +from flask_cors import CORS +import os + +app = Flask(__name__) +CORS(app) + +UPLOAD_FOLDER = 'uploads' +app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER + +if not os.path.exists(UPLOAD_FOLDER): + os.makedirs(UPLOAD_FOLDER) + +@app.route('/upload', methods=['POST']) +def upload_file(): + if 'file' not in request.files: + return jsonify({'error': 'No file part'}), 400 + file = request.files['file'] + if file.filename == '': + return jsonify({'error': 'No selected file'}), 400 + if file: + filename = file.filename + file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) + return jsonify({'success': True, 'filename': filename}) + +@app.route('/images', methods=['GET']) +def get_images(): + images = os.listdir(app.config['UPLOAD_FOLDER']) + return jsonify(images) + +@app.route('/uploads/') +def uploaded_file(filename): + return send_from_directory(app.config['UPLOAD_FOLDER'], filename) + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..97e460b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +version: '3.8' + +services: + imagezoom: + build: . + ports: + - "5000:5000" + volumes: + - ./uploads:/app/uploads + - ./templates:/app/templates + environment: + - FLASK_ENV=development + - FLASK_DEBUG=1 + restart: unless-stopped + + # 可選:如果需要資料庫 + # db: + # image: postgres:15 + # environment: + # POSTGRES_DB: imagezoom + # POSTGRES_USER: postgres + # POSTGRES_PASSWORD: password + # volumes: + # - postgres_data:/var/lib/postgresql/data + # ports: + # - "5432:5432" + +# volumes: +# postgres_data: \ No newline at end of file