ป้องกันไม่ให้เพิ่มเนื้อหาที่แก้ไขได้ <div> บน ENTER - Chrome


131

ฉันมีcontenteditableองค์ประกอบและเมื่อใดก็ตามที่ฉันพิมพ์บางสิ่งและกดENTERมันจะสร้างใหม่<div>และวางข้อความบรรทัดใหม่ไว้ที่นั่น ฉันไม่ชอบอันนี้เลยสักนิด

เป็นไปได้หรือไม่ที่จะป้องกันไม่ให้สิ่งนี้เกิดขึ้นหรืออย่างน้อยก็แทนที่ด้วย a <br>?

นี่คือการสาธิตhttp://jsfiddle.net/jDvau/

หมายเหตุ: นี่ไม่ใช่ปัญหาใน firefox


1
firefox เพิ่ม <br>, chrome - ไม่ใช่ แต่หลังจากแก้ไขสไตล์ของคุณแล้ว div พิเศษจะไม่ทำให้ช่องว่างด้านซ้ายแตก คำถามคือทำไมคุณถึงไม่ชอบ? คิดว่ามันเป็น br ... jsfiddle.net/jDvau/1นอกจากนี้คุณสามารถใช้เหตุการณ์ DOMSubtreeModified ในการจับ div เหล่านี้และลบออก
ViliusL

stackoverflow.com/questions/6024594/…สิ่งนี้ช่วยคุณได้ขอให้โชคดี!
ศิริกรณ์

1
สำหรับฉันวิธีแก้ปัญหาของ Blake Plumb นั้นง่ายที่สุดและจากที่ไกลที่สุดที่นี่
svassr

1
@svassr นั่นไม่ใช่ประเด็นไม่ใช่คุณหรือฉันที่จะใช้มันเป็นลูกค้าที่อาจไม่รู้ด้วยซ้ำว่ากะคืออะไร
iConnor

2
แน่นอนมันเปลี่ยนแปลงทุกอย่าง ที่กล่าวว่าเป็นพฤติกรรมทั่วไปและข้อความช่วยเหลือเล็ก ๆ น้อย ๆ จะไม่เกาะแขน "ให้ปลาชายคนหนึ่งและคุณให้อาหารเขาหนึ่งวันสอนผู้ชายให้ตกปลาและคุณเลี้ยงเขาไปตลอดชีวิต"
svassr

คำตอบ:


161

ลองสิ่งนี้:

$('div[contenteditable]').keydown(function(e) {
    // trap the return key being pressed
    if (e.keyCode === 13) {
        // insert 2 br tags (if only one br tag is inserted the cursor won't go to the next line)
        document.execCommand('insertHTML', false, '<br/>');
        // prevent the default behaviour of return key pressed
        return false;
    }
});

คลิกที่นี่เพื่อสาธิต


แต่ฉันพบความแตกต่างเล็กน้อยที่นี่ วางเคอร์เซอร์ไว้บนบรรทัดว่าง (ด้านบนของ "พิมพ์บางสิ่ง") แล้วกด Enter ตอนนี้เคอร์เซอร์อยู่ก่อน "Type" ไม่ใช่ในบรรทัดว่างใหม่
Andrew

3
สิ่งนี้ใช้ไม่ได้ใน IE11 เนื่องจากไม่รองรับ insertHTML ดูคำตอบด้านล่าง!
webprogrammer

4
หากคุณวางเคอร์เซอร์ไว้ระหว่างอักขระเช่น. 'Ty [เคอร์เซอร์] pe บางสิ่ง' แล้วกด Enter คุณจะได้ 1 บรรทัดมากเกินไป
Chandrew

13
คำตอบไม่เป็นที่ยอมรับ ทางออกจะมีเพียงหนึ่งเดียว <br />
raoulinski

3
การกลับมาครั้งนี้ทั้ง <br> และ <div>
Nishad Up

51

คุณสามารถทำได้เพียงแค่เปลี่ยน CSS:

div{
    background: skyblue;
    padding:10px;
    display: inline-block;
}

pre{
    white-space: pre-wrap;
    background: #EEE;
}

http://jsfiddle.net/ayiem999/HW43Q/


