From 49b2cb998b6ebaf98e89dd9dba8ba9d46d2fd82c Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Mon, 22 Nov 2021 19:40:17 +0800
Subject: [PATCH] Fix project board bug and improve documents (#17753)

* the project board was broken, this PR fixes it, and refactor the code, and we prevent the uncategorized column from being dragged.
* improve the frontend guideline (as discussed in https://github.com/go-gitea/gitea/pull/17699)
---
 .../doc/developers/guidelines-frontend.md     | 26 +++++++++++++++++++
 web_src/js/features/repo-projects.js          | 21 ++++++++-------
 2 files changed, 38 insertions(+), 9 deletions(-)

diff --git a/docs/content/doc/developers/guidelines-frontend.md b/docs/content/doc/developers/guidelines-frontend.md
index c937cfb7b4..8bd0ee0482 100644
--- a/docs/content/doc/developers/guidelines-frontend.md
+++ b/docs/content/doc/developers/guidelines-frontend.md
@@ -40,6 +40,19 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h
 7. Simple pages and SEO-related pages use Go HTML Template render to generate static Fomantic-UI HTML output. Complex pages can use Vue2 (or Vue3 in future).
 
 
+### Framework Usage
+
+Mixing different frameworks together is highly discouraged. A JavaScript module should follow one major framework and follow the framework's best practice.
+
+Recommended implementations:
+* Vue + Native
+* Fomantic-UI (jQuery)
+* Native only
+
+Discouraged implementations:
+* Vue + jQuery
+* jQuery + Native
+
 ### `async` Functions
 
 Only mark a function as `async` if and only if there are `await` calls 
@@ -98,6 +111,19 @@ $('#el').on('click', async (e) => {  // not recommended but acceptable
 });
 ```
 
+### HTML Attributes and `dataset`
+
+We forbid `dataset` usage, its camel-casing behaviour makes it hard to grep for attributes. However there are still some special cases, so the current guideline is:
+
+* For legacy code:
+  * `$.data()` should be refactored to `$.attr()`.
+  * `$.data()` can be used to bind some non-string data to elements in rare cases, but it is highly discouraged.
+
+* For new code:
+  * `node.dataset` should not be used, use `node.getAttribute` instead. 
+  * never bind any user data to a DOM node, use a suitable design pattern to describe the relation between node and data.
+
+
 ### Vue2/Vue3 and JSX
 
 Gitea is using Vue2 now, we plan to upgrade to Vue3. We decided not to introduce JSX to keep the HTML and the JavaScript code separated.
diff --git a/web_src/js/features/repo-projects.js b/web_src/js/features/repo-projects.js
index d9baa58916..5c84d6dff1 100644
--- a/web_src/js/features/repo-projects.js
+++ b/web_src/js/features/repo-projects.js
@@ -1,22 +1,24 @@
 const {csrfToken} = window.config;
 
 async function initRepoProjectSortable() {
-  const els = document.getElementsByClassName('board');
+  const els = document.querySelectorAll('#project-board > .board');
   if (!els.length) return;
 
   const {Sortable} = await import(/* webpackChunkName: "sortable" */'sortablejs');
-  const boardColumns = document.getElementsByClassName('board-column');
 
-  new Sortable(els[0], {
+  // the HTML layout is: #project-board > .board > .board-column .board.cards > .board-card.card .content
+  const mainBoard = els[0];
+  let boardColumns = mainBoard.getElementsByClassName('board-column');
+  new Sortable(mainBoard, {
     group: 'board-column',
     draggable: '.board-column',
+    filter: '[data-id="0"]',
     animation: 150,
     ghostClass: 'card-ghost',
     onSort: () => {
-      const board = document.getElementsByClassName('board')[0];
-      const boardColumns = board.getElementsByClassName('board-column');
-
-      for (const [i, column] of boardColumns.entries()) {
+      boardColumns = mainBoard.getElementsByClassName('board-column');
+      for (let i = 0; i < boardColumns.length; i++) {
+        const column = boardColumns[i];
         if (parseInt($(column).data('sorting')) !== i) {
           $.ajax({
             url: $(column).data('url'),
@@ -33,8 +35,9 @@ async function initRepoProjectSortable() {
     },
   });
 
-  for (const column of boardColumns) {
-    new Sortable(column.getElementsByClassName('board')[0], {
+  for (const boardColumn of boardColumns) {
+    const boardCardList = boardColumn.getElementsByClassName('board')[0];
+    new Sortable(boardCardList, {
       group: 'shared',
       animation: 150,
       ghostClass: 'card-ghost',