├── .DS_Store
├── LICENSE
├── README.md
├── docs
├── .DS_Store
├── faq.md
├── images
│ ├── .DS_Store
│ ├── add_my_domain.png
│ ├── desktop_screen010.png
│ ├── desktop_screen020.png
│ ├── desktop_screen030.png
│ ├── desktop_screen031.png
│ ├── desktop_screen040.png
│ ├── desktop_screen041.png
│ ├── desktop_screen050.png
│ ├── desktop_screen060.png
│ ├── install_jitsi_azure_0010.png
│ ├── install_jitsi_azure_0020.png
│ ├── install_jitsi_dig.png
│ ├── install_jitsi_dns_example.png
│ ├── install_jitsi_google0020.png
│ ├── install_jitsi_nslookup.png
│ └── thesystem_architecture.png
├── install_conferencesystem.md
├── install_customization.md
├── install_jitsi_azure.md
├── install_jitsi_google.md
└── usage.md
└── src_meet_ym
├── .DS_Store
├── YM_JITSI_操作說明.pdf
├── css
├── styles.css
└── waiting.gif
├── images
├── .DS_Store
├── aigia.png
├── browser_setting.mp4
├── mail
│ ├── contact_me.js
│ ├── contact_me.php
│ └── jqBootstrapValidation.js
├── screen00.jpg
├── screen_a01.png
├── screen_a02.png
├── screen_a03.png
├── thesystem.png
├── thesystem_architecture.png
├── welcome_page
│ ├── applestore_qrcode.png
│ ├── appstore.svg
│ ├── googleplay.png
│ └── googleplay_qrcode.png
├── ym.jpg
└── ymlogo.png
├── libs
├── qrcode.min.js
└── scripts.js
├── meet_ym.html
└── meet_ym_browser.html
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/.DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Yu-Chun Chen (陳育群)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BuildYourOwnConferenceSystem
2 | 這個計畫目的在於提供「快速架站懶人包」,協助您快速架構自己專屬、免費、安全、易用的視訊會議系統。
3 |
4 | - [Buiild Your Own Conference System](#BuildYourOwnConferenceSystem)
5 | - [動機](#動機)
6 | - [系統操作畫面](#系統操作畫面)
7 | - [安裝需求](#安裝需求)
8 | - [系統安裝說明](#系統安裝說明)
9 | - [更多訊息](#更多訊息)
10 |
11 | ---
12 |
13 | ## 動機
14 | 2020全世界面臨武漢肺炎(新型冠狀病毒, COVID-19)威脅,使用視訊會議可以兼顧團隊工作效率、減少感染風險,可惜目前市面上部分視訊軟體有[資安隱憂](https://3c.ltn.com.tw/news/40047 "自由時報"),有的軟體設定過於複雜需要專門人員操作管理,更有些需要另外額外收取費用,反而影響參與視訊會議的使用體驗。
15 | 我們依照台灣機關學校視訊會議使用情境重新撰寫視訊系統,以[教育部](https://depart.moe.edu.tw/ED2700/News_Content.aspx?n=727087A8A1328DEE&s=868B3A6EDF9BA52D)、「台灣天才IT大臣」[唐鳳](https://3c.ltn.com.tw/news/40055)推薦使用的開源軟體[Jitsi meet](https://meet.jit.si/)為基礎,重新打造一套快速入手適用於台灣的視訊會議平台。
16 |
17 |
18 | 使用「快速架站懶人包」將有下列好處:
19 | #### 學校機關系統管理者
20 | 免費軟體安裝不易,是管理者心中痛點。我們提供本機、雲端(Google cloud 與 Azure)多種安裝方式、您僅需要照著網頁操作,簡單修改原始碼就可以替單位架設一套免費、安全隱密的會議系統,還可以依照您的需求自行延伸加上傳送開會通知、會議錄影、錄音字幕等等功能。
21 | #### 視訊會議使用者
22 | 與會者一直遲遲無法加入會議,是視訊會議最常見的痛點,我們將常見的視訊會議流程簡化為4個步驟,會議管理者僅需要照著網頁提示即可開啟會議、自動產生開會通知。視訊會議參與者不需高檔配備,僅需用手機掃條碼便可即時參加會議,讓視訊會議流程最通暢。
23 |
24 | ## 系統操作畫面
25 | 我們將以「陽明大學視訊系統」流程為範例。只要跟著「快速架站懶人包」,您即可輕鬆擁有自己專屬、免費、安全、易用的視訊會議系統。
26 | ###### 系統架構
27 | 
28 |
29 | ###### 系統首頁
30 | 
31 |
32 | ###### 視訊會議步驟一:開啟虛擬會議室
33 | 
34 |
35 | ###### 視訊會議步驟二:管理虛擬會議室
36 | 
37 |
38 | ###### 視訊會議步驟三:設定密碼、開會通知
39 | 
40 |
41 | ###### 簡單易懂參加會議說明
42 | 
43 |
44 | ###### 視訊會議實際畫面
45 | 
46 |
47 | ###### 系統說明
48 | 
49 |
50 | ###### 開發團隊
51 | 
52 |
53 | ## 安裝需求
54 | * 您必須有台安裝好Ubuntu LTS主機(或者您可以申請Azure或Google雲端帳號,我們也有提供安裝指引可以安裝在 Azure雲端 與 Google雲端,管理更為方便), **必要**
55 | * 您的網域名稱和DNS紀錄(Valid domain with DNS record), **必要**.
56 |
57 | ## 系統安裝說明
58 | 系統安裝主要分成後端Jitsi視訊主機架設以及視訊系統前台兩大部分。
59 | 1. 安裝設定Jitsi meet系統後端
60 | 1. 使用 [Azure 雲端](./docs/install_jitsi_azure.md)
61 | 2. 使用 [Google 雲端](./docs/install_jitsi_google.md)
62 | 2. [安裝視訊系統前台](./docs/install_conferencesystem.md)
63 | 3. [客製化視訊系統](./docs/install_customization.md)
64 |
65 | ## 更多訊息
66 | 1. [awesome-jitsi 「Jitsi 資源大全」](https://github.com/Yuchunchen/awesome-jitsi) - 有許多很酷的應用,更歡迎您留下您的成品,大家互相觀摩!
67 | 2. [臺北榮民總醫院視訊門診系統](https://www.vghtpe.gov.tw/News!one.action?nid=6396)
68 | 3. [新北市國中小校長使用Jitsi](https://udn.com/news/story/7323/4498815) - 新北市泰山區同榮國小蔡明貴校長帶領「新北校長科技領導社群」率先使用Jitsi
69 |
70 |
--------------------------------------------------------------------------------
/docs/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/.DS_Store
--------------------------------------------------------------------------------
/docs/faq.md:
--------------------------------------------------------------------------------
1 | #常見問題
2 |
--------------------------------------------------------------------------------
/docs/images/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/.DS_Store
--------------------------------------------------------------------------------
/docs/images/add_my_domain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/add_my_domain.png
--------------------------------------------------------------------------------
/docs/images/desktop_screen010.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen010.png
--------------------------------------------------------------------------------
/docs/images/desktop_screen020.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen020.png
--------------------------------------------------------------------------------
/docs/images/desktop_screen030.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen030.png
--------------------------------------------------------------------------------
/docs/images/desktop_screen031.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen031.png
--------------------------------------------------------------------------------
/docs/images/desktop_screen040.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen040.png
--------------------------------------------------------------------------------
/docs/images/desktop_screen041.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen041.png
--------------------------------------------------------------------------------
/docs/images/desktop_screen050.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen050.png
--------------------------------------------------------------------------------
/docs/images/desktop_screen060.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen060.png
--------------------------------------------------------------------------------
/docs/images/install_jitsi_azure_0010.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/install_jitsi_azure_0010.png
--------------------------------------------------------------------------------
/docs/images/install_jitsi_azure_0020.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/install_jitsi_azure_0020.png
--------------------------------------------------------------------------------
/docs/images/install_jitsi_dig.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/install_jitsi_dig.png
--------------------------------------------------------------------------------
/docs/images/install_jitsi_dns_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/install_jitsi_dns_example.png
--------------------------------------------------------------------------------
/docs/images/install_jitsi_google0020.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/install_jitsi_google0020.png
--------------------------------------------------------------------------------
/docs/images/install_jitsi_nslookup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/install_jitsi_nslookup.png
--------------------------------------------------------------------------------
/docs/images/thesystem_architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/thesystem_architecture.png
--------------------------------------------------------------------------------
/docs/install_conferencesystem.md:
--------------------------------------------------------------------------------
1 | # 如何安裝視訊系統前台
2 |
3 | - [如何安裝視訊系統前台](#如何安裝視訊系統前台)
4 | - [1. 從 Github 下載前台程式 ](#step1-從-Github-下載前台程式 )
5 | - [2. 加入您的server](#step2-加入您的server)
6 | - [3. 收工了](#step2-收工了)
7 | - [4. 如何修改](#step3-如何修改)
8 | - [5. 重要系統設定檔案](#step4-重要系統設定檔案)
9 |
10 | ---
11 |
12 |
13 | ## step1. 從 Github 下載前台程式
14 | ```bash
15 | sudo git clone https://github.com/Yuchunchen/BuildYourOwnConferenceSystem.git ./local2 &&
16 | sudo cp -arv ~/local2/src_meet_ym/*.* /usr/share/jitsi-meet/ &&
17 | sudo cp -arv ~/local2/src_meet_ym/css /usr/share/jitsi-meet/ &&
18 | sudo cp -arv ~/local2/src_meet_ym/images /usr/share/jitsi-meet/ &&
19 | sudo cp -arv ~/local2/src_meet_ym/libs /usr/share/jitsi-meet/
20 | ```
21 |
22 | ## step2. 加入您的server
23 | 您可以先測試看看您所架設的jitsi server 是否可以順利運作:
24 | * 如果您安裝於Azure雲端主機,可以使用瀏覽器移至 https://myjitsidemo.westus2.cloudapp.azure.com/
25 | * 如果您安裝於Google雲端,自己取了網域名稱(hosthname),您可以使用瀏覽器移至 https://hostname/
26 | 可以看到啟動 jitsi 啟動畫面,代表您的主機已經順利啟動。
27 |
28 | 接著就是將您的主機加入視訊會議系統 meet_ym.html 網頁,請使用下列 nano 指令修改 第 44 列 (如下圖)後存檔
29 | ```bash
30 | sudo nano /usr/share/jitsi-meet/meet_ym.html
31 | ```
32 | 
33 |
34 | ## step3. 收工了
35 | 是的,這樣就完成囉,趕快來看看安裝成果!
36 | 您可以先使用桌機或筆電,
37 | * 如果您安裝於Azure雲端主機,
38 | 您可以使用瀏覽器移至 https://myjitsidemo.westus2.cloudapp.azure.com/meet_ym.html
39 | * 如果您安裝於Google雲端,自己取了網域名稱(hosthname),
40 | 您可以使用瀏覽器移至 https://hostname/meet_ym.html
41 |
42 | 您也可以依照網頁說明下載APP,隨時隨地手機開會超方便!
43 |
44 | ## step4. 如何修改
45 | 根據這幾天回報,已經有許多朋友安裝成功,希望能夠更近一步修改為自己單位網頁,當然沒問題。
46 | 您只需要打開 meet_ym.html,您就可以修改網頁文字:
47 | ```bash
48 | sudo nano /usr/share/jitsi-meet/meet_ym.html
49 | ```
50 |
51 | 更進階的修改,可以參考[進階客製化](./install_customization.md),您也可以參考[「Jitsi 資源大全」](https://github.com/Yuchunchen/awesome-jitsi)並且留下您的使用心得唷!
52 |
53 | 希望大家使用愉快!
54 |
55 | ## step5. 重要系統設定檔案
56 | Jitsi meet 還有很多神奇功能可以調整唷,底下是一些常用到的檔案位置,您一定要動手試試看!
57 | 依照您安裝的主機名稱(Azure:myjitsidemo.westus2.cloudapp.azure.com) ,檔名會有所不同,請注意替換唷!
58 |
59 | | |檔案 |說明|備註|
60 | |--------------|-------|---|---|
61 | |log |/var/log/jitsi/jvb.log |||
62 | |log |/var/log/jitsi/jicofo.log |||
63 | |log |/var/log/prosody/prosody.log |||
64 | |JITSI設定檔 |/etc/jitsi/meet/myjitsidemo.westus2.cloudapp.azure.com-config.js|!注意主機名稱||
65 | |JITSI介面設定檔|/usr/share/jitsi-meet/interface_config.js | ||
66 | |nginx 設定 |/etc/nginx/sites-available/myjitsidemo.westus2.cloudapp.azure.com.conf|!注意主機名稱|重新啟動sudo systemctl restart nginx.service && sudo nano /etc/jitsi/meet/myjitsidemo.westus2.cloudapp.azure.com-config.js|
67 | | Web Server Entry |/usr/share/jitsi-meet/| Jitsi meet home page|||
68 | | 靜態大頭貼 |/usr/share/jitsi-meet/images/avatar2.png|||
69 | |JIBRI錄影檔 |/var/jbrecord |||
70 | |JIBRI錄影上傳 |/home/jibri/finalize_recording.sh |||
71 |
--------------------------------------------------------------------------------
/docs/install_customization.md:
--------------------------------------------------------------------------------
1 | # 客製化Jitsi更符合您的需求
2 |
3 | - [客製化Jitsi更符合您的需求](#客製化Jitsi更符合您的需求)
4 | - [修改「陽明視訊系統」](#修改「陽明視訊系統」)
5 | - [修改首頁 Welcome page 文字](#修改首頁-welcome-page-文字)
6 | - [如何修改將錄影檔案上傳至雲端](#如何修改將錄影檔案上傳至雲端)
7 | - [更多連結](#更多連結)
8 |
9 | ---
10 |
11 | ## 修改「陽明視訊系統」
12 | 隨著上述步驟安裝,您應該可以直接以 https://您的jitsi名稱/meet_ym.html (如:https://jitsiym.aigia.ai/meet_ym.html ) 方式進入系統試用,當然,您可以直接利用下列指令進行任意修改:
13 | ```bash
14 | sudo nano /usr/share/jitsi-meet/meet_ym.html
15 | ```
16 |
17 | ## 修改首頁 Welcome page 文字
18 |
19 | 有的朋友會希望可以修改官方的 welcome page,大概有兩種方法:
20 | 1. 官方版,建議由 github ( https://github.com/jitsi/jitsi-meet ) 下載原始碼,修改後再重新 compile
21 | 2. 快速版:直接修改 ```app.bundle.min.js``` 檔案:
22 | ```bash
23 | sudo nano /usr/share/jitsi-meet/libs/app.bundle.min.js
24 | ```
25 |
26 | ## 如何修改將錄影檔案上傳至雲端
27 | 在完成錄影後,依照預設檔案會放置於 (/var/jbrecord) 目錄下,同時會執行 /home/jibri/finalize_recording.sh 命令,預設 finalize_recording.sh 不會進行任何動作,您可以自行修改此檔案。 (錄影與上傳相關設定檔案在 /etc/jitsi/jibri/config.json )
28 |
29 | ##### 1. 利用 rclone 上傳至 Google, One drive 等雲端空間
30 | [rclone](https://rclone.org/docs/) 可以將檔案上傳至雲端空間(目前支援37種!),常見的Google, OneDrive, ftp, sftp, webdav 都在支援之列。這篇文章 [How-to to get a WORKING setup of Google Drive, One Drive or other cloud services in Jibri](https://community.jitsi.org/t/how-to-to-get-a-working-setup-of-google-drive-one-drive-or-other-cloud-services-in-jibri-my-comprehensive-tutorial-for-the-beginner/42228)是很重要的參考。
31 |
32 | ##### 2. 上傳到NAS系統,再透過NAS介面下載
33 | 可以參考 [這篇文章](https://community.jitsi.org/t/how-do-you-use-finalize-recordings-sh/28034/2),使用者[toni](https://community.jitsi.org/u/toni)分享了他的 finalize_recording.sh 如下:
34 |
35 | ```bash
36 | #!/bin/bash
37 |
38 | RECORDINGS_DIR=$1
39 |
40 | echo "$RECORDINGS_DIR" > /tmp/verzeichnis.out
41 | #echo "This is a dummy finalize script" > /tmp/finalize.out
42 | #echo "The script was invoked with recordings directory $RECORDINGS_DIR." >> /t$
43 | #echo "You should put any finalize logic (renaming, uploading to a service" >> $
44 | #echo "or storage provider, etc.) in this script" >> /tmp/finalize.out
45 | # The script exctracts the filename to prepare it for moving it to an external place, i.e. NAS drive
46 |
47 | pfad=$(cat /tmp/verzeichnis.out)"/*mp4"
48 | echo "$pfad" > /tmp/pfad.out
49 | ls -l $pfad > /tmp/filename.out
50 | filename=$(cat /tmp/filename.out)
51 | filenameneu=$(echo $filename | cut -c 47-)
52 | echo "$filenameneu" > tmp/filenameneu.out
53 | transfername=$(echo $filenameneu | cut -c 34-)
54 | echo "$transfername" > tmp/transfername.out
55 | # The syno.txt file contains the user password for the NAS drive
56 | sshpass -f /home/username/syno.txt scp $filenameneu username@NASdrivedomain.com/path/to/storagefolder/$transfername
57 | # Save a backup of the previous mp4 for safety reason
58 | cp /home/username/kopie.mp4 /home/username/kopie.mp4.bak
59 | # copy a backup of the actually moved mp4 to the local user folder
60 | cp $filenameneu /home/toni/kopie.mp4
61 | remove=$(cat /tmp/verzeichnis.out)
62 |
63 | rm -r $remove
64 | exit 0
65 |
66 | ```
67 |
68 | ## 更多連結
69 | * 您可以參考[「Jitsi 資源大全」](https://github.com/Yuchunchen/awesome-jitsi),那裡有許多很酷的應用,更歡迎您留下您的成品,大家互相觀摩!世界更美好!
70 | * [easyjitsi](https://docs.easyjitsi.com/docs/config) 有些自訂化設定整理得不錯
71 |
--------------------------------------------------------------------------------
/docs/install_jitsi_azure.md:
--------------------------------------------------------------------------------
1 | # 如何將 Jitsi meet 安裝於 Azure 雲端
2 |
3 | - [如何將 Jitsi meet 安裝於 Azure 雲端](#如何將-Jitsi-meet-安裝於-Azure-雲端)
4 | - [1. Azure 帳號](#step1-Azure-帳號)
5 | - [2. 建立雲端虛擬主機](#step2-建立雲端虛擬主機)
6 | - [3. 安裝 Jitsi meet 前置準備](#step3-安裝-Jitsi-meet-前置準備)
7 | - [4. 安裝 Jitsi meet](#step4-安裝-Jitsi-meet)
8 | - [5. 恭喜 :)](#step5-恭喜)
9 | - [6. 重要系統設定檔案](#step6-重要系統設定檔案)
10 |
11 | ---
12 |
13 | ## step1. Azure 帳號
14 | Jitsi meet對於硬體沒有特別要求,目前Azure提供12個月[免費使用](https://azure.microsoft.com/zh-tw/free/),再加上額外新台幣6100元試用金額,應該足以免費使用1年,是很好的開始。
15 |
16 | ## step2. 建立雲端虛擬主機
17 | 只需要5分鐘,就可以建立您的虛擬主機唷!請跟著下面影片,我們將在Azure雲端建立一台虛擬主機(名稱為: myjitsidemo),所需費用正好在Azure免費額度內,很適合預算有限的中小型機關學校。
18 |
19 |
22 |
23 | 底下是過程中會需要用到的參數如下:
24 |
25 | |參數名稱|設定值|說明|
26 | |-------|-----|---|
27 | |資源群組|jitsidemo||
28 | |電腦名稱|myjitsidemo||
29 | |位置|美國西部2|目前這個位置的主機比較便宜|
30 | |登入帳號|jitsiadm||
31 | |登入密碼| .....|請您自行設定一組12位密碼|
32 | |DNS名稱標籤|myjitsidemo||
33 | |目的地連接埠範圍|80,443,4443,5222,5347,10000-20000||
34 |
35 | 跟隨著影片步驟,您的主機應該看起來像下圖:
36 | 
37 |
38 | 請記下下列資訊,接下來會常常使用到
39 |
40 | |參數名稱|設定值|說明|
41 | |-------|-----|---|
42 | |登入帳號|jitsiadm||
43 | |登入密碼| .....|您剛剛自行設定的12位密碼|
44 | |DNS名稱|myjitsidemo.westus2.cloudapp.azure.com|
45 | |SSH登入指令|`ssh jitsiadm@myjitsidemo.westus2.cloudapp.azure.com`|
46 |
47 | ## step3. 安裝 Jitsi meet 前置準備
48 | 1. 您可以由畫面上面的連結(SSH 登入) 或者 左下角(序列主控台登入)
49 | 
50 |
51 | 2. 系統更新
52 | * 更新 apt
53 | ```bash
54 | sudo apt update &&
55 | sudo apt upgrade &&
56 | sudo apt-get update &&
57 | sudo apt-get install sshfs
58 | ```
59 | * 安裝 nano
60 | ```bash
61 | sudo apt-get install nano
62 | ```
63 | * 下載安裝標準 linux 核心: 下載 linux image
64 | ```bash
65 | sudo apt-get -y install linux-image-generic &&
66 | sudo update-grub
67 | ```
68 | * 開啟 /etc/default/grub
69 | ```bash
70 | sudo nano /etc/default/grub
71 | ```
72 | * 設定 linux image
73 | 找到**GRUB_DEFAULT**,並修改為如下,Ctrl-X存檔
74 | `GRUB_DEFAULT='1>2'`
75 | * 重新開機
76 | ```bash
77 | sudo reboot now
78 | ```
79 |
80 | ## step4. 安裝 Jitsi meet
81 |
82 | 1. 下載 Jitsi 安裝程式
83 | 雖然官方網站已提供安裝說明,但是仍有許多地方需要手工設定,經過測試,[SwITNet](https://github.com/switnet-ltd/quick-jibri-installer)所提供的快速安裝程序,可以省下不少設定帶來的挫折。
84 | ```bash
85 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jigasi.sh &&
86 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jitsi-updater.sh &&
87 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jm-bm.sh &&
88 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jra_nextcloud.sh &&
89 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/quick_jibri_installer.sh &&
90 | chmod +x jigasi.sh &&
91 | chmod +x jitsi-updater.sh &&
92 | chmod +x jm-bm.sh &&
93 | chmod +x jra_nextcloud.sh &&
94 | chmod +x quick_jibri_installer.sh
95 | ```
96 |
97 | 2. 執行 quick_jibri_installer.sh
98 | ```bash
99 | sudo ./quick_jibri_installer.sh
100 | ```
101 |
102 | 3. 經過一小段時間下載更新後,接著我們要進行基本設定:
103 |
104 | | |提示|說明|建議回答|備註|
105 | |---|---|---|-------|---|
106 | |1.|Please set your language (Press enter to default to 'en')|選擇預設語系|zhTW|選擇中文,注意大小寫需一致)|
107 | |2.|Set sysadmin email|管理者電子郵件||此為必須欄位|
108 | |3.|Do you want to drop support for unsecure protocols TLSv1.0/1.1 now: (yes or no)|是否要停止支援不安全的http協定?|yes|我們希望系統僅能透過https加密連線|
109 | |4.|Do you want to setup LetsEncrypt with your domain: (yes or no)|是否需要SSL加密?|yes||
110 | |5.|Do you want to setup the Dropbox feature now: (yes or no)|是否需要「錄影上傳dropbox」功能?|no|防止未經允許側錄會議|
111 | |6.|Do you want to install customized "brandless mode"?: (yes or no)|是否將Jitsi logo刪除?|no|建議保留jitsi logo|
112 | |7.|Do you want to translate 'Participant' to your own language? Leave empty to use the default one (English):|請將'Participant'翻譯成您的語言|Participant|不翻譯|
113 | |8.|Do you want to translate 'me' to your own language?|將 me 翻譯成您的語言|me|不翻譯|
114 | |9.|Do you want to disable the Welcome page: (yes or no)|是否關閉系統預設之首頁?|yes|我們將使用客製化首頁|
115 | |10.|Do you want to enable static avatar?: (yes or no)|是否使用固定大頭貼?|yes|可自行選擇|
116 | |11.|Do you want to enable local audio recording option?: (yes or no)|是否開放與會者自行側錄?|no|不允許側錄會議|
117 | |12.|Do you want to enable secure rooms?: (yes or no)|是否開啟會議室帳號密碼管控?|yes||
118 | |13.|Set username for secure room moderator:|設定管理者帳號|ymteacher|請自行設定|
119 | |14.|Secure room moderator password:|管理者密碼|ym1234|請自行設定|
120 | |15.|Do you want to setup Jibri Records Access via Nextcloud: (yes or no)|是否開放雲端側錄?|no|不允許側錄會議|
121 | |16.|Do you want to setup Jigasi Transcription: (yes or no)|是否開啟自動語音辨識功能?|no|需要額外付費|
122 |
123 | 4. 再等待一下,系統將會自動重新啟動
124 |
125 | 5. 試試看使用Chrome 瀏覽器 瀏覽網址 https://myjitsidemo.westus2.cloudapp.azure.com
126 |
127 |
128 | ## step5. 恭喜
129 | 您現在應該已經有專屬於您的視訊平台囉,接著我們將[安裝視訊系統前台](./docs/install_conferencesystem.md),加上前端介面,讓系統更好用。
130 |
131 | ## step6. 重要系統設定檔案
132 | Jitsi meet 還有很多神奇功能可以調整唷,底下是一些常用到的檔案位置,您一定要動手試試看!
133 |
134 | | |檔案 |說明|備註|
135 | |--------------|-------|---|---|
136 | |log |/var/log/jitsi/jvb.log |||
137 | |log |/var/log/jitsi/jicofo.log |||
138 | |log |/var/log/prosody/prosody.log |||
139 | |JITSI設定檔 |/etc/jitsi/meet/myjitsidemo.westus2.cloudapp.azure.com-config.js| ||
140 | |JITSI介面設定檔|/usr/share/jitsi-meet/interface_config.js | ||
141 | |nginx 設定 |/etc/nginx/sites-available/myjitsidemo.westus2.cloudapp.azure.com.conf||重新啟動sudo systemctl restart nginx.service && sudo nano /etc/jitsi/meet/myjitsidemo.westus2.cloudapp.azure.com-config.js|
142 | | Web Server Entry |/usr/share/jitsi-meet/| Jitsi meet home page|||
143 | | 靜態大頭貼 |/usr/share/jitsi-meet/images/avatar2.png|||
144 | |JIBRI錄影檔 |/var/jbrecord |||
145 | |JIBRI錄影上傳 |/home/jibri/finalize_recording.sh |||
146 |
--------------------------------------------------------------------------------
/docs/install_jitsi_google.md:
--------------------------------------------------------------------------------
1 | # 如何將 Jitsi meet 安裝於 Google 雲端
2 |
3 | - [如何將 Jitsi meet 安裝於 Google 雲端](#如何將-Jitsi-meet-安裝於-Google-雲端)
4 | - [1. Google 帳號](#step1-Google-帳號)
5 | - [2. 建立Google雲端虛擬主機](#step2-建立Google雲端虛擬主機)
6 | - [3. 建立主機的網域名稱](#step3-建立主機的網域名稱domain-name)
7 | - [4. 安裝 Jitsi meet 前置準備](#step4-安裝-Jitsi-meet-前置準備)
8 | - [5. 安裝 Jitsi meet ](#step5-安裝-Jitsi-meet)
9 | - [6. 重要系統設定檔案](#step6-重要系統設定檔案)
10 |
11 | ---
12 |
13 | ## step1. Google 帳號
14 | Jitsi meet對於硬體沒有特別要求,目前Google提供12個月[免費使用](https://cloud.google.com/free/?hl=zh-TW),再加上額外美金300元試用金額,應該足以免費使用1年,是很好的開始。
15 |
16 | ## step2. 建立Google雲端虛擬主機
17 | 只需要6分鐘,就可以建立您的雲端虛擬主機。請跟著下面的影片操作,我們在Google雲端建立一台虛擬主機,所需費用正好在Google免費額度內,很適合預算有限的中小型機關學校。
18 |
19 |
22 |
23 | 底下是過程中會需要用到的參數如下:
24 |
25 | |參數名稱|設定值|說明|
26 | |-------|-----|---|
27 | |專案名稱|jitsi-demo||
28 | |電腦名稱|my-jitsi-demo||
29 | |位置|台灣|放在台灣反應速度最佳|
30 | |開機磁碟|Ubuntu 18.04 LTS|作業系統|
31 | |保留靜態位址:名稱| ip-my-jitsi-demo ||
32 | |保留靜態位址:**外部位址**|xxx.xxx.xxx.xxx|您會獲得一組固定IP(影片中範例為:104.199.168.254),請記下來|
33 | |建立防火牆規則:名稱| port-jitsi||
34 | |建立防火牆規則:來源IP範圍| 0.0.0.0/0 |不設限,您可以視需要設定|
35 | |建立防火牆規則:指定的通訊協定和通訊埠|80,443,4443,5222,5347,10000-20000|tcp, udp都開啟以加快連線速度|
36 |
37 | 跟隨著影片步驟,您的主機應該看起來像下圖:
38 | 
39 |
40 | 請記下下列資訊,
41 |
42 | |參數名稱|設定值|說明|
43 | |-------|-----|---|
44 | |外部IP位址 |(影片中範例為:104.199.168.254)||
45 |
46 |
47 | ## step3. 建立主機的網域名稱domain name
48 | 您一定要設定網域名稱才能安裝jitsi系統。原因是jitsi meet會使用大量內部伺服器,數字型態IP位址無法使用。您必須跟學校/機關網路管理員申請,網路上有些免費網域服務伺服器可能可以試試看,有些學校有向Google購買G-suite服務,可能可以直接使用[G-suite名稱伺服器](https://support.google.com/a/answer/53929?hl=zh-Hant),或者您可以花一點錢(每年約新台幣300-2000元)直接向Google[購買一個專屬您的網域名稱](https://www.wfublog.com/2019/04/google-domains-tw-purchase-transfer-godaddy-dns.html),其他如[GoDaddy](https://tw.godaddy.com/domains/domain-name-search)、[Gandi](https://www.gandi.net/zh-Hant)..等都有提供便宜而且親善的中文介面。
49 |
50 | 無論您使用哪一家的網域名稱,重點在於您必須增加一筆 A紀錄 (A record),A記錄又稱為「地址記錄」(或主機記錄),可將網域連結至代管網域服務的主機實體 IP 位址。下面是我們使用網域伺服器使用畫面:
51 |
52 | 
53 |
54 | **!! 注意 !!** 一般網域名稱設定後,大約需要幾個小時才會生效,請您務必利用確認您的網域名稱生效後,才能繼續安裝。
55 | 您可以使用下列指令來確認網域名稱是否生效:
56 | ###### Windows
57 | ```nslookup 您的網域名稱``` (這裡以 sts.contoso.com 為例,截圖自微軟網頁)
58 | 
59 |
60 | ###### Mac, Linux
61 | ```dig 您的網域名稱``` (這裡以陽明大學視訊系統 jitsiym.aigia.ai 為例)
62 | 
63 |
64 |
65 |
66 | ## step4 安裝 Jitsi meet 前置準備
67 | 1. Google雲端很貼心,不需要另外設定,您可以點擊畫面的SSH直接登入主機:
68 | 
69 |
70 | 2. 系統更新
71 | * 更新 apt
72 | ```bash
73 | sudo apt update &&
74 | sudo apt upgrade &&
75 | sudo apt-get update &&
76 | sudo apt-get install sshfs
77 | ```
78 | * 安裝 nano
79 | ```bash
80 | sudo apt-get install nano
81 | ```
82 | * 下載安裝標準 linux 核心: 下載 linux image
83 | ```bash
84 | sudo apt-get -y install linux-image-generic &&
85 | sudo update-grub
86 | ```
87 | * 開啟 /etc/default/grub
88 | ```bash
89 | sudo nano /etc/default/grub
90 | ```
91 | * 設定 linux image
92 | 找到**GRUB_DEFAULT**,並修改為如下,Ctrl-X存檔
93 | `GRUB_DEFAULT='1>2'`
94 | * 重新開機
95 | ```bash
96 | sudo reboot now
97 | ```
98 |
99 | ## step5. 安裝 Jitsi meet
100 |
101 | 1. 下載 Jitsi 安裝程式
102 | 雖然官方網站已提供安裝說明,但是仍有許多地方需要手工設定,經過測試,[SwITNet](https://github.com/switnet-ltd/quick-jibri-installer)所提供的快速安裝程序,可以省下不少設定帶來的挫折。
103 | ```bash
104 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jigasi.sh &&
105 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jitsi-updater.sh &&
106 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jm-bm.sh &&
107 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jra_nextcloud.sh &&
108 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/quick_jibri_installer.sh &&
109 | chmod +x jigasi.sh &&
110 | chmod +x jitsi-updater.sh &&
111 | chmod +x jm-bm.sh &&
112 | chmod +x jra_nextcloud.sh &&
113 | chmod +x quick_jibri_installer.sh
114 | ```
115 |
116 | 2. 執行 quick_jibri_installer.sh
117 | ```bash
118 | sudo ./quick_jibri_installer.sh
119 | ```
120 |
121 | 3. 經過一小段時間下載更新後,接著我們要進行基本設定:
122 |
123 | | |提示|說明|建議回答|備註|
124 | |---|---|---|-------|---|
125 | |1.|Please set your language (Press enter to default to 'en')|選擇預設語系|zhTW|選擇中文,注意大小寫需一致)|
126 | |2.|Set sysadmin email|管理者電子郵件||此為必須欄位|
127 | |3.|Do you want to drop support for unsecure protocols TLSv1.0/1.1 now: (yes or no)|是否要停止支援不安全的http協定?|yes|我們希望系統僅能透過https加密連線|
128 | |4.|Do you want to setup LetsEncrypt with your domain: (yes or no)|是否需要SSL加密?|yes||
129 | |5.|Do you want to setup the Dropbox feature now: (yes or no)|是否需要「錄影上傳dropbox」功能?|no|防止未經允許側錄會議|
130 | |6.|Do you want to install customized "brandless mode"?: (yes or no)|是否將Jitsi logo刪除?|no|建議保留jitsi logo|
131 | |7.|Do you want to translate 'Participant' to your own language? Leave empty to use the default one (English):|請將'Participant' 翻譯成您的語言|Participant|不翻譯|
132 | |8.|Do you want to translate 'me' to your own language?|將 me 翻譯成您的語言|me|不翻譯|
133 | |9.|Do you want to disable the Welcome page: (yes or no)|是否關閉系統預設之首頁?|yes|我們將使用客製化首頁|
134 | |10.|Do you want to enable static avatar?: (yes or no)|是否使用固定大頭貼?|yes|可自行選擇|
135 | |11.|Do you want to enable local audio recording option?: (yes or no)|是否開放與會者自行側錄?|no|不允許側錄會議|
136 | |12.|Do you want to enable secure rooms?: (yes or no)|是否開啟會議室帳號密碼管控?|yes||
137 | |13.|Set username for secure room moderator:|設定管理者帳號|ymteacher|請自行設定|
138 | |14.|Secure room moderator password:|管理者密碼|ym1234|請自行設定|
139 | |15.|Do you want to setup Jibri Records Access via Nextcloud: (yes or no)|是否開放雲端側錄?|no|不允許側錄會議|
140 | |16.|Do you want to setup Jigasi Transcription: (yes or no)|是否開啟自動語音辨識功能?|no|需要額外付費|
141 |
142 | 4. 再等待一下,系統將會自動重新啟動
143 |
144 | 5. 試試看使用Chrome 瀏覽器 瀏覽看看唷!
145 |
146 |
147 | ## step6. 重要系統設定檔案
148 |
149 | | |檔案 |說明|備註|
150 | |--------------|-------|---|---|
151 | |log |/var/log/jitsi/jvb.log |||
152 | |log |/var/log/jitsi/jicofo.log |||
153 | |log |/var/log/prosody/prosody.log |||
154 | |JITSI設定檔 |/etc/jitsi/meet/myjitsidemo.westus2.cloudapp.azure.com-config.js| ||
155 | |JITSI介面設定檔|/usr/share/jitsi-meet/interface_config.js | ||
156 | |nginx 設定 |/etc/nginx/sites-available/myjitsidemo.westus2.cloudapp.azure.com.conf||重新啟動sudo systemctl restart nginx.service && sudo nano /etc/jitsi/meet/myjitsidemo.westus2.cloudapp.azure.com-config.js|
157 | | Web Server Entry |/usr/share/jitsi-meet/| Jitsi meet home page|||
158 | | 靜態大頭貼 |/usr/share/jitsi-meet/images/avatar2.png|||
159 | |JIBRI錄影檔 | /var/jbrecord |||
160 | |JIBRI錄影上傳 | /home/jibri/finalize_recording.sh |||
161 |
--------------------------------------------------------------------------------
/docs/usage.md:
--------------------------------------------------------------------------------
1 | #STEP1. 安裝您的Jitsi視訊主機
2 |
--------------------------------------------------------------------------------
/src_meet_ym/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/.DS_Store
--------------------------------------------------------------------------------
/src_meet_ym/YM_JITSI_操作說明.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/YM_JITSI_操作說明.pdf
--------------------------------------------------------------------------------
/src_meet_ym/css/waiting.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/css/waiting.gif
--------------------------------------------------------------------------------
/src_meet_ym/images/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/.DS_Store
--------------------------------------------------------------------------------
/src_meet_ym/images/aigia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/aigia.png
--------------------------------------------------------------------------------
/src_meet_ym/images/browser_setting.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/browser_setting.mp4
--------------------------------------------------------------------------------
/src_meet_ym/images/mail/contact_me.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 |
3 | $("#contactForm input,#contactForm textarea").jqBootstrapValidation({
4 | preventSubmit: true,
5 | submitError: function($form, event, errors) {
6 | // additional error messages or events
7 | },
8 | submitSuccess: function($form, event) {
9 | event.preventDefault(); // prevent default submit behaviour
10 | // get values from FORM
11 | var name = $("input#name").val();
12 | var email = $("input#email").val();
13 | var phone = $("input#phone").val();
14 | var message = $("textarea#message").val();
15 | var firstName = name; // For Success/Failure Message
16 | // Check for white space in name for Success/Fail message
17 | if (firstName.indexOf(' ') >= 0) {
18 | firstName = name.split(' ').slice(0, -1).join(' ');
19 | }
20 | $this = $("#sendMessageButton");
21 | $this.prop("disabled", true); // Disable submit button until AJAX call is complete to prevent duplicate messages
22 | $.ajax({
23 | url: "contact_me.php",
24 | type: "POST",
25 | data: {
26 | name: name,
27 | phone: phone,
28 | email: email,
29 | message: message
30 | },
31 | cache: false,
32 | success: function() {
33 | // Success message
34 | $('#success').html("
");
35 | $('#success > .alert-success').html("×")
36 | .append(" ");
37 | $('#success > .alert-success')
38 | .append("Your message has been sent. ");
39 | $('#success > .alert-success')
40 | .append('
');
41 | //clear all fields
42 | $('#contactForm').trigger("reset");
43 | },
44 | error: function() {
45 | // Fail message
46 | $('#success').html("");
47 | $('#success > .alert-danger').html("×")
48 | .append(" ");
49 | $('#success > .alert-danger').append($("").text("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later!"));
50 | $('#success > .alert-danger').append('
');
51 | //clear all fields
52 | $('#contactForm').trigger("reset");
53 | },
54 | complete: function() {
55 | setTimeout(function() {
56 | $this.prop("disabled", false); // Re-enable submit button when AJAX call is complete
57 | }, 1000);
58 | }
59 | });
60 | },
61 | filter: function() {
62 | return $(this).is(":visible");
63 | },
64 | });
65 |
66 | $("a[data-toggle=\"tab\"]").click(function(e) {
67 | e.preventDefault();
68 | $(this).tab("show");
69 | });
70 | });
71 |
72 | /*When clicking on Full hide fail/success boxes */
73 | $('#name').focus(function() {
74 | $('#success').html('');
75 | });
76 |
--------------------------------------------------------------------------------
/src_meet_ym/images/mail/contact_me.php:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/src_meet_ym/images/mail/jqBootstrapValidation.js:
--------------------------------------------------------------------------------
1 | /* jqBootstrapValidation
2 | * A plugin for automating validation on Twitter Bootstrap formatted forms.
3 | *
4 | * v1.3.6
5 | *
6 | * License: MIT - see LICENSE file
7 | *
8 | * http://ReactiveRaven.github.com/jqBootstrapValidation/
9 | */
10 |
11 | (function( $ ){
12 |
13 | var createdElements = [];
14 |
15 | var defaults = {
16 | options: {
17 | prependExistingHelpBlock: false,
18 | sniffHtml: true, // sniff for 'required', 'maxlength', etc
19 | preventSubmit: true, // stop the form submit event from firing if validation fails
20 | submitError: false, // function called if there is an error when trying to submit
21 | submitSuccess: false, // function called just before a successful submit event is sent to the server
22 | semanticallyStrict: false, // set to true to tidy up generated HTML output
23 | autoAdd: {
24 | helpBlocks: true
25 | },
26 | filter: function () {
27 | // return $(this).is(":visible"); // only validate elements you can see
28 | return true; // validate everything
29 | }
30 | },
31 | methods: {
32 | init : function( options ) {
33 |
34 | var settings = $.extend(true, {}, defaults);
35 |
36 | settings.options = $.extend(true, settings.options, options);
37 |
38 | var $siblingElements = this;
39 |
40 | var uniqueForms = $.unique(
41 | $siblingElements.map( function () {
42 | return $(this).parents("form")[0];
43 | }).toArray()
44 | );
45 |
46 | $(uniqueForms).bind("submit", function (e) {
47 | var $form = $(this);
48 | var warningsFound = 0;
49 | var $inputs = $form.find("input,textarea,select").not("[type=submit],[type=image]").filter(settings.options.filter);
50 | $inputs.trigger("submit.validation").trigger("validationLostFocus.validation");
51 |
52 | $inputs.each(function (i, el) {
53 | var $this = $(el),
54 | $controlGroup = $this.parents(".control-group").first();
55 | if (
56 | $controlGroup.hasClass("warning")
57 | ) {
58 | $controlGroup.removeClass("warning").addClass("error");
59 | warningsFound++;
60 | }
61 | });
62 |
63 | $inputs.trigger("validationLostFocus.validation");
64 |
65 | if (warningsFound) {
66 | if (settings.options.preventSubmit) {
67 | e.preventDefault();
68 | }
69 | $form.addClass("error");
70 | if ($.isFunction(settings.options.submitError)) {
71 | settings.options.submitError($form, e, $inputs.jqBootstrapValidation("collectErrors", true));
72 | }
73 | } else {
74 | $form.removeClass("error");
75 | if ($.isFunction(settings.options.submitSuccess)) {
76 | settings.options.submitSuccess($form, e);
77 | }
78 | }
79 | });
80 |
81 | return this.each(function(){
82 |
83 | // Get references to everything we're interested in
84 | var $this = $(this),
85 | $controlGroup = $this.parents(".control-group").first(),
86 | $helpBlock = $controlGroup.find(".help-block").first(),
87 | $form = $this.parents("form").first(),
88 | validatorNames = [];
89 |
90 | // create message container if not exists
91 | if (!$helpBlock.length && settings.options.autoAdd && settings.options.autoAdd.helpBlocks) {
92 | $helpBlock = $('
');
93 | $controlGroup.find('.controls').append($helpBlock);
94 | createdElements.push($helpBlock[0]);
95 | }
96 |
97 | // =============================================================
98 | // SNIFF HTML FOR VALIDATORS
99 | // =============================================================
100 |
101 | // *snort sniff snuffle*
102 |
103 | if (settings.options.sniffHtml) {
104 | var message = "";
105 | // ---------------------------------------------------------
106 | // PATTERN
107 | // ---------------------------------------------------------
108 | if ($this.attr("pattern") !== undefined) {
109 | message = "Not in the expected format";
110 | if ($this.data("validationPatternMessage")) {
111 | message = $this.data("validationPatternMessage");
112 | }
113 | $this.data("validationPatternMessage", message);
114 | $this.data("validationPatternRegex", $this.attr("pattern"));
115 | }
116 | // ---------------------------------------------------------
117 | // MAX
118 | // ---------------------------------------------------------
119 | if ($this.attr("max") !== undefined || $this.attr("aria-valuemax") !== undefined) {
120 | var max = ($this.attr("max") !== undefined ? $this.attr("max") : $this.attr("aria-valuemax"));
121 | message = "Too high: Maximum of '" + max + "'";
122 | if ($this.data("validationMaxMessage")) {
123 | message = $this.data("validationMaxMessage");
124 | }
125 | $this.data("validationMaxMessage", message);
126 | $this.data("validationMaxMax", max);
127 | }
128 | // ---------------------------------------------------------
129 | // MIN
130 | // ---------------------------------------------------------
131 | if ($this.attr("min") !== undefined || $this.attr("aria-valuemin") !== undefined) {
132 | var min = ($this.attr("min") !== undefined ? $this.attr("min") : $this.attr("aria-valuemin"));
133 | message = "Too low: Minimum of '" + min + "'";
134 | if ($this.data("validationMinMessage")) {
135 | message = $this.data("validationMinMessage");
136 | }
137 | $this.data("validationMinMessage", message);
138 | $this.data("validationMinMin", min);
139 | }
140 | // ---------------------------------------------------------
141 | // MAXLENGTH
142 | // ---------------------------------------------------------
143 | if ($this.attr("maxlength") !== undefined) {
144 | message = "Too long: Maximum of '" + $this.attr("maxlength") + "' characters";
145 | if ($this.data("validationMaxlengthMessage")) {
146 | message = $this.data("validationMaxlengthMessage");
147 | }
148 | $this.data("validationMaxlengthMessage", message);
149 | $this.data("validationMaxlengthMaxlength", $this.attr("maxlength"));
150 | }
151 | // ---------------------------------------------------------
152 | // MINLENGTH
153 | // ---------------------------------------------------------
154 | if ($this.attr("minlength") !== undefined) {
155 | message = "Too short: Minimum of '" + $this.attr("minlength") + "' characters";
156 | if ($this.data("validationMinlengthMessage")) {
157 | message = $this.data("validationMinlengthMessage");
158 | }
159 | $this.data("validationMinlengthMessage", message);
160 | $this.data("validationMinlengthMinlength", $this.attr("minlength"));
161 | }
162 | // ---------------------------------------------------------
163 | // REQUIRED
164 | // ---------------------------------------------------------
165 | if ($this.attr("required") !== undefined || $this.attr("aria-required") !== undefined) {
166 | message = settings.builtInValidators.required.message;
167 | if ($this.data("validationRequiredMessage")) {
168 | message = $this.data("validationRequiredMessage");
169 | }
170 | $this.data("validationRequiredMessage", message);
171 | }
172 | // ---------------------------------------------------------
173 | // NUMBER
174 | // ---------------------------------------------------------
175 | if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "number") {
176 | message = settings.builtInValidators.number.message;
177 | if ($this.data("validationNumberMessage")) {
178 | message = $this.data("validationNumberMessage");
179 | }
180 | $this.data("validationNumberMessage", message);
181 | }
182 | // ---------------------------------------------------------
183 | // EMAIL
184 | // ---------------------------------------------------------
185 | if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "email") {
186 | message = "Not a valid email address";
187 | if ($this.data("validationValidemailMessage")) {
188 | message = $this.data("validationValidemailMessage");
189 | } else if ($this.data("validationEmailMessage")) {
190 | message = $this.data("validationEmailMessage");
191 | }
192 | $this.data("validationValidemailMessage", message);
193 | }
194 | // ---------------------------------------------------------
195 | // MINCHECKED
196 | // ---------------------------------------------------------
197 | if ($this.attr("minchecked") !== undefined) {
198 | message = "Not enough options checked; Minimum of '" + $this.attr("minchecked") + "' required";
199 | if ($this.data("validationMincheckedMessage")) {
200 | message = $this.data("validationMincheckedMessage");
201 | }
202 | $this.data("validationMincheckedMessage", message);
203 | $this.data("validationMincheckedMinchecked", $this.attr("minchecked"));
204 | }
205 | // ---------------------------------------------------------
206 | // MAXCHECKED
207 | // ---------------------------------------------------------
208 | if ($this.attr("maxchecked") !== undefined) {
209 | message = "Too many options checked; Maximum of '" + $this.attr("maxchecked") + "' required";
210 | if ($this.data("validationMaxcheckedMessage")) {
211 | message = $this.data("validationMaxcheckedMessage");
212 | }
213 | $this.data("validationMaxcheckedMessage", message);
214 | $this.data("validationMaxcheckedMaxchecked", $this.attr("maxchecked"));
215 | }
216 | }
217 |
218 | // =============================================================
219 | // COLLECT VALIDATOR NAMES
220 | // =============================================================
221 |
222 | // Get named validators
223 | if ($this.data("validation") !== undefined) {
224 | validatorNames = $this.data("validation").split(",");
225 | }
226 |
227 | // Get extra ones defined on the element's data attributes
228 | $.each($this.data(), function (i, el) {
229 | var parts = i.replace(/([A-Z])/g, ",$1").split(",");
230 | if (parts[0] === "validation" && parts[1]) {
231 | validatorNames.push(parts[1]);
232 | }
233 | });
234 |
235 | // =============================================================
236 | // NORMALISE VALIDATOR NAMES
237 | // =============================================================
238 |
239 | var validatorNamesToInspect = validatorNames;
240 | var newValidatorNamesToInspect = [];
241 |
242 | do // repeatedly expand 'shortcut' validators into their real validators
243 | {
244 | // Uppercase only the first letter of each name
245 | $.each(validatorNames, function (i, el) {
246 | validatorNames[i] = formatValidatorName(el);
247 | });
248 |
249 | // Remove duplicate validator names
250 | validatorNames = $.unique(validatorNames);
251 |
252 | // Pull out the new validator names from each shortcut
253 | newValidatorNamesToInspect = [];
254 | $.each(validatorNamesToInspect, function(i, el) {
255 | if ($this.data("validation" + el + "Shortcut") !== undefined) {
256 | // Are these custom validators?
257 | // Pull them out!
258 | $.each($this.data("validation" + el + "Shortcut").split(","), function(i2, el2) {
259 | newValidatorNamesToInspect.push(el2);
260 | });
261 | } else if (settings.builtInValidators[el.toLowerCase()]) {
262 | // Is this a recognised built-in?
263 | // Pull it out!
264 | var validator = settings.builtInValidators[el.toLowerCase()];
265 | if (validator.type.toLowerCase() === "shortcut") {
266 | $.each(validator.shortcut.split(","), function (i, el) {
267 | el = formatValidatorName(el);
268 | newValidatorNamesToInspect.push(el);
269 | validatorNames.push(el);
270 | });
271 | }
272 | }
273 | });
274 |
275 | validatorNamesToInspect = newValidatorNamesToInspect;
276 |
277 | } while (validatorNamesToInspect.length > 0)
278 |
279 | // =============================================================
280 | // SET UP VALIDATOR ARRAYS
281 | // =============================================================
282 |
283 | var validators = {};
284 |
285 | $.each(validatorNames, function (i, el) {
286 | // Set up the 'override' message
287 | var message = $this.data("validation" + el + "Message");
288 | var hasOverrideMessage = (message !== undefined);
289 | var foundValidator = false;
290 | message =
291 | (
292 | message
293 | ? message
294 | : "'" + el + "' validation failed "
295 | )
296 | ;
297 |
298 | $.each(
299 | settings.validatorTypes,
300 | function (validatorType, validatorTemplate) {
301 | if (validators[validatorType] === undefined) {
302 | validators[validatorType] = [];
303 | }
304 | if (!foundValidator && $this.data("validation" + el + formatValidatorName(validatorTemplate.name)) !== undefined) {
305 | validators[validatorType].push(
306 | $.extend(
307 | true,
308 | {
309 | name: formatValidatorName(validatorTemplate.name),
310 | message: message
311 | },
312 | validatorTemplate.init($this, el)
313 | )
314 | );
315 | foundValidator = true;
316 | }
317 | }
318 | );
319 |
320 | if (!foundValidator && settings.builtInValidators[el.toLowerCase()]) {
321 |
322 | var validator = $.extend(true, {}, settings.builtInValidators[el.toLowerCase()]);
323 | if (hasOverrideMessage) {
324 | validator.message = message;
325 | }
326 | var validatorType = validator.type.toLowerCase();
327 |
328 | if (validatorType === "shortcut") {
329 | foundValidator = true;
330 | } else {
331 | $.each(
332 | settings.validatorTypes,
333 | function (validatorTemplateType, validatorTemplate) {
334 | if (validators[validatorTemplateType] === undefined) {
335 | validators[validatorTemplateType] = [];
336 | }
337 | if (!foundValidator && validatorType === validatorTemplateType.toLowerCase()) {
338 | $this.data("validation" + el + formatValidatorName(validatorTemplate.name), validator[validatorTemplate.name.toLowerCase()]);
339 | validators[validatorType].push(
340 | $.extend(
341 | validator,
342 | validatorTemplate.init($this, el)
343 | )
344 | );
345 | foundValidator = true;
346 | }
347 | }
348 | );
349 | }
350 | }
351 |
352 | if (! foundValidator) {
353 | $.error("Cannot find validation info for '" + el + "'");
354 | }
355 | });
356 |
357 | // =============================================================
358 | // STORE FALLBACK VALUES
359 | // =============================================================
360 |
361 | $helpBlock.data(
362 | "original-contents",
363 | (
364 | $helpBlock.data("original-contents")
365 | ? $helpBlock.data("original-contents")
366 | : $helpBlock.html()
367 | )
368 | );
369 |
370 | $helpBlock.data(
371 | "original-role",
372 | (
373 | $helpBlock.data("original-role")
374 | ? $helpBlock.data("original-role")
375 | : $helpBlock.attr("role")
376 | )
377 | );
378 |
379 | $controlGroup.data(
380 | "original-classes",
381 | (
382 | $controlGroup.data("original-clases")
383 | ? $controlGroup.data("original-classes")
384 | : $controlGroup.attr("class")
385 | )
386 | );
387 |
388 | $this.data(
389 | "original-aria-invalid",
390 | (
391 | $this.data("original-aria-invalid")
392 | ? $this.data("original-aria-invalid")
393 | : $this.attr("aria-invalid")
394 | )
395 | );
396 |
397 | // =============================================================
398 | // VALIDATION
399 | // =============================================================
400 |
401 | $this.bind(
402 | "validation.validation",
403 | function (event, params) {
404 |
405 | var value = getValue($this);
406 |
407 | // Get a list of the errors to apply
408 | var errorsFound = [];
409 |
410 | $.each(validators, function (validatorType, validatorTypeArray) {
411 | if (value || value.length || (params && params.includeEmpty) || (!!settings.validatorTypes[validatorType].blockSubmit && params && !!params.submitting)) {
412 | $.each(validatorTypeArray, function (i, validator) {
413 | if (settings.validatorTypes[validatorType].validate($this, value, validator)) {
414 | errorsFound.push(validator.message);
415 | }
416 | });
417 | }
418 | });
419 |
420 | return errorsFound;
421 | }
422 | );
423 |
424 | $this.bind(
425 | "getValidators.validation",
426 | function () {
427 | return validators;
428 | }
429 | );
430 |
431 | // =============================================================
432 | // WATCH FOR CHANGES
433 | // =============================================================
434 | $this.bind(
435 | "submit.validation",
436 | function () {
437 | return $this.triggerHandler("change.validation", {submitting: true});
438 | }
439 | );
440 | $this.bind(
441 | [
442 | "keyup",
443 | "focus",
444 | "blur",
445 | "click",
446 | "keydown",
447 | "keypress",
448 | "change"
449 | ].join(".validation ") + ".validation",
450 | function (e, params) {
451 |
452 | var value = getValue($this);
453 |
454 | var errorsFound = [];
455 |
456 | $controlGroup.find("input,textarea,select").each(function (i, el) {
457 | var oldCount = errorsFound.length;
458 | $.each($(el).triggerHandler("validation.validation", params), function (j, message) {
459 | errorsFound.push(message);
460 | });
461 | if (errorsFound.length > oldCount) {
462 | $(el).attr("aria-invalid", "true");
463 | } else {
464 | var original = $this.data("original-aria-invalid");
465 | $(el).attr("aria-invalid", (original !== undefined ? original : false));
466 | }
467 | });
468 |
469 | $form.find("input,select,textarea").not($this).not("[name=\"" + $this.attr("name") + "\"]").trigger("validationLostFocus.validation");
470 |
471 | errorsFound = $.unique(errorsFound.sort());
472 |
473 | // Were there any errors?
474 | if (errorsFound.length) {
475 | // Better flag it up as a warning.
476 | $controlGroup.removeClass("success error").addClass("warning");
477 |
478 | // How many errors did we find?
479 | if (settings.options.semanticallyStrict && errorsFound.length === 1) {
480 | // Only one? Being strict? Just output it.
481 | $helpBlock.html(errorsFound[0] +
482 | ( settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "" ));
483 | } else {
484 | // Multiple? Being sloppy? Glue them together into an UL.
485 | $helpBlock.html("" + errorsFound.join(" ") + " " +
486 | ( settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "" ));
487 | }
488 | } else {
489 | $controlGroup.removeClass("warning error success");
490 | if (value.length > 0) {
491 | $controlGroup.addClass("success");
492 | }
493 | $helpBlock.html($helpBlock.data("original-contents"));
494 | }
495 |
496 | if (e.type === "blur") {
497 | $controlGroup.removeClass("success");
498 | }
499 | }
500 | );
501 | $this.bind("validationLostFocus.validation", function () {
502 | $controlGroup.removeClass("success");
503 | });
504 | });
505 | },
506 | destroy : function( ) {
507 |
508 | return this.each(
509 | function() {
510 |
511 | var
512 | $this = $(this),
513 | $controlGroup = $this.parents(".control-group").first(),
514 | $helpBlock = $controlGroup.find(".help-block").first();
515 |
516 | // remove our events
517 | $this.unbind('.validation'); // events are namespaced.
518 | // reset help text
519 | $helpBlock.html($helpBlock.data("original-contents"));
520 | // reset classes
521 | $controlGroup.attr("class", $controlGroup.data("original-classes"));
522 | // reset aria
523 | $this.attr("aria-invalid", $this.data("original-aria-invalid"));
524 | // reset role
525 | $helpBlock.attr("role", $this.data("original-role"));
526 | // remove all elements we created
527 | if (createdElements.indexOf($helpBlock[0]) > -1) {
528 | $helpBlock.remove();
529 | }
530 |
531 | }
532 | );
533 |
534 | },
535 | collectErrors : function(includeEmpty) {
536 |
537 | var errorMessages = {};
538 | this.each(function (i, el) {
539 | var $el = $(el);
540 | var name = $el.attr("name");
541 | var errors = $el.triggerHandler("validation.validation", {includeEmpty: true});
542 | errorMessages[name] = $.extend(true, errors, errorMessages[name]);
543 | });
544 |
545 | $.each(errorMessages, function (i, el) {
546 | if (el.length === 0) {
547 | delete errorMessages[i];
548 | }
549 | });
550 |
551 | return errorMessages;
552 |
553 | },
554 | hasErrors: function() {
555 |
556 | var errorMessages = [];
557 |
558 | this.each(function (i, el) {
559 | errorMessages = errorMessages.concat(
560 | $(el).triggerHandler("getValidators.validation") ? $(el).triggerHandler("validation.validation", {submitting: true}) : []
561 | );
562 | });
563 |
564 | return (errorMessages.length > 0);
565 | },
566 | override : function (newDefaults) {
567 | defaults = $.extend(true, defaults, newDefaults);
568 | }
569 | },
570 | validatorTypes: {
571 | callback: {
572 | name: "callback",
573 | init: function ($this, name) {
574 | return {
575 | validatorName: name,
576 | callback: $this.data("validation" + name + "Callback"),
577 | lastValue: $this.val(),
578 | lastValid: true,
579 | lastFinished: true
580 | };
581 | },
582 | validate: function ($this, value, validator) {
583 | if (validator.lastValue === value && validator.lastFinished) {
584 | return !validator.lastValid;
585 | }
586 |
587 | if (validator.lastFinished === true)
588 | {
589 | validator.lastValue = value;
590 | validator.lastValid = true;
591 | validator.lastFinished = false;
592 |
593 | var rrjqbvValidator = validator;
594 | var rrjqbvThis = $this;
595 | executeFunctionByName(
596 | validator.callback,
597 | window,
598 | $this,
599 | value,
600 | function (data) {
601 | if (rrjqbvValidator.lastValue === data.value) {
602 | rrjqbvValidator.lastValid = data.valid;
603 | if (data.message) {
604 | rrjqbvValidator.message = data.message;
605 | }
606 | rrjqbvValidator.lastFinished = true;
607 | rrjqbvThis.data("validation" + rrjqbvValidator.validatorName + "Message", rrjqbvValidator.message);
608 | // Timeout is set to avoid problems with the events being considered 'already fired'
609 | setTimeout(function () {
610 | rrjqbvThis.trigger("change.validation");
611 | }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
612 | }
613 | }
614 | );
615 | }
616 |
617 | return false;
618 |
619 | }
620 | },
621 | ajax: {
622 | name: "ajax",
623 | init: function ($this, name) {
624 | return {
625 | validatorName: name,
626 | url: $this.data("validation" + name + "Ajax"),
627 | lastValue: $this.val(),
628 | lastValid: true,
629 | lastFinished: true
630 | };
631 | },
632 | validate: function ($this, value, validator) {
633 | if (""+validator.lastValue === ""+value && validator.lastFinished === true) {
634 | return validator.lastValid === false;
635 | }
636 |
637 | if (validator.lastFinished === true)
638 | {
639 | validator.lastValue = value;
640 | validator.lastValid = true;
641 | validator.lastFinished = false;
642 | $.ajax({
643 | url: validator.url,
644 | data: "value=" + value + "&field=" + $this.attr("name"),
645 | dataType: "json",
646 | success: function (data) {
647 | if (""+validator.lastValue === ""+data.value) {
648 | validator.lastValid = !!(data.valid);
649 | if (data.message) {
650 | validator.message = data.message;
651 | }
652 | validator.lastFinished = true;
653 | $this.data("validation" + validator.validatorName + "Message", validator.message);
654 | // Timeout is set to avoid problems with the events being considered 'already fired'
655 | setTimeout(function () {
656 | $this.trigger("change.validation");
657 | }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
658 | }
659 | },
660 | failure: function () {
661 | validator.lastValid = true;
662 | validator.message = "ajax call failed";
663 | validator.lastFinished = true;
664 | $this.data("validation" + validator.validatorName + "Message", validator.message);
665 | // Timeout is set to avoid problems with the events being considered 'already fired'
666 | setTimeout(function () {
667 | $this.trigger("change.validation");
668 | }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
669 | }
670 | });
671 | }
672 |
673 | return false;
674 |
675 | }
676 | },
677 | regex: {
678 | name: "regex",
679 | init: function ($this, name) {
680 | return {regex: regexFromString($this.data("validation" + name + "Regex"))};
681 | },
682 | validate: function ($this, value, validator) {
683 | return (!validator.regex.test(value) && ! validator.negative)
684 | || (validator.regex.test(value) && validator.negative);
685 | }
686 | },
687 | required: {
688 | name: "required",
689 | init: function ($this, name) {
690 | return {};
691 | },
692 | validate: function ($this, value, validator) {
693 | return !!(value.length === 0 && ! validator.negative)
694 | || !!(value.length > 0 && validator.negative);
695 | },
696 | blockSubmit: true
697 | },
698 | match: {
699 | name: "match",
700 | init: function ($this, name) {
701 | var element = $this.parents("form").first().find("[name=\"" + $this.data("validation" + name + "Match") + "\"]").first();
702 | element.bind("validation.validation", function () {
703 | $this.trigger("change.validation", {submitting: true});
704 | });
705 | return {"element": element};
706 | },
707 | validate: function ($this, value, validator) {
708 | return (value !== validator.element.val() && ! validator.negative)
709 | || (value === validator.element.val() && validator.negative);
710 | },
711 | blockSubmit: true
712 | },
713 | max: {
714 | name: "max",
715 | init: function ($this, name) {
716 | return {max: $this.data("validation" + name + "Max")};
717 | },
718 | validate: function ($this, value, validator) {
719 | return (parseFloat(value, 10) > parseFloat(validator.max, 10) && ! validator.negative)
720 | || (parseFloat(value, 10) <= parseFloat(validator.max, 10) && validator.negative);
721 | }
722 | },
723 | min: {
724 | name: "min",
725 | init: function ($this, name) {
726 | return {min: $this.data("validation" + name + "Min")};
727 | },
728 | validate: function ($this, value, validator) {
729 | return (parseFloat(value) < parseFloat(validator.min) && ! validator.negative)
730 | || (parseFloat(value) >= parseFloat(validator.min) && validator.negative);
731 | }
732 | },
733 | maxlength: {
734 | name: "maxlength",
735 | init: function ($this, name) {
736 | return {maxlength: $this.data("validation" + name + "Maxlength")};
737 | },
738 | validate: function ($this, value, validator) {
739 | return ((value.length > validator.maxlength) && ! validator.negative)
740 | || ((value.length <= validator.maxlength) && validator.negative);
741 | }
742 | },
743 | minlength: {
744 | name: "minlength",
745 | init: function ($this, name) {
746 | return {minlength: $this.data("validation" + name + "Minlength")};
747 | },
748 | validate: function ($this, value, validator) {
749 | return ((value.length < validator.minlength) && ! validator.negative)
750 | || ((value.length >= validator.minlength) && validator.negative);
751 | }
752 | },
753 | maxchecked: {
754 | name: "maxchecked",
755 | init: function ($this, name) {
756 | var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]");
757 | elements.bind("click.validation", function () {
758 | $this.trigger("change.validation", {includeEmpty: true});
759 | });
760 | return {maxchecked: $this.data("validation" + name + "Maxchecked"), elements: elements};
761 | },
762 | validate: function ($this, value, validator) {
763 | return (validator.elements.filter(":checked").length > validator.maxchecked && ! validator.negative)
764 | || (validator.elements.filter(":checked").length <= validator.maxchecked && validator.negative);
765 | },
766 | blockSubmit: true
767 | },
768 | minchecked: {
769 | name: "minchecked",
770 | init: function ($this, name) {
771 | var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]");
772 | elements.bind("click.validation", function () {
773 | $this.trigger("change.validation", {includeEmpty: true});
774 | });
775 | return {minchecked: $this.data("validation" + name + "Minchecked"), elements: elements};
776 | },
777 | validate: function ($this, value, validator) {
778 | return (validator.elements.filter(":checked").length < validator.minchecked && ! validator.negative)
779 | || (validator.elements.filter(":checked").length >= validator.minchecked && validator.negative);
780 | },
781 | blockSubmit: true
782 | }
783 | },
784 | builtInValidators: {
785 | email: {
786 | name: "Email",
787 | type: "shortcut",
788 | shortcut: "validemail"
789 | },
790 | validemail: {
791 | name: "Validemail",
792 | type: "regex",
793 | regex: "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\.[A-Za-z]{2,4}",
794 | message: "Not a valid email address"
795 | },
796 | passwordagain: {
797 | name: "Passwordagain",
798 | type: "match",
799 | match: "password",
800 | message: "Does not match the given password"
801 | },
802 | positive: {
803 | name: "Positive",
804 | type: "shortcut",
805 | shortcut: "number,positivenumber"
806 | },
807 | negative: {
808 | name: "Negative",
809 | type: "shortcut",
810 | shortcut: "number,negativenumber"
811 | },
812 | number: {
813 | name: "Number",
814 | type: "regex",
815 | regex: "([+-]?\\\d+(\\\.\\\d*)?([eE][+-]?[0-9]+)?)?",
816 | message: "Must be a number"
817 | },
818 | integer: {
819 | name: "Integer",
820 | type: "regex",
821 | regex: "[+-]?\\\d+",
822 | message: "No decimal places allowed"
823 | },
824 | positivenumber: {
825 | name: "Positivenumber",
826 | type: "min",
827 | min: 0,
828 | message: "Must be a positive number"
829 | },
830 | negativenumber: {
831 | name: "Negativenumber",
832 | type: "max",
833 | max: 0,
834 | message: "Must be a negative number"
835 | },
836 | required: {
837 | name: "Required",
838 | type: "required",
839 | message: "This is required"
840 | },
841 | checkone: {
842 | name: "Checkone",
843 | type: "minchecked",
844 | minchecked: 1,
845 | message: "Check at least one option"
846 | }
847 | }
848 | };
849 |
850 | var formatValidatorName = function (name) {
851 | return name
852 | .toLowerCase()
853 | .replace(
854 | /(^|\s)([a-z])/g ,
855 | function(m,p1,p2) {
856 | return p1+p2.toUpperCase();
857 | }
858 | )
859 | ;
860 | };
861 |
862 | var getValue = function ($this) {
863 | // Extract the value we're talking about
864 | var value = $this.val();
865 | var type = $this.attr("type");
866 | if (type === "checkbox") {
867 | value = ($this.is(":checked") ? value : "");
868 | }
869 | if (type === "radio") {
870 | value = ($('input[name="' + $this.attr("name") + '"]:checked').length > 0 ? value : "");
871 | }
872 | return value;
873 | };
874 |
875 | function regexFromString(inputstring) {
876 | return new RegExp("^" + inputstring + "$");
877 | }
878 |
879 | /**
880 | * Thanks to Jason Bunting via StackOverflow.com
881 | *
882 | * http://stackoverflow.com/questions/359788/how-to-execute-a-javascript-function-when-i-have-its-name-as-a-string#answer-359910
883 | * Short link: http://tinyurl.com/executeFunctionByName
884 | **/
885 | function executeFunctionByName(functionName, context /*, args*/) {
886 | var args = Array.prototype.slice.call(arguments).splice(2);
887 | var namespaces = functionName.split(".");
888 | var func = namespaces.pop();
889 | for(var i = 0; i < namespaces.length; i++) {
890 | context = context[namespaces[i]];
891 | }
892 | return context[func].apply(this, args);
893 | }
894 |
895 | $.fn.jqBootstrapValidation = function( method ) {
896 |
897 | if ( defaults.methods[method] ) {
898 | return defaults.methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
899 | } else if ( typeof method === 'object' || ! method ) {
900 | return defaults.methods.init.apply( this, arguments );
901 | } else {
902 | $.error( 'Method ' + method + ' does not exist on jQuery.jqBootstrapValidation' );
903 | return null;
904 | }
905 |
906 | };
907 |
908 | $.jqBootstrapValidation = function (options) {
909 | $(":input").not("[type=image],[type=submit]").jqBootstrapValidation.apply(this,arguments);
910 | };
911 |
912 | })( jQuery );
913 |
--------------------------------------------------------------------------------
/src_meet_ym/images/screen00.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/screen00.jpg
--------------------------------------------------------------------------------
/src_meet_ym/images/screen_a01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/screen_a01.png
--------------------------------------------------------------------------------
/src_meet_ym/images/screen_a02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/screen_a02.png
--------------------------------------------------------------------------------
/src_meet_ym/images/screen_a03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/screen_a03.png
--------------------------------------------------------------------------------
/src_meet_ym/images/thesystem.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/thesystem.png
--------------------------------------------------------------------------------
/src_meet_ym/images/thesystem_architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/thesystem_architecture.png
--------------------------------------------------------------------------------
/src_meet_ym/images/welcome_page/applestore_qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/welcome_page/applestore_qrcode.png
--------------------------------------------------------------------------------
/src_meet_ym/images/welcome_page/appstore.svg:
--------------------------------------------------------------------------------
1 |
2 | Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src_meet_ym/images/welcome_page/googleplay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/welcome_page/googleplay.png
--------------------------------------------------------------------------------
/src_meet_ym/images/welcome_page/googleplay_qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/welcome_page/googleplay_qrcode.png
--------------------------------------------------------------------------------
/src_meet_ym/images/ym.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/ym.jpg
--------------------------------------------------------------------------------
/src_meet_ym/images/ymlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/ymlogo.png
--------------------------------------------------------------------------------
/src_meet_ym/libs/qrcode.min.js:
--------------------------------------------------------------------------------
1 | var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push(' ');g.push(" ")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}();
--------------------------------------------------------------------------------
/src_meet_ym/libs/scripts.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Start Bootstrap - Freelancer v6.0.0 (https://startbootstrap.com/themes/freelancer)
3 | * Copyright 2013-2020 Start Bootstrap
4 | * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap-freelancer/blob/master/LICENSE)
5 | */
6 | (function($) {
7 | "use strict"; // Start of use strict
8 |
9 | // Smooth scrolling using jQuery easing
10 | $('a.js-scroll-trigger[href*="#"]:not([href="#"])').click(function() {
11 | if (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname) {
12 | var target = $(this.hash);
13 | target = target.length ? target : $('[name=' + this.hash.slice(1) + ']');
14 | if (target.length) {
15 | $('html, body').animate({
16 | scrollTop: (target.offset().top - 71)
17 | }, 1000, "easeInOutExpo");
18 | return false;
19 | }
20 | }
21 | });
22 |
23 | // Scroll to top button appear
24 | $(document).scroll(function() {
25 | var scrollDistance = $(this).scrollTop();
26 | if (scrollDistance > 100) {
27 | $('.scroll-to-top').fadeIn();
28 | } else {
29 | $('.scroll-to-top').fadeOut();
30 | }
31 | });
32 |
33 | // Closes responsive menu when a scroll trigger link is clicked
34 | $('.js-scroll-trigger').click(function() {
35 | $('.navbar-collapse').collapse('hide');
36 | });
37 |
38 | // Activate scrollspy to add active class to navbar items on scroll
39 | $('body').scrollspy({
40 | target: '#mainNav',
41 | offset: 80
42 | });
43 |
44 | // Collapse Navbar
45 | var navbarCollapse = function() {
46 | if ($("#mainNav").offset().top > 100) {
47 | $("#mainNav").addClass("navbar-shrink");
48 | } else {
49 | $("#mainNav").removeClass("navbar-shrink");
50 | }
51 | };
52 | // Collapse now if page is not at top
53 | navbarCollapse();
54 | // Collapse the navbar when page is scrolled
55 | $(window).scroll(navbarCollapse);
56 |
57 | // Floating label headings for the contact form
58 | $(function() {
59 | $("body").on("input propertychange", ".floating-label-form-group", function(e) {
60 | $(this).toggleClass("floating-label-form-group-with-value", !!$(e.target).val());
61 | }).on("focus", ".floating-label-form-group", function() {
62 | $(this).addClass("floating-label-form-group-with-focus");
63 | }).on("blur", ".floating-label-form-group", function() {
64 | $(this).removeClass("floating-label-form-group-with-focus");
65 | });
66 | });
67 |
68 | })(jQuery); // End of use strict
69 |
--------------------------------------------------------------------------------
/src_meet_ym/meet_ym.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 陽明大學視訊會議系統
10 |
32 |
33 |
34 |
35 |
36 |
37 |
39 |
40 |
41 |
42 |
43 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
陽明大學視訊會議系統
164 | Menu
165 |
166 |
167 |
168 |
169 | 開會囉
170 |
171 |
172 | 參加會議
173 |
174 |
175 | 關於系統
176 |
177 |
178 | 關於愛家實驗室
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 | 陽明大學。愛家實驗室。Jitsi
192 |
193 |
194 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 | 我要開會!
215 |
216 |
217 |
223 |
236 |
237 |
238 |
STEP2.
239 |
240 |
241 |
242 |
243 | 輸入會議名稱後,請按這裏 開啟視訊會議室
245 |
246 |
247 |
248 |
249 |
250 |
STEP3.
251 |
252 |
253 |
260 |
261 |
262 |
STEP4.
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 | 課程密碼
272 |
274 |
275 |
276 |
277 |
278 |
279 | 設定密碼以及會議通知
280 |
281 |
282 |
283 |
284 |
285 |
286 | (您可將下列訊息複製寄送給與會同仁)
287 |
288 |
289 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 | 開會去
315 |
316 |
317 |
318 |
319 |
手機桌機攏ㄟ通,我們提供多種使用者友善連線方法,您可以依照您的需求、環境與設備選擇最適合您的方式參與會議。(使用說明 )
320 |
321 |
322 |
323 |
324 |
327 |
328 | 1. 請先安裝視訊會議用APP (掃描QR-Code)
329 |
330 |
350 |
351 | 2. 掃描會議管理者給您的QR-code開會去!
352 |
353 |
354 |
355 |
357 |
358 |
您不需要另外安裝軟體,打開瀏覽器或直接點擊會議管理者給您的連結就可以開會去囉!
359 |
經過測試,我們發現使用Google Chrome瀏覽器參加視訊會議會有最好的使用者感受,如果使用其他瀏覽器有問題的話,建議您可以改用Google Chrome試試看。(使用說明在這邊 )
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 | 關於陽明大學視訊會議系統
371 |
372 |
373 |
378 |
379 |
380 |
381 |
382 | 「陽明大學視訊會議系統」為醫學系陳育群副教授愛家實驗室團隊為陽明大學客製化開發,以自由開源的跨平台語音、視訊會議和即時通訊應用平台JITSI meet 為基礎,架設安全且隱私的視訊會議系統。(參考使用說明 )
383 |
384 |
自由、安全、隱私是此視訊系統設計最重要考量,我們不會收集任何資料,除了Jitis meet系統資訊安全設計(了解更多 )外,我們更在陽明視訊會議系統中採取下列設計確保與會者個人隱私:
385 |
386 | 主持人身份驗證,可設置會議室密碼,確保會議安全性。
387 | 不須額外註冊,減少與會者個資外洩風險。
388 | 所有人離開會議後,會議室自動關閉,連結及密碼均將被重置,維護會議的單一性。
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 | 關於愛家實驗室
401 |
402 |
403 |
408 |
409 |
410 |
411 |
412 | 「愛家實驗室」是由陽明大學醫學系家庭醫學科陳育群副教授於2016年所創立,
413 | 秉持著「科技助人」的理念,透過移動科技及人工智慧,將最好的醫療服務帶到每個人家中,
414 | 為大家解決醫療照護上大大小小問題,共同創造智慧新體驗。(了解更多 )
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 | Copyright © 愛家實驗室 2020
423 |
424 |
425 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
--------------------------------------------------------------------------------
/src_meet_ym/meet_ym_browser.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
陽明大學視訊會議系統()
10 |
36 |
37 |
38 |
39 |
40 |
42 |
43 |
44 |
45 |
46 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
陽明大學視訊會議系統
166 | Menu
167 |
168 |
169 |
170 |
171 | 開會APP
172 |
173 |
174 | 參加會議
175 |
176 |
177 | 關於系統
178 |
179 |
180 | 關於愛家實驗室
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 | 陽明大學。愛家實驗室
192 |
193 |
194 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 | 我要開會!
215 |
216 |
217 |
218 |
219 |
STEP1.下載APP
220 |
221 |
225 |
226 | 安卓Android:
227 |
228 |
229 |
230 |
231 |
232 |
STEP2.
233 |
234 |
235 |
236 |
237 | (只要設定一次即可)依照下面影片從螢幕左上角 > Settings > Server URL 改成 https://jitsiym.aigia.ai 。
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
STEP3.
246 |
247 |
248 |
249 |
250 | 1.輸入此次會議名稱(建議可用會議名稱字尾加上日期,如:medfaculy_20200415)
251 |
252 |
253 |
254 | 2.系統提示您是此次會議管理者,按下 OK
255 |
256 |
257 |
258 | 3.請輸入會議管理者帳號密碼。沒有此帳號密碼無法開啟會議。
259 |
260 |
261 |
262 |
263 |
264 |
265 |
STEP4.
266 | 請告知與會者您剛剛設定的「會議名稱」即可開始會議。
267 |
268 |
269 |
270 |
271 |
STEP5.
272 | 小提醒,進入會議後記得設定開會密碼保護隱私唷!
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 | 參加會議
291 |
292 |
293 |
298 |
299 |
300 |
STEP1.先安裝APP
301 |
302 |
306 |
307 | 安卓Android:
308 |
309 |
310 |
311 |
STEP2. 請點擊會議管理者給您的連結或開啟相機掃QR-code登入即可。
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 | 關於陽明大學視訊會議系統
324 |
325 |
326 |
331 |
332 |
333 |
334 | 「陽明大學視訊會議系統」為醫學系陳育群副教授愛家實驗室團隊為陽明大學客製化開發,以自由開源的跨平台語音、視訊會議和即時通訊應用平台
JITSI meet 為基礎,架設安全且隱私的視訊會議系統。(
參考使用說明 )
335 |
336 |
自由、安全、隱私是這視訊系統設計最重要考量,我們不會收集任何資料,除了Jitis meet系統資訊安全設計(了解更多 )外,我們更在陽明視訊會議系統中採取下列設計確保與會者個人隱私:
337 |
338 | 主持人身份驗證,可設置會議室密碼,確保會議安全性。
339 | 不須額外註冊,減少與會者個資外洩風險。
340 | 所有人離開會議後,會議室自動關閉,連結及密碼均將被重置,維護會議的單一性。
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 | 關於愛家實驗室
352 |
353 |
354 |
359 |
360 |
361 |
362 |
363 | 「愛家實驗室」是由陽明大學醫學系家庭醫學科陳育群副教授於2016年所創立,
364 | 秉持著「科技助人」的理念,透過移動科技及人工智慧,將最好的醫療服務帶到每個人家中,
365 | 為大家解決醫療照護上大大小小問題,共同創造智慧新體驗。(了解更多 )
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 | Copyright © 愛家實驗室 2020
374 |
375 |
376 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
--------------------------------------------------------------------------------