1
มีปัญหาเมื่อฉันใช้อินไลน์บล็อกเรียกใช้ execCommand ("insertHTML", xxx) เพื่อแทรกสิ่งใด ๆ "<br>" จะเพิ่มที่ส่วนท้ายขององค์ประกอบที่แทรกซึ่งทดสอบใน Chrome33
Imskull

5
หาได้ดี น่าเสียดายที่สิ่งนี้ใช้ไม่ได้กับตำแหน่ง: องค์ประกอบที่แน่นอนหรือองค์ประกอบที่เป็นลูกโดยตรงของผู้ปกครองที่มี display: flex คุณสามารถแฮ็คให้ใช้งานได้โดยคำนึงถึงสิ่งนั้น ตรวจสอบให้แน่ใจว่าไม่ใช่ลูกขององค์ประกอบดิ้นโดยตรง หากคุณต้องการตำแหน่ง: แน่นอนเพียงแค่ให้ผู้ปกครองพิเศษกับสิ่งนั้น
สงสัย

@ReinoutvanKempen เริ่มใช้ flex เมื่อ 3 เดือนก่อนแล้ว ฉันเดาว่าแฮ็คนี้ใช้ไม่ได้
kittu

นี่เป็นวิธีการแก้ปัญหาที่ดีที่สุดจริงๆมันสะอาดและชัดเจนมากไม่มีอะไรจะแทรกแม้แต่ br และโดยเฉพาะอย่างยิ่งวิธีนี้ที่ไม่มี js
ปกป้อง orca

2
ว้าว ... นี่แก้ Chrome ได้ แต่ทำให้ Firefox เริ่มเพิ่ม div ที่ enter ซึ่งมันไม่ได้เป็นค่าเริ่มต้น
cbdeveloper

40

เพิ่มรูปแบบdisplay:inline-block;การcontenteditableก็จะไม่สร้างdiv, pและspanใน Chrome โดยอัตโนมัติ


6
นี่เป็นทางออกที่ยอดเยี่ยม *[contenteditable="true"]{display: inline-block;}
ericjbasti

รักมันเรียบง่าย แต่มีประสิทธิภาพ
user25794

ใน IE11 สิ่งนี้จะเพิ่มพิเศษ<p></p> :(
Betty St

1
แต่มันจะสร้างข้อผิดพลาด Chromeอีกอันที่น่ารำคาญ(เบราว์เซอร์ห่วย ๆ )
vsync

4
ไม่ทำงานอีกต่อไปกับ Chrome เวอร์ชัน 63.0.3239.84
Mikaël Mayer

21

ลองสิ่งนี้:

$('div[contenteditable="true"]').keypress(function(event) {

    if (event.which != 13)
        return true;

    var docFragment = document.createDocumentFragment();

    //add a new line
    var newEle = document.createTextNode('\n');
    docFragment.appendChild(newEle);

    //add the br, or p, or something else
    newEle = document.createElement('br');
    docFragment.appendChild(newEle);

    //make the br replace selection
    var range = window.getSelection().getRangeAt(0);
    range.deleteContents();
    range.insertNode(docFragment);

    //create a new range
    range = document.createRange();
    range.setStartAfter(newEle);
    range.collapse(true);

    //make the cursor there
    var sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);

    return false;
});

http://jsfiddle.net/rooseve/jDvau/3/


ดีใช้ได้ :) ฉันจะรอความสนใจมากกว่านี้ก่อนตัดสินใจขอบคุณ
iConnor

ทำงานไม่ถูกต้องบน firefox มันเพิ่มบรรทัดพิเศษหนึ่งบรรทัด
agpt

สิ่งนี้ทำให้inputไม่ถูกทริกเกอร์เมื่อคุณกด Enter วิธีง่ายๆ: เพิ่ม sth เช่น$(this).trigger('input')
LarsW

insertHTMLแก้ปัญหาไม่สิ่งบางลางเมื่อมีซ้อนกันcontenteditableองค์ประกอบหลีกเลี่ยงวิธีการแก้ปัญหาของคุณที่ แต่มีปัญหาบางอย่างมากขึ้นผมเพิ่มว่ามันเป็นทางออกที่เป็นไปใหม่
skerit

