Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ The Notification plugin helps us to send messages to third-party notification sy
- [x] [Ding talk](https://github.com/apache/answer-plugins/tree/main/notification-dingtalk)
- [x] [WeCom](https://github.com/apache/answer-plugins/tree/main/notification-wecom)

### Sidebar

Add custom content on the sidebar navigation

- [x] [Quicklinks](https://github.com/apache/answer-plugins/tree/main/quick-links)

### Route

Support for custom routing.
Expand Down
18 changes: 18 additions & 0 deletions quick-links/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
8 changes: 8 additions & 0 deletions quick-links/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"trailingComma": "all",
"tabWidth": 2,
"singleQuote": true,
"jsxBracketSameLine": true,
"printWidth": 80,
"endOfLine": "auto"
}
91 changes: 91 additions & 0 deletions quick-links/Component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { useTranslation } from 'react-i18next';
import useSWR from 'swr'

const Component = ({ navigate, request }) => {

const { t } = useTranslation('plugin', {
keyPrefix: 'quick_links.frontend',
});

const { data } = useSWR(
['/answer/api/v1/sidebar/config'],
request.instance.get,
);
const tags = data?.tags || [];
const links = data?.links_text?.split('\n') || [];

const handleNavigate = (e) => {
e.preventDefault();
e.stopPropagation();
const url = e.currentTarget.getAttribute('href');
if (!url || url.trim() === '') return;

if (url.startsWith('/')) {
navigate(url);
} else if (/^https?:\/\//.test(url)) {
window.open(url, '_blank', 'noopener,noreferrer');
} else {
console.warn('Ignoring potentially unsafe URL:', url);
}
}

if (!tags.length && !data?.links_text) {
return null;
}

return (
<div>
<div className="py-2 px-3 mt-3 small fw-bold quick-link">{t('quick_links')}</div>
{tags?.map((tag) => {
const href = `/tags/${encodeURIComponent(tag.slug_name)}`
return (
<a
href={href}
key={href}
className={`nav-link ${window.location.pathname === href ? 'active' : ''}`}
onClick={handleNavigate}>
<span>{tag.display_name}</span>
</a>
)
})}

{links?.map((link) => {
const name = link.split(',')[0]
const url = link.split(',')[1]?.trim()
if (!url || !name) {
return null;
}
return (
<a
href={url}
key={url}
className={`nav-link ${window.location.pathname === url ? 'active' : ''}`}
onClick={handleNavigate}
>
<span>{name}</span>
</a>
)
})}
</div>
);
};

export default Component;
16 changes: 16 additions & 0 deletions quick-links/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# QuickLinks Plugin

QuickLinks is a sidebar plugin for [Apache Answer](https://github.com/apache/answer) that allows administrators to customize frequently used links and tags, improving community navigation efficiency.

## Features

- Custom tag filtering
- Custom multi-line quick links
- Sidebar integration for easy access to resources

## Configuration

Configure the following fields in the plugin management panel:

- `tags`: Select tags to display.
- `links_text`: Enter quick links, one per line, Markdown supported.
93 changes: 93 additions & 0 deletions quick-links/basic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

// Package quick_links
package quick_links

import (
"embed"
"encoding/json"

"github.com/apache/answer-plugins/quick-links/i18n"
"github.com/apache/answer-plugins/util"
"github.com/apache/answer/plugin"
)

//go:embed info.yaml
var Info embed.FS

type QuickLinks struct {
Config *plugin.SidebarConfig
}

func init() {
plugin.Register(&QuickLinks{
Config: &plugin.SidebarConfig{},
})
}

func (q *QuickLinks) Info() plugin.Info {
info := &util.Info{}
info.GetInfo(Info)

return plugin.Info{
Name: plugin.MakeTranslator(i18n.InfoName),
SlugName: info.SlugName,
Description: plugin.MakeTranslator(i18n.InfoDescription),
Author: info.Author,
Version: info.Version,
Link: info.Link,
}
}

func (e *QuickLinks) ConfigFields() []plugin.ConfigField {
return []plugin.ConfigField{
{
Name: "tags",
Type: plugin.ConfigTypeTagSelector,
Title: plugin.MakeTranslator(i18n.ConfigTagsTitle),
Description: plugin.MakeTranslator(i18n.ConfigTagsDescription),
Value: e.Config.Tags,
},
{
Name: "links_text",
Type: plugin.ConfigTypeTextarea,
Title: plugin.MakeTranslator(i18n.ConfigLinksTitle),
Description: plugin.MakeTranslator(i18n.ConfigLinksDescription),
Value: e.Config.LinksText,
UIOptions: plugin.ConfigFieldUIOptions{
Rows: "5",
ClassName: "small font-monospace",
},
},
}
}

func (e *QuickLinks) ConfigReceiver(config []byte) error {
c := &plugin.SidebarConfig{}
_ = json.Unmarshal(config, c)
e.Config = c
return nil
}

// todo
func (q *QuickLinks) GetSidebarConfig() (sidebarConfig *plugin.SidebarConfig, err error) {
sidebarConfig = q.Config
return
}
55 changes: 55 additions & 0 deletions quick-links/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
module github.com/apache/answer-plugins/quick-links

go 1.23.3

require (
github.com/apache/answer v1.6.0
github.com/apache/answer-plugins/util v1.0.3
)

require (
github.com/LinkinStars/go-i18n/v2 v2.2.2 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bytedance/sonic v1.12.2 // indirect
github.com/bytedance/sonic/loader v0.2.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.10.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/wire v0.5.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/segmentfault/pacman v1.0.5-0.20230822083413-c0075a2d401f // indirect
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20230822083413-c0075a2d401f // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tidwall/gjson v1.17.3 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/yuin/goldmark v1.7.4 // indirect
golang.org/x/arch v0.10.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
xorm.io/builder v0.3.13 // indirect
xorm.io/xorm v1.3.2 // indirect
)
Loading