ไม่ทำงานบน Chrome ครั้งแรกเมื่อคุณกด Enter ที่ท้ายสตริงจะเป็นการเพิ่มช่องว่าง ครั้งที่สองมันใช้งานได้

20
document.execCommand('defaultParagraphSeparator', false, 'p');

มันจะแทนที่พฤติกรรมเริ่มต้นเพื่อให้มีย่อหน้าแทน

ใน chrome พฤติกรรมเริ่มต้นของ Enter คือ:

<div>
    <br>
</div>

ด้วยคำสั่งนั้นมันจะเป็น

<p>
    <br>
</p>

ตอนนี้มันเป็นเส้นตรงมากขึ้นแล้วมันง่ายมากที่<br>คุณจะต้องทำเท่านั้น


เมื่อคุณกด Enter แทนที่จะมี div และ br คุณจะมี p และ br ข้ามเบราว์เซอร์ ลองมัน.
Ced

ขอบคุณ! คำอธิบายมีประโยชน์เสมอ
jpaugh

@jpaugh np ฉันแก้ไขคำตอบเพิ่มเติมเพื่อให้อธิบายได้ดีขึ้น
Ced

สิ่งนี้ใช้ไม่ได้กับ firefox (ฉันทดสอบใน chrome และ firefox เท่านั้น)
medBouzid

FireFox ได้รับการแก้ไขเพื่อให้สอดคล้องกับ defaultParagraphSeparator IMHO ทำให้คำตอบนี้ดีที่สุดเนื่องจากทำให้เบราว์เซอร์ทั้งหมดสอดคล้องและสอดคล้องกับข้อมูลจำเพาะ หากคุณไม่ต้องการให้แท็ก P มี "ระยะห่าง" ระยะขอบจากบล็อกข้อความก่อนหน้าในเนื้อหาที่แก้ไขได้คุณสามารถใช้ css เพื่อแก้ไขได้
blackmamba

9

ใช้shift+ enterแทนenterการใส่<br>แท็กเดียวหรือรวมข้อความของคุณไว้ใน<p>แท็ก


9
+1ขอบคุณสิ่งนี้มีประโยชน์มากที่จะรู้ แต่ถ้าคุณมีลูกค้าที่กำลังเขียนหนังสือนั่นจะเป็นความเจ็บปวดใน ***
iConnor

1
สิ่งนี้ไม่ช่วยอะไรหากคุณกำลังวางเนื้อหา
vsync

5

วิธีการcontenteditableทำงานเมื่อคุณกดenterขึ้นอยู่กับเบราว์เซอร์สิ่งที่<div>เกิดขึ้นบน webkit (chrome, safari) และ IE

ฉันต่อสู้กับเรื่องนี้เมื่อสองสามเดือนที่แล้วและฉันแก้ไขด้วยวิธีนี้:

//I recommand you trigger this in case of focus on your contenteditable
if( navigator.userAgent.indexOf("msie") > 0 || navigator.userAgent.indexOf("webkit") > 0 ) {
    //Add <br> to the end of the field for chrome and safari to allow further insertion
    if(navigator.userAgent.indexOf("webkit") > 0)
    {
        if ( !this.lastChild || this.lastChild.nodeName.toLowerCase() != "br" ) {
            $(this).html( $(this).html()+'<br />' );
        }
    }

    $(this).keypress( function(e) {
        if( ( e.keyCode || e.witch ) == 13 ) {
            e.preventDefault();

            if( navigator.userAgent.indexOf("msie") > 0 ) {
                insertHtml('<br />');
            }
            else {
              var selection = window.getSelection(),
              range = selection.getRangeAt(0),
              br = document.createElement('br');

              range.deleteContents();
              range.insertNode(br);
              range.setStartAfter(br);
              range.setEndAfter(br);
              range.collapse(false);

              selection.removeAllRanges();
              selection.addRange(range);
            }
        }
    });
}

ฉันหวังว่ามันจะช่วยได้และขออภัยสำหรับภาษาอังกฤษของฉันหากไม่ชัดเจนเท่าที่จำเป็น

แก้ไข : การแก้ไขฟังก์ชัน jQuery ที่ถูกลบออกjQuery.browser


เพียงเพื่อแจ้งให้คุณทราบjQuery.browserไม่ใช่ส่วนหนึ่งของ jQuery อีกต่อไป
iConnor

คุณพูดถูกฉันควรพูดถึงสิ่งนี้และใช้สิ่งที่ชอบnavigator.userAgent.indexOf("msie") > 0และnavigator.userAgent.indexOf("webkit") > 0
Elie

1
if( ( e.keyCode || e.witch ) == 13 ) { ... }จำเป็นต้องเป็นif (e.keyCode === 13 || e.which === 13) { ... }
Sebastian Sandqvist

5

คุณสามารถมี<p>แท็กแยกกันสำหรับแต่ละบรรทัดแทนที่จะใช้<br>แท็กและเพิ่มความเข้ากันได้ของเบราว์เซอร์นอกกรอบ

ในการดำเนินการนี้ให้ใส่<p>แท็กที่มีข้อความเริ่มต้นอยู่ภายใน div ที่แก้ไขเนื้อหาได้

ตัวอย่างเช่นแทนที่จะเป็น:

<div contenteditable></div>

ใช้:

<div contenteditable>
   <p>Replace this text with something awesome!</p>
</div>

jsfiddle

ทดสอบใน Chrome, Firefox และ Edge และอย่างที่สองใช้งานได้เหมือนกันในแต่ละรายการ

อย่างไรก็ตามขั้นแรกสร้าง div ใน Chrome สร้างตัวแบ่งบรรทัดใน Firefox และใน Edge จะสร้าง div และเคอร์เซอร์จะถูกนำกลับไปที่จุดเริ่มต้นของ div ปัจจุบันแทนที่จะย้ายไปยังอันถัดไป

ทดสอบใน Chrome, Firefox และ Edge


นี่ไม่ใช่วิธีแก้ปัญหาที่ไม่ดี
AaronHS

5

ฉันชอบใช้กับดักหนูเพื่อจัดการกับปุ่มลัด: https://craig.is/killing/mice

จากนั้นฉันเพียงแค่สกัดกั้นเหตุการณ์ enter โดยเรียกใช้คำสั่งinsertLineBreak :

Mousetrap.bindGlobal('enter', (e)=>{
  window.document.execCommand('insertLineBreak', false, null);
  e.preventDefault();
});

คำสั่งทั้งหมด: https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand

ทำงานโดยใช้ Chrome 75 และองค์ประกอบที่แก้ไขได้ดังต่อไปนี้:

<pre contenteditable="true"></pre>

นอกจากนี้ยังสามารถใช้insertHTML :

window.document.execCommand('insertHTML', false, "\n");

4

เพิ่มค่าป้องกันเริ่มต้นให้กับ div

document.body.div.onkeydown = function(e) {
    if ( e.keycode == 13 ){
        e.preventDefault();
            //add a <br>
        div = document.getElementById("myDiv");
        div.innerHTML += "<br>";
    }
}

@connorspiracist ขออภัยdocument.body.div(คุณอาจต้องใส่รหัสอื่นเพื่อชี้เพื่อแก้ไขdivความคิดทั่วไป)
Math chiller

4

ฉันจะใช้สไตล์ (Css) เพื่อแก้ไขปัญหา

div[contenteditable=true] > div {
  padding: 0;
} 

Firefox เพิ่มตัวแบ่งองค์ประกอบบล็อก
ในขณะที่ Chrome ห่อแต่ละส่วนไว้ในแท็ก คุณ css ทำให้ div มีช่องว่างภายใน10pxพร้อมกับสีพื้นหลัง

div{
  background: skyblue;
  padding:10px;
}

หรือคุณสามารถจำลองเอฟเฟกต์ที่ต้องการเหมือนกันใน jQuery:

var style = $('<style>p[contenteditable=true] > div { padding: 0;}</style>');
$('html > head').append(style);

นี่คือทางแยกของซอของคุณhttp://jsfiddle.net/R4Jdz/7/


1
สิ่งนี้อาจช่วยบางคนได้ แต่ฉันกังวลมากขึ้นเกี่ยวกับมาร์กอัปที่ไม่จำเป็นเนื่องจากฉันจะใช้ HTML สุดท้ายของcontenteditableและใช้ที่อื่น
iConnor

3

คุณสามารถตัดย่อหน้าของคุณด้วย<p>แท็กได้เช่นมันจะขึ้นบรรทัดใหม่แทน div ตัวอย่าง:
<div contenteditable="true"><p>Line</p></div>
หลังจากแทรกสตริงใหม่:
<div contenteditable="true"><p>Line</p><p>New Line</p></div>


3

inserHTMLแก้ปัญหาคำสั่งไม่สิ่งบางลางเมื่อคุณมีซ้อนกันcontenteditableองค์ประกอบ

ฉันได้แนวคิดจากคำตอบหลาย ๆ คำที่นี่และดูเหมือนว่าจะเหมาะกับความต้องการของฉันในตอนนี้:

element.addEventListener('keydown', function onKeyDown(e) {

    // Only listen for plain returns, without any modifier keys
    if (e.which != 13 || e.shiftKey || e.ctrlKey || e.altKey) {
        return;
    }

    let doc_fragment = document.createDocumentFragment();

    // Create a new break element
    let new_ele = document.createElement('br');
    doc_fragment.appendChild(new_ele);

    // Get the current selection, and make sure the content is removed (if any)
    let range = window.getSelection().getRangeAt(0);
    range.deleteContents();

    // See if the selection container has any next siblings
    // If not: add another break, otherwise the cursor won't move
    if (!hasNextSibling(range.endContainer)) {
        let extra_break = document.createElement('br');
        doc_fragment.appendChild(extra_break);
    }

    range.insertNode(doc_fragment);

    //create a new range
    range = document.createRange();
    range.setStartAfter(new_ele);
    range.collapse(true);

    //make the cursor there
    let sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);

    e.stopPropagation();
    e.preventDefault();

    return false;
});

// See if the given node has a next sibling.
// Either any element or a non-empty node
function hasNextSibling(node) {

    if (node.nextElementSibling) {
        return true;
    }

    while (node.nextSibling) {
        node = node.nextSibling;

        if (node.length > 0) {
            return true;
        }
    }

    return false;
}


2

ก่อนอื่นเราต้องจับผู้ใช้คีย์ทุกคนเข้าเพื่อดูว่ากด Enter หรือไม่จากนั้นเราจะป้องกันการ<div>สร้างและสร้าง<br>แท็กของเราเอง

มีปัญหาอย่างหนึ่งเมื่อเราสร้างเคอร์เซอร์ของเราจะอยู่ที่ตำแหน่งเดิมดังนั้นเราจึงใช้Selection APIเพื่อวางเคอร์เซอร์ไว้ที่ส่วนท้าย

อย่าลืมใส่<br>แท็กที่ท้ายข้อความเพราะถ้าคุณไม่ป้อนครั้งแรกจะไม่ขึ้นบรรทัดใหม่

$('div[contenteditable]').on('keydown', function(e) {
    var key = e.keyCode,
        el  = $(this)[0];
    // If Enter    
    if (key === 13) {
        e.preventDefault(); // Prevent the <div /> creation.
        $(this).append('<br>'); // Add the <br at the end

        // Place selection at the end 
        // http://stackoverflow.com/questions/4233265/contenteditable-set-caret-at-the-end-of-the-text-cross-browser
        if (typeof window.getSelection != "undefined"
            && typeof document.createRange != "undefined") {
            var range = document.createRange();
            range.selectNodeContents(el);
            range.collapse(false);
            var sel = window.getSelection();
            sel.removeAllRanges();
            sel.addRange(range);
        } else if (typeof document.body.createTextRange != "undefined") {
            var textRange = document.body.createTextRange();
            textRange.moveToElementText(el);
            textRange.collapse(false);
            textRange.select();
        }
    }
});

ซอ


2

นี่คือโปรแกรมแก้ไข HTML5 ที่กำหนดโดยเบราว์เซอร์ คุณสามารถตัดข้อความของคุณด้วย<p>...</p>แล้วเมื่อใดก็ตามที่คุณกด ENTER <p></p>คุณจะได้รับ นอกจากนี้การแก้ไขการทำงานด้วยวิธีนี้ว่าเมื่อใดก็ตามที่คุณกด SHIFT + ENTER <br />มันแทรก

<div contenteditable="true"><p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolor veniam asperiores laudantium repudiandae doloremque sed perferendis obcaecati delectus autem perspiciatis aut excepturi et nesciunt error ad incidunt impedit quia dolores rerum animi provident dolore corporis libero sunt enim. Ad magnam omnis quidem qui voluptas ut minima similique obcaecati doloremque atque!
<br /><br />
Type some stuff, hit ENTER a few times, then press the button.
</p>
</div>

ตรวจสอบสิ่งนี้: http://jsfiddle.net/ZQztJ/


2

ใช้งานได้กับเบราว์เซอร์หลัก ๆ ทั้งหมด (Chrome, Firefox, Safari, Edge)

document.addEventListener('keydown', event => {
  if (event.key === 'Enter') {
    document.execCommand('insertLineBreak')
    event.preventDefault()
  }
})
<div class="element" contenteditable="true">Sample text</div>
<p class="element" contenteditable="true">Sample text</p>

มีความไม่สะดวกอย่างหนึ่ง หลังจากคุณแก้ไขเสร็จแล้วองค์ประกอบอาจมีตอนจบ<br>อยู่ภายใน แต่คุณสามารถเพิ่มโค้ดเพื่อตัดทอนได้หากต้องการ

ตรวจสอบคำตอบนี้เพื่อลบส่วนท้าย<br> https://stackoverflow.com/a/61237737/670839


ห่า? วิธีแก้ปัญหาเดียวที่ใช้งานได้จริงสำหรับฉันจะอยู่ในหน้านี้ได้อย่างไร ขอบคุณมาก.
user12861

1
if (navigator.userAgent.toLowerCase().indexOf('msie') > -1) {
   var range = document.getSelection();
   range.pasteHTML(range.htmlText + '<br><br>');
}
else if(navigator.userAgent.toLocaleLowerCase().indexOf('trident') > -1)                           {
   var range = document.getSelection().getRangeAt(0); //get caret
   var nnode = document.createElement('br');
   var bnode = document.createTextNode('\u00A0'); //&nbsp;
   range.insertNode(nnode);
   this.appendChild(bnode);
   range.insertNode(nnode);                                
}
else
   document.execCommand('insertHTML', false, '<br><br>')

ในกรณีที่เป็นบริบทที่เกิดขึ้นจริงซึ่งหมายความว่าthisdocument.getElementById('test');


0

อีกวิธีหนึ่งที่จะทำได้

$('button').click(function(){
    $('pre').text($('div')[0].outerHTML)
});

$("#content-edit").keydown(function(e) {
    if(e.which == 13) {
       $(this).find("div").prepend('<br />').contents().unwrap();
      }
});

http://jsfiddle.net/jDvau/11/


ไม่ปล่อยให้เคอร์เซอร์เลื่อนไปข้างหน้าขณะที่คุณพิมพ์
Jack Lu

เป็นเรื่องแปลกฉันเพิ่งลองใน Chrome และ IE 11 และใช้งานได้ดี ข้อมูลอ้างอิง คุณช่วยบอกฉันได้ไหมว่าคุณใช้เบราว์เซอร์ใด รายละเอียดเพิ่มเติมเกี่ยวกับพฤติกรรมจะเป็นประโยชน์ในการแก้ไขปัญหา
MrAJ

0

ป้องกันการสร้าง Div ใหม่ใน Div ที่แก้ไขเนื้อหาได้ในแต่ละปุ่ม Enter: วิธีแก้ปัญหาที่ฉันพบนั้นง่ายมาก:

var newdiv = document.createElement("div"); 
newdiv.innerHTML = "Your content of div goes here";
myEditablediv.appendChild(newdiv);

สิ่งนี้ --- เนื้อหา innerHTML ป้องกันการสร้าง Div ใหม่ในเนื้อหา Div ที่แก้ไขได้ในแต่ละปุ่ม Enter


0

ใน Draft ของ W3C Editor มีข้อมูลบางอย่างเกี่ยวกับการเพิ่มสถานะในContentEditableและเพื่อป้องกันไม่ให้เบราว์เซอร์เพิ่มองค์ประกอบใหม่เมื่อกด Enter คุณสามารถplaintext-onlyใช้ได้

<div contentEditable="plaintext-only"></div>

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.